Merge lp:~epics-core/epics-base/lockopt into lp:~epics-core/epics-base/3.16
- lockopt
- Merge into 3.16
Status: | Merged |
---|---|
Merged at revision: | 12684 |
Proposed branch: | lp:~epics-core/epics-base/lockopt |
Merge into: | lp:~epics-core/epics-base/3.16 |
Diff against target: |
2923 lines (+1815/-572) (has conflicts) 20 files modified
src/ioc/db/callback.c (+0/-1) src/ioc/db/dbAccess.c (+15/-8) src/ioc/db/dbCaTest.c (+2/-2) src/ioc/db/dbChannel.c (+0/-1) src/ioc/db/dbCommon.dbd (+6/-0) src/ioc/db/dbLink.c (+20/-12) src/ioc/db/dbLink.h (+4/-2) src/ioc/db/dbLock.c (+849/-513) src/ioc/db/dbLock.h (+17/-7) src/ioc/db/dbLockPvt.h (+110/-0) src/ioc/db/dbNotify.c (+0/-1) src/ioc/db/dbScan.c (+0/-1) src/ioc/db/test/Makefile (+10/-0) src/ioc/db/test/dbLockTest.c (+360/-13) src/ioc/db/test/dbLockTest.db (+3/-0) src/ioc/db/test/dbStressLock.c (+357/-0) src/ioc/db/test/dbStressLock.db (+40/-0) src/ioc/dbStatic/dbStaticRun.c (+6/-4) src/ioc/dbStatic/link.h (+2/-0) src/ioc/misc/iocInit.c (+14/-7) Text conflict in src/ioc/db/test/Makefile Text conflict in src/ioc/misc/iocInit.c |
To merge this branch: | bzr merge lp:~epics-core/epics-base/lockopt |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andrew Johnson | Approve | ||
mdavidsaver | Approve | ||
Ralph Lange | Pending | ||
Review via email: mp+250073@code.launchpad.net |
Commit message
Description of the change
Re-implementation of dbLock.c focusing on addition of multi-locking API, and elimination of global locks.
API changes:
* Add dbLockPvt.h for non-public API. Moved some previously "public" functions here (eg. dbLockSetMerge())
* Tracking backwards DB_LINK with the BKLNK field.
* Add 'struct dbLocker' and dbScanLockMany()
Changes affecting performance
* dbLockSetSplit no longer allocates memory
* dbScanLock() no longer involves any global locks.
Build time options
* LOCKSET_DEBUG - Enable additional runtime consistency checks
* LOCKSET_NOFREE - Disable the lockSet freelist
* LOCKSET_NOCNT - Disable the global recompute counter optimization
Andrew Johnson (anj) wrote : | # |
There will be merge conflicts when trying to merge this branch, indicated by the conflict markers shown in the diff that Launchpad generates. It may be worth doing a rebase against the latest 3.16 to clean these out.
BTW there are comments in the dbCa.c file (comment block headed "caLink locking") that mention dbScanLock and the global lock. Please review and fix those comments too.
mdavidsaver (mdavidsaver) wrote : | # |
Documentation lp:~epics-core/epics-appdev/lockopt
> There will be merge conflicts when trying to merge this branch
Ok, easier now then later. FYI the conflicts are trivial whitespace and #include
> BTW there are comments in the dbCa.c ... mention dbScanLock and the global lock
I think this is just an unfortunate mis-use of the word 'global', but I'll go through the code anyway.
> * dbScanLock
> * dbCaAddLink and dbCaRemoveLink are only called by dbAccess or iocInit
> * They are only called by dbAccess when it has a global lock on lock set.
> * It is assumed that ALL other dbCaxxx calls are made only if dbScanLock
> * is already active. These routines are intended for use by record/device
> * support.
mdavidsaver (mdavidsaver) wrote : | # |
> epicsAtomicDefa
Ok, some I'm not going to do the rebase right now. Instead I'm going to fix epicsAtomic so that it's consistent about 'static inline'.
mdavidsaver (mdavidsaver) wrote : | # |
> BTW there are comments in the dbCa.c ... mention dbScanLock and the global lock
dbCa has no dependence on the global locks of dbLock. To avoid merge complication I'll see to the comments in dbCa.c in lp:~epics-core/epics-base/calinktest.
- 12641. By mdavidsaver
-
populate RDES early
- 12642. By mdavidsaver
-
dbLock: multi-locking
new API to lock 2 or more lockSets simultaneously
removes global locks for dbScanLock() only
one global lock for debugging/freelist
Introduce dbLockPvt.h for internal API - 12643. By mdavidsaver
-
dbLockTest and dbStressLock
- 12644. By mdavidsaver
-
dbAccess: multi-locking in dbPutFieldLink
Use new locking API in dbPutFieldLink()
Adjust dbAddLink() and dbRemoveLink()
to pass a dbLocker* through to lockSet merge/split - 12645. By mdavidsaver
-
iocInit: links now initialized in dbLockInitRecords()
- 12646. By mdavidsaver
-
dbLink: backward link tracking
- 12647. By mdavidsaver
-
dbLock: use new backref tracking
- 12648. By mdavidsaver
-
dbCaTest: adjust locking in dbcar()
- 12649. By mdavidsaver
-
dbLock: describe build options
- 12650. By mdavidsaver
-
dbAccess.c: dbLocker needs at most two refs
- 12651. By mdavidsaver
-
dbLock: no c++ comments in c code
- 12652. By mdavidsaver
-
dbLock: default build options
Enable extra debugging.
Disable lockSet free list.
Enable recomputeCnt optimization - 12653. By mdavidsaver
-
dbLock: comments
- 12654. By mdavidsaver
-
iocInit: remove no-op
The work which was done here is moved to dbCloseLinks()
- 12655. By mdavidsaver
-
dbLock: remove some unnecessary code
no need to hold spinlock for lockRecordList
the lockRecordList is protected by the lockSet::lock - 12656. By mdavidsaver
-
dbLock: minor
- 12657. By mdavidsaver
-
dbLock: comments
mdavidsaver (mdavidsaver) wrote : | # |
Rebase to latest w/ atomic linking fix.
- 12658. By mdavidsaver
-
dbLock: fix initialization of self links
initPVLinks() doesn't correctly handle case where a record links
to itself. This results it being added twice to lockRecordList,
which corrupts the list count, and the lockset ref. counter.The error appears in dbLockCleanupRe
cords() when not all
locksets are free'd. - 12659. By mdavidsaver
-
dbLock: minor
must match following condition
mdavidsaver (mdavidsaver) wrote : | # |
I think the self link issue is resolved now.
- 12660. By mdavidsaver
-
iocInit: Don't free LSET until scan tasks have stopped
- 12661. By mdavidsaver
-
dbLock: fix unlock w/o lock during iocInit
mdavidsaver (mdavidsaver) wrote : | # |
Fix one startup and one shutdown bug. I think this is ready to merge now.
Andrew Johnson (anj) wrote : | # |
I fixed one merge conflict in src/ioc/
../dbStressLo
That was a missing explicit dependency in src/ioc/
dbLockTest.o: In function `testLinkMake':
base-
base-
base-
base-
dbLockTest.o: In function `testLinkChange':
base-
base-
base-
base-
These were due to missing epicsShareFunc decorators in dbLockPvt.h.
With those fixes all tests passed on linux-x86_64; I'll run them on VxWorks this afternoon before committing.
This does need an entry in the Release Notes, but I'll leave you to commit that directly to the base-3.16 branch after I've merged this. Could you also check/confirm that the lp:~epics-core/epics-appdev/lockopt branch is up to date.
Question: Have you tested this code much with dbNotify? We don't have any in-tree tests that exercise that subsystem. There are some tests in the lp:epics-base-tests module, but as written they aren't very conducive to automating.
Thanks.
Andrew Johnson (anj) wrote : | # |
Problem.
I built the lp:epics-base-test module against the merged version and against the current base-3.16 branch. With a little tweaking the original runs the softcallback tests OK, but when I try to start the IOC built against this branch I get this:
iocInit();
Starting iocInit
#######
## EPICS R3.16.0-DEV $$Date$$
## EPICS Base built Aug 18 2015
#######
Illegal field value PV: mrk:mbbidirectpget devMbbiDirectSo
Illegal field value PV: mrk:longinpget devLiSoftCallback (add_record) Illegal INP field
Illegal field value PV: mrk:bipget devBiSoftCallback (add_record) Illegal INP field
Illegal field value PV: mrk:mbbipget devMbbiSoftCallback (add_record) Illegal INP field
Illegal field value PV: mrk:stringinpget devSiSoftCallback (add_record) Illegal INP field
Illegal field value PV: mrk:aipget devAiSoftCallback (add_record) Illegal INP field
Illegal field value PV: mrk:mbbidirectpget devMbbiSoftCallback (init_record) Illegal INP field
Illegal field value PV: mrk:longinpget devLiSoftCallback (init_record) Illegal INP field
Illegal field value PV: mrk:bipget devBiSoftCallback (init_record) Illegal INP field
Illegal field value PV: mrk:mbbipget devMbbiSoftCallback (init_record) Illegal INP field
Illegal field value PV: mrk:stringinpget devSiSoftCallback (init_record) Illegal INP field
Illegal field value PV: mrk:aipget devAiSoftCallback (init_record) Illegal INP field
iocRun: All initialization complete
Stopping the IOC also gives an assertion failure.
These error messages are coming from the device initialization routines in the standard "Async Soft Channel" device support in Base, e.g. src/std/
Previously the pdsxt->add_record() routines were being called before the link type had been changed from PV_LINK to DB_LINK or CA_LINK, but it seems that is no longer the case. I think you should probably take a close look at how these input devices work in case there are issues in here related to record locking. To find out which device files are affected, grep for PN_LINK in the src/std/dev directory. Here's a database you can load into the softIoc to demonstrate the issue for yourself:
record(
{
field(
field(
}
record(
{
field(
field(
field(
field(
}
record(
{
field(
field(
}
record(
{
field(
field(
field(
field(
field(
field(
}
record(
{
field(
field(
}
record(stringin,...
mdavidsaver (mdavidsaver) wrote : | # |
I'll look into this. Would you consider writing a short unittest to exercise some of the dbNotify code? I'll confess I've not looked into it in enough detail.
- 12662. By Andrew Johnson
-
Export private dbLock*Ref() functions for tests
Andrew Johnson (anj) wrote : | # |
Thanks. I suspect the problems will not necessarily be in the dbNotify code (which already calls dbScanLock() on the target record as needed) but in supporting the PN_LINK types that these input device supports use. They only support local links, but a PN_LINK can cross lock-sets (i.e. a PN_LINK should never cause two lock-sets to be combined into one).
The simplest unit tests to write would be to check the Async Soft Channel input device support, which should be relatively simple to do using your dbUnitTest facility, I'll see what I can do. The Async Soft Channel output device support also need tests, but those will require local CA links to actually work, and I don't know how far you've got with allowing that (and they aren't relevant to this branch anyway).
I suspect the fix is going to involve making the add_record() routines convert a DB_LINK into a PN_LINK and adjusting the locksets as necessary, but remember that add_record() and del_record() are called for run-time link changes as well as at database initialization and these devices do support run-time changes.
The PN_LINK type isn't considered at all in my link-support changes, but eventually it probably should be. We should also modify Async Soft Channel output devices to use PN_LINKs as well as CA_LINKs, but that's a bit more work and again not relevant to merging this branch.
- Andrew
mdavidsaver (mdavidsaver) wrote : | # |
> I suspect the problems will not necessarily be in the dbNotify code
Agreed, but would be good to know when I break it.
> ... will require local CA links to actually work, and I don't know how far you've got with allowing that ...
It's in.
If/when you write your tests, please run them against the 3.16 main branch. I have a suspicion the Async stuff was broken earlier by the link parsing changes.
Andrew Johnson (anj) wrote : | # |
The database I posted above does load on a 3.16 softIoc without errors, I already tried that, and all of the records are in their own lock-sets as expected. Processing mrk:aipget correctly causes mrk:delayInc to process, but I'm not sure if the completion notification is still working, I'll check that this afternoon.
Andrew Johnson (anj) wrote : | # |
Here's a database that tests all of the Async Soft Chanel input device types. Run it with:
softIoc -d <file>.db
Then start the test with
dbpf anj:aipget.PROC 1
It traces all the ASC records as they process with a 1 second delay between each, and finishes by printing "Chain completed". Runs perfectly for me on the latest 3.16 branch. It may take me a few days to come up with a proper test program, but for now this should help you out.
record(
{
field(
field(
field(TPRO,1)
field(
}
record(
{
field(
field(
field(
field(
field(TPRO,1)
field(
}
record(
{
field(
field(
field(TPRO,1)
field(
}
record(
{
field(
field(
field(
field(
field(
field(
field(TPRO,1)
field(
}
record(
{
field(
field(
field(TPRO,1)
field(
}
record(
{
field(
field(
field(TPRO,1)
field(
}
record(
{
field(
field(
field(
}
record(
{
field(DLY0,1)
field(
}
- 12663. By mdavidsaver
-
dbLock: restore initialization of PV_LINK
PV_LINK -> DB_LINK must happen in doResolveLinks after add_record
mdavidsaver (mdavidsaver) wrote : | # |
Ok, I think this fixes the PN_LINK initialization issue by restoring the original init sequence.
- 12664. By Andrew Johnson
-
Make dbLockTest work when LOCKSET_DEBUG undefined
- 12665. By Andrew Johnson
-
Remove some unnecesary #includes
Andrew Johnson (anj) wrote : | # |
In dblsr() and dbLockShowLocked() you replaced all the calls to printf() with errlogPrintf(). This is wrong, these are both iocsh commands whose output should appear on stdout and/or stderr and hence be redirectable to a file, for example 'dblsr *,1>filename'. The use of errlogPrint() is correct in all the other routines since they get run in background threads, but iocsh commands should almost always use printf() or fprintf(stderr). This also removes the need to call errlogFlush() at the end of these routines.
I will commit a fix, still reviewing.
- 12666. By Andrew Johnson
-
Undo s/printf/
errlogPrintf/ in iocsh commands - 12667. By mdavidsaver
-
db/test: dbStressTest conditional TIME_STATS
Andrew Johnson (anj) wrote : | # |
Found another regression. This database works on the 3.16 branch and earlier, but not here:
record(ai, "$(user):ai") {
field(INP, "0")
}
record(ai, "$(user):src1") {
field(VAL, "1")
}
record(ai, "$(user):src2") {
field(VAL, "2")
}
record(stringout, "$(user):link1") {
field(VAL, "$(user):src1")
field(OUT, "$(user):ai.INP CA")
}
record(stringout, "$(user):link2") {
field(VAL, "$(user):src2 CP")
field(OUT, "$(user):ai.INP CA")
}
tux% bin/linux-
Starting iocInit
#######
## EPICS R3.16.0-DEV $$Date: Wed 2015-03-11 17:20:40 -0500 $$
## EPICS Base built Aug 21 2015
#######
iocRun: All initialization complete
epics> dbpf anj:link1.PROC 1
DBR_UCHAR: 1 0x1
epics> dbpf anj:link2.PROC 1
DBR_UCHAR: 1 0x1
epics> filename=
dbCa: eventCallback Logic Error
The stringout.OUT links must be CA links, you can't modify a link field value using a DB link.
Andrew Johnson (anj) wrote : | # |
Weird, arg.type is 22 (DBR_GR_SHORT) when the Logic Error gets raised. There are no changes to dbCa.c on this branch, so I don't know what exactly is causing this but it fails every time the ai.INP field connects as a CA link with the CP flag set (using dbpf from the iocshell or caput give the same error). Convert the CP flag into CA or make it a DB link and there's no error.
Here's the output from epicsStackTrace() at that point:
epics> dbpf anj:ai.INP 'anj:src1 CP'
DBR_STRING: "anj:src1 CP"
epics> filename=
dbCa: eventCallback Logic Error
Dumping a stack trace of thread 'CAC-event':
[ 0x7ffc66fa1903]: /local/
[ 0x7ffc6744cb7d]: /local/
[ 0x7ffc671fbb28]: /local/
[ 0x7ffc6744e745]: /local/
[ 0x7ffc674443f6]: /local/
[ 0x7ffc66f9cd54]: /local/
[ 0x3809e079d1]: /lib64/
[ 0x3809ae88fd]: /lib64/
mdavidsaver (mdavidsaver) wrote : | # |
I don't see this issue with my integration branch, so I suspect this is a rediscovery of the issue fixed by http://
Andrew Johnson (anj) wrote : | # |
Ahh, I'd forgotten about that fix; I agree, that makes perfect sense now. I will switch my testing back to the result of merging this into 3.16.
- 12668. By Andrew Johnson
-
db/test: Add missing dependency
Andrew Johnson (anj) wrote : | # |
Accepted as is, I'll wait for a final response from you on the assert/return status issue before merging.
Despite all my niggling issues, this is a really nice enhancement and seems to have been very well designed.
Thanks Michael!
- 12669. By mdavidsaver
-
dbLockTest: check additional recursive case
- 12670. By mdavidsaver
-
dbLock: better error check when recursive locking attempted
mdavidsaver (mdavidsaver) wrote : | # |
I've made two small changes as we discussed. I think it's now ready to
merge.
- 12671. By Andrew Johnson
-
Fix comment
- 12672. By Andrew Johnson
-
Undefine LOCKSET_DEBUG & LOCKSET_NOFREE
Andrew Johnson (anj) wrote : | # |
Merging. Please don't forget to commit a RELEASE_NOTES entry to the branch.
Preview Diff
1 | === modified file 'src/ioc/db/callback.c' |
2 | --- src/ioc/db/callback.c 2015-08-24 17:18:23 +0000 |
3 | +++ src/ioc/db/callback.c 2015-08-31 21:08:35 +0000 |
4 | @@ -24,7 +24,6 @@ |
5 | #include "dbDefs.h" |
6 | #include "epicsAtomic.h" |
7 | #include "epicsEvent.h" |
8 | -#include "epicsExit.h" |
9 | #include "epicsInterrupt.h" |
10 | #include "epicsRingPointer.h" |
11 | #include "epicsString.h" |
12 | |
13 | === modified file 'src/ioc/db/dbAccess.c' |
14 | --- src/ioc/db/dbAccess.c 2015-08-20 16:24:07 +0000 |
15 | +++ src/ioc/db/dbAccess.c 2015-08-31 21:08:35 +0000 |
16 | @@ -50,7 +50,7 @@ |
17 | #include "dbFldTypes.h" |
18 | #include "dbFldTypes.h" |
19 | #include "dbLink.h" |
20 | -#include "dbLock.h" |
21 | +#include "dbLockPvt.h" |
22 | #include "dbNotify.h" |
23 | #include "dbScan.h" |
24 | #include "dbServer.h" |
25 | @@ -944,6 +944,8 @@ |
26 | dbLinkInfo link_info; |
27 | DBADDR *pdbaddr = NULL; |
28 | dbCommon *precord = paddr->precord; |
29 | + dbCommon *lockrecs[2]; |
30 | + dbLocker locker; |
31 | dbFldDes *pfldDes = paddr->pfldDes; |
32 | long special = paddr->special; |
33 | struct link *plink = (struct link *)paddr->pfield; |
34 | @@ -956,6 +958,8 @@ |
35 | int isDevLink; |
36 | short scan; |
37 | |
38 | + STATIC_ASSERT(DBLOCKER_NALLOC>=2); |
39 | + |
40 | switch (dbrType) { |
41 | case DBR_CHAR: |
42 | case DBR_UCHAR: |
43 | @@ -992,10 +996,12 @@ |
44 | isDevLink = ellCount(&precord->rdes->devList) > 0 && |
45 | pfldDes->isDevLink; |
46 | |
47 | - dbLockSetGblLock(); |
48 | - dbLockSetRecordLock(precord); |
49 | - if (pdbaddr) |
50 | - dbLockSetRecordLock(pdbaddr->precord); |
51 | + memset(&locker, 0, sizeof(locker)); |
52 | + lockrecs[0] = precord; |
53 | + lockrecs[1] = pdbaddr ? pdbaddr->precord : NULL; |
54 | + dbLockerPrepare(&locker, lockrecs, 2); |
55 | + |
56 | + dbScanLockMany(&locker); |
57 | |
58 | scan = precord->scan; |
59 | |
60 | @@ -1044,7 +1050,7 @@ |
61 | switch (plink->type) { /* Old link type */ |
62 | case DB_LINK: |
63 | case CA_LINK: |
64 | - dbRemoveLink(plink); |
65 | + dbRemoveLink(&locker, precord, plink); /* link type becomes PV_LINK */ |
66 | break; |
67 | |
68 | case PV_LINK: |
69 | @@ -1091,7 +1097,7 @@ |
70 | |
71 | switch (plink->type) { /* New link type */ |
72 | case PV_LINK: |
73 | - dbAddLink(precord, plink, pfldDes->field_type, pdbaddr); |
74 | + dbAddLink(&locker, precord, plink, pfldDes->field_type, pdbaddr); |
75 | break; |
76 | |
77 | case CONSTANT: |
78 | @@ -1121,7 +1127,8 @@ |
79 | if (scan != precord->scan) |
80 | db_post_events(precord, &precord->scan, DBE_VALUE | DBE_LOG); |
81 | unlock: |
82 | - dbLockSetGblUnlock(); |
83 | + dbScanUnlockMany(&locker); |
84 | + dbLockerFinalize(&locker); |
85 | cleanup: |
86 | free(link_info.target); |
87 | return status; |
88 | |
89 | === modified file 'src/ioc/db/dbCaTest.c' |
90 | --- src/ioc/db/dbCaTest.c 2015-02-20 17:14:23 +0000 |
91 | +++ src/ioc/db/dbCaTest.c 2015-08-31 21:08:35 +0000 |
92 | @@ -87,10 +87,10 @@ |
93 | !dbIsAlias(pdbentry)) { |
94 | pdbRecordType = pdbentry->precordType; |
95 | precord = (dbCommon *)pdbentry->precnode->precord; |
96 | + dbScanLock(precord); |
97 | for (j=0; j<pdbRecordType->no_links; j++) { |
98 | pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[j]]; |
99 | plink = (DBLINK *)((char *)precord + pdbFldDes->offset); |
100 | - dbLockSetGblLock(); |
101 | if (plink->type == CA_LINK) { |
102 | ncalinks++; |
103 | pca = (caLink *)plink->value.pv_link.pvt; |
104 | @@ -135,8 +135,8 @@ |
105 | } |
106 | } |
107 | } |
108 | - dbLockSetGblUnlock(); |
109 | } |
110 | + dbScanUnlock(precord); |
111 | if (precordname) goto done; |
112 | } |
113 | status = dbNextRecord(pdbentry); |
114 | |
115 | === modified file 'src/ioc/db/dbChannel.c' |
116 | --- src/ioc/db/dbChannel.c 2015-03-18 21:48:07 +0000 |
117 | +++ src/ioc/db/dbChannel.c 2015-08-31 21:08:35 +0000 |
118 | @@ -19,7 +19,6 @@ |
119 | |
120 | #include "cantProceed.h" |
121 | #include "epicsAssert.h" |
122 | -#include "epicsExit.h" |
123 | #include "epicsString.h" |
124 | #include "errlog.h" |
125 | #include "freeList.h" |
126 | |
127 | === modified file 'src/ioc/db/dbCommon.dbd' |
128 | --- src/ioc/db/dbCommon.dbd 2012-12-10 15:25:58 +0000 |
129 | +++ src/ioc/db/dbCommon.dbd 2015-08-31 21:08:35 +0000 |
130 | @@ -92,6 +92,12 @@ |
131 | interest(4) |
132 | extra("ELLLIST mlis") |
133 | } |
134 | + field(BKLNK,DBF_NOACCESS) { |
135 | + prompt("Backwards link tracking") |
136 | + special(SPC_NOMOD) |
137 | + interest(4) |
138 | + extra("ELLLIST bklnk") |
139 | + } |
140 | field(DISP,DBF_UCHAR) { |
141 | prompt("Disable putField") |
142 | } |
143 | |
144 | === modified file 'src/ioc/db/dbLink.c' |
145 | --- src/ioc/db/dbLink.c 2015-02-03 06:38:23 +0000 |
146 | +++ src/ioc/db/dbLink.c 2015-08-31 21:08:35 +0000 |
147 | @@ -45,7 +45,7 @@ |
148 | #include "dbFldTypes.h" |
149 | #include "dbFldTypes.h" |
150 | #include "dbLink.h" |
151 | -#include "dbLock.h" |
152 | +#include "dbLockPvt.h" |
153 | #include "dbNotify.h" |
154 | #include "dbScan.h" |
155 | #include "dbStaticLib.h" |
156 | @@ -126,7 +126,7 @@ |
157 | |
158 | /***************************** Database Links *****************************/ |
159 | |
160 | -static long dbDbInitLink(struct link *plink, short dbfType) |
161 | +static long dbDbInitLink(struct dbCommon *precord, struct link *plink, short dbfType) |
162 | { |
163 | DBADDR dbaddr; |
164 | long status; |
165 | @@ -140,18 +140,25 @@ |
166 | pdbAddr = dbCalloc(1, sizeof(struct dbAddr)); |
167 | *pdbAddr = dbaddr; /* structure copy */ |
168 | plink->value.pv_link.pvt = pdbAddr; |
169 | - dbLockSetMerge(plink->value.pv_link.precord, pdbAddr->precord); |
170 | + ellAdd(&dbaddr.precord->bklnk, &plink->value.pv_link.backlinknode); |
171 | + /* merging into the same lockset is deferred to the caller. |
172 | + * cf. initPVLinks() |
173 | + */ |
174 | + dbLockSetMerge(NULL, precord, dbaddr.precord); |
175 | + assert(precord->lset->plockSet==dbaddr.precord->lset->plockSet); |
176 | return 0; |
177 | } |
178 | |
179 | -static void dbDbRemoveLink(struct link *plink) |
180 | +static void dbDbRemoveLink(dbLocker *locker, struct dbCommon *prec, struct link *plink) |
181 | { |
182 | - free(plink->value.pv_link.pvt); |
183 | + DBADDR *pdbAddr = (DBADDR *) plink->value.pv_link.pvt; |
184 | plink->value.pv_link.pvt = 0; |
185 | plink->value.pv_link.getCvt = 0; |
186 | plink->value.pv_link.lastGetdbrType = 0; |
187 | plink->type = PV_LINK; |
188 | - dbLockSetSplit(plink->value.pv_link.precord); |
189 | + ellDelete(&pdbAddr->precord->bklnk, &plink->value.pv_link.backlinknode); |
190 | + dbLockSetSplit(locker, prec, pdbAddr->precord); |
191 | + free(pdbAddr); |
192 | } |
193 | |
194 | static int dbDbIsLinkConnected(const struct link *plink) |
195 | @@ -380,7 +387,7 @@ |
196 | dbScanPassive(precord, paddr->precord); |
197 | } |
198 | |
199 | -lset dbDb_lset = { dbDbRemoveLink, |
200 | +lset dbDb_lset = { NULL, |
201 | dbDbIsLinkConnected, dbDbGetDBFtype, dbDbGetElements, dbDbGetValue, |
202 | dbDbGetControlLimits, dbDbGetGraphicLimits, dbDbGetAlarmLimits, |
203 | dbDbGetPrecision, dbDbGetUnits, dbDbGetAlarm, dbDbGetTimeStamp, |
204 | @@ -397,7 +404,7 @@ |
205 | |
206 | if (!(plink->value.pv_link.pvlMask & (pvlOptCA | pvlOptCP | pvlOptCPP))) { |
207 | /* Make it a DB link if possible */ |
208 | - if (!dbDbInitLink(plink, dbfType)) |
209 | + if (!dbDbInitLink(precord, plink, dbfType)) |
210 | return; |
211 | } |
212 | |
213 | @@ -422,7 +429,7 @@ |
214 | } |
215 | } |
216 | |
217 | -void dbAddLink(struct dbCommon *precord, struct link *plink, short dbfType, DBADDR *ptargetaddr) |
218 | +void dbAddLink(dbLocker *locker, struct dbCommon *precord, struct link *plink, short dbfType, DBADDR *ptargetaddr) |
219 | { |
220 | plink->value.pv_link.precord = precord; |
221 | |
222 | @@ -434,9 +441,10 @@ |
223 | |
224 | plink->type = DB_LINK; |
225 | plink->value.pv_link.pvt = ptargetaddr; |
226 | + ellAdd(&ptargetaddr->precord->bklnk, &plink->value.pv_link.backlinknode); |
227 | |
228 | /* target record is already locked in dbPutFieldLink() */ |
229 | - dbLockSetMerge(plink->value.pv_link.precord, ptargetaddr->precord); |
230 | + dbLockSetMerge(locker, plink->value.pv_link.precord, ptargetaddr->precord); |
231 | |
232 | return; |
233 | } |
234 | @@ -463,11 +471,11 @@ |
235 | return S_db_notFound; |
236 | } |
237 | |
238 | -void dbRemoveLink(struct link *plink) |
239 | +void dbRemoveLink(dbLocker *locker, dbCommon *prec, struct link *plink) |
240 | { |
241 | switch (plink->type) { |
242 | case DB_LINK: |
243 | - dbDbRemoveLink(plink); |
244 | + dbDbRemoveLink(locker, prec, plink); |
245 | break; |
246 | case CA_LINK: |
247 | dbCaRemoveLink(plink); |
248 | |
249 | === modified file 'src/ioc/db/dbLink.h' |
250 | --- src/ioc/db/dbLink.h 2015-02-03 06:38:23 +0000 |
251 | +++ src/ioc/db/dbLink.h 2015-08-31 21:08:35 +0000 |
252 | @@ -50,13 +50,15 @@ |
253 | #define dbGetSevr(PLINK, PSEVERITY) \ |
254 | dbGetAlarm((PLINK), NULL, (PSEVERITY)) |
255 | |
256 | +struct dbLocker; |
257 | + |
258 | epicsShareFunc void dbInitLink(struct dbCommon *precord, struct link *plink, |
259 | short dbfType); |
260 | -epicsShareFunc void dbAddLink(struct dbCommon *precord, struct link *plink, |
261 | +epicsShareFunc void dbAddLink(struct dbLocker *locker, struct dbCommon *precord, struct link *plink, |
262 | short dbfType, DBADDR *ptargetaddr); |
263 | epicsShareFunc long dbLoadLink(struct link *plink, short dbrType, |
264 | void *pbuffer); |
265 | -epicsShareFunc void dbRemoveLink(struct link *plink); |
266 | +epicsShareFunc void dbRemoveLink(struct dbLocker *locker, struct dbCommon *prec, struct link *plink); |
267 | epicsShareFunc long dbGetNelements(const struct link *plink, long *nelements); |
268 | epicsShareFunc int dbIsLinkConnected(const struct link *plink); |
269 | epicsShareFunc int dbGetLinkDBFtype(const struct link *plink); |
270 | |
271 | === modified file 'src/ioc/db/dbLock.c' |
272 | --- src/ioc/db/dbLock.c 2014-11-18 19:48:15 +0000 |
273 | +++ src/ioc/db/dbLock.c 2015-08-31 21:08:35 +0000 |
274 | @@ -7,41 +7,7 @@ |
275 | * and higher are distributed subject to a Software License Agreement found |
276 | * in file LICENSE that is included with this distribution. |
277 | \*************************************************************************/ |
278 | -/* dbLock.c */ |
279 | -/* Author: Marty Kraimer Date: 12MAR96 */ |
280 | - |
281 | -/************** DISCUSSION OF DYNAMIC LINK MODIFICATION ********************** |
282 | - |
283 | -A routine attempting to modify a link must do the following: |
284 | - |
285 | -Call dbLockSetGblLock before modifying any link and dbLockSetGblUnlock after. |
286 | -Call dbLockSetRecordLock for any record referenced during change. It MUST NOT UNLOCK |
287 | -Call dbLockSetSplit before changing any link that is originally a DB_LINK |
288 | -Call dbLockSetMerge if changed link becomes a DB_LINK. |
289 | - |
290 | -Since the purpose of lock sets is to prevent multiple thread from simultaneously |
291 | -accessing records in set, dynamically changing lock sets presents a problem. |
292 | - |
293 | -Three problems arise: |
294 | - |
295 | -1) Two threads simultaneoulsy trying to change lock sets |
296 | -2) Another thread has successfully issued a dbScanLock and currently owns it. |
297 | -3) While lock set is being changed, a thread issues a dbScanLock. |
298 | - |
299 | -solution: |
300 | - |
301 | -1) globalLock is locked during the entire time a thread is modifying lock sets |
302 | - |
303 | -2) lockSetModifyLock is locked whenever any fields in lockSet are being accessed |
304 | -or lockRecord.plockSet is being accessed. |
305 | - |
306 | -NOTE: |
307 | - |
308 | -dblsr may crash if executed while lock sets are being modified. |
309 | -It is NOT a good idea to make it more robust by issuing dbLockSetGblLock |
310 | -since this will delay all other threads. |
311 | -*****************************************************************************/ |
312 | - |
313 | |
314 | + |
315 | #include <stddef.h> |
316 | #include <stdlib.h> |
317 | #include <stdio.h> |
318 | @@ -51,9 +17,10 @@ |
319 | #include "dbDefs.h" |
320 | #include "ellLib.h" |
321 | #include "epicsAssert.h" |
322 | -#include "epicsExit.h" |
323 | +#include "epicsAtomic.h" |
324 | #include "epicsMutex.h" |
325 | #include "epicsPrint.h" |
326 | +#include "epicsSpin.h" |
327 | #include "epicsStdio.h" |
328 | #include "epicsThread.h" |
329 | #include "errMdef.h" |
330 | @@ -62,455 +29,857 @@ |
331 | #include "dbAccessDefs.h" |
332 | #include "dbAddr.h" |
333 | #include "dbBase.h" |
334 | +#include "dbLink.h" |
335 | #include "dbCommon.h" |
336 | #include "dbFldTypes.h" |
337 | -#include "dbLock.h" |
338 | +#include "dbLockPvt.h" |
339 | #include "dbStaticLib.h" |
340 | #include "link.h" |
341 | |
342 | - |
343 | -static int dbLockIsInitialized = FALSE; |
344 | - |
345 | -typedef enum { |
346 | - listTypeScanLock = 0, |
347 | - listTypeRecordLock = 1, |
348 | - listTypeFree = 2 |
349 | -} listType; |
350 | - |
351 | -#define nlistType listTypeFree + 1 |
352 | - |
353 | -static ELLLIST lockSetList[nlistType]; |
354 | -static epicsMutexId globalLock; |
355 | -static epicsMutexId lockSetModifyLock; |
356 | -static unsigned long id = 0; |
357 | -static char *msstring[4]={"NMS","MS","MSI","MSS"}; |
358 | - |
359 | -typedef enum { |
360 | - lockSetStateFree=0, lockSetStateScanLock, lockSetStateRecordLock |
361 | -} lockSetState; |
362 | - |
363 | -typedef struct lockSet { |
364 | - ELLNODE node; |
365 | - ELLLIST lockRecordList; |
366 | - epicsMutexId lock; |
367 | - unsigned long id; |
368 | - listType type; |
369 | - lockSetState state; |
370 | - epicsThreadId thread_id; |
371 | - dbCommon *precord; |
372 | - int nRecursion; |
373 | - int nWaiting; |
374 | - int trace; /*For field TPRO*/ |
375 | -} lockSet; |
376 | - |
377 | -/* dbCommon.LSET is a plockRecord */ |
378 | -typedef struct lockRecord { |
379 | - ELLNODE node; |
380 | - lockSet *plockSet; |
381 | - dbCommon *precord; |
382 | -} lockRecord; |
383 | +typedef struct dbScanLockNode dbScanLockNode; |
384 | + |
385 | +static epicsThreadOnceId dbLockOnceInit = EPICS_THREAD_ONCE_INIT; |
386 | + |
387 | +static ELLLIST lockSetsActive; /* in use */ |
388 | +#ifndef LOCKSET_NOFREE |
389 | +static ELLLIST lockSetsFree; /* free list */ |
390 | +#endif |
391 | + |
392 | +/* Guard the global list */ |
393 | +static epicsMutexId lockSetsGuard; |
394 | + |
395 | +#ifndef LOCKSET_NOCNT |
396 | +/* Counter which we increment whenever |
397 | + * any lockRecord::plockSet is changed. |
398 | + * An optimization to avoid checking lockSet |
399 | + * associations when no links have changed. |
400 | + */ |
401 | +static size_t recomputeCnt; |
402 | +#endif |
403 | |
404 | /*private routines */ |
405 | -static void dbLockInitialize(void) |
406 | -{ |
407 | - int i; |
408 | - |
409 | - if(dbLockIsInitialized) return; |
410 | - for(i=0; i< nlistType; i++) ellInit(&lockSetList[i]); |
411 | - globalLock = epicsMutexMustCreate(); |
412 | - lockSetModifyLock = epicsMutexMustCreate(); |
413 | - dbLockIsInitialized = TRUE; |
414 | -} |
415 | - |
416 | -static lockSet * allocLockSet( |
417 | - lockRecord *plockRecord, listType type, |
418 | - lockSetState state, epicsThreadId thread_id) |
419 | -{ |
420 | - lockSet *plockSet; |
421 | - |
422 | - assert(dbLockIsInitialized); |
423 | - plockSet = (lockSet *)ellFirst(&lockSetList[listTypeFree]); |
424 | - if(plockSet) { |
425 | - ellDelete(&lockSetList[listTypeFree],&plockSet->node); |
426 | - } else { |
427 | - plockSet = dbCalloc(1,sizeof(lockSet)); |
428 | - plockSet->lock = epicsMutexMustCreate(); |
429 | - } |
430 | - ellInit(&plockSet->lockRecordList); |
431 | - plockRecord->plockSet = plockSet; |
432 | - id++; |
433 | - plockSet->id = id; |
434 | - plockSet->type = type; |
435 | - plockSet->state = state; |
436 | - plockSet->thread_id = thread_id; |
437 | - plockSet->precord = 0; |
438 | - plockSet->nRecursion = 0; |
439 | - plockSet->nWaiting = 0; |
440 | - ellAdd(&plockSet->lockRecordList,&plockRecord->node); |
441 | - ellAdd(&lockSetList[type],&plockSet->node); |
442 | - return(plockSet); |
443 | +static void dbLockOnce(void* ignore) |
444 | +{ |
445 | + lockSetsGuard = epicsMutexMustCreate(); |
446 | +} |
447 | + |
448 | +/* global ID number assigned to each lockSet on creation. |
449 | + * When the free-list is in use will never exceed |
450 | + * the number of records +1. |
451 | + * Without the free-list it can roll over, potentially |
452 | + * leading to duplicate IDs. |
453 | + */ |
454 | +static size_t next_id = 1; |
455 | + |
456 | +static lockSet* makeSet(void) |
457 | +{ |
458 | + lockSet *ls; |
459 | + int iref; |
460 | + epicsMutexMustLock(lockSetsGuard); |
461 | +#ifndef LOCKSET_NOFREE |
462 | + ls = (lockSet*)ellGet(&lockSetsFree); |
463 | + if(!ls) { |
464 | + epicsMutexUnlock(lockSetsGuard); |
465 | +#endif |
466 | + |
467 | + ls=dbCalloc(1,sizeof(*ls)); |
468 | + ellInit(&ls->lockRecordList); |
469 | + ls->lock = epicsMutexMustCreate(); |
470 | + ls->id = epicsAtomicIncrSizeT(&next_id); |
471 | + |
472 | +#ifndef LOCKSET_NOFREE |
473 | + epicsMutexMustLock(lockSetsGuard); |
474 | + } |
475 | +#endif |
476 | + /* the initial reference for the first lockRecord */ |
477 | + iref = epicsAtomicIncrIntT(&ls->refcount); |
478 | + ellAdd(&lockSetsActive, &ls->node); |
479 | + epicsMutexUnlock(lockSetsGuard); |
480 | + |
481 | + assert(ls->id>0); |
482 | + assert(iref>0); |
483 | + assert(ellCount(&ls->lockRecordList)==0); |
484 | + |
485 | + return ls; |
486 | +} |
487 | + |
488 | +unsigned long dbLockGetRefs(struct dbCommon* prec) |
489 | +{ |
490 | + return (unsigned long)epicsAtomicGetIntT(&prec->lset->plockSet->refcount); |
491 | +} |
492 | + |
493 | +unsigned long dbLockCountSets(void) |
494 | +{ |
495 | + unsigned long count; |
496 | + epicsMutexMustLock(lockSetsGuard); |
497 | + count = (unsigned long)ellCount(&lockSetsActive); |
498 | + epicsMutexUnlock(lockSetsGuard); |
499 | + return count; |
500 | +} |
501 | + |
502 | +/* caller must lock accessLock.*/ |
503 | +void dbLockIncRef(lockSet* ls) |
504 | +{ |
505 | + int cnt = epicsAtomicIncrIntT(&ls->refcount); |
506 | + if(cnt<=1) { |
507 | + errlogPrintf("dbLockIncRef(%p) on dead lockSet. refs: %d\n", ls, cnt); |
508 | + cantProceed(NULL); |
509 | + } |
510 | +} |
511 | + |
512 | +/* caller must lock accessLock. |
513 | + * lockSet must *not* be locked |
514 | + */ |
515 | +void dbLockDecRef(lockSet *ls) |
516 | +{ |
517 | + int cnt = epicsAtomicDecrIntT(&ls->refcount); |
518 | + assert(cnt>=0); |
519 | + |
520 | + if(cnt) |
521 | + return; |
522 | + |
523 | + /* lockSet is unused and will be free'd */ |
524 | + |
525 | + /* not necessary as no one else (should) hold a reference, |
526 | + * but lock anyway to quiet valgrind |
527 | + */ |
528 | + epicsMutexMustLock(ls->lock); |
529 | + |
530 | + if(ellCount(&ls->lockRecordList)!=0) { |
531 | + errlogPrintf("dbLockDecRef(%p) would free lockSet with %d records\n", |
532 | + ls, ellCount(&ls->lockRecordList)); |
533 | + cantProceed(NULL); |
534 | + } |
535 | + |
536 | + epicsMutexUnlock(ls->lock); |
537 | + |
538 | + epicsMutexMustLock(lockSetsGuard); |
539 | + ellDelete(&lockSetsActive, &ls->node); |
540 | +#ifndef LOCKSET_NOFREE |
541 | + ellAdd(&lockSetsFree, &ls->node); |
542 | +#else |
543 | + epicsMutexDestroy(ls->lock); |
544 | + memset(ls, 0, sizeof(*ls)); /* paranoia */ |
545 | + free(ls); |
546 | +#endif |
547 | + epicsMutexUnlock(lockSetsGuard); |
548 | +} |
549 | + |
550 | +lockSet* dbLockGetRef(lockRecord *lr) |
551 | +{ |
552 | + lockSet *ls; |
553 | + epicsSpinLock(lr->spin); |
554 | + ls = lr->plockSet; |
555 | + dbLockIncRef(ls); |
556 | + epicsSpinUnlock(lr->spin); |
557 | + return ls; |
558 | } |
559 | |
560 | unsigned long dbLockGetLockId(dbCommon *precord) |
561 | { |
562 | - lockRecord *plockRecord = precord->lset; |
563 | - lockSet *plockSet; |
564 | - long id = 0; |
565 | - |
566 | - assert(plockRecord); |
567 | - epicsMutexMustLock(lockSetModifyLock); |
568 | - plockSet = plockRecord->plockSet; |
569 | - if(plockSet) id = plockSet->id; |
570 | - epicsMutexUnlock(lockSetModifyLock); |
571 | - return(id); |
572 | -} |
573 | - |
574 | |
575 | -void dbLockSetGblLock(void) |
576 | -{ |
577 | - assert(dbLockIsInitialized); |
578 | - epicsMutexMustLock(globalLock); |
579 | -} |
580 | - |
581 | -void dbLockSetGblUnlock(void) |
582 | -{ |
583 | - lockSet *plockSet; |
584 | - lockSet *pnext; |
585 | - epicsMutexMustLock(lockSetModifyLock); |
586 | - plockSet = (lockSet *)ellFirst(&lockSetList[listTypeRecordLock]); |
587 | - while(plockSet) { |
588 | - pnext = (lockSet *)ellNext(&plockSet->node); |
589 | - ellDelete(&lockSetList[listTypeRecordLock],&plockSet->node); |
590 | - plockSet->type = listTypeScanLock; |
591 | - plockSet->state = lockSetStateFree; |
592 | - plockSet->thread_id = 0; |
593 | - plockSet->precord = 0; |
594 | - plockSet->nRecursion = 0; |
595 | - plockSet->nWaiting = 0; |
596 | - ellAdd(&lockSetList[listTypeScanLock],&plockSet->node); |
597 | - plockSet = pnext; |
598 | - } |
599 | - epicsMutexUnlock(lockSetModifyLock); |
600 | - epicsMutexUnlock(globalLock); |
601 | - return; |
602 | -} |
603 | - |
604 | -void dbLockSetRecordLock(dbCommon *precord) |
605 | -{ |
606 | - lockRecord *plockRecord = precord->lset; |
607 | - lockSet *plockSet; |
608 | - |
609 | - /*Must make sure that no other thread has lock*/ |
610 | - assert(plockRecord); |
611 | - epicsMutexMustLock(lockSetModifyLock); |
612 | - plockSet = plockRecord->plockSet; |
613 | - assert(plockSet); |
614 | - if(plockSet->type==listTypeRecordLock) { |
615 | - epicsMutexUnlock(lockSetModifyLock); |
616 | - return; |
617 | - } |
618 | - assert(plockSet->thread_id!=epicsThreadGetIdSelf()); |
619 | - plockSet->state = lockSetStateRecordLock; |
620 | - /*Wait until owner finishes and all waiting get to change state*/ |
621 | - while(1) { |
622 | - epicsMutexUnlock(lockSetModifyLock); |
623 | - epicsMutexMustLock(plockSet->lock); |
624 | - epicsMutexUnlock(plockSet->lock); |
625 | - epicsMutexMustLock(lockSetModifyLock); |
626 | - if(plockSet->nWaiting == 0 && plockSet->nRecursion==0) break; |
627 | - epicsThreadSleep(.1); |
628 | - } |
629 | - assert(plockSet->nWaiting == 0 && plockSet->nRecursion==0); |
630 | - assert(plockSet->type==listTypeScanLock); |
631 | - assert(plockSet->state==lockSetStateRecordLock); |
632 | - ellDelete(&lockSetList[plockSet->type],&plockSet->node); |
633 | - ellAdd(&lockSetList[listTypeRecordLock],&plockSet->node); |
634 | - plockSet->type = listTypeRecordLock; |
635 | - plockSet->thread_id = epicsThreadGetIdSelf(); |
636 | - plockSet->precord = 0; |
637 | - epicsMutexUnlock(lockSetModifyLock); |
638 | -} |
639 | - |
640 | |
641 | + unsigned long id=0; |
642 | + epicsSpinLock(precord->lset->spin); |
643 | + id = precord->lset->plockSet->id; |
644 | + epicsSpinUnlock(precord->lset->spin); |
645 | + return id; |
646 | +} |
647 | + |
648 | void dbScanLock(dbCommon *precord) |
649 | { |
650 | - lockRecord *plockRecord = precord->lset; |
651 | - lockSet *plockSet; |
652 | - epicsMutexLockStatus status; |
653 | - epicsThreadId idSelf = epicsThreadGetIdSelf(); |
654 | - |
655 | - /* |
656 | - * If this assertion is failing it is likely because iocInit |
657 | - * has not completed. It must complete before normal record |
658 | - * processing is possible. Consider using an initHook to |
659 | - * detect when this occurs. |
660 | + int cnt; |
661 | + lockRecord * const lr = precord->lset; |
662 | + lockSet *ls; |
663 | + |
664 | + ls = dbLockGetRef(lr); |
665 | + assert(epicsAtomicGetIntT(&ls->refcount)>0); |
666 | + |
667 | +retry: |
668 | + epicsMutexMustLock(ls->lock); |
669 | + |
670 | + epicsSpinLock(lr->spin); |
671 | + if(ls!=lr->plockSet) { |
672 | + /* oops, collided with recompute. |
673 | + * take a reference to the new lockSet. |
674 | + */ |
675 | + lockSet *ls2 = lr->plockSet; |
676 | + int newcnt = epicsAtomicIncrIntT(&ls2->refcount); |
677 | + assert(newcnt>=2); /* at least lockRecord and us */ |
678 | + epicsSpinUnlock(lr->spin); |
679 | + |
680 | + epicsMutexUnlock(ls->lock); |
681 | + dbLockDecRef(ls); |
682 | + |
683 | + ls = ls2; |
684 | + goto retry; |
685 | + } |
686 | + epicsSpinUnlock(lr->spin); |
687 | + |
688 | + /* Release reference taken within this |
689 | + * function. The count will *never* fall to zero |
690 | + * as the lockRecords can't be changed while |
691 | + * we hold the lock. |
692 | */ |
693 | - assert(dbLockIsInitialized); |
694 | - while(1) { |
695 | - epicsMutexMustLock(lockSetModifyLock); |
696 | - plockSet = plockRecord->plockSet; |
697 | - if(!plockSet) goto getGlobalLock; |
698 | - switch(plockSet->state) { |
699 | - case lockSetStateFree: |
700 | - status = epicsMutexTryLock(plockSet->lock); |
701 | - assert(status==epicsMutexLockOK); |
702 | - plockSet->nRecursion = 1; |
703 | - plockSet->thread_id = idSelf; |
704 | - plockSet->precord = precord; |
705 | - plockSet->state = lockSetStateScanLock; |
706 | - epicsMutexUnlock(lockSetModifyLock); |
707 | - return; |
708 | - case lockSetStateScanLock: |
709 | - if(plockSet->thread_id!=idSelf) { |
710 | - plockSet->nWaiting +=1; |
711 | - epicsMutexUnlock(lockSetModifyLock); |
712 | - epicsMutexMustLock(plockSet->lock); |
713 | - epicsMutexMustLock(lockSetModifyLock); |
714 | - plockSet->nWaiting -=1; |
715 | - if(plockSet->state==lockSetStateRecordLock) { |
716 | - epicsMutexUnlock(plockSet->lock); |
717 | - goto getGlobalLock; |
718 | - } |
719 | - assert(plockSet->state==lockSetStateScanLock); |
720 | - plockSet->nRecursion = 1; |
721 | - plockSet->thread_id = idSelf; |
722 | - plockSet->precord = precord; |
723 | - } else { |
724 | - plockSet->nRecursion += 1; |
725 | - } |
726 | - epicsMutexUnlock(lockSetModifyLock); |
727 | - return; |
728 | - case lockSetStateRecordLock: |
729 | - /*Only recursive locking is permitted*/ |
730 | - if((plockSet->nRecursion==0) || (plockSet->thread_id!=idSelf)) |
731 | - goto getGlobalLock; |
732 | - plockSet->nRecursion += 1; |
733 | - epicsMutexUnlock(lockSetModifyLock); |
734 | - return; |
735 | - default: |
736 | - cantProceed("dbScanLock. Bad case choice"); |
737 | - } |
738 | -getGlobalLock: |
739 | - epicsMutexUnlock(lockSetModifyLock); |
740 | - epicsMutexMustLock(globalLock); |
741 | - epicsMutexUnlock(globalLock); |
742 | + cnt = epicsAtomicDecrIntT(&ls->refcount); |
743 | + assert(cnt>0); |
744 | + |
745 | +#ifdef LOCKSET_DEBUG |
746 | + if(ls->owner) { |
747 | + assert(ls->owner==epicsThreadGetIdSelf()); |
748 | + assert(ls->ownercount>=1); |
749 | + ls->ownercount++; |
750 | + } else { |
751 | + assert(ls->ownercount==0); |
752 | + ls->owner = epicsThreadGetIdSelf(); |
753 | + ls->ownercount = 1; |
754 | } |
755 | +#endif |
756 | } |
757 | - |
758 | |
759 | + |
760 | void dbScanUnlock(dbCommon *precord) |
761 | { |
762 | - lockRecord *plockRecord = precord->lset; |
763 | - lockSet *plockSet; |
764 | - |
765 | - assert(plockRecord); |
766 | - epicsMutexMustLock(lockSetModifyLock); |
767 | - plockSet = plockRecord->plockSet; |
768 | - assert(plockSet); |
769 | - assert(epicsThreadGetIdSelf()==plockSet->thread_id); |
770 | - assert(plockSet->nRecursion>=1); |
771 | - plockSet->nRecursion -= 1; |
772 | - if(plockSet->nRecursion==0) { |
773 | - plockSet->thread_id = 0; |
774 | - plockSet->precord = 0; |
775 | - if((plockSet->state == lockSetStateScanLock) |
776 | - && (plockSet->nWaiting==0)) plockSet->state = lockSetStateFree; |
777 | - epicsMutexUnlock(plockSet->lock); |
778 | - } |
779 | - epicsMutexUnlock(lockSetModifyLock); |
780 | - return; |
781 | -} |
782 | - |
783 | -static lockRecord *lockRecordAlloc; |
784 | + lockSet *ls = precord->lset->plockSet; |
785 | + dbLockIncRef(ls); |
786 | +#ifdef LOCKSET_DEBUG |
787 | + assert(ls->owner==epicsThreadGetIdSelf()); |
788 | + assert(ls->ownercount>=1); |
789 | + ls->ownercount--; |
790 | + if(ls->ownercount==0) |
791 | + ls->owner = NULL; |
792 | +#endif |
793 | + epicsMutexUnlock(ls->lock); |
794 | + dbLockDecRef(ls); |
795 | +} |
796 | + |
797 | +static |
798 | +int lrrcompare(const void *rawA, const void *rawB) |
799 | +{ |
800 | + const lockRecordRef *refA=rawA, *refB=rawB; |
801 | + const lockSet *A=refA->plockSet, *B=refB->plockSet; |
802 | + if(!A && !B) |
803 | + return 0; /* NULL == NULL */ |
804 | + else if(!A) |
805 | + return 1; /* NULL > !NULL */ |
806 | + else if(!B) |
807 | + return -1; /* !NULL < NULL */ |
808 | + else if(A < B) |
809 | + return -1; |
810 | + else if(A > B) |
811 | + return 1; |
812 | + else |
813 | + return 0; |
814 | +} |
815 | + |
816 | +/* Call w/ update=1 before locking to update cached lockSet entries. |
817 | + * Call w/ update=0 after locking to verify that lockRecord weren't updated |
818 | + */ |
819 | +static |
820 | +int dbLockUpdateRefs(dbLocker *locker, int update) |
821 | +{ |
822 | + int changed = 0; |
823 | + size_t i, nlock = locker->maxrefs; |
824 | + |
825 | +#ifndef LOCKSET_NOCNT |
826 | + const size_t recomp = epicsAtomicGetSizeT(&recomputeCnt); |
827 | + if(locker->recomp!=recomp) { |
828 | +#endif |
829 | + /* some lockset recompute happened. |
830 | + * must re-check our references. |
831 | + */ |
832 | + |
833 | + for(i=0; i<nlock; i++) { |
834 | + lockRecordRef *ref = &locker->refs[i]; |
835 | + lockSet *oldref = NULL; |
836 | + if(!ref->plr) { /* this lockRecord slot not used */ |
837 | + assert(!ref->plockSet); |
838 | + continue; |
839 | + } |
840 | + |
841 | + epicsSpinLock(ref->plr->spin); |
842 | + if(ref->plockSet!=ref->plr->plockSet) { |
843 | + changed = 1; |
844 | + if(update) { |
845 | + /* exchange saved lockSet reference */ |
846 | + oldref = ref->plockSet; /* will be NULL on first iteration */ |
847 | + ref->plockSet = ref->plr->plockSet; |
848 | + dbLockIncRef(ref->plockSet); |
849 | + } |
850 | + } |
851 | + epicsSpinUnlock(ref->plr->spin); |
852 | + if(oldref) |
853 | + dbLockDecRef(oldref); |
854 | + if(!update && changed) |
855 | + return changed; |
856 | + } |
857 | +#ifndef LOCKSET_NOCNT |
858 | + /* Use the value captured before we started. |
859 | + * If it has changed in the intrim we will catch this later |
860 | + * during the update==0 pass (which triggers a re-try) |
861 | + */ |
862 | + if(update) |
863 | + locker->recomp = recomp; |
864 | + } |
865 | +#endif |
866 | + |
867 | + if(changed && update) { |
868 | + qsort(locker->refs, nlock, sizeof(lockRecordRef), |
869 | + &lrrcompare); |
870 | + } |
871 | + return changed; |
872 | +} |
873 | + |
874 | +void dbLockerPrepare(struct dbLocker *locker, |
875 | + struct dbCommon **precs, |
876 | + size_t nrecs) |
877 | +{ |
878 | + size_t i; |
879 | + locker->maxrefs = nrecs; |
880 | + /* intentionally spoil the recomp count to ensure that |
881 | + * references will be updated this first time |
882 | + */ |
883 | +#ifndef LOCKSET_NOCNT |
884 | + locker->recomp = epicsAtomicGetSizeT(&recomputeCnt)-1; |
885 | +#endif |
886 | + |
887 | + for(i=0; i<nrecs; i++) { |
888 | + locker->refs[i].plr = precs[i] ? precs[i]->lset : NULL; |
889 | + } |
890 | + |
891 | + /* acquire a reference to all lockRecords */ |
892 | + dbLockUpdateRefs(locker, 1); |
893 | +} |
894 | + |
895 | +dbLocker *dbLockerAlloc(dbCommon **precs, |
896 | + size_t nrecs, |
897 | + unsigned int flags) |
898 | +{ |
899 | + size_t Nextra = nrecs>DBLOCKER_NALLOC ? nrecs-DBLOCKER_NALLOC : 0; |
900 | + dbLocker *locker = calloc(1, sizeof(*locker)+Nextra*sizeof(lockRecordRef)); |
901 | + |
902 | + if(locker) |
903 | + dbLockerPrepare(locker, precs, nrecs); |
904 | + |
905 | + return locker; |
906 | +} |
907 | + |
908 | +void dbLockerFinalize(dbLocker *locker) |
909 | +{ |
910 | + size_t i; |
911 | + assert(ellCount(&locker->locked)==0); |
912 | + |
913 | + /* release references taken in dbLockUpdateRefs() */ |
914 | + for(i=0; i<locker->maxrefs; i++) { |
915 | + if(locker->refs[i].plockSet) |
916 | + dbLockDecRef(locker->refs[i].plockSet); |
917 | + } |
918 | +} |
919 | + |
920 | +void dbLockerFree(dbLocker *locker) |
921 | +{ |
922 | + dbLockerFinalize(locker); |
923 | + free(locker); |
924 | +} |
925 | + |
926 | +/* Lock the given list of records. |
927 | + * This function modifies its arguments. |
928 | + */ |
929 | +void dbScanLockMany(dbLocker* locker) |
930 | +{ |
931 | + size_t i, nlock = locker->maxrefs; |
932 | + lockSet *plock; |
933 | +#ifdef LOCKSET_DEBUG |
934 | + const epicsThreadId myself = epicsThreadGetIdSelf(); |
935 | +#endif |
936 | + |
937 | + if(ellCount(&locker->locked)!=0) { |
938 | + cantProceed("dbScanLockMany(%p) already locked. Recursive locking not allowed", locker); |
939 | + return; |
940 | + } |
941 | + |
942 | +retry: |
943 | + assert(ellCount(&locker->locked)==0); |
944 | + dbLockUpdateRefs(locker, 1); |
945 | + |
946 | + for(i=0, plock=NULL; i<nlock; i++) { |
947 | + lockRecordRef *ref = &locker->refs[i]; |
948 | + |
949 | + /* skip duplicates (same lockSet |
950 | + * referenced by more than one lockRecord). |
951 | + * Sorting will group these together. |
952 | + */ |
953 | + if(!ref->plr || (plock && plock==ref->plockSet)) |
954 | + continue; |
955 | + plock = ref->plockSet; |
956 | + |
957 | + epicsMutexMustLock(plock->lock); |
958 | + assert(plock->ownerlocker==NULL); |
959 | + plock->ownerlocker = locker; |
960 | + ellAdd(&locker->locked, &plock->lockernode); |
961 | + /* An extra ref for the locked list */ |
962 | + dbLockIncRef(plock); |
963 | + |
964 | +#ifdef LOCKSET_DEBUG |
965 | + if(plock->owner) { |
966 | + if(plock->owner!=myself || plock->ownercount<1) { |
967 | + errlogPrintf("dbScanLockMany(%p) ownership violation %p (%p) %u\n", |
968 | + locker, plock->owner, myself, plock->ownercount); |
969 | + cantProceed(NULL); |
970 | + } |
971 | + plock->ownercount++; |
972 | + } else { |
973 | + assert(plock->ownercount==0); |
974 | + plock->owner = myself; |
975 | + plock->ownercount = 1; |
976 | + } |
977 | +#endif |
978 | + |
979 | + } |
980 | + |
981 | + if(dbLockUpdateRefs(locker, 0)) { |
982 | + /* oops, collided with recompute */ |
983 | + dbScanUnlockMany(locker); |
984 | + goto retry; |
985 | + } |
986 | + if(nlock!=0 && ellCount(&locker->locked)<=0) { |
987 | + /* if we have at least one lockRecord, then we will always lock |
988 | + * at least its present lockSet |
989 | + */ |
990 | + errlogPrintf("dbScanLockMany(%p) didn't lock anything\n", locker); |
991 | + cantProceed(NULL); |
992 | + } |
993 | +} |
994 | + |
995 | +void dbScanUnlockMany(dbLocker* locker) |
996 | +{ |
997 | + ELLNODE *cur; |
998 | +#ifdef LOCKSET_DEBUG |
999 | + const epicsThreadId myself = epicsThreadGetIdSelf(); |
1000 | +#endif |
1001 | + |
1002 | + while((cur=ellGet(&locker->locked))!=NULL) { |
1003 | + lockSet *plock = CONTAINER(cur, lockSet, lockernode); |
1004 | + |
1005 | + assert(plock->ownerlocker==locker); |
1006 | + plock->ownerlocker = NULL; |
1007 | +#ifdef LOCKSET_DEBUG |
1008 | + assert(plock->owner==myself); |
1009 | + assert(plock->ownercount>=1); |
1010 | + plock->ownercount--; |
1011 | + if(plock->ownercount==0) |
1012 | + plock->owner = NULL; |
1013 | +#endif |
1014 | + |
1015 | + epicsMutexUnlock(plock->lock); |
1016 | + /* release ref for locked list */ |
1017 | + dbLockDecRef(plock); |
1018 | + } |
1019 | +} |
1020 | + |
1021 | +typedef int (*reciter)(void*, DBENTRY*); |
1022 | +static int forEachRecord(void *priv, dbBase *pdbbase, reciter fn) |
1023 | +{ |
1024 | + long status; |
1025 | + int ret = 0; |
1026 | + DBENTRY dbentry; |
1027 | + dbInitEntry(pdbbase,&dbentry); |
1028 | + status = dbFirstRecordType(&dbentry); |
1029 | + while(!status) |
1030 | + { |
1031 | + status = dbFirstRecord(&dbentry); |
1032 | + while(!status) |
1033 | + { |
1034 | + /* skip alias names */ |
1035 | + if(!dbentry.precnode->recordname[0] || dbentry.precnode->flags & DBRN_FLAGS_ISALIAS) { |
1036 | + /* skip */ |
1037 | + } else { |
1038 | + ret = fn(priv, &dbentry); |
1039 | + if(ret) |
1040 | + goto done; |
1041 | + } |
1042 | + |
1043 | + status = dbNextRecord(&dbentry); |
1044 | + } |
1045 | + |
1046 | + status = dbNextRecordType(&dbentry); |
1047 | + } |
1048 | +done: |
1049 | + dbFinishEntry(&dbentry); |
1050 | + return ret; |
1051 | +} |
1052 | + |
1053 | +static int createLockRecord(void* junk, DBENTRY* pdbentry) |
1054 | +{ |
1055 | + dbCommon *prec = pdbentry->precnode->precord; |
1056 | + lockRecord *lrec; |
1057 | + assert(!prec->lset); |
1058 | + |
1059 | + /* TODO: one allocation for all records? */ |
1060 | + lrec = callocMustSucceed(1, sizeof(*lrec), "lockRecord"); |
1061 | + lrec->spin = epicsSpinCreate(); |
1062 | + if(!lrec->spin) |
1063 | + cantProceed("no memory for spinlock in lockRecord"); |
1064 | + |
1065 | + lrec->precord = prec; |
1066 | + |
1067 | + prec->lset = lrec; |
1068 | + |
1069 | + prec->lset->plockSet = makeSet(); |
1070 | + ellAdd(&prec->lset->plockSet->lockRecordList, &prec->lset->node); |
1071 | + return 0; |
1072 | +} |
1073 | |
1074 | void dbLockInitRecords(dbBase *pdbbase) |
1075 | { |
1076 | - dbRecordType *pdbRecordType; |
1077 | - int nrecords = 0; |
1078 | - lockRecord *plockRecord; |
1079 | - |
1080 | - dbLockInitialize(); |
1081 | - /*Allocate and initialize a lockRecord for each record instance*/ |
1082 | - for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); |
1083 | - pdbRecordType; |
1084 | - pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { |
1085 | - |
1086 | - nrecords += ellCount(&pdbRecordType->recList) |
1087 | - - pdbRecordType->no_aliases; |
1088 | - } |
1089 | - |
1090 | - /*Allocate all of them at once */ |
1091 | - lockRecordAlloc = plockRecord = dbCalloc(nrecords,sizeof(lockRecord)); |
1092 | - for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); |
1093 | - pdbRecordType; |
1094 | - pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { |
1095 | - dbRecordNode *pdbRecordNode; |
1096 | - |
1097 | - for (pdbRecordNode=(dbRecordNode *)ellFirst(&pdbRecordType->recList); |
1098 | - pdbRecordNode; |
1099 | - pdbRecordNode = (dbRecordNode *)ellNext(&pdbRecordNode->node)) { |
1100 | - dbCommon *precord = pdbRecordNode->precord; |
1101 | - |
1102 | - if (!precord->name[0] || |
1103 | - pdbRecordNode->flags & DBRN_FLAGS_ISALIAS) |
1104 | - continue; |
1105 | - |
1106 | - plockRecord->precord = precord; |
1107 | - precord->lset = plockRecord; |
1108 | - plockRecord++; |
1109 | - } |
1110 | - } |
1111 | - |
1112 | - for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); |
1113 | - pdbRecordType; |
1114 | - pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { |
1115 | - dbRecordNode *pdbRecordNode; |
1116 | - |
1117 | - for (pdbRecordNode=(dbRecordNode *)ellFirst(&pdbRecordType->recList); |
1118 | - pdbRecordNode; |
1119 | - pdbRecordNode = (dbRecordNode *)ellNext(&pdbRecordNode->node)) { |
1120 | - dbCommon *precord = pdbRecordNode->precord; |
1121 | - |
1122 | - if (!precord->name[0] || |
1123 | - pdbRecordNode->flags & DBRN_FLAGS_ISALIAS) |
1124 | - continue; |
1125 | - |
1126 | - plockRecord = precord->lset; |
1127 | - if (!plockRecord->plockSet) |
1128 | - allocLockSet(plockRecord, listTypeScanLock, lockSetStateFree, 0); |
1129 | - } |
1130 | - } |
1131 | + epicsThreadOnce(&dbLockOnceInit, &dbLockOnce, NULL); |
1132 | + |
1133 | + /* create all lockRecords and lockSets */ |
1134 | + forEachRecord(NULL, pdbbase, &createLockRecord); |
1135 | +} |
1136 | + |
1137 | +static int freeLockRecord(void* junk, DBENTRY* pdbentry) |
1138 | +{ |
1139 | + dbCommon *prec = pdbentry->precnode->precord; |
1140 | + lockRecord *lr = prec->lset; |
1141 | + lockSet *ls = lr->plockSet; |
1142 | + |
1143 | + prec->lset = NULL; |
1144 | + lr->precord = NULL; |
1145 | + |
1146 | + assert(ls->refcount>0); |
1147 | + assert(ellCount(&ls->lockRecordList)>0); |
1148 | + ellDelete(&ls->lockRecordList, &lr->node); |
1149 | + dbLockDecRef(ls); |
1150 | + |
1151 | + epicsSpinDestroy(lr->spin); |
1152 | + free(lr); |
1153 | + return 0; |
1154 | } |
1155 | |
1156 | void dbLockCleanupRecords(dbBase *pdbbase) |
1157 | { |
1158 | - ELLNODE *cur; |
1159 | - |
1160 | - free(lockRecordAlloc); |
1161 | - lockRecordAlloc = NULL; |
1162 | - |
1163 | - /* free lockSets */ |
1164 | - /* ensure no lockSets are locked for re-compute */ |
1165 | - assert(ellCount(&lockSetList[listTypeRecordLock])==0); |
1166 | - /* move allocated locks back to the free list */ |
1167 | - while((cur=ellGet(&lockSetList[listTypeScanLock]))!=NULL) |
1168 | - { |
1169 | - lockSet *pset = CONTAINER(cur, lockSet, node); |
1170 | - assert(pset->state == lockSetStateFree); /* lock not held */ |
1171 | - pset->type = listTypeFree; |
1172 | - ellAdd(&lockSetList[listTypeFree],&pset->node); |
1173 | - } |
1174 | - /* clean up free list */ |
1175 | - while((cur=ellGet(&lockSetList[listTypeFree]))!=NULL) |
1176 | - { |
1177 | - lockSet *pset = CONTAINER(cur, lockSet, node); |
1178 | - epicsMutexDestroy(pset->lock); |
1179 | - free(pset); |
1180 | - } |
1181 | -} |
1182 | - |
1183 | -void dbLockSetMerge(dbCommon *pfirst,dbCommon *psecond) |
1184 | -{ |
1185 | - lockRecord *p1lockRecord = pfirst->lset; |
1186 | - lockRecord *p2lockRecord = psecond->lset; |
1187 | - lockSet *p1lockSet; |
1188 | - lockSet *p2lockSet; |
1189 | - lockRecord *plockRecord; |
1190 | - lockRecord *pnext; |
1191 | - |
1192 | - epicsMutexMustLock(lockSetModifyLock); |
1193 | - if(pfirst==psecond) goto all_done; |
1194 | - p1lockSet = p1lockRecord->plockSet; |
1195 | - p2lockSet = p2lockRecord->plockSet; |
1196 | - assert(p1lockSet || p2lockSet); |
1197 | - if(p1lockSet == p2lockSet) goto all_done; |
1198 | - if(!p1lockSet) { |
1199 | - p1lockRecord->plockSet = p2lockSet; |
1200 | - ellAdd(&p2lockSet->lockRecordList,&p1lockRecord->node); |
1201 | - goto all_done; |
1202 | - } |
1203 | - if(!p2lockSet) { |
1204 | - p2lockRecord->plockSet = p1lockSet; |
1205 | - ellAdd(&p1lockSet->lockRecordList,&p2lockRecord->node); |
1206 | - goto all_done; |
1207 | - } |
1208 | - /*Move entire second list to first*/ |
1209 | - assert(p1lockSet->type == p2lockSet->type); |
1210 | - plockRecord = (lockRecord *)ellFirst(&p2lockSet->lockRecordList); |
1211 | - while(plockRecord) { |
1212 | - pnext = (lockRecord *)ellNext(&plockRecord->node); |
1213 | - ellDelete(&p2lockSet->lockRecordList,&plockRecord->node); |
1214 | - plockRecord->plockSet = p1lockSet; |
1215 | - ellAdd(&p1lockSet->lockRecordList,&plockRecord->node); |
1216 | - plockRecord = pnext; |
1217 | - } |
1218 | - ellDelete(&lockSetList[p2lockSet->type],&p2lockSet->node); |
1219 | - p2lockSet->type = listTypeFree; |
1220 | - ellAdd(&lockSetList[listTypeFree],&p2lockSet->node); |
1221 | -all_done: |
1222 | - epicsMutexUnlock(lockSetModifyLock); |
1223 | - return; |
1224 | -} |
1225 | - |
1226 | |
1227 | -void dbLockSetSplit(dbCommon *psource) |
1228 | -{ |
1229 | - lockSet *plockSet; |
1230 | - lockRecord *plockRecord; |
1231 | - lockRecord *pnext; |
1232 | - dbCommon *precord; |
1233 | - int link; |
1234 | - dbRecordType *pdbRecordType; |
1235 | - dbFldDes *pdbFldDes; |
1236 | - DBLINK *plink; |
1237 | - int indlockRecord,nlockRecords; |
1238 | - lockRecord **paplockRecord; |
1239 | - epicsThreadId idself = epicsThreadGetIdSelf(); |
1240 | - |
1241 | - |
1242 | - plockRecord = psource->lset; |
1243 | - assert(plockRecord); |
1244 | - plockSet = plockRecord->plockSet; |
1245 | - assert(plockSet); |
1246 | - assert(plockSet->state==lockSetStateRecordLock); |
1247 | - assert(plockSet->type==listTypeRecordLock); |
1248 | - /*First remove all records from lock set and store in paplockRecord*/ |
1249 | - nlockRecords = ellCount(&plockSet->lockRecordList); |
1250 | - paplockRecord = dbCalloc(nlockRecords,sizeof(lockRecord*)); |
1251 | - epicsMutexMustLock(lockSetModifyLock); |
1252 | - plockRecord = (lockRecord *)ellFirst(&plockSet->lockRecordList); |
1253 | - for(indlockRecord=0; indlockRecord<nlockRecords; indlockRecord++) { |
1254 | - pnext = (lockRecord *)ellNext(&plockRecord->node); |
1255 | - ellDelete(&plockSet->lockRecordList,&plockRecord->node); |
1256 | - plockRecord->plockSet = 0; |
1257 | - paplockRecord[indlockRecord] = plockRecord; |
1258 | - plockRecord = pnext; |
1259 | - } |
1260 | - ellDelete(&lockSetList[plockSet->type],&plockSet->node); |
1261 | - plockSet->state = lockSetStateFree; |
1262 | - plockSet->type = listTypeFree; |
1263 | - ellAdd(&lockSetList[listTypeFree],&plockSet->node); |
1264 | - epicsMutexUnlock(lockSetModifyLock); |
1265 | - /*Now recompute lock sets */ |
1266 | - for(indlockRecord=0; indlockRecord<nlockRecords; indlockRecord++) { |
1267 | - plockRecord = paplockRecord[indlockRecord]; |
1268 | - epicsMutexMustLock(lockSetModifyLock); |
1269 | - if(!plockRecord->plockSet) { |
1270 | - allocLockSet(plockRecord,listTypeRecordLock, |
1271 | - lockSetStateRecordLock,idself); |
1272 | - } |
1273 | - precord = plockRecord->precord; |
1274 | - epicsMutexUnlock(lockSetModifyLock); |
1275 | - pdbRecordType = precord->rdes; |
1276 | - for(link=0; link<pdbRecordType->no_links; link++) { |
1277 | - DBADDR *pdbAddr; |
1278 | - |
1279 | - pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[link]]; |
1280 | - plink = (DBLINK *)((char *)precord + pdbFldDes->offset); |
1281 | - if(plink->type != DB_LINK) continue; |
1282 | - pdbAddr = (DBADDR *)(plink->value.pv_link.pvt); |
1283 | - dbLockSetMerge(precord,pdbAddr->precord); |
1284 | - } |
1285 | - } |
1286 | - free(paplockRecord); |
1287 | -} |
1288 | - |
1289 | |
1290 | +#ifndef LOCKSET_NOFREE |
1291 | + ELLNODE *cur; |
1292 | +#endif |
1293 | + epicsThreadOnce(&dbLockOnceInit, &dbLockOnce, NULL); |
1294 | + |
1295 | + forEachRecord(NULL, pdbbase, &freeLockRecord); |
1296 | + if(ellCount(&lockSetsActive)) { |
1297 | + errlogMessage("Warning: dbLockCleanupRecords() leaking lockSets\n"); |
1298 | + dblsr(NULL,2); |
1299 | + } |
1300 | + |
1301 | + assert(ellCount(&lockSetsActive)==0); |
1302 | + |
1303 | +#ifndef LOCKSET_NOFREE |
1304 | + while((cur=ellGet(&lockSetsFree))!=NULL) { |
1305 | + lockSet *ls = (lockSet*)cur; |
1306 | + |
1307 | + assert(ls->refcount==0); |
1308 | + assert(ellCount(&ls->lockRecordList)==0); |
1309 | + epicsMutexDestroy(ls->lock); |
1310 | + free(ls); |
1311 | + } |
1312 | +#endif |
1313 | +} |
1314 | + |
1315 | +/* Called in two modes. |
1316 | + * During dbLockInitRecords w/ locker==NULL, then no mutex are locked. |
1317 | + * After dbLockInitRecords w/ locker!=NULL, then |
1318 | + * the caller must lock both pfirst and psecond. |
1319 | + * |
1320 | + * Assumes that pfirst has been modified |
1321 | + * to link to psecond. |
1322 | + */ |
1323 | +void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) |
1324 | +{ |
1325 | + ELLNODE *cur; |
1326 | + lockSet *A=pfirst->lset->plockSet, |
1327 | + *B=psecond->lset->plockSet; |
1328 | + int Nb; |
1329 | +#ifdef LOCKSET_DEBUG |
1330 | + const epicsThreadId myself = epicsThreadGetIdSelf(); |
1331 | +#endif |
1332 | + |
1333 | + assert(A && B); |
1334 | + |
1335 | +#ifdef LOCKSET_DEBUG |
1336 | + if(locker && (A->owner!=myself || B->owner!=myself)) { |
1337 | + errlogPrintf("dbLockSetMerge(%p,\"%s\",\"%s\") ownership violation %p %p (%p)\n", |
1338 | + locker, pfirst->name, psecond->name, |
1339 | + A->owner, B->owner, myself); |
1340 | + cantProceed(NULL); |
1341 | + } |
1342 | +#endif |
1343 | + if(locker && (A->ownerlocker!=locker || B->ownerlocker!=locker)) { |
1344 | + errlogPrintf("dbLockSetMerge(%p,\"%s\",\"%s\") locker ownership violation %p %p (%p)\n", |
1345 | + locker, pfirst->name, psecond->name, |
1346 | + A->ownerlocker, B->ownerlocker, locker); |
1347 | + cantProceed(NULL); |
1348 | + } |
1349 | + |
1350 | + if(A==B) |
1351 | + return; /* already in the same lockSet */ |
1352 | + |
1353 | + Nb = ellCount(&B->lockRecordList); |
1354 | + assert(Nb>0); |
1355 | + |
1356 | + /* move all records from B to A */ |
1357 | + while((cur=ellGet(&B->lockRecordList))!=NULL) |
1358 | + { |
1359 | + lockRecord *lr = CONTAINER(cur, lockRecord, node); |
1360 | + assert(lr->plockSet==B); |
1361 | + ellAdd(&A->lockRecordList, cur); |
1362 | + |
1363 | + epicsSpinLock(lr->spin); |
1364 | + lr->plockSet = A; |
1365 | +#ifndef LOCKSET_NOCNT |
1366 | + epicsAtomicIncrSizeT(&recomputeCnt); |
1367 | +#endif |
1368 | + epicsSpinUnlock(lr->spin); |
1369 | + } |
1370 | + |
1371 | + /* there are at minimum, 1 ref for each lockRecord, |
1372 | + * and one for the locker's locked list |
1373 | + * (and perhaps another for its refs cache) |
1374 | + */ |
1375 | + assert(epicsAtomicGetIntT(&B->refcount)>=Nb+(locker?1:0)); |
1376 | + |
1377 | + /* update ref counters. for lockRecords */ |
1378 | + epicsAtomicAddIntT(&A->refcount, Nb); |
1379 | + epicsAtomicAddIntT(&B->refcount, -Nb+1); /* drop all but one ref, see below */ |
1380 | + |
1381 | + if(locker) { |
1382 | + /* at least two refs, possibly three, remain. |
1383 | + * # One ref from above |
1384 | + * # locker->locked list, which is released now. |
1385 | + * # locker->refs array, assuming it is directly referenced, |
1386 | + * and not added as the result of a dbLockSetSplit, |
1387 | + * which will be cleaned when the locker is free'd (not here). |
1388 | + */ |
1389 | +#ifdef LOCKSET_DEBUG |
1390 | + B->owner = NULL; |
1391 | + B->ownercount = 0; |
1392 | +#endif |
1393 | + assert(B->ownerlocker==locker); |
1394 | + ellDelete(&locker->locked, &B->lockernode); |
1395 | + B->ownerlocker = NULL; |
1396 | + epicsAtomicDecrIntT(&B->refcount); |
1397 | + |
1398 | + epicsMutexUnlock(B->lock); |
1399 | + } |
1400 | + |
1401 | + dbLockDecRef(B); /* last ref we hold */ |
1402 | + |
1403 | + assert(A==psecond->lset->plockSet); |
1404 | +} |
1405 | + |
1406 | +/* recompute assuming a link from pfirst to psecond |
1407 | + * may have been removed. |
1408 | + * pfirst and psecond must currently be in the same lockset, |
1409 | + * which the caller must lock before calling this function. |
1410 | + * If a new lockset is created, then it is locked |
1411 | + * when this function returns. |
1412 | + */ |
1413 | +void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) |
1414 | +{ |
1415 | + lockSet *ls = pfirst->lset->plockSet; |
1416 | + ELLLIST toInspect, newLS; |
1417 | +#ifdef LOCKSET_DEBUG |
1418 | + const epicsThreadId myself = epicsThreadGetIdSelf(); |
1419 | +#endif |
1420 | + |
1421 | +#ifdef LOCKSET_DEBUG |
1422 | + if(ls->owner!=myself || psecond->lset->plockSet->owner!=myself) { |
1423 | + errlogPrintf("dbLockSetSplit(%p,\"%s\",\"%s\") ownership violation %p %p (%p)\n", |
1424 | + locker, pfirst->name, psecond->name, |
1425 | + ls->owner, psecond->lset->plockSet->owner, myself); |
1426 | + cantProceed(NULL); |
1427 | + } |
1428 | +#endif |
1429 | + |
1430 | + /* lockset consistency violation */ |
1431 | + if(ls!=psecond->lset->plockSet) { |
1432 | + errlogPrintf("dbLockSetSplit(%p,\"%s\",\"%s\") consistency violation %p %p\n", |
1433 | + locker, pfirst->name, psecond->name, |
1434 | + pfirst->lset->plockSet, psecond->lset->plockSet); |
1435 | + cantProceed(NULL); |
1436 | + } |
1437 | + |
1438 | + |
1439 | + if(pfirst==psecond) |
1440 | + return; |
1441 | + |
1442 | + /* at least 1 ref for each lockRecord, |
1443 | + * and one for the locker |
1444 | + */ |
1445 | + assert(epicsAtomicGetIntT(&ls->refcount)>=ellCount(&ls->lockRecordList)+1); |
1446 | + |
1447 | + ellInit(&toInspect); |
1448 | + ellInit(&newLS); |
1449 | + |
1450 | + /* strategy is to start with psecond and do |
1451 | + * a breadth first traversal until all records are |
1452 | + * visited. If we encounter pfirst, then there |
1453 | + * is no need to create a new lockset so we abort |
1454 | + * early. |
1455 | + */ |
1456 | + ellAdd(&toInspect, &psecond->lset->compnode); |
1457 | + psecond->lset->compflag = 1; |
1458 | + |
1459 | + { |
1460 | + lockSet *splitset; |
1461 | + ELLNODE *cur; |
1462 | + while((cur=ellGet(&toInspect))!=NULL) |
1463 | + { |
1464 | + lockRecord *lr=CONTAINER(cur,lockRecord,compnode); |
1465 | + dbCommon *prec=lr->precord; |
1466 | + dbRecordType *rtype = prec->rdes; |
1467 | + size_t i; |
1468 | + ELLNODE *bcur; |
1469 | + |
1470 | + ellAdd(&newLS, cur); |
1471 | + prec->lset->compflag = 2; |
1472 | + |
1473 | + /* Visit all the links originating from prec */ |
1474 | + for(i=0; i<rtype->no_links; i++) { |
1475 | + dbFldDes *pdesc = rtype->papFldDes[rtype->link_ind[i]]; |
1476 | + DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset); |
1477 | + DBADDR *ptarget; |
1478 | + lockRecord *lr; |
1479 | + |
1480 | + if(plink->type!=DB_LINK) |
1481 | + continue; |
1482 | + |
1483 | + ptarget = plink->value.pv_link.pvt; |
1484 | + lr = ptarget->precord->lset; |
1485 | + assert(lr); |
1486 | + |
1487 | + if(lr->precord==pfirst) { |
1488 | + /* so pfirst is still reachable from psecond, |
1489 | + * no new lock set should be created. |
1490 | + */ |
1491 | + goto nosplit; |
1492 | + } |
1493 | + |
1494 | + /* have we already visited this record? */ |
1495 | + if(lr->compflag) |
1496 | + continue; |
1497 | + |
1498 | + ellAdd(&toInspect, &lr->compnode); |
1499 | + lr->compflag = 1; |
1500 | + } |
1501 | + |
1502 | + /* Visit all links terminating at prec */ |
1503 | + for(bcur=ellFirst(&prec->bklnk); bcur; bcur=ellNext(bcur)) |
1504 | + { |
1505 | + struct pv_link *plink1 = CONTAINER(bcur, struct pv_link, backlinknode); |
1506 | + union value *plink2 = CONTAINER(plink1, union value, pv_link); |
1507 | + DBLINK *plink = CONTAINER(plink2, DBLINK, value); |
1508 | + lockRecord *lr = plink->value.pv_link.precord->lset; |
1509 | + |
1510 | + /* plink->type==DB_LINK is implied. Only DB_LINKs are tracked from BKLNK */ |
1511 | + |
1512 | + if(lr->precord==pfirst) { |
1513 | + goto nosplit; |
1514 | + } |
1515 | + |
1516 | + if(lr->compflag) |
1517 | + continue; |
1518 | + |
1519 | + ellAdd(&toInspect, &lr->compnode); |
1520 | + lr->compflag = 1; |
1521 | + } |
1522 | + } |
1523 | + /* All links involving psecond were traversed without finding |
1524 | + * pfirst. So we must create a new lockset. |
1525 | + * newLS contains the nodes which will |
1526 | + * make up this new lockset. |
1527 | + */ |
1528 | + /* newLS will have at least psecond in it */ |
1529 | + assert(ellCount(&newLS) > 0); |
1530 | + /* If we didn't find pfirst, then it must be in the |
1531 | + * original lockset, and not the new one |
1532 | + */ |
1533 | + assert(ellCount(&newLS) < ellCount(&ls->lockRecordList)); |
1534 | + assert(ellCount(&newLS) < ls->refcount); |
1535 | + |
1536 | + splitset = makeSet(); /* reference for locker->locked */ |
1537 | + |
1538 | + epicsMutexMustLock(splitset->lock); |
1539 | + |
1540 | + assert(splitset->ownerlocker==NULL); |
1541 | + ellAdd(&locker->locked, &splitset->lockernode); |
1542 | + splitset->ownerlocker = locker; |
1543 | + |
1544 | + assert(splitset->refcount==1); |
1545 | + |
1546 | +#ifdef LOCKSET_DEBUG |
1547 | + splitset->owner = ls->owner; |
1548 | + splitset->ownercount = 1; |
1549 | + assert(ls->ownercount==1); |
1550 | +#endif |
1551 | + |
1552 | + while((cur=ellGet(&newLS))!=NULL) |
1553 | + { |
1554 | + lockRecord *lr=CONTAINER(cur,lockRecord,compnode); |
1555 | + |
1556 | + lr->compflag = 0; /* reset for next time */ |
1557 | + |
1558 | + assert(lr->plockSet == ls); |
1559 | + ellDelete(&ls->lockRecordList, &lr->node); |
1560 | + ellAdd(&splitset->lockRecordList, &lr->node); |
1561 | + |
1562 | + epicsSpinLock(lr->spin); |
1563 | + lr->plockSet = splitset; |
1564 | +#ifndef LOCKSET_NOCNT |
1565 | + epicsAtomicIncrSizeT(&recomputeCnt); |
1566 | +#endif |
1567 | + epicsSpinUnlock(lr->spin); |
1568 | + /* new lockSet is "live" at this point |
1569 | + * as other threads may find it. |
1570 | + */ |
1571 | + } |
1572 | + |
1573 | + /* refcount of ls can't go to zero as the locker |
1574 | + * holds at least one reference (its locked list) |
1575 | + */ |
1576 | + epicsAtomicAddIntT(&ls->refcount, -ellCount(&splitset->lockRecordList)); |
1577 | + assert(ls->refcount>0); |
1578 | + epicsAtomicAddIntT(&splitset->refcount, ellCount(&splitset->lockRecordList)); |
1579 | + |
1580 | + assert(splitset->refcount>=ellCount(&splitset->lockRecordList)+1); |
1581 | + |
1582 | + assert(psecond->lset->plockSet==splitset); |
1583 | + |
1584 | + /* must have refs from pfirst lockRecord, |
1585 | + * and the locked list. |
1586 | + */ |
1587 | + assert(epicsAtomicGetIntT(&ls->refcount)>=2); |
1588 | + |
1589 | + return; |
1590 | + } |
1591 | + |
1592 | +nosplit: |
1593 | + { |
1594 | + /* reset compflag for all nodes visited |
1595 | + * during the aborted search |
1596 | + */ |
1597 | + ELLNODE *cur; |
1598 | + while((cur=ellGet(&toInspect))!=NULL) |
1599 | + { |
1600 | + lockRecord *lr=CONTAINER(cur,lockRecord,compnode); |
1601 | + lr->compflag = 0; |
1602 | + } |
1603 | + while((cur=ellGet(&newLS))!=NULL) |
1604 | + { |
1605 | + lockRecord *lr=CONTAINER(cur,lockRecord,compnode); |
1606 | + lr->compflag = 0; |
1607 | + } |
1608 | + return; |
1609 | + } |
1610 | +} |
1611 | + |
1612 | +static char *msstring[4]={"NMS","MS","MSI","MSS"}; |
1613 | + |
1614 | long dblsr(char *recordname,int level) |
1615 | { |
1616 | int link; |
1617 | @@ -524,8 +893,6 @@ |
1618 | dbFldDes *pdbFldDes; |
1619 | DBLINK *plink; |
1620 | |
1621 | - printf("globalLock %p\n",globalLock); |
1622 | - printf("lockSetModifyLock %p\n",lockSetModifyLock); |
1623 | if (recordname && ((*recordname == '\0') || !strcmp(recordname,"*"))) |
1624 | recordname = NULL; |
1625 | if(recordname) { |
1626 | @@ -534,29 +901,20 @@ |
1627 | if(status) { |
1628 | printf("Record not found\n"); |
1629 | dbFinishEntry(pdbentry); |
1630 | - return(0); |
1631 | + return 0; |
1632 | } |
1633 | precord = pdbentry->precnode->precord; |
1634 | dbFinishEntry(pdbentry); |
1635 | plockRecord = precord->lset; |
1636 | - if(!plockRecord) return(0); |
1637 | + if (!plockRecord) return 0; /* before iocInit */ |
1638 | plockSet = plockRecord->plockSet; |
1639 | } else { |
1640 | - plockSet = (lockSet *)ellFirst(&lockSetList[listTypeScanLock]); |
1641 | + plockSet = (lockSet *)ellFirst(&lockSetsActive); |
1642 | } |
1643 | for( ; plockSet; plockSet = (lockSet *)ellNext(&plockSet->node)) { |
1644 | - printf("Lock Set %lu %d members epicsMutexId %p", |
1645 | - plockSet->id,ellCount(&plockSet->lockRecordList),plockSet->lock); |
1646 | - if(epicsMutexTryLock(plockSet->lock)==epicsMutexLockOK) { |
1647 | - epicsMutexUnlock(plockSet->lock); |
1648 | - printf(" Not Locked\n"); |
1649 | - } else { |
1650 | - printf(" thread %p",plockSet->thread_id); |
1651 | - if(! plockSet->precord || !plockSet->precord->name) |
1652 | - printf(" NULL record or record name\n"); |
1653 | - else |
1654 | - printf(" record %s\n",plockSet->precord->name); |
1655 | - } |
1656 | + printf("Lock Set %lu %d members %d refs epicsMutexId %p\n", |
1657 | + plockSet->id,ellCount(&plockSet->lockRecordList),plockSet->refcount,plockSet->lock); |
1658 | + |
1659 | if(level==0) { if(recordname) break; continue; } |
1660 | for(plockRecord = (lockRecord *)ellFirst(&plockSet->lockRecordList); |
1661 | plockRecord; plockRecord = (lockRecord *)ellNext(&plockRecord->node)) { |
1662 | @@ -586,43 +944,27 @@ |
1663 | } |
1664 | if(recordname) break; |
1665 | } |
1666 | - return(0); |
1667 | + return 0; |
1668 | } |
1669 | |
1670 | long dbLockShowLocked(int level) |
1671 | { |
1672 | int indListType; |
1673 | lockSet *plockSet; |
1674 | - epicsMutexLockStatus status; |
1675 | - epicsMutexLockStatus lockSetModifyLockStatus = epicsMutexLockOK; |
1676 | - int itry; |
1677 | - |
1678 | - printf("listTypeScanLock %d listTypeRecordLock %d listTypeFree %d\n", |
1679 | - ellCount(&lockSetList[0]), |
1680 | - ellCount(&lockSetList[1]), |
1681 | - ellCount(&lockSetList[2])); |
1682 | - for(itry=0; itry<100; itry++) { |
1683 | - lockSetModifyLockStatus = epicsMutexTryLock(lockSetModifyLock); |
1684 | - if(lockSetModifyLockStatus==epicsMutexLockOK) break; |
1685 | - epicsThreadSleep(.05); |
1686 | - } |
1687 | - if(lockSetModifyLockStatus!=epicsMutexLockOK) { |
1688 | - printf("Could not lock lockSetModifyLock\n"); |
1689 | - epicsMutexShow(lockSetModifyLock,level); |
1690 | - } |
1691 | - status = epicsMutexTryLock(globalLock); |
1692 | - if(status==epicsMutexLockOK) { |
1693 | - epicsMutexUnlock(globalLock); |
1694 | - } else { |
1695 | - printf("globalLock is locked\n"); |
1696 | - epicsMutexShow(globalLock,level); |
1697 | - } |
1698 | + |
1699 | + printf("Active lockSets: %d\n", ellCount(&lockSetsActive)); |
1700 | +#ifndef LOCKSET_NOFREE |
1701 | + printf("Free lockSets: %d\n", ellCount(&lockSetsFree)); |
1702 | +#endif |
1703 | + |
1704 | /*Even if failure on lockSetModifyLock will continue */ |
1705 | for(indListType=0; indListType <= 1; ++indListType) { |
1706 | - plockSet = (lockSet *)ellFirst(&lockSetList[indListType]); |
1707 | + plockSet = (lockSet *)ellFirst(&lockSetsActive); |
1708 | if(plockSet) { |
1709 | - if(indListType==0) printf("listTypeScanLock\n"); |
1710 | - else printf("listTypeRecordLock\n"); |
1711 | + if (indListType==0) |
1712 | + printf("listTypeScanLock\n"); |
1713 | + else |
1714 | + printf("listTypeRecordLock\n"); |
1715 | } |
1716 | while(plockSet) { |
1717 | epicsMutexLockStatus status; |
1718 | @@ -630,19 +972,13 @@ |
1719 | status = epicsMutexTryLock(plockSet->lock); |
1720 | if(status==epicsMutexLockOK) epicsMutexUnlock(plockSet->lock); |
1721 | if(status!=epicsMutexLockOK || indListType==1) { |
1722 | - if(plockSet->precord) |
1723 | - printf("%s ",plockSet->precord->name); |
1724 | - printf("state %d thread_id %p nRecursion %d nWaiting %d\n", |
1725 | - plockSet->state,plockSet->thread_id, |
1726 | - plockSet->nRecursion,plockSet->nWaiting); |
1727 | + |
1728 | epicsMutexShow(plockSet->lock,level); |
1729 | } |
1730 | plockSet = (lockSet *)ellNext(&plockSet->node); |
1731 | } |
1732 | } |
1733 | - if(lockSetModifyLockStatus==epicsMutexLockOK) |
1734 | - epicsMutexUnlock(lockSetModifyLock); |
1735 | - return(0); |
1736 | + return 0; |
1737 | } |
1738 | |
1739 | int * dbLockSetAddrTrace(dbCommon *precord) |
1740 | |
1741 | === modified file 'src/ioc/db/dbLock.h' |
1742 | --- src/ioc/db/dbLock.h 2014-06-23 20:28:28 +0000 |
1743 | +++ src/ioc/db/dbLock.h 2015-08-31 21:08:35 +0000 |
1744 | @@ -13,6 +13,7 @@ |
1745 | #ifndef INCdbLockh |
1746 | #define INCdbLockh |
1747 | |
1748 | +#include "ellLib.h" |
1749 | #include "shareLib.h" |
1750 | |
1751 | #ifdef __cplusplus |
1752 | @@ -21,21 +22,26 @@ |
1753 | |
1754 | struct dbCommon; |
1755 | struct dbBase; |
1756 | +typedef struct dbLocker dbLocker; |
1757 | |
1758 | epicsShareFunc void dbScanLock(struct dbCommon *precord); |
1759 | epicsShareFunc void dbScanUnlock(struct dbCommon *precord); |
1760 | + |
1761 | +epicsShareFunc dbLocker *dbLockerAlloc(struct dbCommon **precs, |
1762 | + size_t nrecs, |
1763 | + unsigned int flags); |
1764 | + |
1765 | +epicsShareFunc void dbLockerFree(dbLocker *); |
1766 | + |
1767 | +epicsShareFunc void dbScanLockMany(dbLocker*); |
1768 | +epicsShareFunc void dbScanUnlockMany(dbLocker*); |
1769 | + |
1770 | epicsShareFunc unsigned long dbLockGetLockId( |
1771 | struct dbCommon *precord); |
1772 | |
1773 | epicsShareFunc void dbLockInitRecords(struct dbBase *pdbbase); |
1774 | epicsShareFunc void dbLockCleanupRecords(struct dbBase *pdbbase); |
1775 | -epicsShareFunc void dbLockSetMerge( |
1776 | - struct dbCommon *pfirst,struct dbCommon *psecond); |
1777 | -epicsShareFunc void dbLockSetSplit(struct dbCommon *psource); |
1778 | -/*The following are for code that modifies lock sets*/ |
1779 | -epicsShareFunc void dbLockSetGblLock(void); |
1780 | -epicsShareFunc void dbLockSetGblUnlock(void); |
1781 | -epicsShareFunc void dbLockSetRecordLock(struct dbCommon *precord); |
1782 | + |
1783 | |
1784 | /* Lock Set Report */ |
1785 | epicsShareFunc long dblsr(char *recordname,int level); |
1786 | @@ -47,6 +53,10 @@ |
1787 | /*KLUDGE to support field TPRO*/ |
1788 | epicsShareFunc int * dbLockSetAddrTrace(struct dbCommon *precord); |
1789 | |
1790 | +/* debugging */ |
1791 | +epicsShareFunc unsigned long dbLockGetRefs(struct dbCommon*); |
1792 | +epicsShareFunc unsigned long dbLockCountSets(void); |
1793 | + |
1794 | #ifdef __cplusplus |
1795 | } |
1796 | #endif |
1797 | |
1798 | === added file 'src/ioc/db/dbLockPvt.h' |
1799 | --- src/ioc/db/dbLockPvt.h 1970-01-01 00:00:00 +0000 |
1800 | +++ src/ioc/db/dbLockPvt.h 2015-08-31 21:08:35 +0000 |
1801 | @@ -0,0 +1,110 @@ |
1802 | +/*************************************************************************\ |
1803 | +* Copyright (c) 2014 Brookhaven Science Assoc., as Operator of Brookhaven |
1804 | +* National Laboratory. |
1805 | +* EPICS BASE is distributed subject to a Software License Agreement found |
1806 | +* in file LICENSE that is included with this distribution. |
1807 | +\*************************************************************************/ |
1808 | + |
1809 | +#ifndef DBLOCKPVT_H |
1810 | +#define DBLOCKPVT_H |
1811 | + |
1812 | +#include "dbLock.h" |
1813 | +#include "epicsSpin.h" |
1814 | + |
1815 | +/* Define to enable additional error checking */ |
1816 | +#undef LOCKSET_DEBUG |
1817 | + |
1818 | +/* Define to disable the free list for lockSets */ |
1819 | +#undef LOCKSET_NOFREE |
1820 | + |
1821 | +/* Define to disable use of recomputeCnt optimization */ |
1822 | +#undef LOCKSET_NOCNT |
1823 | + |
1824 | +/* except for refcount (and lock), all members of dbLockSet |
1825 | + * are guarded by its lock. |
1826 | + */ |
1827 | +typedef struct dbLockSet { |
1828 | + ELLNODE node; |
1829 | + ELLLIST lockRecordList; /* holds lockRecord::node */ |
1830 | + epicsMutexId lock; |
1831 | + unsigned long id; |
1832 | + |
1833 | + int refcount; |
1834 | +#ifdef LOCKSET_DEBUG |
1835 | + int ownercount; |
1836 | + epicsThreadId owner; |
1837 | +#endif |
1838 | + dbLocker *ownerlocker; |
1839 | + ELLNODE lockernode; |
1840 | + |
1841 | + int trace; /*For field TPRO*/ |
1842 | +} lockSet; |
1843 | + |
1844 | +struct lockRecord; |
1845 | + |
1846 | +/* dbCommon.LSET is a plockRecord. |
1847 | + * Except for spin and plockSet, all members of lockRecord are guarded |
1848 | + * by the present lockset lock. |
1849 | + * plockSet is guarded by spin. |
1850 | + */ |
1851 | +typedef struct lockRecord { |
1852 | + ELLNODE node; /* in lockSet::lockRecordList */ |
1853 | + /* The association between lockRecord and lockSet |
1854 | + * can only be changed while the lockSet is held, |
1855 | + * and the lockRecord's spinlock is held. |
1856 | + * It may be read which either lock is held. |
1857 | + */ |
1858 | + lockSet *plockSet; |
1859 | + /* the association between lockRecord and dbCommon never changes */ |
1860 | + dbCommon *precord; |
1861 | + epicsSpinId spin; |
1862 | + |
1863 | + /* temp used during lockset split. |
1864 | + * lockSet must be locked for access |
1865 | + */ |
1866 | + ELLNODE compnode; |
1867 | + unsigned int compflag; |
1868 | +} lockRecord; |
1869 | + |
1870 | +typedef struct { |
1871 | + lockRecord *plr; |
1872 | + /* the last lock found associated with the ref. |
1873 | + * not stable unless lock is locked, or ref spin |
1874 | + * is locked. |
1875 | + */ |
1876 | + lockSet *plockSet; |
1877 | +} lockRecordRef; |
1878 | + |
1879 | +#define DBLOCKER_NALLOC 2 |
1880 | +/* a dbLocker can only be used by a single thread. */ |
1881 | +struct dbLocker { |
1882 | + ELLLIST locked; |
1883 | +#ifndef LOCKSET_NOCNT |
1884 | + size_t recomp; /* snapshot of recomputeCnt when refs[] cache updated */ |
1885 | +#endif |
1886 | + size_t maxrefs; |
1887 | + lockRecordRef refs[DBLOCKER_NALLOC]; /* actual length is maxrefs */ |
1888 | +}; |
1889 | + |
1890 | +/* These are exported for testing only */ |
1891 | +epicsShareFunc lockSet* dbLockGetRef(lockRecord *lr); /* lookup lockset and increment ref count */ |
1892 | +epicsShareFunc void dbLockIncRef(lockSet* ls); |
1893 | +epicsShareFunc void dbLockDecRef(lockSet *ls); |
1894 | + |
1895 | +/* Calling dbLockerPrepare directly is an internal |
1896 | + * optimization used when dbLocker on the stack. |
1897 | + * nrecs must be <=DBLOCKER_NALLOC. |
1898 | + */ |
1899 | +void dbLockerPrepare(struct dbLocker *locker, |
1900 | + struct dbCommon **precs, |
1901 | + size_t nrecs); |
1902 | +void dbLockerFinalize(dbLocker *); |
1903 | + |
1904 | +void dbLockSetMerge(struct dbLocker *locker, |
1905 | + struct dbCommon *pfirst, |
1906 | + struct dbCommon *psecond); |
1907 | +void dbLockSetSplit(struct dbLocker *locker, |
1908 | + struct dbCommon *psource, |
1909 | + struct dbCommon *psecond); |
1910 | + |
1911 | +#endif /* DBLOCKPVT_H */ |
1912 | |
1913 | === modified file 'src/ioc/db/dbNotify.c' |
1914 | --- src/ioc/db/dbNotify.c 2015-03-13 19:24:07 +0000 |
1915 | +++ src/ioc/db/dbNotify.c 2015-08-31 21:08:35 +0000 |
1916 | @@ -25,7 +25,6 @@ |
1917 | #include "ellLib.h" |
1918 | #include "epicsAssert.h" |
1919 | #include "epicsEvent.h" |
1920 | -#include "epicsExit.h" |
1921 | #include "epicsMutex.h" |
1922 | #include "epicsString.h" |
1923 | #include "epicsThread.h" |
1924 | |
1925 | === modified file 'src/ioc/db/dbScan.c' |
1926 | --- src/ioc/db/dbScan.c 2015-07-24 17:01:53 +0000 |
1927 | +++ src/ioc/db/dbScan.c 2015-08-31 21:08:35 +0000 |
1928 | @@ -25,7 +25,6 @@ |
1929 | #include "dbDefs.h" |
1930 | #include "ellLib.h" |
1931 | #include "epicsEvent.h" |
1932 | -#include "epicsExit.h" |
1933 | #include "epicsMutex.h" |
1934 | #include "epicsPrint.h" |
1935 | #include "epicsRingBytes.h" |
1936 | |
1937 | === modified file 'src/ioc/db/test/Makefile' |
1938 | --- src/ioc/db/test/Makefile 2015-08-18 13:07:18 +0000 |
1939 | +++ src/ioc/db/test/Makefile 2015-08-31 21:08:35 +0000 |
1940 | @@ -61,6 +61,11 @@ |
1941 | TESTS += dbLockTest |
1942 | TESTFILES += ../dbLockTest.db |
1943 | |
1944 | +TESTPROD_HOST += dbStressTest |
1945 | +dbStressTest_SRCS += dbStressLock.c |
1946 | +dbStressTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp |
1947 | +TESTS += dbStressTest |
1948 | + |
1949 | TESTPROD_HOST += testdbConvert |
1950 | testdbConvert_SRCS += testdbConvert.c |
1951 | testHarness_SRCS += testdbConvert.c |
1952 | @@ -151,7 +156,12 @@ |
1953 | xRecord$(DEP): $(COMMON_DIR)/xRecord.h |
1954 | arrRecord$(DEP): $(COMMON_DIR)/arrRecord.h |
1955 | dbPutLinkTest$(DEP): $(COMMON_DIR)/xRecord.h |
1956 | +<<<<<<< TREE |
1957 | devx$(DEP): $(COMMON_DIR)/xRecord.h |
1958 | scanIoTest$(DEP): $(COMMON_DIR)/xRecord.h |
1959 | dbCaLinkTest$(DEP): $(COMMON_DIR)/xRecord.h $(COMMON_DIR)/arrRecord.h |
1960 | +======= |
1961 | +dbStressLock$(DEP): $(COMMON_DIR)/xRecord.h |
1962 | +scanIoTest$(DEP): $(COMMON_DIR)/yRecord.h |
1963 | +>>>>>>> MERGE-SOURCE |
1964 | |
1965 | |
1966 | === modified file 'src/ioc/db/test/dbLockTest.c' |
1967 | --- src/ioc/db/test/dbLockTest.c 2014-09-04 03:59:34 +0000 |
1968 | +++ src/ioc/db/test/dbLockTest.c 2015-08-31 21:08:35 +0000 |
1969 | @@ -9,7 +9,15 @@ |
1970 | * Author: Michael Davidsaver <mdavidsaver@bnl.gov> |
1971 | */ |
1972 | |
1973 | -#include "dbLock.h" |
1974 | +#include <stdlib.h> |
1975 | + |
1976 | +#include "epicsSpin.h" |
1977 | +#include "epicsMutex.h" |
1978 | +#include "dbCommon.h" |
1979 | +#include "epicsThread.h" |
1980 | + |
1981 | +#include "dbLockPvt.h" |
1982 | +#include "dbStaticLib.h" |
1983 | |
1984 | #include "dbUnitTest.h" |
1985 | #include "testMain.h" |
1986 | @@ -28,26 +36,51 @@ |
1987 | rA = testdbRecordPtr(A); |
1988 | rB = testdbRecordPtr(B); |
1989 | |
1990 | - actual = dbLockGetLockId(rA)==dbLockGetLockId(rB); |
1991 | + actual = rA->lset->plockSet==rB->lset->plockSet; |
1992 | |
1993 | testOk(match==actual, "dbLockGetLockId(\"%s\")%c=dbLockGetLockId(\"%s\")", |
1994 | A, match?'=':'!', B); |
1995 | } |
1996 | |
1997 | +#define testIntOk1(A, OP, B) testOk((A) OP (B), "%s (%d) %s %s (%d)", #A, A, #OP, #B, B); |
1998 | +#define testPtrOk1(A, OP, B) testOk((A) OP (B), "%s (%p) %s %s (%p)", #A, A, #OP, #B, B); |
1999 | + |
2000 | static |
2001 | void testSets(void) { |
2002 | + DBENTRY entry; |
2003 | + long status; |
2004 | + |
2005 | + testdbPrepare(); |
2006 | + |
2007 | + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); |
2008 | + dbTestIoc_registerRecordDeviceDriver(pdbbase); |
2009 | + testdbReadDatabase("dbLockTest.db", NULL, NULL); |
2010 | + |
2011 | + eltc(0); |
2012 | + testIocInitOk(); |
2013 | + eltc(1); |
2014 | + |
2015 | + testDiag("Check that all records have initialized lockRecord and lockSet"); |
2016 | + |
2017 | + dbInitEntry(pdbbase, &entry); |
2018 | + for(status = dbFirstRecordType(&entry); |
2019 | + !status; |
2020 | + status = dbNextRecordType(&entry)) { |
2021 | + for(status = dbFirstRecord(&entry); |
2022 | + !status; |
2023 | + status = dbNextRecord(&entry)) { |
2024 | + dbCommon *prec = entry.precnode->precord; |
2025 | + testOk(prec->lset!=NULL, "%s.LSET != NULL", prec->name); |
2026 | + if(prec->lset!=NULL) |
2027 | + testOk(prec->lset->plockSet!=NULL, "%s.LSET.plockSet != NULL", prec->name); |
2028 | + else |
2029 | + testSkip(1, "lockRecord missing"); |
2030 | + } |
2031 | + } |
2032 | + dbFinishEntry(&entry); |
2033 | + |
2034 | testDiag("Check initial creation of DB links"); |
2035 | |
2036 | - testdbPrepare(); |
2037 | - |
2038 | - testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); |
2039 | - dbTestIoc_registerRecordDeviceDriver(pdbbase); |
2040 | - testdbReadDatabase("dbLockTest.db", NULL, NULL); |
2041 | - |
2042 | - eltc(0); |
2043 | - testIocInitOk(); |
2044 | - eltc(1); |
2045 | - |
2046 | /* reca is by itself */ |
2047 | compareSets(0, "reca", "recb"); |
2048 | compareSets(0, "reca", "recc"); |
2049 | @@ -71,6 +104,310 @@ |
2050 | |
2051 | compareSets(1, "rece", "recf"); |
2052 | |
2053 | + testOk1(testdbRecordPtr("reca")->lset->plockSet->refcount==1); |
2054 | + testOk1(testdbRecordPtr("recb")->lset->plockSet->refcount==2); |
2055 | + testOk1(testdbRecordPtr("recd")->lset->plockSet->refcount==3); |
2056 | + |
2057 | + testIocShutdownOk(); |
2058 | + |
2059 | + testdbCleanup(); |
2060 | +} |
2061 | + |
2062 | +static void testSingleLock(void) |
2063 | +{ |
2064 | + dbCommon *prec; |
2065 | + testDiag("testing dbScanLock()/dbScanUnlock()"); |
2066 | + |
2067 | + testdbPrepare(); |
2068 | + |
2069 | + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); |
2070 | + dbTestIoc_registerRecordDeviceDriver(pdbbase); |
2071 | + testdbReadDatabase("dbLockTest.db", NULL, NULL); |
2072 | + |
2073 | + eltc(0); |
2074 | + testIocInitOk(); |
2075 | + eltc(1); |
2076 | + |
2077 | + prec = testdbRecordPtr("reca"); |
2078 | + testOk1(prec->lset->plockSet->refcount==1); |
2079 | + dbScanLock(prec); |
2080 | + /* scan lock does not keep a reference to the lockSet */ |
2081 | + testOk1(prec->lset->plockSet->refcount==1); |
2082 | + dbScanUnlock(prec); |
2083 | + |
2084 | + dbScanLock(prec); |
2085 | + dbScanLock(prec); |
2086 | + /* scan lock can be recursive */ |
2087 | + testOk1(prec->lset->plockSet->refcount==1); |
2088 | + dbScanUnlock(prec); |
2089 | + dbScanUnlock(prec); |
2090 | + |
2091 | + testIocShutdownOk(); |
2092 | + |
2093 | + testdbCleanup(); |
2094 | +} |
2095 | + |
2096 | +static void testMultiLock(void) |
2097 | +{ |
2098 | + dbCommon *prec[8]; |
2099 | + dbLocker *plockA; |
2100 | +#ifdef LOCKSET_DEBUG |
2101 | + epicsThreadId myself = epicsThreadGetIdSelf(); |
2102 | +#endif |
2103 | + |
2104 | + testDiag("Test multi-locker function (lock everything)"); |
2105 | + |
2106 | + testdbPrepare(); |
2107 | + |
2108 | + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); |
2109 | + dbTestIoc_registerRecordDeviceDriver(pdbbase); |
2110 | + testdbReadDatabase("dbLockTest.db", NULL, NULL); |
2111 | + |
2112 | + eltc(0); |
2113 | + testIocInitOk(); |
2114 | + eltc(1); |
2115 | + |
2116 | + prec[0] = testdbRecordPtr("reca"); |
2117 | + prec[1] = testdbRecordPtr("recb"); |
2118 | + prec[2] = testdbRecordPtr("recc"); |
2119 | + prec[3] = NULL; |
2120 | + prec[4] = testdbRecordPtr("recd"); |
2121 | + prec[5] = testdbRecordPtr("rece"); |
2122 | + prec[6] = testdbRecordPtr("recf"); |
2123 | + prec[7] = testdbRecordPtr("recg"); |
2124 | + |
2125 | + testDiag("Test init refcounts"); |
2126 | + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,1); |
2127 | + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,2); |
2128 | + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,3); |
2129 | + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,1); |
2130 | + |
2131 | + plockA = dbLockerAlloc(prec, 8, 0); |
2132 | + if(!plockA) |
2133 | + testAbort("dbLockerAlloc() failed"); |
2134 | + |
2135 | + |
2136 | + testDiag("After locker created"); |
2137 | + /* locker takes 7 references, one for each lockRecord. */ |
2138 | + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,2); |
2139 | + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,4); |
2140 | + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,6); |
2141 | + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,2); |
2142 | +#ifdef LOCKSET_DEBUG |
2143 | + testPtrOk1(testdbRecordPtr("reca")->lset->plockSet->owner,==,NULL); |
2144 | + testPtrOk1(testdbRecordPtr("recb")->lset->plockSet->owner,==,NULL); |
2145 | + testPtrOk1(testdbRecordPtr("recd")->lset->plockSet->owner,==,NULL); |
2146 | + testPtrOk1(testdbRecordPtr("recg")->lset->plockSet->owner,==,NULL); |
2147 | +#endif |
2148 | + |
2149 | + dbScanLockMany(plockA); |
2150 | + |
2151 | + testDiag("After locker locked"); |
2152 | + /* locker takes 4 references, one for each lockSet. */ |
2153 | + testIntOk1(ellCount(&plockA->locked),==,4); |
2154 | + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,3); |
2155 | + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,5); |
2156 | + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,7); |
2157 | + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,3); |
2158 | +#ifdef LOCKSET_DEBUG |
2159 | + testPtrOk1(testdbRecordPtr("reca")->lset->plockSet->owner,==,myself); |
2160 | + testPtrOk1(testdbRecordPtr("recb")->lset->plockSet->owner,==,myself); |
2161 | + testPtrOk1(testdbRecordPtr("recd")->lset->plockSet->owner,==,myself); |
2162 | + testPtrOk1(testdbRecordPtr("recg")->lset->plockSet->owner,==,myself); |
2163 | +#endif |
2164 | + |
2165 | + /* recursive locking of individual records is allowed */ |
2166 | + dbScanLock(prec[0]); |
2167 | + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,3); |
2168 | + dbScanUnlock(prec[0]); |
2169 | + |
2170 | + /* recursive locking with dbScanLockMany() isn't |
2171 | + * dbScanLockMany(plockA); <-- would fail |
2172 | + */ |
2173 | + |
2174 | + dbScanUnlockMany(plockA); |
2175 | + |
2176 | + testDiag("After locker unlocked"); |
2177 | + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,2); |
2178 | + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,4); |
2179 | + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,6); |
2180 | + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,2); |
2181 | +#ifdef LOCKSET_DEBUG |
2182 | + testPtrOk1(testdbRecordPtr("reca")->lset->plockSet->owner,==,NULL); |
2183 | + testPtrOk1(testdbRecordPtr("recb")->lset->plockSet->owner,==,NULL); |
2184 | + testPtrOk1(testdbRecordPtr("recd")->lset->plockSet->owner,==,NULL); |
2185 | + testPtrOk1(testdbRecordPtr("recg")->lset->plockSet->owner,==,NULL); |
2186 | +#endif |
2187 | + |
2188 | + dbLockerFree(plockA); |
2189 | + |
2190 | + testDiag("After locker free'd"); |
2191 | + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,1); |
2192 | + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,2); |
2193 | + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,3); |
2194 | + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,1); |
2195 | + |
2196 | + testIocShutdownOk(); |
2197 | + |
2198 | + testdbCleanup(); |
2199 | +} |
2200 | + |
2201 | +static void testLinkBreak(void) |
2202 | +{ |
2203 | + dbCommon *precB, *precC; |
2204 | + testDiag("Test break link"); |
2205 | + |
2206 | + testdbPrepare(); |
2207 | + |
2208 | + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); |
2209 | + dbTestIoc_registerRecordDeviceDriver(pdbbase); |
2210 | + testdbReadDatabase("dbLockTest.db", NULL, NULL); |
2211 | + |
2212 | + eltc(0); |
2213 | + testIocInitOk(); |
2214 | + eltc(1); |
2215 | + |
2216 | + precB = testdbRecordPtr("recb"); |
2217 | + precC = testdbRecordPtr("recc"); |
2218 | + |
2219 | + testOk1(precB->lset->plockSet==precC->lset->plockSet); |
2220 | + testOk1(precB->lset->plockSet->refcount==2); |
2221 | + |
2222 | + /* break the link between B and C */ |
2223 | + testdbPutFieldOk("recb.SDIS", DBR_STRING, ""); |
2224 | + |
2225 | + testOk1(precB->lset->plockSet!=precC->lset->plockSet); |
2226 | + testIntOk1(precB->lset->plockSet->refcount, ==, 1); |
2227 | + testIntOk1(precC->lset->plockSet->refcount, ==, 1); |
2228 | + |
2229 | + testIocShutdownOk(); |
2230 | + |
2231 | + testdbCleanup(); |
2232 | +} |
2233 | + |
2234 | +static void testLinkMake(void) |
2235 | +{ |
2236 | + dbCommon *precA, *precG; |
2237 | + lockSet *lA, *lG; |
2238 | + testDiag("Test make link"); |
2239 | + |
2240 | + testdbPrepare(); |
2241 | + |
2242 | + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); |
2243 | + dbTestIoc_registerRecordDeviceDriver(pdbbase); |
2244 | + testdbReadDatabase("dbLockTest.db", NULL, NULL); |
2245 | + |
2246 | + eltc(0); |
2247 | + testIocInitOk(); |
2248 | + eltc(1); |
2249 | + |
2250 | + precA = testdbRecordPtr("reca"); |
2251 | + lA = dbLockGetRef(precA->lset); |
2252 | + precG = testdbRecordPtr("recg"); |
2253 | + lG = dbLockGetRef(precG->lset); |
2254 | + |
2255 | + testPtrOk1(precA->lset->plockSet, !=, precG->lset->plockSet); |
2256 | + testIntOk1(precA->lset->plockSet->refcount, ==, 2); |
2257 | + testIntOk1(precG->lset->plockSet->refcount, ==, 2); |
2258 | + |
2259 | + /* make a link between A and G */ |
2260 | + testdbPutFieldOk("reca.SDIS", DBR_STRING, "recg"); |
2261 | + |
2262 | + testPtrOk1(precA->lset->plockSet, ==, precG->lset->plockSet); |
2263 | + testIntOk1(precA->lset->plockSet->refcount, ==, 3); |
2264 | + |
2265 | + if(precA->lset->plockSet==lG) { |
2266 | + testIntOk1(lA->refcount, ==, 1); |
2267 | + testIntOk1(lG->refcount, ==, 3); |
2268 | + } else { |
2269 | + testIntOk1(lA->refcount, ==, 3); |
2270 | + testIntOk1(lG->refcount, ==, 1); |
2271 | + } |
2272 | + dbLockDecRef(lG); |
2273 | + dbLockDecRef(lA); |
2274 | + |
2275 | + testIocShutdownOk(); |
2276 | + |
2277 | + testdbCleanup(); |
2278 | +} |
2279 | + |
2280 | +static void testLinkChange(void) |
2281 | +{ |
2282 | + dbCommon *precB, *precC, *precG; |
2283 | + lockSet *lB, *lG; |
2284 | + testDiag("Test re-target link"); |
2285 | + |
2286 | + testdbPrepare(); |
2287 | + |
2288 | + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); |
2289 | + dbTestIoc_registerRecordDeviceDriver(pdbbase); |
2290 | + testdbReadDatabase("dbLockTest.db", NULL, NULL); |
2291 | + |
2292 | + eltc(0); |
2293 | + testIocInitOk(); |
2294 | + eltc(1); |
2295 | + |
2296 | + precB = testdbRecordPtr("recb"); |
2297 | + precC = testdbRecordPtr("recc"); |
2298 | + precG = testdbRecordPtr("recg"); |
2299 | + |
2300 | + lB = dbLockGetRef(precB->lset); |
2301 | + lG = dbLockGetRef(precG->lset); |
2302 | + |
2303 | + testPtrOk1(lB,==,precC->lset->plockSet); |
2304 | + testPtrOk1(lB,!=,lG); |
2305 | + testIntOk1(lB->refcount,==,3); |
2306 | + testIntOk1(lG->refcount,==,2); |
2307 | + |
2308 | + /* break the link between B and C and replace it |
2309 | + * with a link between B and G |
2310 | + */ |
2311 | + testdbPutFieldOk("recb.SDIS", DBR_STRING, "recg"); |
2312 | + |
2313 | + testPtrOk1(precB->lset->plockSet,==,lB); |
2314 | + testPtrOk1(precG->lset->plockSet,==,lB); |
2315 | + testPtrOk1(precC->lset->plockSet,!=,lB); |
2316 | + testPtrOk1(precC->lset->plockSet,!=,lG); |
2317 | + |
2318 | + testIntOk1(lB->refcount,==,3); |
2319 | + testIntOk1(lG->refcount,==,1); |
2320 | + testIntOk1(precC->lset->plockSet->refcount,==,1); |
2321 | + |
2322 | + dbLockDecRef(lB); |
2323 | + dbLockDecRef(lG); |
2324 | + |
2325 | + testIocShutdownOk(); |
2326 | + |
2327 | + testdbCleanup(); |
2328 | +} |
2329 | + |
2330 | +static void testLinkNOP(void) |
2331 | +{ |
2332 | + dbCommon *precB, *precC; |
2333 | + testDiag("Test re-target link to the same destination"); |
2334 | + |
2335 | + testdbPrepare(); |
2336 | + |
2337 | + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); |
2338 | + dbTestIoc_registerRecordDeviceDriver(pdbbase); |
2339 | + testdbReadDatabase("dbLockTest.db", NULL, NULL); |
2340 | + |
2341 | + eltc(0); |
2342 | + testIocInitOk(); |
2343 | + eltc(1); |
2344 | + |
2345 | + precB = testdbRecordPtr("recb"); |
2346 | + precC = testdbRecordPtr("recc"); |
2347 | + |
2348 | + testOk1(precB->lset->plockSet==precC->lset->plockSet); |
2349 | + testOk1(precB->lset->plockSet->refcount==2); |
2350 | + |
2351 | + /* renew link between B and C */ |
2352 | + testdbPutFieldOk("recb.SDIS", DBR_STRING, "recc"); |
2353 | + |
2354 | + testOk1(precB->lset->plockSet==precC->lset->plockSet); |
2355 | + testOk1(precB->lset->plockSet->refcount==2); |
2356 | + |
2357 | testIocShutdownOk(); |
2358 | |
2359 | testdbCleanup(); |
2360 | @@ -78,7 +415,17 @@ |
2361 | |
2362 | MAIN(dbLockTest) |
2363 | { |
2364 | - testPlan(15); |
2365 | +#ifdef LOCKSET_DEBUG |
2366 | + testPlan(100); |
2367 | +#else |
2368 | + testPlan(88); |
2369 | +#endif |
2370 | testSets(); |
2371 | + testSingleLock(); |
2372 | + testMultiLock(); |
2373 | + testLinkBreak(); |
2374 | + testLinkMake(); |
2375 | + testLinkChange(); |
2376 | + testLinkNOP(); |
2377 | return testDone(); |
2378 | } |
2379 | |
2380 | === modified file 'src/ioc/db/test/dbLockTest.db' |
2381 | --- src/ioc/db/test/dbLockTest.db 2014-07-28 18:33:17 +0000 |
2382 | +++ src/ioc/db/test/dbLockTest.db 2015-08-31 21:08:35 +0000 |
2383 | @@ -19,3 +19,6 @@ |
2384 | |
2385 | record(x, "recf") { |
2386 | } |
2387 | + |
2388 | +record(x, "recg") { |
2389 | +} |
2390 | |
2391 | === added file 'src/ioc/db/test/dbStressLock.c' |
2392 | --- src/ioc/db/test/dbStressLock.c 1970-01-01 00:00:00 +0000 |
2393 | +++ src/ioc/db/test/dbStressLock.c 2015-08-31 21:08:35 +0000 |
2394 | @@ -0,0 +1,357 @@ |
2395 | +/*************************************************************************\ |
2396 | +* Copyright (c) 2014 Brookhaven Science Assoc. as operator of Brookhaven |
2397 | +* National Laboratory. |
2398 | +* EPICS BASE is distributed subject to a Software License Agreement found |
2399 | +* in file LICENSE that is included with this distribution. |
2400 | + \*************************************************************************/ |
2401 | + |
2402 | +/* |
2403 | + * Lockset stress test. |
2404 | + * |
2405 | + * The test stratagy is for N threads to contend for M records. |
2406 | + * Each thread will perform one of three operations: |
2407 | + * 1) Lock a single record. |
2408 | + * 2) Lock several records. |
2409 | + * 3) Retarget the TSEL link of a record |
2410 | + * |
2411 | + * Author: Michael Davidsaver <mdavidsaver@bnl.gov> |
2412 | + */ |
2413 | + |
2414 | +#include <stdlib.h> |
2415 | +#include <string.h> |
2416 | +#include <time.h> |
2417 | + |
2418 | +#include "envDefs.h" |
2419 | +#include "epicsStdlib.h" |
2420 | +#include "epicsSpin.h" |
2421 | +#include "epicsThread.h" |
2422 | +#include "epicsMutex.h" |
2423 | +#include "dbCommon.h" |
2424 | + |
2425 | +#include "dbLockPvt.h" |
2426 | +#include "dbStaticLib.h" |
2427 | + |
2428 | +#include "dbUnitTest.h" |
2429 | +#include "testMain.h" |
2430 | + |
2431 | +#include "dbAccess.h" |
2432 | +#include "errlog.h" |
2433 | + |
2434 | +#include "xRecord.h" |
2435 | + |
2436 | +#if defined(CLOCK_MONOTONIC) |
2437 | +# define TIME_STATS |
2438 | +#endif |
2439 | + |
2440 | +#define testIntOk1(A, OP, B) testOk((A) OP (B), "%s (%d) %s %s (%d)", #A, A, #OP, #B, B); |
2441 | +#define testPtrOk1(A, OP, B) testOk((A) OP (B), "%s (%p) %s %s (%p)", #A, A, #OP, #B, B); |
2442 | + |
2443 | +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); |
2444 | + |
2445 | +/* number of seconds for the test to run */ |
2446 | +static double runningtime = 18.0; |
2447 | + |
2448 | +/* number of worker threads */ |
2449 | +static unsigned int nworkers = 5; |
2450 | + |
2451 | +static unsigned int nrecords; |
2452 | + |
2453 | +#define MAXLOCK 20 |
2454 | + |
2455 | +static dbCommon **precords; |
2456 | + |
2457 | +typedef struct { |
2458 | + int id; |
2459 | + unsigned long N[3]; |
2460 | +#ifdef TIME_STATS |
2461 | + double X[3]; |
2462 | + double X2[3]; |
2463 | +#endif |
2464 | + |
2465 | + unsigned int done; |
2466 | + epicsEventId donevent; |
2467 | + |
2468 | + dbCommon *prec[MAXLOCK]; |
2469 | +} workerPriv; |
2470 | + |
2471 | +/* hopefully a uniform random number in [0.0, 1.0] */ |
2472 | +static |
2473 | +double getRand(void) |
2474 | +{ |
2475 | + return rand()/(double)RAND_MAX; |
2476 | +} |
2477 | + |
2478 | +static |
2479 | +void doSingle(workerPriv *p) |
2480 | +{ |
2481 | + size_t recn = (size_t)(getRand()*(nrecords-1)); |
2482 | + dbCommon *prec = precords[recn]; |
2483 | + xRecord *px = (xRecord*)prec; |
2484 | + |
2485 | + dbScanLock(prec); |
2486 | + px->val++; |
2487 | + dbScanUnlock(prec); |
2488 | +} |
2489 | + |
2490 | +static volatile int bitbucket; |
2491 | + |
2492 | +static |
2493 | +void doMulti(workerPriv *p) |
2494 | +{ |
2495 | + int sum = 0; |
2496 | + size_t i; |
2497 | + size_t nlock = 2 + (size_t)(getRand()*(MAXLOCK-3)); |
2498 | + size_t nrec = (size_t)(getRand()*(nrecords-1)); |
2499 | + dbLocker *locker; |
2500 | + |
2501 | + assert(nlock>=2); |
2502 | + assert(nlock<nrecords); |
2503 | + |
2504 | + for(i=0; i<nlock; i++, nrec=(nrec+1)%nrecords) { |
2505 | + p->prec[i] = precords[nrec]; |
2506 | + } |
2507 | + |
2508 | + locker = dbLockerAlloc(p->prec, nlock, 0); |
2509 | + if(!locker) |
2510 | + testAbort("locker allocation fails"); |
2511 | + |
2512 | + dbScanLockMany(locker); |
2513 | + for(i=0; i<nlock; i++) { |
2514 | + xRecord *px = (xRecord*)p->prec[i]; |
2515 | + sum += px->val; |
2516 | + } |
2517 | + dbScanUnlockMany(locker); |
2518 | + |
2519 | + dbLockerFree(locker); |
2520 | +} |
2521 | + |
2522 | +static |
2523 | +void doreTarget(workerPriv *p) |
2524 | +{ |
2525 | + char scratchsrc[60]; |
2526 | + char scratchdst[MAX_STRING_SIZE]; |
2527 | + long ret; |
2528 | + DBADDR dbaddr; |
2529 | + double action = getRand(); |
2530 | + size_t nsrc = (size_t)(getRand()*(nrecords-1)); |
2531 | + size_t ntarg = (size_t)(getRand()*(nrecords-1)); |
2532 | + xRecord *psrc = (xRecord*)precords[nsrc]; |
2533 | + xRecord *ptarg = (xRecord*)precords[ntarg]; |
2534 | + |
2535 | + strcpy(scratchsrc, psrc->name); |
2536 | + strcat(scratchsrc, ".TSEL"); |
2537 | + |
2538 | + ret = dbNameToAddr(scratchsrc, &dbaddr); |
2539 | + if(ret) |
2540 | + testAbort("bad record name? %ld", ret); |
2541 | + |
2542 | + if(action<=0.6) { |
2543 | + scratchdst[0] = '\0'; |
2544 | + } else { |
2545 | + strcpy(scratchdst, ptarg->name); |
2546 | + } |
2547 | + |
2548 | + ret = dbPutField(&dbaddr, DBR_STRING, ptarg->name, 1); |
2549 | + if(ret) |
2550 | + testAbort("put fails with %ld", ret); |
2551 | +} |
2552 | + |
2553 | +static |
2554 | +void worker(void *raw) |
2555 | +{ |
2556 | +#ifdef TIME_STATS |
2557 | + struct timespec before; |
2558 | +#endif |
2559 | + workerPriv *priv = raw; |
2560 | + |
2561 | + testDiag("worker %d is %p", priv->id, epicsThreadGetIdSelf()); |
2562 | + |
2563 | +#ifdef TIME_STATS |
2564 | + clock_gettime(CLOCK_MONOTONIC, &before); |
2565 | +#endif |
2566 | + |
2567 | + while(!priv->done) { |
2568 | + double sel = getRand(); |
2569 | +#ifdef TIME_STATS |
2570 | + struct timespec after; |
2571 | +#endif |
2572 | + double duration; |
2573 | + |
2574 | + int act; |
2575 | + if(sel<0.33) { |
2576 | + doSingle(priv); |
2577 | + act = 0; |
2578 | + } else if(sel<0.66) { |
2579 | + doMulti(priv); |
2580 | + act = 1; |
2581 | + } else { |
2582 | + doreTarget(priv); |
2583 | + act = 2; |
2584 | + } |
2585 | + |
2586 | +#ifdef TIME_STATS |
2587 | + clock_gettime(CLOCK_MONOTONIC, &after); |
2588 | + |
2589 | + duration = (double)((long)after.tv_nsec - (long)before.tv_nsec); |
2590 | + duration *= 1e-9; |
2591 | + duration += (double)(after.tv_sec - before.tv_sec); |
2592 | +#endif |
2593 | + |
2594 | + priv->N[act]++; |
2595 | +#ifdef TIME_STATS |
2596 | + priv->X[act] += duration; |
2597 | + priv->X2[act] += duration*duration; |
2598 | +#endif |
2599 | + } |
2600 | + |
2601 | + epicsEventMustTrigger(priv->donevent); |
2602 | +} |
2603 | + |
2604 | +MAIN(dbStressTest) |
2605 | +{ |
2606 | + DBENTRY ent; |
2607 | + long status; |
2608 | + unsigned int i; |
2609 | + workerPriv *priv; |
2610 | + char *nwork=getenv("NWORK"); |
2611 | + epicsTimeStamp seed; |
2612 | + |
2613 | + epicsTimeGetCurrent(&seed); |
2614 | + |
2615 | + srand(seed.nsec); |
2616 | + |
2617 | + if(nwork) { |
2618 | + long val = 0; |
2619 | + epicsParseLong(nwork, &val, 0, NULL); |
2620 | + if(val>2) |
2621 | + nworkers = val; |
2622 | + } |
2623 | + |
2624 | + testPlan(80+nworkers*3); |
2625 | + |
2626 | + priv = callocMustSucceed(nworkers, sizeof(*priv), "no memory"); |
2627 | + |
2628 | + testDiag("lock set stress test"); |
2629 | + |
2630 | + testdbPrepare(); |
2631 | + |
2632 | + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); |
2633 | + dbTestIoc_registerRecordDeviceDriver(pdbbase); |
2634 | + testdbReadDatabase("dbStressLock.db", NULL, NULL); |
2635 | + |
2636 | + eltc(0); |
2637 | + testIocInitOk(); |
2638 | + eltc(1); |
2639 | + |
2640 | + /* collect an array of all records */ |
2641 | + dbInitEntry(pdbbase, &ent); |
2642 | + for(status = dbFirstRecordType(&ent); |
2643 | + !status; |
2644 | + status = dbNextRecordType(&ent)) |
2645 | + { |
2646 | + for(status = dbFirstRecord(&ent); |
2647 | + !status; |
2648 | + status = dbNextRecord(&ent)) |
2649 | + { |
2650 | + if(ent.precnode->flags&DBRN_FLAGS_ISALIAS) |
2651 | + continue; |
2652 | + nrecords++; |
2653 | + } |
2654 | + |
2655 | + } |
2656 | + if(nrecords<2) |
2657 | + testAbort("where are the records!"); |
2658 | + precords = callocMustSucceed(nrecords, sizeof(*precords), "no mem"); |
2659 | + for(status = dbFirstRecordType(&ent), i = 0; |
2660 | + !status; |
2661 | + status = dbNextRecordType(&ent)) |
2662 | + { |
2663 | + for(status = dbFirstRecord(&ent); |
2664 | + !status; |
2665 | + status = dbNextRecord(&ent)) |
2666 | + { |
2667 | + if(ent.precnode->flags&DBRN_FLAGS_ISALIAS) |
2668 | + continue; |
2669 | + precords[i++] = ent.precnode->precord; |
2670 | + } |
2671 | + |
2672 | + } |
2673 | + dbFinishEntry(&ent); |
2674 | + |
2675 | + testDiag("Running with %u workers and %u records", |
2676 | + nworkers, nrecords); |
2677 | + |
2678 | + for(i=0; i<nworkers; i++) { |
2679 | + priv[i].id = i; |
2680 | + priv[i].donevent = epicsEventMustCreate(epicsEventEmpty); |
2681 | + } |
2682 | + |
2683 | + for(i=0; i<nworkers; i++) { |
2684 | + epicsThreadMustCreate("runner", epicsThreadPriorityMedium, |
2685 | + epicsThreadGetStackSize(epicsThreadStackSmall), |
2686 | + &worker, &priv[i]); |
2687 | + } |
2688 | + |
2689 | + testDiag("All started. Will run for %f sec", runningtime); |
2690 | + |
2691 | + epicsThreadSleep(runningtime); |
2692 | + |
2693 | + testDiag("Stopping"); |
2694 | + |
2695 | + for(i=0; i<nworkers; i++) { |
2696 | + priv[i].done = 1; |
2697 | + } |
2698 | + |
2699 | + for(i=0; i<nworkers; i++) { |
2700 | + epicsEventMustWait(priv[i].donevent); |
2701 | + epicsEventDestroy(priv[i].donevent); |
2702 | + } |
2703 | + |
2704 | + testDiag("All stopped"); |
2705 | + |
2706 | + testDiag("Validate lockSet ref counts"); |
2707 | + dbInitEntry(pdbbase, &ent); |
2708 | + for(status = dbFirstRecordType(&ent); |
2709 | + !status; |
2710 | + status = dbNextRecordType(&ent)) |
2711 | + { |
2712 | + for(status = dbFirstRecord(&ent); |
2713 | + !status; |
2714 | + status = dbNextRecord(&ent)) |
2715 | + { |
2716 | + dbCommon *prec = ent.precnode->precord; |
2717 | + lockSet *ls; |
2718 | + if(ent.precnode->flags&DBRN_FLAGS_ISALIAS) |
2719 | + continue; |
2720 | + ls = prec->lset->plockSet; |
2721 | + testOk(ellCount(&ls->lockRecordList)==ls->refcount, "%s only lockRecords hold refs. %d == %d", |
2722 | + prec->name,ellCount(&ls->lockRecordList),ls->refcount); |
2723 | + testOk1(ls->ownerlocker==NULL); |
2724 | + } |
2725 | + |
2726 | + } |
2727 | + dbFinishEntry(&ent); |
2728 | + |
2729 | + testDiag("Statistics"); |
2730 | + for(i=0; i<nworkers; i++) { |
2731 | + testDiag("Worker %u", i); |
2732 | + testDiag("N = %lu %lu %lu", priv[i].N[0], priv[i].N[1], priv[i].N[2]); |
2733 | +#ifdef TIME_STATS |
2734 | + testDiag("X = %g %g %g", priv[i].X[0], priv[i].X[1], priv[i].X[2]); |
2735 | + testDiag("X2= %g %g %g", priv[i].X2[0], priv[i].X2[1], priv[i].X2[2]); |
2736 | +#endif |
2737 | + |
2738 | + testOk1(priv[i].N[0]>0); |
2739 | + testOk1(priv[i].N[1]>0); |
2740 | + testOk1(priv[i].N[2]>0); |
2741 | + } |
2742 | + |
2743 | + testIocShutdownOk(); |
2744 | + |
2745 | + testdbCleanup(); |
2746 | + |
2747 | + free(priv); |
2748 | + free(precords); |
2749 | + |
2750 | + return testDone(); |
2751 | +} |
2752 | |
2753 | === added file 'src/ioc/db/test/dbStressLock.db' |
2754 | --- src/ioc/db/test/dbStressLock.db 1970-01-01 00:00:00 +0000 |
2755 | +++ src/ioc/db/test/dbStressLock.db 2015-08-31 21:08:35 +0000 |
2756 | @@ -0,0 +1,40 @@ |
2757 | +record(x, "rec01") {} |
2758 | +record(x, "rec02") {} |
2759 | +record(x, "rec03") {} |
2760 | +record(x, "rec04") {} |
2761 | +record(x, "rec05") {} |
2762 | +record(x, "rec06") {} |
2763 | +record(x, "rec07") {} |
2764 | +record(x, "rec08") {} |
2765 | +record(x, "rec09") {} |
2766 | +record(x, "rec10") {} |
2767 | +record(x, "rec11") {} |
2768 | +record(x, "rec12") {} |
2769 | +record(x, "rec13") {} |
2770 | +record(x, "rec14") {} |
2771 | +record(x, "rec15") {} |
2772 | +record(x, "rec16") {} |
2773 | +record(x, "rec17") {} |
2774 | +record(x, "rec18") {} |
2775 | +record(x, "rec19") {} |
2776 | +record(x, "rec20") {} |
2777 | +record(x, "rec21") {} |
2778 | +record(x, "rec22") {} |
2779 | +record(x, "rec23") {} |
2780 | +record(x, "rec24") {} |
2781 | +record(x, "rec25") {} |
2782 | +record(x, "rec26") {} |
2783 | +record(x, "rec27") {} |
2784 | +record(x, "rec28") {} |
2785 | +record(x, "rec29") {} |
2786 | +record(x, "rec30") {} |
2787 | +record(x, "rec31") {} |
2788 | +record(x, "rec32") {} |
2789 | +record(x, "rec33") {} |
2790 | +record(x, "rec34") {} |
2791 | +record(x, "rec35") {} |
2792 | +record(x, "rec36") {} |
2793 | +record(x, "rec37") {} |
2794 | +record(x, "rec38") {} |
2795 | +record(x, "rec39") {} |
2796 | +record(x, "rec40") {} |
2797 | |
2798 | === modified file 'src/ioc/dbStatic/dbStaticRun.c' |
2799 | --- src/ioc/dbStatic/dbStaticRun.c 2015-03-17 15:34:36 +0000 |
2800 | +++ src/ioc/dbStatic/dbStaticRun.c 2015-08-31 21:08:35 +0000 |
2801 | @@ -25,6 +25,7 @@ |
2802 | |
2803 | #define epicsExportSharedSymbols |
2804 | #include "dbBase.h" |
2805 | +#include "dbCommon.h" |
2806 | #include "dbStaticLib.h" |
2807 | #include "dbStaticPvt.h" |
2808 | #include "devSup.h" |
2809 | @@ -198,7 +199,7 @@ |
2810 | dbRecordNode *precnode = pdbentry->precnode; |
2811 | dbFldDes *pflddes; |
2812 | int i; |
2813 | - char *precord; |
2814 | + dbCommon *precord; |
2815 | char *pfield; |
2816 | |
2817 | if(!pdbRecordType) return(S_dbLib_recordTypeNotFound); |
2818 | @@ -210,7 +211,8 @@ |
2819 | return(S_dbLib_noRecSup); |
2820 | } |
2821 | precnode->precord = dbCalloc(1,pdbRecordType->rec_size); |
2822 | - precord = (char *)precnode->precord; |
2823 | + precord = precnode->precord; |
2824 | + precord->rdes = pdbRecordType; |
2825 | pflddes = pdbRecordType->papFldDes[0]; |
2826 | if(!pflddes) { |
2827 | epicsPrintf("dbAllocRecord pflddes for NAME not found\n"); |
2828 | @@ -220,13 +222,13 @@ |
2829 | epicsPrintf("dbAllocRecord: NAME(%s) too long\n",precordName); |
2830 | return(S_dbLib_nameLength); |
2831 | } |
2832 | - pfield = precord + pflddes->offset; |
2833 | + pfield = (char*)precord + pflddes->offset; |
2834 | strcpy(pfield,precordName); |
2835 | for(i=1; i<pdbRecordType->no_fields; i++) { |
2836 | |
2837 | pflddes = pdbRecordType->papFldDes[i]; |
2838 | if(!pflddes) continue; |
2839 | - pfield = precord + pflddes->offset; |
2840 | + pfield = (char*)precord + pflddes->offset; |
2841 | pdbentry->pfield = (void *)pfield; |
2842 | pdbentry->pflddes = pflddes; |
2843 | pdbentry->indfield = i; |
2844 | |
2845 | === modified file 'src/ioc/dbStatic/link.h' |
2846 | --- src/ioc/dbStatic/link.h 2012-07-11 23:07:23 +0000 |
2847 | +++ src/ioc/dbStatic/link.h 2015-08-31 21:08:35 +0000 |
2848 | @@ -17,6 +17,7 @@ |
2849 | #define INC_link_H |
2850 | |
2851 | #include "dbDefs.h" |
2852 | +#include "ellLib.h" |
2853 | #include "shareLib.h" |
2854 | |
2855 | #ifdef __cplusplus |
2856 | @@ -79,6 +80,7 @@ |
2857 | struct pvlet; |
2858 | |
2859 | struct pv_link { |
2860 | + ELLNODE backlinknode; |
2861 | char *pvname; /* pvname link points to */ |
2862 | struct dbCommon *precord; /* Address of record owning link */ |
2863 | void *pvt; /* CA or DB private */ |
2864 | |
2865 | === modified file 'src/ioc/misc/iocInit.c' |
2866 | --- src/ioc/misc/iocInit.c 2015-08-18 13:07:18 +0000 |
2867 | +++ src/ioc/misc/iocInit.c 2015-08-31 21:08:35 +0000 |
2868 | @@ -467,7 +467,6 @@ |
2869 | if (!prset) return; /* unlikely */ |
2870 | |
2871 | precord->rset = prset; |
2872 | - precord->rdes = pdbRecordType; |
2873 | precord->mlok = epicsMutexMustCreate(); |
2874 | ellInit(&precord->mlis); |
2875 | |
2876 | @@ -496,7 +495,7 @@ |
2877 | /* For all the links in the record type... */ |
2878 | for (j = 0; j < pdbRecordType->no_links; j++) { |
2879 | dbFldDes *pdbFldDes = papFldDes[link_ind[j]]; |
2880 | - DBLINK *plink = (DBLINK *)((char *)precord + pdbFldDes->offset); |
2881 | + DBLINK *plink = (DBLINK*)((char*)precord + pdbFldDes->offset); |
2882 | |
2883 | if (ellCount(&precord->rdes->devList) > 0 && pdbFldDes->isDevLink) { |
2884 | devSup *pdevSup = dbDTYPtoDevSup(pdbRecordType, precord->dtyp); |
2885 | @@ -636,12 +635,12 @@ |
2886 | locked = 1; |
2887 | } |
2888 | dbCaRemoveLink(plink); |
2889 | - plink->type = CONSTANT; |
2890 | + plink->type = PV_LINK; |
2891 | |
2892 | } else if (plink->type == DB_LINK) { |
2893 | /* free link, but don't split lockset like dbDbRemoveLink() */ |
2894 | free(plink->value.pv_link.pvt); |
2895 | - plink->type = CONSTANT; |
2896 | + plink->type = PV_LINK; |
2897 | } |
2898 | } |
2899 | |
2900 | @@ -666,11 +665,17 @@ |
2901 | void *user) |
2902 | { |
2903 | int j; |
2904 | - struct rset *prset = pdbRecordType->prset; |
2905 | - |
2906 | - if (!prset) return; /* unlikely */ |
2907 | + |
2908 | + for (j = 0; j < pdbRecordType->no_links; j++) { |
2909 | + dbFldDes *pdbFldDes = |
2910 | + pdbRecordType->papFldDes[pdbRecordType->link_ind[j]]; |
2911 | + DBLINK *plink = (DBLINK *)((char *)precord + pdbFldDes->offset); |
2912 | + |
2913 | + dbFreeLinkContents(plink); |
2914 | + } |
2915 | |
2916 | epicsMutexDestroy(precord->mlok); |
2917 | +<<<<<<< TREE |
2918 | |
2919 | for (j = 0; j < pdbRecordType->no_links; j++) { |
2920 | dbFldDes *pdbFldDes = |
2921 | @@ -682,6 +687,8 @@ |
2922 | |
2923 | // may be allocated in dbNotify.c |
2924 | free(precord->ppnr); |
2925 | +======= |
2926 | +>>>>>>> MERGE-SOURCE |
2927 | } |
2928 | |
2929 | int iocShutdown(void) |
Documentation is in progress.