diff --git a/src/ioc/db/dbAccess.c b/src/ioc/db/dbAccess.c index 116c8319e..73ee20cf4 100644 --- a/src/ioc/db/dbAccess.c +++ b/src/ioc/db/dbAccess.c @@ -32,6 +32,7 @@ #include "epicsThread.h" #include "epicsTime.h" #include "errlog.h" +#include "epicsSpin.h" #include "errMdef.h" #define epicsExportSharedSymbols @@ -50,7 +51,7 @@ #include "dbFldTypes.h" #include "dbFldTypes.h" #include "dbLink.h" -#include "dbLock.h" +#include "dbLockPvt.h" #include "dbNotify.h" #include "dbScan.h" #include "dbServer.h" @@ -944,6 +945,8 @@ static long dbPutFieldLink(DBADDR *paddr, dbLinkInfo link_info; DBADDR *pdbaddr = NULL; dbCommon *precord = paddr->precord; + dbCommon *lockrecs[2]; + dbLocker locker; dbFldDes *pfldDes = paddr->pfldDes; long special = paddr->special; struct link *plink = (struct link *)paddr->pfield; @@ -956,6 +959,8 @@ static long dbPutFieldLink(DBADDR *paddr, int isDevLink; short scan; + STATIC_ASSERT(DBLOCKER_NALLOC>=2); + switch (dbrType) { case DBR_CHAR: case DBR_UCHAR: @@ -992,10 +997,12 @@ static long dbPutFieldLink(DBADDR *paddr, isDevLink = ellCount(&precord->rdes->devList) > 0 && pfldDes->isDevLink; - dbLockSetGblLock(); - dbLockSetRecordLock(precord); - if (pdbaddr) - dbLockSetRecordLock(pdbaddr->precord); + memset(&locker, 0, sizeof(locker)); + lockrecs[0] = precord; + lockrecs[1] = pdbaddr ? pdbaddr->precord : NULL; + dbLockerPrepare(&locker, lockrecs, 2); + + dbScanLockMany(&locker); scan = precord->scan; @@ -1044,7 +1051,7 @@ static long dbPutFieldLink(DBADDR *paddr, switch (plink->type) { /* Old link type */ case DB_LINK: case CA_LINK: - dbRemoveLink(plink); + dbRemoveLink(&locker, precord, plink); /* link type becomes PV_LINK */ break; case PV_LINK: @@ -1091,7 +1098,7 @@ static long dbPutFieldLink(DBADDR *paddr, switch (plink->type) { /* New link type */ case PV_LINK: - dbAddLink(precord, plink, pfldDes->field_type, pdbaddr); + dbAddLink(&locker, precord, plink, pfldDes->field_type, pdbaddr); break; case CONSTANT: @@ -1121,7 +1128,8 @@ postScanEvent: if (scan != precord->scan) db_post_events(precord, &precord->scan, DBE_VALUE | DBE_LOG); unlock: - dbLockSetGblUnlock(); + dbScanUnlockMany(&locker); + dbLockerFinalize(&locker); cleanup: free(link_info.target); return status; diff --git a/src/ioc/db/dbCaTest.c b/src/ioc/db/dbCaTest.c index 409fd526b..450eb9463 100644 --- a/src/ioc/db/dbCaTest.c +++ b/src/ioc/db/dbCaTest.c @@ -87,10 +87,10 @@ long dbcar(char *precordname, int level) !dbIsAlias(pdbentry)) { pdbRecordType = pdbentry->precordType; precord = (dbCommon *)pdbentry->precnode->precord; + dbScanLock(precord); for (j=0; jno_links; j++) { pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[j]]; plink = (DBLINK *)((char *)precord + pdbFldDes->offset); - dbLockSetGblLock(); if (plink->type == CA_LINK) { ncalinks++; pca = (caLink *)plink->value.pv_link.pvt; @@ -135,8 +135,8 @@ long dbcar(char *precordname, int level) } } } - dbLockSetGblUnlock(); } + dbScanUnlock(precord); if (precordname) goto done; } status = dbNextRecord(pdbentry); diff --git a/src/ioc/db/dbCommon.dbd b/src/ioc/db/dbCommon.dbd index f87853bec..f58d8d78c 100644 --- a/src/ioc/db/dbCommon.dbd +++ b/src/ioc/db/dbCommon.dbd @@ -92,6 +92,12 @@ interest(4) extra("ELLLIST mlis") } + field(BKLNK,DBF_NOACCESS) { + prompt("Backwards link tracking") + special(SPC_NOMOD) + interest(4) + extra("ELLLIST bklnk") + } field(DISP,DBF_UCHAR) { prompt("Disable putField") } diff --git a/src/ioc/db/dbLink.c b/src/ioc/db/dbLink.c index 2ad9c6606..1d5ca9944 100644 --- a/src/ioc/db/dbLink.c +++ b/src/ioc/db/dbLink.c @@ -23,6 +23,7 @@ #include "cantProceed.h" #include "cvtFast.h" #include "dbDefs.h" +#include "epicsSpin.h" #include "ellLib.h" #include "epicsThread.h" #include "epicsTime.h" @@ -45,7 +46,7 @@ #include "dbFldTypes.h" #include "dbFldTypes.h" #include "dbLink.h" -#include "dbLock.h" +#include "dbLockPvt.h" #include "dbNotify.h" #include "dbScan.h" #include "dbStaticLib.h" @@ -126,7 +127,7 @@ static long dbConstGetLink(struct link *plink, short dbrType, void *pbuffer, /***************************** Database Links *****************************/ -static long dbDbInitLink(struct link *plink, short dbfType) +static long dbDbInitLink(struct dbCommon *precord, struct link *plink, short dbfType) { DBADDR dbaddr; long status; @@ -140,18 +141,25 @@ static long dbDbInitLink(struct link *plink, short dbfType) pdbAddr = dbCalloc(1, sizeof(struct dbAddr)); *pdbAddr = dbaddr; /* structure copy */ plink->value.pv_link.pvt = pdbAddr; - dbLockSetMerge(plink->value.pv_link.precord, pdbAddr->precord); + ellAdd(&dbaddr.precord->bklnk, &plink->value.pv_link.backlinknode); + /* merging into the same lockset is deferred to the caller. + * cf. initPVLinks() + */ + dbLockSetMerge(NULL, precord, dbaddr.precord); + assert(precord->lset->plockSet==dbaddr.precord->lset->plockSet); return 0; } -static void dbDbRemoveLink(struct link *plink) +static void dbDbRemoveLink(dbLocker *locker, struct dbCommon *prec, struct link *plink) { - free(plink->value.pv_link.pvt); + DBADDR *pdbAddr = (DBADDR *) plink->value.pv_link.pvt; plink->value.pv_link.pvt = 0; plink->value.pv_link.getCvt = 0; plink->value.pv_link.lastGetdbrType = 0; plink->type = PV_LINK; - dbLockSetSplit(plink->value.pv_link.precord); + ellDelete(&pdbAddr->precord->bklnk, &plink->value.pv_link.backlinknode); + dbLockSetSplit(locker, prec, pdbAddr->precord); + free(pdbAddr); } static int dbDbIsLinkConnected(const struct link *plink) @@ -380,7 +388,7 @@ static void dbDbScanFwdLink(struct link *plink) dbScanPassive(precord, paddr->precord); } -lset dbDb_lset = { dbDbRemoveLink, +lset dbDb_lset = { NULL, dbDbIsLinkConnected, dbDbGetDBFtype, dbDbGetElements, dbDbGetValue, dbDbGetControlLimits, dbDbGetGraphicLimits, dbDbGetAlarmLimits, dbDbGetPrecision, dbDbGetUnits, dbDbGetAlarm, dbDbGetTimeStamp, @@ -397,7 +405,7 @@ void dbInitLink(struct dbCommon *precord, struct link *plink, short dbfType) if (!(plink->value.pv_link.pvlMask & (pvlOptCA | pvlOptCP | pvlOptCPP))) { /* Make it a DB link if possible */ - if (!dbDbInitLink(plink, dbfType)) + if (!dbDbInitLink(precord, plink, dbfType)) return; } @@ -422,7 +430,7 @@ void dbInitLink(struct dbCommon *precord, struct link *plink, short dbfType) } } -void dbAddLink(struct dbCommon *precord, struct link *plink, short dbfType, DBADDR *ptargetaddr) +void dbAddLink(dbLocker *locker, struct dbCommon *precord, struct link *plink, short dbfType, DBADDR *ptargetaddr) { plink->value.pv_link.precord = precord; @@ -434,9 +442,10 @@ void dbAddLink(struct dbCommon *precord, struct link *plink, short dbfType, DBAD plink->type = DB_LINK; plink->value.pv_link.pvt = ptargetaddr; + ellAdd(&ptargetaddr->precord->bklnk, &plink->value.pv_link.backlinknode); /* target record is already locked in dbPutFieldLink() */ - dbLockSetMerge(plink->value.pv_link.precord, ptargetaddr->precord); + dbLockSetMerge(locker, plink->value.pv_link.precord, ptargetaddr->precord); return; } @@ -463,11 +472,11 @@ long dbLoadLink(struct link *plink, short dbrType, void *pbuffer) return S_db_notFound; } -void dbRemoveLink(struct link *plink) +void dbRemoveLink(dbLocker *locker, dbCommon *prec, struct link *plink) { switch (plink->type) { case DB_LINK: - dbDbRemoveLink(plink); + dbDbRemoveLink(locker, prec, plink); break; case CA_LINK: dbCaRemoveLink(plink); diff --git a/src/ioc/db/dbLink.h b/src/ioc/db/dbLink.h index f96f8ece1..006189728 100644 --- a/src/ioc/db/dbLink.h +++ b/src/ioc/db/dbLink.h @@ -50,13 +50,15 @@ typedef struct lset { #define dbGetSevr(PLINK, PSEVERITY) \ dbGetAlarm((PLINK), NULL, (PSEVERITY)) +struct dbLocker; + epicsShareFunc void dbInitLink(struct dbCommon *precord, struct link *plink, short dbfType); -epicsShareFunc void dbAddLink(struct dbCommon *precord, struct link *plink, +epicsShareFunc void dbAddLink(struct dbLocker *locker, struct dbCommon *precord, struct link *plink, short dbfType, DBADDR *ptargetaddr); epicsShareFunc long dbLoadLink(struct link *plink, short dbrType, void *pbuffer); -epicsShareFunc void dbRemoveLink(struct link *plink); +epicsShareFunc void dbRemoveLink(struct dbLocker *locker, struct dbCommon *prec, struct link *plink); epicsShareFunc long dbGetNelements(const struct link *plink, long *nelements); epicsShareFunc int dbIsLinkConnected(const struct link *plink); epicsShareFunc int dbGetLinkDBFtype(const struct link *plink); diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index 1c59dc068..b30d1a28e 100644 --- a/src/ioc/db/dbLock.c +++ b/src/ioc/db/dbLock.c @@ -7,41 +7,7 @@ * and higher are distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ -/* dbLock.c */ -/* Author: Marty Kraimer Date: 12MAR96 */ -/************** DISCUSSION OF DYNAMIC LINK MODIFICATION ********************** - -A routine attempting to modify a link must do the following: - -Call dbLockSetGblLock before modifying any link and dbLockSetGblUnlock after. -Call dbLockSetRecordLock for any record referenced during change. It MUST NOT UNLOCK -Call dbLockSetSplit before changing any link that is originally a DB_LINK -Call dbLockSetMerge if changed link becomes a DB_LINK. - -Since the purpose of lock sets is to prevent multiple thread from simultaneously -accessing records in set, dynamically changing lock sets presents a problem. - -Three problems arise: - -1) Two threads simultaneoulsy trying to change lock sets -2) Another thread has successfully issued a dbScanLock and currently owns it. -3) While lock set is being changed, a thread issues a dbScanLock. - -solution: - -1) globalLock is locked during the entire time a thread is modifying lock sets - -2) lockSetModifyLock is locked whenever any fields in lockSet are being accessed -or lockRecord.plockSet is being accessed. - -NOTE: - -dblsr may crash if executed while lock sets are being modified. -It is NOT a good idea to make it more robust by issuing dbLockSetGblLock -since this will delay all other threads. -*****************************************************************************/ - #include #include #include @@ -51,466 +17,864 @@ since this will delay all other threads. #include "dbDefs.h" #include "ellLib.h" #include "epicsAssert.h" -#include "epicsExit.h" #include "epicsMutex.h" #include "epicsPrint.h" #include "epicsStdio.h" #include "epicsThread.h" +#include "epicsSpin.h" +#include "epicsAtomic.h" #include "errMdef.h" #define epicsExportSharedSymbols #include "dbAccessDefs.h" #include "dbAddr.h" #include "dbBase.h" +#include "dbLink.h" #include "dbCommon.h" #include "dbFldTypes.h" -#include "dbLock.h" +#include "dbLockPvt.h" #include "dbStaticLib.h" #include "link.h" +typedef struct dbScanLockNode dbScanLockNode; -static int dbLockIsInitialized = FALSE; +static epicsThreadOnceId dbLockOnceInit = EPICS_THREAD_ONCE_INIT; -typedef enum { - listTypeScanLock = 0, - listTypeRecordLock = 1, - listTypeFree = 2 -} listType; +static ELLLIST lockSetsActive; /* in use */ +#ifndef LOCKSET_NOFREE +static ELLLIST lockSetsFree; /* free list */ +#endif -#define nlistType listTypeFree + 1 +/* Guard the global list */ +static epicsMutexId lockSetsGuard; -static ELLLIST lockSetList[nlistType]; -static epicsMutexId globalLock; -static epicsMutexId lockSetModifyLock; -static unsigned long id = 0; -static char *msstring[4]={"NMS","MS","MSI","MSS"}; - -typedef enum { - lockSetStateFree=0, lockSetStateScanLock, lockSetStateRecordLock -} lockSetState; - -typedef struct lockSet { - ELLNODE node; - ELLLIST lockRecordList; - epicsMutexId lock; - unsigned long id; - listType type; - lockSetState state; - epicsThreadId thread_id; - dbCommon *precord; - int nRecursion; - int nWaiting; - int trace; /*For field TPRO*/ -} lockSet; - -/* dbCommon.LSET is a plockRecord */ -typedef struct lockRecord { - ELLNODE node; - lockSet *plockSet; - dbCommon *precord; -} lockRecord; +#ifndef LOCKSET_NOCNT +/* Counter which we increment whenever + * any lockRecord::plockSet is changed. + * An optimization to avoid checking lockSet + * associations when no links have changed. + */ +static size_t recomputeCnt; +#endif /*private routines */ -static void dbLockInitialize(void) +static void dbLockOnce(void* ignore) { - int i; - - if(dbLockIsInitialized) return; - for(i=0; i< nlistType; i++) ellInit(&lockSetList[i]); - globalLock = epicsMutexMustCreate(); - lockSetModifyLock = epicsMutexMustCreate(); - dbLockIsInitialized = TRUE; + lockSetsGuard = epicsMutexMustCreate(); } -static lockSet * allocLockSet( - lockRecord *plockRecord, listType type, - lockSetState state, epicsThreadId thread_id) -{ - lockSet *plockSet; +/* global ID number assigned to each lockSet on creation. + * When the free-list is in use will never exceed + * the number of records +1. + * Without the free-list it can roll over, potentially + * leading to duplicate IDs. + */ +static size_t next_id = 1; - assert(dbLockIsInitialized); - plockSet = (lockSet *)ellFirst(&lockSetList[listTypeFree]); - if(plockSet) { - ellDelete(&lockSetList[listTypeFree],&plockSet->node); - } else { - plockSet = dbCalloc(1,sizeof(lockSet)); - plockSet->lock = epicsMutexMustCreate(); +static lockSet* makeSet(void) +{ + lockSet *ls; + int iref; + epicsMutexMustLock(lockSetsGuard); +#ifndef LOCKSET_NOFREE + ls = (lockSet*)ellGet(&lockSetsFree); + if(!ls) { + epicsMutexUnlock(lockSetsGuard); +#endif + + ls=dbCalloc(1,sizeof(*ls)); + ellInit(&ls->lockRecordList); + ls->lock = epicsMutexMustCreate(); + ls->id = epicsAtomicIncrSizeT(&next_id); + +#ifndef LOCKSET_NOFREE + epicsMutexMustLock(lockSetsGuard); } - ellInit(&plockSet->lockRecordList); - plockRecord->plockSet = plockSet; - id++; - plockSet->id = id; - plockSet->type = type; - plockSet->state = state; - plockSet->thread_id = thread_id; - plockSet->precord = 0; - plockSet->nRecursion = 0; - plockSet->nWaiting = 0; - ellAdd(&plockSet->lockRecordList,&plockRecord->node); - ellAdd(&lockSetList[type],&plockSet->node); - return(plockSet); +#endif + /* the initial reference for the first lockRecord */ + iref = epicsAtomicIncrIntT(&ls->refcount); + ellAdd(&lockSetsActive, &ls->node); + epicsMutexUnlock(lockSetsGuard); + + assert(ls->id>0); + assert(iref>0); + assert(ellCount(&ls->lockRecordList)==0); + + return ls; +} + +unsigned long dbLockGetRefs(struct dbCommon* prec) +{ + return (unsigned long)epicsAtomicGetIntT(&prec->lset->plockSet->refcount); +} + +unsigned long dbLockCountSets(void) +{ + unsigned long count; + epicsMutexMustLock(lockSetsGuard); + count = (unsigned long)ellCount(&lockSetsActive); + epicsMutexUnlock(lockSetsGuard); + return count; +} + +/* caller must lock accessLock.*/ +void dbLockIncRef(lockSet* ls) +{ + int cnt = epicsAtomicIncrIntT(&ls->refcount); + if(cnt<=1) { + errlogPrintf("dbLockIncRef(%p) on dead lockSet. refs: %d\n", ls, cnt); + cantProceed(NULL); + } +} + +/* caller must lock accessLock. + * lockSet must *not* be locked + */ +void dbLockDecRef(lockSet *ls) +{ + int cnt = epicsAtomicDecrIntT(&ls->refcount); + assert(cnt>=0); + + if(cnt) + return; + + /* lockSet is unused and will be free'd */ + + /* not necessary as no one else (should) hold a reference, + * but lock anyway to quiet valgrind + */ + epicsMutexMustLock(ls->lock); + + if(ellCount(&ls->lockRecordList)!=0) { + errlogPrintf("dbLockDecRef(%p) would free lockSet with %d records\n", + ls, ellCount(&ls->lockRecordList)); + cantProceed(NULL); + } + + epicsMutexUnlock(ls->lock); + + epicsMutexMustLock(lockSetsGuard); + ellDelete(&lockSetsActive, &ls->node); +#ifndef LOCKSET_NOFREE + ellAdd(&lockSetsFree, &ls->node); +#else + epicsMutexDestroy(ls->lock); + memset(ls, 0, sizeof(*ls)); /* paranoia */ + free(ls); +#endif + epicsMutexUnlock(lockSetsGuard); +} + +lockSet* dbLockGetRef(lockRecord *lr) +{ + lockSet *ls; + epicsSpinLock(lr->spin); + ls = lr->plockSet; + dbLockIncRef(ls); + epicsSpinUnlock(lr->spin); + return ls; } unsigned long dbLockGetLockId(dbCommon *precord) { - lockRecord *plockRecord = precord->lset; - lockSet *plockSet; - long id = 0; - - assert(plockRecord); - epicsMutexMustLock(lockSetModifyLock); - plockSet = plockRecord->plockSet; - if(plockSet) id = plockSet->id; - epicsMutexUnlock(lockSetModifyLock); - return(id); -} - -void dbLockSetGblLock(void) -{ - assert(dbLockIsInitialized); - epicsMutexMustLock(globalLock); + unsigned long id=0; + epicsSpinLock(precord->lset->spin); + id = precord->lset->plockSet->id; + epicsSpinUnlock(precord->lset->spin); + return id; } -void dbLockSetGblUnlock(void) -{ - lockSet *plockSet; - lockSet *pnext; - epicsMutexMustLock(lockSetModifyLock); - plockSet = (lockSet *)ellFirst(&lockSetList[listTypeRecordLock]); - while(plockSet) { - pnext = (lockSet *)ellNext(&plockSet->node); - ellDelete(&lockSetList[listTypeRecordLock],&plockSet->node); - plockSet->type = listTypeScanLock; - plockSet->state = lockSetStateFree; - plockSet->thread_id = 0; - plockSet->precord = 0; - plockSet->nRecursion = 0; - plockSet->nWaiting = 0; - ellAdd(&lockSetList[listTypeScanLock],&plockSet->node); - plockSet = pnext; - } - epicsMutexUnlock(lockSetModifyLock); - epicsMutexUnlock(globalLock); - return; -} - -void dbLockSetRecordLock(dbCommon *precord) -{ - lockRecord *plockRecord = precord->lset; - lockSet *plockSet; - - /*Must make sure that no other thread has lock*/ - assert(plockRecord); - epicsMutexMustLock(lockSetModifyLock); - plockSet = plockRecord->plockSet; - assert(plockSet); - if(plockSet->type==listTypeRecordLock) { - epicsMutexUnlock(lockSetModifyLock); - return; - } - assert(plockSet->thread_id!=epicsThreadGetIdSelf()); - plockSet->state = lockSetStateRecordLock; - /*Wait until owner finishes and all waiting get to change state*/ - while(1) { - epicsMutexUnlock(lockSetModifyLock); - epicsMutexMustLock(plockSet->lock); - epicsMutexUnlock(plockSet->lock); - epicsMutexMustLock(lockSetModifyLock); - if(plockSet->nWaiting == 0 && plockSet->nRecursion==0) break; - epicsThreadSleep(.1); - } - assert(plockSet->nWaiting == 0 && plockSet->nRecursion==0); - assert(plockSet->type==listTypeScanLock); - assert(plockSet->state==lockSetStateRecordLock); - ellDelete(&lockSetList[plockSet->type],&plockSet->node); - ellAdd(&lockSetList[listTypeRecordLock],&plockSet->node); - plockSet->type = listTypeRecordLock; - plockSet->thread_id = epicsThreadGetIdSelf(); - plockSet->precord = 0; - epicsMutexUnlock(lockSetModifyLock); -} - void dbScanLock(dbCommon *precord) { - lockRecord *plockRecord = precord->lset; - lockSet *plockSet; - epicsMutexLockStatus status; - epicsThreadId idSelf = epicsThreadGetIdSelf(); + int cnt; + lockRecord * const lr = precord->lset; + lockSet *ls; - /* - * If this assertion is failing it is likely because iocInit - * has not completed. It must complete before normal record - * processing is possible. Consider using an initHook to - * detect when this occurs. - */ - assert(dbLockIsInitialized); - while(1) { - epicsMutexMustLock(lockSetModifyLock); - plockSet = plockRecord->plockSet; - if(!plockSet) goto getGlobalLock; - switch(plockSet->state) { - case lockSetStateFree: - status = epicsMutexTryLock(plockSet->lock); - assert(status==epicsMutexLockOK); - plockSet->nRecursion = 1; - plockSet->thread_id = idSelf; - plockSet->precord = precord; - plockSet->state = lockSetStateScanLock; - epicsMutexUnlock(lockSetModifyLock); - return; - case lockSetStateScanLock: - if(plockSet->thread_id!=idSelf) { - plockSet->nWaiting +=1; - epicsMutexUnlock(lockSetModifyLock); - epicsMutexMustLock(plockSet->lock); - epicsMutexMustLock(lockSetModifyLock); - plockSet->nWaiting -=1; - if(plockSet->state==lockSetStateRecordLock) { - epicsMutexUnlock(plockSet->lock); - goto getGlobalLock; - } - assert(plockSet->state==lockSetStateScanLock); - plockSet->nRecursion = 1; - plockSet->thread_id = idSelf; - plockSet->precord = precord; - } else { - plockSet->nRecursion += 1; - } - epicsMutexUnlock(lockSetModifyLock); - return; - case lockSetStateRecordLock: - /*Only recursive locking is permitted*/ - if((plockSet->nRecursion==0) || (plockSet->thread_id!=idSelf)) - goto getGlobalLock; - plockSet->nRecursion += 1; - epicsMutexUnlock(lockSetModifyLock); - return; - default: - cantProceed("dbScanLock. Bad case choice"); - } -getGlobalLock: - epicsMutexUnlock(lockSetModifyLock); - epicsMutexMustLock(globalLock); - epicsMutexUnlock(globalLock); + ls = dbLockGetRef(lr); + assert(epicsAtomicGetIntT(&ls->refcount)>0); + +retry: + epicsMutexMustLock(ls->lock); + + epicsSpinLock(lr->spin); + if(ls!=lr->plockSet) { + /* oops, collided with recompute. + * take a reference to the new lockSet. + */ + lockSet *ls2 = lr->plockSet; + int newcnt = epicsAtomicIncrIntT(&ls2->refcount); + assert(newcnt>=2); /* at least lockRecord and us */ + epicsSpinUnlock(lr->spin); + + epicsMutexUnlock(ls->lock); + dbLockDecRef(ls); + + ls = ls2; + goto retry; } + epicsSpinUnlock(lr->spin); + + /* Release reference taken within this + * function. The count will *never* fall to zero + * as the lockRecords can't be changed while + * we hold the lock. + */ + cnt = epicsAtomicDecrIntT(&ls->refcount); + assert(cnt>0); + +#ifdef LOCKSET_DEBUG + if(ls->owner) { + assert(ls->owner==epicsThreadGetIdSelf()); + assert(ls->ownercount>=1); + ls->ownercount++; + } else { + assert(ls->ownercount==0); + ls->owner = epicsThreadGetIdSelf(); + ls->ownercount = 1; + } +#endif } - + void dbScanUnlock(dbCommon *precord) { - lockRecord *plockRecord = precord->lset; - lockSet *plockSet; - - assert(plockRecord); - epicsMutexMustLock(lockSetModifyLock); - plockSet = plockRecord->plockSet; - assert(plockSet); - assert(epicsThreadGetIdSelf()==plockSet->thread_id); - assert(plockSet->nRecursion>=1); - plockSet->nRecursion -= 1; - if(plockSet->nRecursion==0) { - plockSet->thread_id = 0; - plockSet->precord = 0; - if((plockSet->state == lockSetStateScanLock) - && (plockSet->nWaiting==0)) plockSet->state = lockSetStateFree; - epicsMutexUnlock(plockSet->lock); - } - epicsMutexUnlock(lockSetModifyLock); - return; + lockSet *ls = precord->lset->plockSet; + dbLockIncRef(ls); +#ifdef LOCKSET_DEBUG + assert(ls->owner==epicsThreadGetIdSelf()); + assert(ls->ownercount>=1); + ls->ownercount--; + if(ls->ownercount==0) + ls->owner = NULL; +#endif + epicsMutexUnlock(ls->lock); + dbLockDecRef(ls); } -static lockRecord *lockRecordAlloc; +static +int lrrcompare(const void *rawA, const void *rawB) +{ + const lockRecordRef *refA=rawA, *refB=rawB; + const lockSet *A=refA->plockSet, *B=refB->plockSet; + if(!A && !B) + return 0; /* NULL == NULL */ + else if(!A) + return 1; /* NULL > !NULL */ + else if(!B) + return -1; /* !NULL < NULL */ + else if(A < B) + return -1; + else if(A > B) + return 1; + else + return 0; +} + +/* Call w/ update=1 before locking to update cached lockSet entries. + * Call w/ update=0 after locking to verify that lockRecord weren't updated + */ +static +int dbLockUpdateRefs(dbLocker *locker, int update) +{ + int changed = 0; + size_t i, nlock = locker->maxrefs; + +#ifndef LOCKSET_NOCNT + const size_t recomp = epicsAtomicGetSizeT(&recomputeCnt); + if(locker->recomp!=recomp) { +#endif + /* some lockset recompute happened. + * must re-check our references. + */ + + for(i=0; irefs[i]; + lockSet *oldref = NULL; + if(!ref->plr) { /* this lockRecord slot not used */ + assert(!ref->plockSet); + continue; + } + + epicsSpinLock(ref->plr->spin); + if(ref->plockSet!=ref->plr->plockSet) { + changed = 1; + if(update) { + /* exchange saved lockSet reference */ + oldref = ref->plockSet; /* will be NULL on first iteration */ + ref->plockSet = ref->plr->plockSet; + dbLockIncRef(ref->plockSet); + } + } + epicsSpinUnlock(ref->plr->spin); + if(oldref) + dbLockDecRef(oldref); + if(!update && changed) + return changed; + } +#ifndef LOCKSET_NOCNT + /* Use the value captured before we started. + * If it has changed in the intrim we will catch this later + * during the update==0 pass (which triggers a re-try) + */ + if(update) + locker->recomp = recomp; + } +#endif + + if(changed && update) { + qsort(locker->refs, nlock, sizeof(lockRecordRef), + &lrrcompare); + } + return changed; +} + +void dbLockerPrepare(struct dbLocker *locker, + struct dbCommon **precs, + size_t nrecs) +{ + size_t i; + locker->maxrefs = nrecs; + /* intentionally spoil the recomp count to ensure that + * references will be updated this first time + */ +#ifndef LOCKSET_NOCNT + locker->recomp = epicsAtomicGetSizeT(&recomputeCnt)-1; +#endif + + for(i=0; irefs[i].plr = precs[i] ? precs[i]->lset : NULL; + } + + /* acquire a reference to all lockRecords */ + dbLockUpdateRefs(locker, 1); +} + +dbLocker *dbLockerAlloc(dbCommon **precs, + size_t nrecs, + unsigned int flags) +{ + size_t Nextra = nrecs>DBLOCKER_NALLOC ? nrecs-DBLOCKER_NALLOC : 0; + dbLocker *locker = calloc(1, sizeof(*locker)+Nextra*sizeof(lockRecordRef)); + + if(locker) + dbLockerPrepare(locker, precs, nrecs); + + return locker; +} + +void dbLockerFinalize(dbLocker *locker) +{ + size_t i; + assert(ellCount(&locker->locked)==0); + + /* release references taken in dbLockUpdateRefs() */ + for(i=0; imaxrefs; i++) { + if(locker->refs[i].plockSet) + dbLockDecRef(locker->refs[i].plockSet); + } +} + +void dbLockerFree(dbLocker *locker) +{ + dbLockerFinalize(locker); + free(locker); +} + +/* Lock the given list of records. + * This function modifies its arguments. + */ +void dbScanLockMany(dbLocker* locker) +{ + size_t i, nlock = locker->maxrefs; + lockSet *plock; +#ifdef LOCKSET_DEBUG + const epicsThreadId myself = epicsThreadGetIdSelf(); +#endif + +retry: + assert(ellCount(&locker->locked)==0); + dbLockUpdateRefs(locker, 1); + + for(i=0, plock=NULL; irefs[i]; + + /* skip duplicates (same lockSet + * referenced by more than one lockRecord). + * Sorting will group these together. + */ + if(!ref->plr || (plock && plock==ref->plockSet)) + continue; + plock = ref->plockSet; + + epicsMutexMustLock(plock->lock); + assert(plock->ownerlocker==NULL); + plock->ownerlocker = locker; + ellAdd(&locker->locked, &plock->lockernode); + /* An extra ref for the locked list */ + dbLockIncRef(plock); + +#ifdef LOCKSET_DEBUG + if(plock->owner) { + if(plock->owner!=myself || plock->ownercount<1) { + errlogPrintf("dbScanLockMany(%p) ownership violation %p (%p) %u\n", + locker, plock->owner, myself, plock->ownercount); + cantProceed(NULL); + } + plock->ownercount++; + } else { + assert(plock->ownercount==0); + plock->owner = myself; + plock->ownercount = 1; + } +#endif + + } + + if(dbLockUpdateRefs(locker, 0)) { + /* oops, collided with recompute */ + dbScanUnlockMany(locker); + goto retry; + } + if(nlock!=0 && ellCount(&locker->locked)<=0) { + /* if we have at least one lockRecord, then we will always lock + * at least its present lockSet + */ + errlogPrintf("dbScanLockMany(%p) didn't lock anything\n", locker); + cantProceed(NULL); + } +} + +void dbScanUnlockMany(dbLocker* locker) +{ + ELLNODE *cur; +#ifdef LOCKSET_DEBUG + const epicsThreadId myself = epicsThreadGetIdSelf(); +#endif + + while((cur=ellGet(&locker->locked))!=NULL) { + lockSet *plock = CONTAINER(cur, lockSet, lockernode); + + assert(plock->ownerlocker==locker); + plock->ownerlocker = NULL; +#ifdef LOCKSET_DEBUG + assert(plock->owner==myself); + assert(plock->ownercount>=1); + plock->ownercount--; + if(plock->ownercount==0) + plock->owner = NULL; +#endif + + epicsMutexUnlock(plock->lock); + /* release ref for locked list */ + dbLockDecRef(plock); + } +} + +typedef int (*reciter)(void*, DBENTRY*); +static int forEachRecord(void *priv, dbBase *pdbbase, reciter fn) +{ + long status; + int ret = 0; + DBENTRY dbentry; + dbInitEntry(pdbbase,&dbentry); + status = dbFirstRecordType(&dbentry); + while(!status) + { + status = dbFirstRecord(&dbentry); + while(!status) + { + /* skip alias names */ + if(!dbentry.precnode->recordname[0] || dbentry.precnode->flags & DBRN_FLAGS_ISALIAS) { + /* skip */ + } else { + ret = fn(priv, &dbentry); + if(ret) + goto done; + } + + status = dbNextRecord(&dbentry); + } + + status = dbNextRecordType(&dbentry); + } +done: + dbFinishEntry(&dbentry); + return ret; +} + +static int createLockRecord(void* junk, DBENTRY* pdbentry) +{ + dbCommon *prec = pdbentry->precnode->precord; + lockRecord *lrec; + assert(!prec->lset); + + /* TODO: one allocation for all records? */ + lrec = callocMustSucceed(1, sizeof(*lrec), "lockRecord"); + lrec->spin = epicsSpinCreate(); + if(!lrec->spin) + cantProceed("no memory for spinlock in lockRecord"); + + lrec->precord = prec; + + prec->lset = lrec; + + prec->lset->plockSet = makeSet(); + ellAdd(&prec->lset->plockSet->lockRecordList, &prec->lset->node); + return 0; +} void dbLockInitRecords(dbBase *pdbbase) { - dbRecordType *pdbRecordType; - int nrecords = 0; - lockRecord *plockRecord; + epicsThreadOnce(&dbLockOnceInit, &dbLockOnce, NULL); - dbLockInitialize(); - /*Allocate and initialize a lockRecord for each record instance*/ - for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); - pdbRecordType; - pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { + /* create all lockRecords and lockSets */ + forEachRecord(NULL, pdbbase, &createLockRecord); +} - nrecords += ellCount(&pdbRecordType->recList) - - pdbRecordType->no_aliases; - } +static int freeLockRecord(void* junk, DBENTRY* pdbentry) +{ + dbCommon *prec = pdbentry->precnode->precord; + lockRecord *lr = prec->lset; + lockSet *ls = lr->plockSet; - /*Allocate all of them at once */ - lockRecordAlloc = plockRecord = dbCalloc(nrecords,sizeof(lockRecord)); - for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); - pdbRecordType; - pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { - dbRecordNode *pdbRecordNode; + prec->lset = NULL; + lr->precord = NULL; - for (pdbRecordNode=(dbRecordNode *)ellFirst(&pdbRecordType->recList); - pdbRecordNode; - pdbRecordNode = (dbRecordNode *)ellNext(&pdbRecordNode->node)) { - dbCommon *precord = pdbRecordNode->precord; + assert(ls->refcount>0); + assert(ellCount(&ls->lockRecordList)>0); + ellDelete(&ls->lockRecordList, &lr->node); + dbLockDecRef(ls); - if (!precord->name[0] || - pdbRecordNode->flags & DBRN_FLAGS_ISALIAS) - continue; - - plockRecord->precord = precord; - precord->lset = plockRecord; - plockRecord++; - } - } - - for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); - pdbRecordType; - pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { - dbRecordNode *pdbRecordNode; - - for (pdbRecordNode=(dbRecordNode *)ellFirst(&pdbRecordType->recList); - pdbRecordNode; - pdbRecordNode = (dbRecordNode *)ellNext(&pdbRecordNode->node)) { - dbCommon *precord = pdbRecordNode->precord; - - if (!precord->name[0] || - pdbRecordNode->flags & DBRN_FLAGS_ISALIAS) - continue; - - plockRecord = precord->lset; - if (!plockRecord->plockSet) - allocLockSet(plockRecord, listTypeScanLock, lockSetStateFree, 0); - } - } + epicsSpinDestroy(lr->spin); + free(lr); + return 0; } void dbLockCleanupRecords(dbBase *pdbbase) { +#ifndef LOCKSET_NOFREE ELLNODE *cur; +#endif + epicsThreadOnce(&dbLockOnceInit, &dbLockOnce, NULL); - free(lockRecordAlloc); - lockRecordAlloc = NULL; + forEachRecord(NULL, pdbbase, &freeLockRecord); + if(ellCount(&lockSetsActive)) { + errlogMessage("Warning: dbLockCleanupRecords() leaking lockSets\n"); + dblsr(NULL,2); + } - /* free lockSets */ - /* ensure no lockSets are locked for re-compute */ - assert(ellCount(&lockSetList[listTypeRecordLock])==0); - /* move allocated locks back to the free list */ - while((cur=ellGet(&lockSetList[listTypeScanLock]))!=NULL) - { - lockSet *pset = CONTAINER(cur, lockSet, node); - assert(pset->state == lockSetStateFree); /* lock not held */ - pset->type = listTypeFree; - ellAdd(&lockSetList[listTypeFree],&pset->node); - } - /* clean up free list */ - while((cur=ellGet(&lockSetList[listTypeFree]))!=NULL) - { - lockSet *pset = CONTAINER(cur, lockSet, node); - epicsMutexDestroy(pset->lock); - free(pset); + assert(ellCount(&lockSetsActive)==0); + +#ifndef LOCKSET_NOFREE + while((cur=ellGet(&lockSetsFree))!=NULL) { + lockSet *ls = (lockSet*)cur; + + assert(ls->refcount==0); + assert(ellCount(&ls->lockRecordList)==0); + epicsMutexDestroy(ls->lock); + free(ls); } +#endif } -void dbLockSetMerge(dbCommon *pfirst,dbCommon *psecond) +/* Called in two modes. + * During dbLockInitRecords w/ locker==NULL, then no mutex are locked. + * After dbLockInitRecords w/ locker!=NULL, then + * the caller must lock both pfirst and psecond. + * + * Assumes that pfirst has been modified + * to link to psecond. + */ +void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) { - lockRecord *p1lockRecord = pfirst->lset; - lockRecord *p2lockRecord = psecond->lset; - lockSet *p1lockSet; - lockSet *p2lockSet; - lockRecord *plockRecord; - lockRecord *pnext; + ELLNODE *cur; + lockSet *A=pfirst->lset->plockSet, + *B=psecond->lset->plockSet; + int Nb; +#ifdef LOCKSET_DEBUG + const epicsThreadId myself = epicsThreadGetIdSelf(); +#endif - epicsMutexMustLock(lockSetModifyLock); - if(pfirst==psecond) goto all_done; - p1lockSet = p1lockRecord->plockSet; - p2lockSet = p2lockRecord->plockSet; - assert(p1lockSet || p2lockSet); - if(p1lockSet == p2lockSet) goto all_done; - if(!p1lockSet) { - p1lockRecord->plockSet = p2lockSet; - ellAdd(&p2lockSet->lockRecordList,&p1lockRecord->node); - goto all_done; + assert(A && B); + +#ifdef LOCKSET_DEBUG + if(locker && (A->owner!=myself || B->owner!=myself)) { + errlogPrintf("dbLockSetMerge(%p,\"%s\",\"%s\") ownership violation %p %p (%p)\n", + locker, pfirst->name, psecond->name, + A->owner, B->owner, myself); + cantProceed(NULL); } - if(!p2lockSet) { - p2lockRecord->plockSet = p1lockSet; - ellAdd(&p1lockSet->lockRecordList,&p2lockRecord->node); - goto all_done; +#endif + if(locker && (A->ownerlocker!=locker || B->ownerlocker!=locker)) { + errlogPrintf("dbLockSetMerge(%p,\"%s\",\"%s\") locker ownership violation %p %p (%p)\n", + locker, pfirst->name, psecond->name, + A->ownerlocker, B->ownerlocker, locker); + cantProceed(NULL); } - /*Move entire second list to first*/ - assert(p1lockSet->type == p2lockSet->type); - plockRecord = (lockRecord *)ellFirst(&p2lockSet->lockRecordList); - while(plockRecord) { - pnext = (lockRecord *)ellNext(&plockRecord->node); - ellDelete(&p2lockSet->lockRecordList,&plockRecord->node); - plockRecord->plockSet = p1lockSet; - ellAdd(&p1lockSet->lockRecordList,&plockRecord->node); - plockRecord = pnext; + + if(A==B) + return; /* already in the same lockSet */ + + Nb = ellCount(&B->lockRecordList); + assert(Nb>0); + + /* move all records from B to A */ + while((cur=ellGet(&B->lockRecordList))!=NULL) + { + lockRecord *lr = CONTAINER(cur, lockRecord, node); + assert(lr->plockSet==B); + ellAdd(&A->lockRecordList, cur); + + epicsSpinLock(lr->spin); + lr->plockSet = A; +#ifndef LOCKSET_NOCNT + epicsAtomicIncrSizeT(&recomputeCnt); +#endif + epicsSpinUnlock(lr->spin); } - ellDelete(&lockSetList[p2lockSet->type],&p2lockSet->node); - p2lockSet->type = listTypeFree; - ellAdd(&lockSetList[listTypeFree],&p2lockSet->node); -all_done: - epicsMutexUnlock(lockSetModifyLock); - return; + + /* there are at minimum, 1 ref for each lockRecord, + * and one for the locker's locked list + * (and perhaps another for its refs cache) + */ + assert(epicsAtomicGetIntT(&B->refcount)>=Nb+(locker?1:0)); + + /* update ref counters. for lockRecords */ + epicsAtomicAddIntT(&A->refcount, Nb); + epicsAtomicAddIntT(&B->refcount, -Nb+1); /* drop all but one ref, see below */ + + if(locker) { + /* at least two refs, possibly three, remain. + * # One ref from above + * # locker->locked list, which is released now. + * # locker->refs array, assuming it is directly referenced, + * and not added as the result of a dbLockSetSplit, + * which will be cleaned when the locker is free'd (not here). + */ +#ifdef LOCKSET_DEBUG + B->owner = NULL; + B->ownercount = 0; +#endif + assert(B->ownerlocker==locker); + ellDelete(&locker->locked, &B->lockernode); + B->ownerlocker = NULL; + epicsAtomicDecrIntT(&B->refcount); + + epicsMutexUnlock(B->lock); + } + + dbLockDecRef(B); /* last ref we hold */ + + assert(A==psecond->lset->plockSet); } - -void dbLockSetSplit(dbCommon *psource) + +/* recompute assuming a link from pfirst to psecond + * may have been removed. + * pfirst and psecond must currently be in the same lockset, + * which the caller must lock before calling this function. + * If a new lockset is created, then it is locked + * when this function returns. + */ +void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) { - lockSet *plockSet; - lockRecord *plockRecord; - lockRecord *pnext; - dbCommon *precord; - int link; - dbRecordType *pdbRecordType; - dbFldDes *pdbFldDes; - DBLINK *plink; - int indlockRecord,nlockRecords; - lockRecord **paplockRecord; - epicsThreadId idself = epicsThreadGetIdSelf(); - + lockSet *ls = pfirst->lset->plockSet; + ELLLIST toInspect, newLS; +#ifdef LOCKSET_DEBUG + const epicsThreadId myself = epicsThreadGetIdSelf(); +#endif - plockRecord = psource->lset; - assert(plockRecord); - plockSet = plockRecord->plockSet; - assert(plockSet); - assert(plockSet->state==lockSetStateRecordLock); - assert(plockSet->type==listTypeRecordLock); - /*First remove all records from lock set and store in paplockRecord*/ - nlockRecords = ellCount(&plockSet->lockRecordList); - paplockRecord = dbCalloc(nlockRecords,sizeof(lockRecord*)); - epicsMutexMustLock(lockSetModifyLock); - plockRecord = (lockRecord *)ellFirst(&plockSet->lockRecordList); - for(indlockRecord=0; indlockRecordnode); - ellDelete(&plockSet->lockRecordList,&plockRecord->node); - plockRecord->plockSet = 0; - paplockRecord[indlockRecord] = plockRecord; - plockRecord = pnext; +#ifdef LOCKSET_DEBUG + if(ls->owner!=myself || psecond->lset->plockSet->owner!=myself) { + errlogPrintf("dbLockSetSplit(%p,\"%s\",\"%s\") ownership violation %p %p (%p)\n", + locker, pfirst->name, psecond->name, + ls->owner, psecond->lset->plockSet->owner, myself); + cantProceed(NULL); } - ellDelete(&lockSetList[plockSet->type],&plockSet->node); - plockSet->state = lockSetStateFree; - plockSet->type = listTypeFree; - ellAdd(&lockSetList[listTypeFree],&plockSet->node); - epicsMutexUnlock(lockSetModifyLock); - /*Now recompute lock sets */ - for(indlockRecord=0; indlockRecordplockSet) { - allocLockSet(plockRecord,listTypeRecordLock, - lockSetStateRecordLock,idself); - } - precord = plockRecord->precord; - epicsMutexUnlock(lockSetModifyLock); - pdbRecordType = precord->rdes; - for(link=0; linkno_links; link++) { - DBADDR *pdbAddr; +#endif - pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[link]]; - plink = (DBLINK *)((char *)precord + pdbFldDes->offset); - if(plink->type != DB_LINK) continue; - pdbAddr = (DBADDR *)(plink->value.pv_link.pvt); - dbLockSetMerge(precord,pdbAddr->precord); - } + /* lockset consistency violation */ + if(ls!=psecond->lset->plockSet) { + errlogPrintf("dbLockSetSplit(%p,\"%s\",\"%s\") consistency violation %p %p\n", + locker, pfirst->name, psecond->name, + pfirst->lset->plockSet, psecond->lset->plockSet); + cantProceed(NULL); + } + + + if(pfirst==psecond) + return; + + /* at least 1 ref for each lockRecord, + * and one for the locker + */ + assert(epicsAtomicGetIntT(&ls->refcount)>=ellCount(&ls->lockRecordList)+1); + + ellInit(&toInspect); + ellInit(&newLS); + + /* strategy is to start with psecond and do + * a breadth first traversal until all records are + * visited. If we encounter pfirst, then there + * is no need to create a new lockset so we abort + * early. + */ + ellAdd(&toInspect, &psecond->lset->compnode); + psecond->lset->compflag = 1; + + { + lockSet *splitset; + ELLNODE *cur; + while((cur=ellGet(&toInspect))!=NULL) + { + lockRecord *lr=CONTAINER(cur,lockRecord,compnode); + dbCommon *prec=lr->precord; + dbRecordType *rtype = prec->rdes; + size_t i; + ELLNODE *bcur; + + ellAdd(&newLS, cur); + prec->lset->compflag = 2; + + /* Visit all the links originating from prec */ + for(i=0; ino_links; i++) { + dbFldDes *pdesc = rtype->papFldDes[rtype->link_ind[i]]; + DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset); + DBADDR *ptarget; + lockRecord *lr; + + if(plink->type!=DB_LINK) + continue; + + ptarget = plink->value.pv_link.pvt; + lr = ptarget->precord->lset; + assert(lr); + + if(lr->precord==pfirst) { + /* so pfirst is still reachable from psecond, + * no new lock set should be created. + */ + goto nosplit; + } + + /* have we already visited this record? */ + if(lr->compflag) + continue; + + ellAdd(&toInspect, &lr->compnode); + lr->compflag = 1; + } + + /* Visit all links terminating at prec */ + for(bcur=ellFirst(&prec->bklnk); bcur; bcur=ellNext(bcur)) + { + struct pv_link *plink1 = CONTAINER(bcur, struct pv_link, backlinknode); + union value *plink2 = CONTAINER(plink1, union value, pv_link); + DBLINK *plink = CONTAINER(plink2, DBLINK, value); + lockRecord *lr = plink->value.pv_link.precord->lset; + + /* plink->type==DB_LINK is implied. Only DB_LINKs are tracked from BKLNK */ + + if(lr->precord==pfirst) { + goto nosplit; + } + + if(lr->compflag) + continue; + + ellAdd(&toInspect, &lr->compnode); + lr->compflag = 1; + } + } + /* All links involving psecond were traversed without finding + * pfirst. So we must create a new lockset. + * newLS contains the nodes which will + * make up this new lockset. + */ + /* newLS will have at least psecond in it */ + assert(ellCount(&newLS) > 0); + /* If we didn't find pfirst, then it must be in the + * original lockset, and not the new one + */ + assert(ellCount(&newLS) < ellCount(&ls->lockRecordList)); + assert(ellCount(&newLS) < ls->refcount); + + splitset = makeSet(); /* reference for locker->locked */ + + epicsMutexMustLock(splitset->lock); + + assert(splitset->ownerlocker==NULL); + ellAdd(&locker->locked, &splitset->lockernode); + splitset->ownerlocker = locker; + + assert(splitset->refcount==1); + +#ifdef LOCKSET_DEBUG + splitset->owner = ls->owner; + splitset->ownercount = 1; + assert(ls->ownercount==1); +#endif + + while((cur=ellGet(&newLS))!=NULL) + { + lockRecord *lr=CONTAINER(cur,lockRecord,compnode); + + lr->compflag = 0; /* reset for next time */ + + assert(lr->plockSet == ls); + ellDelete(&ls->lockRecordList, &lr->node); + ellAdd(&splitset->lockRecordList, &lr->node); + + epicsSpinLock(lr->spin); + lr->plockSet = splitset; +#ifndef LOCKSET_NOCNT + epicsAtomicIncrSizeT(&recomputeCnt); +#endif + epicsSpinUnlock(lr->spin); + /* new lockSet is "live" at this point + * as other threads may find it. + */ + } + + /* refcount of ls can't go to zero as the locker + * holds at least one reference (its locked list) + */ + epicsAtomicAddIntT(&ls->refcount, -ellCount(&splitset->lockRecordList)); + assert(ls->refcount>0); + epicsAtomicAddIntT(&splitset->refcount, ellCount(&splitset->lockRecordList)); + + assert(splitset->refcount>=ellCount(&splitset->lockRecordList)+1); + + assert(psecond->lset->plockSet==splitset); + + /* must have refs from pfirst lockRecord, + * and the locked list. + */ + assert(epicsAtomicGetIntT(&ls->refcount)>=2); + + return; + } + +nosplit: + { + /* reset compflag for all nodes visited + * during the aborted search + */ + ELLNODE *cur; + while((cur=ellGet(&toInspect))!=NULL) + { + lockRecord *lr=CONTAINER(cur,lockRecord,compnode); + lr->compflag = 0; + } + while((cur=ellGet(&newLS))!=NULL) + { + lockRecord *lr=CONTAINER(cur,lockRecord,compnode); + lr->compflag = 0; + } + return; } - free(paplockRecord); } - + +static char *msstring[4]={"NMS","MS","MSI","MSS"}; + long dblsr(char *recordname,int level) { int link; @@ -524,45 +888,34 @@ long dblsr(char *recordname,int level) dbFldDes *pdbFldDes; DBLINK *plink; - printf("globalLock %p\n",globalLock); - printf("lockSetModifyLock %p\n",lockSetModifyLock); if (recordname && ((*recordname == '\0') || !strcmp(recordname,"*"))) recordname = NULL; if(recordname) { dbInitEntry(pdbbase,pdbentry); status = dbFindRecord(pdbentry,recordname); if(status) { - printf("Record not found\n"); + errlogPrintf("Record not found\n"); dbFinishEntry(pdbentry); - return(0); + goto done; } precord = pdbentry->precnode->precord; dbFinishEntry(pdbentry); plockRecord = precord->lset; - if(!plockRecord) return(0); + if(!plockRecord) goto done; /* too early (before iocInit) */ plockSet = plockRecord->plockSet; } else { - plockSet = (lockSet *)ellFirst(&lockSetList[listTypeScanLock]); + plockSet = (lockSet *)ellFirst(&lockSetsActive); } for( ; plockSet; plockSet = (lockSet *)ellNext(&plockSet->node)) { - printf("Lock Set %lu %d members epicsMutexId %p", - plockSet->id,ellCount(&plockSet->lockRecordList),plockSet->lock); - if(epicsMutexTryLock(plockSet->lock)==epicsMutexLockOK) { - epicsMutexUnlock(plockSet->lock); - printf(" Not Locked\n"); - } else { - printf(" thread %p",plockSet->thread_id); - if(! plockSet->precord || !plockSet->precord->name) - printf(" NULL record or record name\n"); - else - printf(" record %s\n",plockSet->precord->name); - } + errlogPrintf("Lock Set %lu %d members %d refs epicsMutexId %p\n", + plockSet->id,ellCount(&plockSet->lockRecordList),plockSet->refcount,plockSet->lock); + if(level==0) { if(recordname) break; continue; } for(plockRecord = (lockRecord *)ellFirst(&plockSet->lockRecordList); plockRecord; plockRecord = (lockRecord *)ellNext(&plockRecord->node)) { precord = plockRecord->precord; pdbRecordType = precord->rdes; - printf("%s\n",precord->name); + errlogPrintf("%s\n",precord->name); if(level<=1) continue; for(link=0; (linkno_links) ; link++) { DBADDR *pdbAddr; @@ -570,22 +923,24 @@ long dblsr(char *recordname,int level) plink = (DBLINK *)((char *)precord + pdbFldDes->offset); if(plink->type != DB_LINK) continue; pdbAddr = (DBADDR *)(plink->value.pv_link.pvt); - printf("\t%s",pdbFldDes->name); + errlogPrintf("\t%s",pdbFldDes->name); if(pdbFldDes->field_type==DBF_INLINK) { - printf("\t INLINK"); + errlogPrintf("\t INLINK"); } else if(pdbFldDes->field_type==DBF_OUTLINK) { - printf("\tOUTLINK"); + errlogPrintf("\tOUTLINK"); } else if(pdbFldDes->field_type==DBF_FWDLINK) { - printf("\tFWDLINK"); + errlogPrintf("\tFWDLINK"); } - printf(" %s %s", + errlogPrintf(" %s %s", ((plink->value.pv_link.pvlMask&pvlOptPP)?" PP":"NPP"), msstring[plink->value.pv_link.pvlMask&pvlOptMsMode]); - printf(" %s\n",pdbAddr->precord->name); + errlogPrintf(" %s\n",pdbAddr->precord->name); } } if(recordname) break; } +done: + errlogFlush(); return(0); } @@ -593,36 +948,22 @@ long dbLockShowLocked(int level) { int indListType; lockSet *plockSet; - epicsMutexLockStatus status; - epicsMutexLockStatus lockSetModifyLockStatus = epicsMutexLockOK; - int itry; - printf("listTypeScanLock %d listTypeRecordLock %d listTypeFree %d\n", - ellCount(&lockSetList[0]), - ellCount(&lockSetList[1]), - ellCount(&lockSetList[2])); - for(itry=0; itry<100; itry++) { - lockSetModifyLockStatus = epicsMutexTryLock(lockSetModifyLock); - if(lockSetModifyLockStatus==epicsMutexLockOK) break; - epicsThreadSleep(.05); - } - if(lockSetModifyLockStatus!=epicsMutexLockOK) { - printf("Could not lock lockSetModifyLock\n"); - epicsMutexShow(lockSetModifyLock,level); - } - status = epicsMutexTryLock(globalLock); - if(status==epicsMutexLockOK) { - epicsMutexUnlock(globalLock); - } else { - printf("globalLock is locked\n"); - epicsMutexShow(globalLock,level); - } + errlogPrintf("lockSets %d listTypeFree %d\n", + ellCount(&lockSetsActive), +#ifndef LOCKSET_NOFREE + ellCount(&lockSetsFree) +#else + -1 +#endif + ); + /*Even if failure on lockSetModifyLock will continue */ for(indListType=0; indListType <= 1; ++indListType) { - plockSet = (lockSet *)ellFirst(&lockSetList[indListType]); + plockSet = (lockSet *)ellFirst(&lockSetsActive); if(plockSet) { - if(indListType==0) printf("listTypeScanLock\n"); - else printf("listTypeRecordLock\n"); + if(indListType==0) errlogPrintf("listTypeScanLock\n"); + else errlogPrintf("listTypeRecordLock\n"); } while(plockSet) { epicsMutexLockStatus status; @@ -630,18 +971,13 @@ long dbLockShowLocked(int level) status = epicsMutexTryLock(plockSet->lock); if(status==epicsMutexLockOK) epicsMutexUnlock(plockSet->lock); if(status!=epicsMutexLockOK || indListType==1) { - if(plockSet->precord) - printf("%s ",plockSet->precord->name); - printf("state %d thread_id %p nRecursion %d nWaiting %d\n", - plockSet->state,plockSet->thread_id, - plockSet->nRecursion,plockSet->nWaiting); + epicsMutexShow(plockSet->lock,level); } plockSet = (lockSet *)ellNext(&plockSet->node); } } - if(lockSetModifyLockStatus==epicsMutexLockOK) - epicsMutexUnlock(lockSetModifyLock); + errlogFlush(); return(0); } diff --git a/src/ioc/db/dbLock.h b/src/ioc/db/dbLock.h index e13ce38b1..9f2714057 100644 --- a/src/ioc/db/dbLock.h +++ b/src/ioc/db/dbLock.h @@ -13,6 +13,7 @@ #ifndef INCdbLockh #define INCdbLockh +#include "ellLib.h" #include "shareLib.h" #ifdef __cplusplus @@ -21,21 +22,26 @@ extern "C" { struct dbCommon; struct dbBase; +typedef struct dbLocker dbLocker; epicsShareFunc void dbScanLock(struct dbCommon *precord); epicsShareFunc void dbScanUnlock(struct dbCommon *precord); + +epicsShareFunc dbLocker *dbLockerAlloc(struct dbCommon **precs, + size_t nrecs, + unsigned int flags); + +epicsShareFunc void dbLockerFree(dbLocker *); + +epicsShareFunc void dbScanLockMany(dbLocker*); +epicsShareFunc void dbScanUnlockMany(dbLocker*); + epicsShareFunc unsigned long dbLockGetLockId( struct dbCommon *precord); epicsShareFunc void dbLockInitRecords(struct dbBase *pdbbase); epicsShareFunc void dbLockCleanupRecords(struct dbBase *pdbbase); -epicsShareFunc void dbLockSetMerge( - struct dbCommon *pfirst,struct dbCommon *psecond); -epicsShareFunc void dbLockSetSplit(struct dbCommon *psource); -/*The following are for code that modifies lock sets*/ -epicsShareFunc void dbLockSetGblLock(void); -epicsShareFunc void dbLockSetGblUnlock(void); -epicsShareFunc void dbLockSetRecordLock(struct dbCommon *precord); + /* Lock Set Report */ epicsShareFunc long dblsr(char *recordname,int level); @@ -47,6 +53,10 @@ epicsShareFunc long dbLockShowLocked(int level); /*KLUDGE to support field TPRO*/ epicsShareFunc int * dbLockSetAddrTrace(struct dbCommon *precord); +/* debugging */ +epicsShareFunc unsigned long dbLockGetRefs(struct dbCommon*); +epicsShareFunc unsigned long dbLockCountSets(void); + #ifdef __cplusplus } #endif diff --git a/src/ioc/db/dbLockPvt.h b/src/ioc/db/dbLockPvt.h new file mode 100644 index 000000000..58b92f7eb --- /dev/null +++ b/src/ioc/db/dbLockPvt.h @@ -0,0 +1,109 @@ +/*************************************************************************\ +* Copyright (c) 2014 Brookhaven Science Assoc., as Operator of Brookhaven +* National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef DBLOCKPVT_H +#define DBLOCKPVT_H + +#include "dbLock.h" +#include "epicsSpin.h" + +/* enable additional error checking */ +#define LOCKSET_DEBUG +/* disable the free list for lockSets */ +#define LOCKSET_NOFREE +/* disable use of recomputeCnt optimization */ +/*#define LOCKSET_NOCNT*/ + +/* except for refcount (and lock), all members of dbLockSet + * are guarded by its lock. + */ +typedef struct dbLockSet { + ELLNODE node; + ELLLIST lockRecordList; /* holds lockRecord::node */ + epicsMutexId lock; + unsigned long id; + + int refcount; +#ifdef LOCKSET_DEBUG + int ownercount; + epicsThreadId owner; +#endif + dbLocker *ownerlocker; + ELLNODE lockernode; + + int trace; /*For field TPRO*/ +} lockSet; + +struct lockRecord; + +/* dbCommon.LSET is a plockRecord. + * Except for spin and plockSet, all members of lockRecord are guarded + * by the present lockset lock. + * plockSet is guarded by spin. + */ +typedef struct lockRecord { + ELLNODE node; /* in lockSet::lockRecordList */ + /* The association between lockRecord and lockSet + * can only be changed while the lockSet is held, + * and the lockRecord's spinlock is held. + * It may be read which either lock is held. + */ + lockSet *plockSet; + /* the association between lockRecord and dbCommon never changes */ + dbCommon *precord; + epicsSpinId spin; + + /* temp used during lockset split. + * lockSet must be locked for access + */ + ELLNODE compnode; + unsigned int compflag; +} lockRecord; + +typedef struct { + lockRecord *plr; + /* the last lock found associated with the ref. + * not stable unless lock is locked, or ref spin + * is locked. + */ + lockSet *plockSet; +} lockRecordRef; + +#define DBLOCKER_NALLOC 2 +/* a dbLocker can only be used by a single thread. */ +struct dbLocker { + ELLLIST locked; +#ifndef LOCKSET_NOCNT + size_t recomp; /* snapshot of recomputeCnt when refs[] cache updated */ +#endif + size_t maxrefs; + lockRecordRef refs[DBLOCKER_NALLOC]; /* actual length is maxrefs */ +}; + +/* These are exported for testing only */ +epicsShareFunc lockSet* dbLockGetRef(lockRecord *lr); /* lookup lockset and increment ref count */ +epicsShareFunc void dbLockIncRef(lockSet* ls); +epicsShareFunc void dbLockDecRef(lockSet *ls); + +/* Calling dbLockerPrepare directly is an internal + * optimization used when dbLocker on the stack. + * nrecs must be <=DBLOCKER_NALLOC. + */ +void dbLockerPrepare(struct dbLocker *locker, + struct dbCommon **precs, + size_t nrecs); +void dbLockerFinalize(dbLocker *); + +void dbLockSetMerge(struct dbLocker *locker, + struct dbCommon *pfirst, + struct dbCommon *psecond); +void dbLockSetSplit(struct dbLocker *locker, + struct dbCommon *psource, + struct dbCommon *psecond); + +#endif /* DBLOCKPVT_H */ diff --git a/src/ioc/db/test/Makefile b/src/ioc/db/test/Makefile index 4bab6707f..0477208a2 100644 --- a/src/ioc/db/test/Makefile +++ b/src/ioc/db/test/Makefile @@ -61,6 +61,11 @@ testHarness_SRCS += dbLockTest.c TESTS += dbLockTest TESTFILES += ../dbLockTest.db +TESTPROD_HOST += dbStressTest +dbStressTest_SRCS += dbStressLock.c +dbStressTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +TESTS += dbStressTest + TESTPROD_HOST += testdbConvert testdbConvert_SRCS += testdbConvert.c testHarness_SRCS += testdbConvert.c diff --git a/src/ioc/db/test/dbLockTest.c b/src/ioc/db/test/dbLockTest.c index defb92f87..042917546 100644 --- a/src/ioc/db/test/dbLockTest.c +++ b/src/ioc/db/test/dbLockTest.c @@ -9,7 +9,15 @@ * Author: Michael Davidsaver */ -#include "dbLock.h" +#include + +#include "epicsSpin.h" +#include "epicsMutex.h" +#include "dbCommon.h" +#include "epicsThread.h" + +#include "dbLockPvt.h" +#include "dbStaticLib.h" #include "dbUnitTest.h" #include "testMain.h" @@ -28,15 +36,19 @@ void compareSets(int match, const char *A, const char *B) rA = testdbRecordPtr(A); rB = testdbRecordPtr(B); - actual = dbLockGetLockId(rA)==dbLockGetLockId(rB); + actual = rA->lset->plockSet==rB->lset->plockSet; testOk(match==actual, "dbLockGetLockId(\"%s\")%c=dbLockGetLockId(\"%s\")", A, match?'=':'!', B); } +#define testIntOk1(A, OP, B) testOk((A) OP (B), "%s (%d) %s %s (%d)", #A, A, #OP, #B, B); +#define testPtrOk1(A, OP, B) testOk((A) OP (B), "%s (%p) %s %s (%p)", #A, A, #OP, #B, B); + static void testSets(void) { - testDiag("Check initial creation of DB links"); + DBENTRY entry; + long status; testdbPrepare(); @@ -48,6 +60,27 @@ void testSets(void) { testIocInitOk(); eltc(1); + testDiag("Check that all records have initialized lockRecord and lockSet"); + + dbInitEntry(pdbbase, &entry); + for(status = dbFirstRecordType(&entry); + !status; + status = dbNextRecordType(&entry)) { + for(status = dbFirstRecord(&entry); + !status; + status = dbNextRecord(&entry)) { + dbCommon *prec = entry.precnode->precord; + testOk(prec->lset!=NULL, "%s.LSET != NULL", prec->name); + if(prec->lset!=NULL) + testOk(prec->lset->plockSet!=NULL, "%s.LSET.plockSet != NULL", prec->name); + else + testSkip(1, "lockRecord missing"); + } + } + dbFinishEntry(&entry); + + testDiag("Check initial creation of DB links"); + /* reca is by itself */ compareSets(0, "reca", "recb"); compareSets(0, "reca", "recc"); @@ -71,6 +104,299 @@ void testSets(void) { compareSets(1, "rece", "recf"); + testOk1(testdbRecordPtr("reca")->lset->plockSet->refcount==1); + testOk1(testdbRecordPtr("recb")->lset->plockSet->refcount==2); + testOk1(testdbRecordPtr("recd")->lset->plockSet->refcount==3); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testSingleLock(void) +{ + dbCommon *prec; + testDiag("testing dbScanLock()/dbScanUnlock()"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + prec = testdbRecordPtr("reca"); + testOk1(prec->lset->plockSet->refcount==1); + dbScanLock(prec); + /* scan lock does not keep a reference to the lockSet */ + testOk1(prec->lset->plockSet->refcount==1); + dbScanUnlock(prec); + + dbScanLock(prec); + dbScanLock(prec); + /* scan lock can be recursive */ + testOk1(prec->lset->plockSet->refcount==1); + dbScanUnlock(prec); + dbScanUnlock(prec); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testMultiLock(void) +{ + dbCommon *prec[8]; + dbLocker *plockA; + epicsThreadId myself = epicsThreadGetIdSelf(); + + testDiag("Test multi-locker function (lock everything)"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + prec[0] = testdbRecordPtr("reca"); + prec[1] = testdbRecordPtr("recb"); + prec[2] = testdbRecordPtr("recc"); + prec[3] = NULL; + prec[4] = testdbRecordPtr("recd"); + prec[5] = testdbRecordPtr("rece"); + prec[6] = testdbRecordPtr("recf"); + prec[7] = testdbRecordPtr("recg"); + + testDiag("Test init refcounts"); + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,1); + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,2); + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,3); + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,1); + + plockA = dbLockerAlloc(prec, 8, 0); + if(!plockA) + testAbort("dbLockerAlloc() failed"); + + + testDiag("After locker created"); + /* locker takes 7 references, one for each lockRecord. */ + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,2); + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,4); + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,6); + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,2); +#ifdef LOCKSET_DEBUG + testPtrOk1(testdbRecordPtr("reca")->lset->plockSet->owner,==,NULL); + testPtrOk1(testdbRecordPtr("recb")->lset->plockSet->owner,==,NULL); + testPtrOk1(testdbRecordPtr("recd")->lset->plockSet->owner,==,NULL); + testPtrOk1(testdbRecordPtr("recg")->lset->plockSet->owner,==,NULL); +#endif + + dbScanLockMany(plockA); + + testDiag("After locker locked"); + /* locker takes 4 references, one for each lockSet. */ + testIntOk1(ellCount(&plockA->locked),==,4); + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,3); + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,5); + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,7); + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,3); +#ifdef LOCKSET_DEBUG + testPtrOk1(testdbRecordPtr("reca")->lset->plockSet->owner,==,myself); + testPtrOk1(testdbRecordPtr("recb")->lset->plockSet->owner,==,myself); + testPtrOk1(testdbRecordPtr("recd")->lset->plockSet->owner,==,myself); + testPtrOk1(testdbRecordPtr("recg")->lset->plockSet->owner,==,myself); +#endif + + dbScanUnlockMany(plockA); + + testDiag("After locker unlocked"); + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,2); + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,4); + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,6); + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,2); +#ifdef LOCKSET_DEBUG + testPtrOk1(testdbRecordPtr("reca")->lset->plockSet->owner,==,NULL); + testPtrOk1(testdbRecordPtr("recb")->lset->plockSet->owner,==,NULL); + testPtrOk1(testdbRecordPtr("recd")->lset->plockSet->owner,==,NULL); + testPtrOk1(testdbRecordPtr("recg")->lset->plockSet->owner,==,NULL); +#endif + + dbLockerFree(plockA); + + testDiag("After locker free'd"); + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,1); + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,2); + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,3); + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,1); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testLinkBreak(void) +{ + dbCommon *precB, *precC; + testDiag("Test break link"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + precB = testdbRecordPtr("recb"); + precC = testdbRecordPtr("recc"); + + testOk1(precB->lset->plockSet==precC->lset->plockSet); + testOk1(precB->lset->plockSet->refcount==2); + + /* break the link between B and C */ + testdbPutFieldOk("recb.SDIS", DBR_STRING, ""); + + testOk1(precB->lset->plockSet!=precC->lset->plockSet); + testIntOk1(precB->lset->plockSet->refcount, ==, 1); + testIntOk1(precC->lset->plockSet->refcount, ==, 1); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testLinkMake(void) +{ + dbCommon *precA, *precG; + lockSet *lA, *lG; + testDiag("Test make link"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + precA = testdbRecordPtr("reca"); + lA = dbLockGetRef(precA->lset); + precG = testdbRecordPtr("recg"); + lG = dbLockGetRef(precG->lset); + + testPtrOk1(precA->lset->plockSet, !=, precG->lset->plockSet); + testIntOk1(precA->lset->plockSet->refcount, ==, 2); + testIntOk1(precG->lset->plockSet->refcount, ==, 2); + + /* make a link between A and G */ + testdbPutFieldOk("reca.SDIS", DBR_STRING, "recg"); + + testPtrOk1(precA->lset->plockSet, ==, precG->lset->plockSet); + testIntOk1(precA->lset->plockSet->refcount, ==, 3); + + if(precA->lset->plockSet==lG) { + testIntOk1(lA->refcount, ==, 1); + testIntOk1(lG->refcount, ==, 3); + } else { + testIntOk1(lA->refcount, ==, 3); + testIntOk1(lG->refcount, ==, 1); + } + dbLockDecRef(lG); + dbLockDecRef(lA); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testLinkChange(void) +{ + dbCommon *precB, *precC, *precG; + lockSet *lB, *lG; + testDiag("Test re-target link"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + precB = testdbRecordPtr("recb"); + precC = testdbRecordPtr("recc"); + precG = testdbRecordPtr("recg"); + + lB = dbLockGetRef(precB->lset); + lG = dbLockGetRef(precG->lset); + + testPtrOk1(lB,==,precC->lset->plockSet); + testPtrOk1(lB,!=,lG); + testIntOk1(lB->refcount,==,3); + testIntOk1(lG->refcount,==,2); + + /* break the link between B and C and replace it + * with a link between B and G + */ + testdbPutFieldOk("recb.SDIS", DBR_STRING, "recg"); + + testPtrOk1(precB->lset->plockSet,==,lB); + testPtrOk1(precG->lset->plockSet,==,lB); + testPtrOk1(precC->lset->plockSet,!=,lB); + testPtrOk1(precC->lset->plockSet,!=,lG); + + testIntOk1(lB->refcount,==,3); + testIntOk1(lG->refcount,==,1); + testIntOk1(precC->lset->plockSet->refcount,==,1); + + dbLockDecRef(lB); + dbLockDecRef(lG); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testLinkNOP(void) +{ + dbCommon *precB, *precC; + testDiag("Test re-target link to the same destination"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + precB = testdbRecordPtr("recb"); + precC = testdbRecordPtr("recc"); + + testOk1(precB->lset->plockSet==precC->lset->plockSet); + testOk1(precB->lset->plockSet->refcount==2); + + /* renew link between B and C */ + testdbPutFieldOk("recb.SDIS", DBR_STRING, "recc"); + + testOk1(precB->lset->plockSet==precC->lset->plockSet); + testOk1(precB->lset->plockSet->refcount==2); + testIocShutdownOk(); testdbCleanup(); @@ -78,7 +404,13 @@ void testSets(void) { MAIN(dbLockTest) { - testPlan(15); + testPlan(99); testSets(); + testSingleLock(); + testMultiLock(); + testLinkBreak(); + testLinkMake(); + testLinkChange(); + testLinkNOP(); return testDone(); } diff --git a/src/ioc/db/test/dbLockTest.db b/src/ioc/db/test/dbLockTest.db index 5c8ceb19c..37ff6022e 100644 --- a/src/ioc/db/test/dbLockTest.db +++ b/src/ioc/db/test/dbLockTest.db @@ -19,3 +19,6 @@ record(x, "rece") { record(x, "recf") { } + +record(x, "recg") { +} diff --git a/src/ioc/db/test/dbStressLock.c b/src/ioc/db/test/dbStressLock.c new file mode 100644 index 000000000..7aa33100c --- /dev/null +++ b/src/ioc/db/test/dbStressLock.c @@ -0,0 +1,338 @@ +/*************************************************************************\ +* Copyright (c) 2014 Brookhaven Science Assoc. as operator of Brookhaven +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Lockset stress test. + * + * The test stratagy is for N threads to contend for M records. + * Each thread will perform one of three operations: + * 1) Lock a single record. + * 2) Lock several records. + * 3) Retarget the TSEL link of a record + * + * Author: Michael Davidsaver + */ + +#include +#include +#include + +#include "envDefs.h" +#include "epicsStdlib.h" +#include "epicsSpin.h" +#include "epicsThread.h" +#include "epicsMutex.h" +#include "dbCommon.h" + +#include "dbLockPvt.h" +#include "dbStaticLib.h" + +#include "dbUnitTest.h" +#include "testMain.h" + +#include "dbAccess.h" +#include "errlog.h" + +#include "xRecord.h" + +#define testIntOk1(A, OP, B) testOk((A) OP (B), "%s (%d) %s %s (%d)", #A, A, #OP, #B, B); +#define testPtrOk1(A, OP, B) testOk((A) OP (B), "%s (%p) %s %s (%p)", #A, A, #OP, #B, B); + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +/* number of seconds for the test to run */ +static double runningtime = 18.0; + +/* number of worker threads */ +static unsigned int nworkers = 5; + +static unsigned int nrecords; + +#define MAXLOCK 20 + +static dbCommon **precords; + +typedef struct { + int id; + unsigned long N[3]; + double X[3]; + double X2[3]; + + unsigned int done; + epicsEventId donevent; + + dbCommon *prec[MAXLOCK]; +} workerPriv; + +/* hopefully a uniform random number in [0.0, 1.0] */ +static +double getRand(void) +{ + return rand()/(double)RAND_MAX; +} + +static +void doSingle(workerPriv *p) +{ + size_t recn = (size_t)(getRand()*(nrecords-1)); + dbCommon *prec = precords[recn]; + xRecord *px = (xRecord*)prec; + + dbScanLock(prec); + px->val++; + dbScanUnlock(prec); +} + +static volatile int bitbucket; + +static +void doMulti(workerPriv *p) +{ + int sum = 0; + size_t i; + size_t nlock = 2 + (size_t)(getRand()*(MAXLOCK-3)); + size_t nrec = (size_t)(getRand()*(nrecords-1)); + dbLocker *locker; + + assert(nlock>=2); + assert(nlockprec[i] = precords[nrec]; + } + + locker = dbLockerAlloc(p->prec, nlock, 0); + if(!locker) + testAbort("locker allocation fails"); + + dbScanLockMany(locker); + for(i=0; iprec[i]; + sum += px->val; + } + dbScanUnlockMany(locker); + + dbLockerFree(locker); +} + +static +void doreTarget(workerPriv *p) +{ + char scratchsrc[60]; + char scratchdst[MAX_STRING_SIZE]; + long ret; + DBADDR dbaddr; + double action = getRand(); + size_t nsrc = (size_t)(getRand()*(nrecords-1)); + size_t ntarg = (size_t)(getRand()*(nrecords-1)); + xRecord *psrc = (xRecord*)precords[nsrc]; + xRecord *ptarg = (xRecord*)precords[ntarg]; + + strcpy(scratchsrc, psrc->name); + strcat(scratchsrc, ".TSEL"); + + ret = dbNameToAddr(scratchsrc, &dbaddr); + if(ret) + testAbort("bad record name? %ld", ret); + + if(action<=0.6) { + scratchdst[0] = '\0'; + } else { + strcpy(scratchdst, ptarg->name); + } + + ret = dbPutField(&dbaddr, DBR_STRING, ptarg->name, 1); + if(ret) + testAbort("put fails with %ld", ret); +} + +static +void worker(void *raw) +{ + struct timespec before; + workerPriv *priv = raw; + + testDiag("worker %d is %p", priv->id, epicsThreadGetIdSelf()); + + clock_gettime(CLOCK_MONOTONIC, &before); + + while(!priv->done) { + double sel = getRand(); + struct timespec after; + double duration; + + int act; + if(sel<0.33) { + doSingle(priv); + act = 0; + } else if(sel<0.66) { + doMulti(priv); + act = 1; + } else { + doreTarget(priv); + act = 2; + } + + clock_gettime(CLOCK_MONOTONIC, &after); + + duration = (double)((long)after.tv_nsec - (long)before.tv_nsec); + duration *= 1e-9; + duration += (double)(after.tv_sec - before.tv_sec); + + priv->N[act]++; + priv->X[act] += duration; + priv->X2[act] += duration*duration; + } + + epicsEventMustTrigger(priv->donevent); +} + +MAIN(dbStressTest) +{ + DBENTRY ent; + long status; + unsigned int i; + workerPriv *priv; + char *nwork=getenv("NWORK"); + struct timespec seed; + + testPlan(95); + + clock_gettime(CLOCK_REALTIME, &seed); + srand(seed.tv_nsec); + + if(nwork) { + long val = 0; + epicsParseLong(nwork, &val, 0, NULL); + if(val>2) + nworkers = val; + } + + priv = callocMustSucceed(nworkers, sizeof(*priv), "no memory"); + + testDiag("lock set stress test"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbStressLock.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + /* collect an array of all records */ + dbInitEntry(pdbbase, &ent); + for(status = dbFirstRecordType(&ent); + !status; + status = dbNextRecordType(&ent)) + { + for(status = dbFirstRecord(&ent); + !status; + status = dbNextRecord(&ent)) + { + if(ent.precnode->flags&DBRN_FLAGS_ISALIAS) + continue; + nrecords++; + } + + } + if(nrecords<2) + testAbort("where are the records!"); + precords = callocMustSucceed(nrecords, sizeof(*precords), "no mem"); + for(status = dbFirstRecordType(&ent), i = 0; + !status; + status = dbNextRecordType(&ent)) + { + for(status = dbFirstRecord(&ent); + !status; + status = dbNextRecord(&ent)) + { + if(ent.precnode->flags&DBRN_FLAGS_ISALIAS) + continue; + precords[i++] = ent.precnode->precord; + } + + } + dbFinishEntry(&ent); + + testDiag("Running with %u workers and %u records", + nworkers, nrecords); + + for(i=0; iprecord; + lockSet *ls; + if(ent.precnode->flags&DBRN_FLAGS_ISALIAS) + continue; + ls = prec->lset->plockSet; + testOk(ellCount(&ls->lockRecordList)==ls->refcount, "%s only lockRecords hold refs. %d == %d", + prec->name,ellCount(&ls->lockRecordList),ls->refcount); + testOk1(ls->ownerlocker==NULL); + } + + } + dbFinishEntry(&ent); + + testDiag("Statistics"); + for(i=0; i0); + testOk1(priv[i].N[1]>0); + testOk1(priv[i].N[2]>0); + } + + testIocShutdownOk(); + + testdbCleanup(); + + free(priv); + free(precords); + + return testDone(); +} diff --git a/src/ioc/db/test/dbStressLock.db b/src/ioc/db/test/dbStressLock.db new file mode 100644 index 000000000..5aecf860a --- /dev/null +++ b/src/ioc/db/test/dbStressLock.db @@ -0,0 +1,40 @@ +record(x, "rec01") {} +record(x, "rec02") {} +record(x, "rec03") {} +record(x, "rec04") {} +record(x, "rec05") {} +record(x, "rec06") {} +record(x, "rec07") {} +record(x, "rec08") {} +record(x, "rec09") {} +record(x, "rec10") {} +record(x, "rec11") {} +record(x, "rec12") {} +record(x, "rec13") {} +record(x, "rec14") {} +record(x, "rec15") {} +record(x, "rec16") {} +record(x, "rec17") {} +record(x, "rec18") {} +record(x, "rec19") {} +record(x, "rec20") {} +record(x, "rec21") {} +record(x, "rec22") {} +record(x, "rec23") {} +record(x, "rec24") {} +record(x, "rec25") {} +record(x, "rec26") {} +record(x, "rec27") {} +record(x, "rec28") {} +record(x, "rec29") {} +record(x, "rec30") {} +record(x, "rec31") {} +record(x, "rec32") {} +record(x, "rec33") {} +record(x, "rec34") {} +record(x, "rec35") {} +record(x, "rec36") {} +record(x, "rec37") {} +record(x, "rec38") {} +record(x, "rec39") {} +record(x, "rec40") {} diff --git a/src/ioc/dbStatic/dbStaticRun.c b/src/ioc/dbStatic/dbStaticRun.c index 2ab477fd6..bd53bb13e 100644 --- a/src/ioc/dbStatic/dbStaticRun.c +++ b/src/ioc/dbStatic/dbStaticRun.c @@ -25,6 +25,7 @@ #define epicsExportSharedSymbols #include "dbBase.h" +#include "dbCommon.h" #include "dbStaticLib.h" #include "dbStaticPvt.h" #include "devSup.h" @@ -198,7 +199,7 @@ long dbAllocRecord(DBENTRY *pdbentry,const char *precordName) dbRecordNode *precnode = pdbentry->precnode; dbFldDes *pflddes; int i; - char *precord; + dbCommon *precord; char *pfield; if(!pdbRecordType) return(S_dbLib_recordTypeNotFound); @@ -210,7 +211,8 @@ long dbAllocRecord(DBENTRY *pdbentry,const char *precordName) return(S_dbLib_noRecSup); } precnode->precord = dbCalloc(1,pdbRecordType->rec_size); - precord = (char *)precnode->precord; + precord = precnode->precord; + precord->rdes = pdbRecordType; pflddes = pdbRecordType->papFldDes[0]; if(!pflddes) { epicsPrintf("dbAllocRecord pflddes for NAME not found\n"); @@ -220,13 +222,13 @@ long dbAllocRecord(DBENTRY *pdbentry,const char *precordName) epicsPrintf("dbAllocRecord: NAME(%s) too long\n",precordName); return(S_dbLib_nameLength); } - pfield = precord + pflddes->offset; + pfield = (char*)precord + pflddes->offset; strcpy(pfield,precordName); for(i=1; ino_fields; i++) { pflddes = pdbRecordType->papFldDes[i]; if(!pflddes) continue; - pfield = precord + pflddes->offset; + pfield = (char*)precord + pflddes->offset; pdbentry->pfield = (void *)pfield; pdbentry->pflddes = pflddes; pdbentry->indfield = i; diff --git a/src/ioc/dbStatic/link.h b/src/ioc/dbStatic/link.h index 5ad7aeee3..5f2510616 100644 --- a/src/ioc/dbStatic/link.h +++ b/src/ioc/dbStatic/link.h @@ -17,6 +17,7 @@ #define INC_link_H #include "dbDefs.h" +#include "ellLib.h" #include "shareLib.h" #ifdef __cplusplus @@ -79,6 +80,7 @@ struct dbCommon; struct pvlet; struct pv_link { + ELLNODE backlinknode; char *pvname; /* pvname link points to */ struct dbCommon *precord; /* Address of record owning link */ void *pvt; /* CA or DB private */ diff --git a/src/ioc/misc/iocInit.c b/src/ioc/misc/iocInit.c index d2d369f7b..155c0c715 100644 --- a/src/ioc/misc/iocInit.c +++ b/src/ioc/misc/iocInit.c @@ -467,7 +467,6 @@ static void doInitRecord0(dbRecordType *pdbRecordType, dbCommon *precord, if (!prset) return; /* unlikely */ precord->rset = prset; - precord->rdes = pdbRecordType; precord->mlok = epicsMutexMustCreate(); ellInit(&precord->mlis); @@ -496,7 +495,7 @@ static void doResolveLinks(dbRecordType *pdbRecordType, dbCommon *precord, /* For all the links in the record type... */ for (j = 0; j < pdbRecordType->no_links; j++) { dbFldDes *pdbFldDes = papFldDes[link_ind[j]]; - DBLINK *plink = (DBLINK *)((char *)precord + pdbFldDes->offset); + DBLINK *plink = (DBLINK*)((char*)precord + pdbFldDes->offset); if (ellCount(&precord->rdes->devList) > 0 && pdbFldDes->isDevLink) { devSup *pdevSup = dbDTYPtoDevSup(pdbRecordType, precord->dtyp); @@ -636,12 +635,12 @@ static void doCloseLinks(dbRecordType *pdbRecordType, dbCommon *precord, locked = 1; } dbCaRemoveLink(plink); - plink->type = CONSTANT; + plink->type = PV_LINK; } else if (plink->type == DB_LINK) { /* free link, but don't split lockset like dbDbRemoveLink() */ free(plink->value.pv_link.pvt); - plink->type = CONSTANT; + plink->type = PV_LINK; } } @@ -666,11 +665,6 @@ static void doFreeRecord(dbRecordType *pdbRecordType, dbCommon *precord, void *user) { int j; - struct rset *prset = pdbRecordType->prset; - - if (!prset) return; /* unlikely */ - - epicsMutexDestroy(precord->mlok); for (j = 0; j < pdbRecordType->no_links; j++) { dbFldDes *pdbFldDes = @@ -680,6 +674,8 @@ static void doFreeRecord(dbRecordType *pdbRecordType, dbCommon *precord, dbFreeLinkContents(plink); } + epicsMutexDestroy(precord->mlok); + // may be allocated in dbNotify.c free(precord->ppnr); }