Merge lp:~epics-core/epics-base/lockopt into lp:~epics-core/epics-base/3.16

Proposed by mdavidsaver
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
Reviewer Review Type Date Requested Status
Andrew Johnson Approve
mdavidsaver Approve
Ralph Lange Pending
Review via email: mp+250073@code.launchpad.net

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

To post a comment you must log in.
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Documentation is in progress.

review: Needs Fixing
Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> epicsAtomicDefault.h:137: multiple definition of `epicsAtomicSetIntT'

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'.

Revision history for this message
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.

lp:~epics-core/epics-base/lockopt updated
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

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Rebase to latest w/ atomic linking fix.

review: Needs Resubmitting
lp:~epics-core/epics-base/lockopt updated
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 dbLockCleanupRecords() when not all
locksets are free'd.

12659. By mdavidsaver

dbLock: minor

must match following condition

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

I think the self link issue is resolved now.

review: Approve
lp:~epics-core/epics-base/lockopt updated
12660. By mdavidsaver

iocInit: Don't free LSET until scan tasks have stopped

12661. By mdavidsaver

dbLock: fix unlock w/o lock during iocInit

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Fix one startup and one shutdown bug. I think this is ready to merge now.

Revision history for this message
Andrew Johnson (anj) wrote :

I fixed one merge conflict in src/ioc/misc/iocInit.c and some build failures from a parallel build:

  ../dbStressLock.c:40:21: error: xRecord.h: No such file or directory
That was a missing explicit dependency in src/ioc/db/test/Makefile.

  dbLockTest.o: In function `testLinkMake':
  base-3.16/src/ioc/db/test/O.linux-x86_64/../dbLockTest.c:294: undefined reference to `dbLockGetRef'
  base-3.16/src/ioc/db/test/O.linux-x86_64/../dbLockTest.c:296: undefined reference to `dbLockGetRef'
  base-3.16/src/ioc/db/test/O.linux-x86_64/../dbLockTest.c:315: undefined reference to `dbLockDecRef'
  base-3.16/src/ioc/db/test/O.linux-x86_64/../dbLockTest.c:316: undefined reference to `dbLockDecRef'
  dbLockTest.o: In function `testLinkChange':
  base-3.16/src/ioc/db/test/O.linux-x86_64/../dbLockTest.c:343: undefined reference to `dbLockGetRef'
  base-3.16/src/ioc/db/test/O.linux-x86_64/../dbLockTest.c:344: undefined reference to `dbLockGetRef'
  base-3.16/src/ioc/db/test/O.linux-x86_64/../dbLockTest.c:365: undefined reference to `dbLockDecRef'
  base-3.16/src/ioc/db/test/O.linux-x86_64/../dbLockTest.c:366: undefined reference to `dbLockDecRef'
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.

review: Approve
Revision history for this message
Andrew Johnson (anj) wrote :
Download full text (3.3 KiB)

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 devMbbiDirectSoftCallback (add_record) Illegal INP field
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/dev/devAiSoftCallback.c. Note that the "Async Soft Channel" device code for output record types is very different to the input record types, the output types use dbCaPutLinkCallback() and should not be affected, but the input types use dbNotify.

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(ai,"mrk:aipget")
{
    field(DTYP,"Async Soft Channel")
    field(INP,"mrk:delayInc")
}
record(bi,"mrk:bipget")
{
    field(DTYP,"Async Soft Channel")
    field(INP,"mrk:delayInc")
    field(ZNAM,"zero")
    field(ONAM,"one")
}
record(longin,"mrk:longinpget")
{
    field(DTYP,"Async Soft Channel")
    field(INP,"mrk:delayInc")
}
record(mbbi,"mrk:mbbipget")
{
    field(DTYP,"Async Soft Channel")
    field(INP,"mrk:delayInc")
    field(ZRST,"zero")
    field(ONST,"one")
    field(TWST,"two")
    field(THST,"three")
}
record(mbbiDirect,"mrk:mbbidirectpget")
{
    field(DTYP,"Async Soft Channel")
    field(INP,"mrk:delayInc")
}
record(stringin,...

Read more...

review: Needs Fixing
Revision history for this message
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.

lp:~epics-core/epics-base/lockopt updated
12662. By Andrew Johnson

Export private dbLock*Ref() functions for tests

Revision history for this message
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

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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(ai,"anj:aipget")
{
    field(DTYP,"Async Soft Channel")
    field(INP,"anj:delayInc")
    field(TPRO,1)
    field(FLNK,"anj:bipget")
}
record(bi,"anj:bipget")
{
    field(DTYP,"Async Soft Channel")
    field(INP,"anj:delayInc")
    field(ZNAM,"zero")
    field(ONAM,"one")
    field(TPRO,1)
    field(FLNK,"anj:longinpget")
}
record(longin,"anj:longinpget")
{
    field(DTYP,"Async Soft Channel")
    field(INP,"anj:delayInc")
    field(TPRO,1)
    field(FLNK,"anj:mbbipget")
}
record(mbbi,"anj:mbbipget")
{
    field(DTYP,"Async Soft Channel")
    field(INP,"anj:delayInc")
    field(ZRST,"zero")
    field(ONST,"one")
    field(TWST,"two")
    field(THST,"three")
    field(TPRO,1)
    field(FLNK,"anj:mbbidirectpget")
}
record(mbbiDirect,"anj:mbbidirectpget")
{
    field(DTYP,"Async Soft Channel")
    field(INP,"anj:delayInc")
    field(TPRO,1)
    field(FLNK,"anj:stringinpget")
}
record(stringin,"anj:stringinpget")
{
    field(DTYP,"Async Soft Channel")
    field(INP,"anj:delayInc")
    field(TPRO,1)
    field(FLNK,"anj:finished")
}
record(stringout,"anj:finished")
{
    field(VAL,"Chain completed")
    field(DTYP,"stdio")
    field(OUT,"@stdout")
}
record(seq,"anj:delayInc")
{
    field(DLY0,1)
    field(LNK0,"anj:delayInc.DO0")
}

lp:~epics-core/epics-base/lockopt updated
12663. By mdavidsaver

dbLock: restore initialization of PV_LINK

PV_LINK -> DB_LINK must happen in doResolveLinks after add_record

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Ok, I think this fixes the PN_LINK initialization issue by restoring the original init sequence.

lp:~epics-core/epics-base/lockopt updated
12664. By Andrew Johnson

Make dbLockTest work when LOCKSET_DEBUG undefined

12665. By Andrew Johnson

Remove some unnecesary #includes

Revision history for this message
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.

lp:~epics-core/epics-base/lockopt updated
12666. By Andrew Johnson

Undo s/printf/errlogPrintf/ in iocsh commands

12667. By mdavidsaver

db/test: dbStressTest conditional TIME_STATS

Revision history for this message
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-x86_64/softIoc -m user=anj -d ~/db/links.db
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="../../../src/ioc/db/dbCa.c" line number=793
dbCa: eventCallback Logic Error

The stringout.OUT links must be CA links, you can't modify a link field value using a DB link.

Revision history for this message
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="../../../src/ioc/db/dbCa.c" line number=794
dbCa: eventCallback Logic Error

Dumping a stack trace of thread 'CAC-event':
[ 0x7ffc66fa1903]: /local/anj/lockopt/lib/linux-x86_64/libCom.so.3.16.0(epicsStackTrace+0x73)
[ 0x7ffc6744cb7d]: /local/anj/lockopt/lib/linux-x86_64/libdbCore.so.3.16.0(eventCallback+0x25d)
[ 0x7ffc671fbb28]: /local/anj/lockopt/lib/linux-x86_64/libca.so.3.16.0(oldSubscription::current(epicsGuard<epicsMutex>&, unsigned int, unsigned long, void const*)+0xa8)
[ 0x7ffc6744e745]: /local/anj/lockopt/lib/linux-x86_64/libdbCore.so.3.16.0(dbContext::callStateNotify(dbChannel*, unsigned int, unsigned long, db_field_log const*, cacStateNotify&)+0x185)
[ 0x7ffc674443f6]: /local/anj/lockopt/lib/linux-x86_64/libdbCore.so.3.16.0(event_task+0x1f6)
[ 0x7ffc66f9cd54]: /local/anj/lockopt/lib/linux-x86_64/libCom.so.3.16.0(start_routine+0xf4)
[ 0x3809e079d1]: /lib64/libpthread.so.0(<no symbol information>)
[ 0x3809ae88fd]: /lib64/libc.so.6(clone+0x6d)

Revision history for this message
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://bazaar.launchpad.net/~epics-core/epics-base/3.16/revision/12618.1.53 which hasn't been merged into this branch.

Revision history for this message
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.

lp:~epics-core/epics-base/lockopt updated
12668. By Andrew Johnson

db/test: Add missing dependency

Revision history for this message
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!

review: Approve
lp:~epics-core/epics-base/lockopt updated
12669. By mdavidsaver

dbLockTest: check additional recursive case

12670. By mdavidsaver

dbLock: better error check when recursive locking attempted

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

I've made two small changes as we discussed. I think it's now ready to
merge.

lp:~epics-core/epics-base/lockopt updated
12671. By Andrew Johnson

Fix comment

12672. By Andrew Johnson

Undefine LOCKSET_DEBUG & LOCKSET_NOFREE

Revision history for this message
Andrew Johnson (anj) wrote :

Merging. Please don't forget to commit a RELEASE_NOTES entry to the branch.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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)

Subscribers

People subscribed via source and target branches