From adcde46e9ea7f6e0a31f43cc623d5717bf34b87e Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:12:00 -0400 Subject: [PATCH 01/23] populate RDES early --- src/ioc/dbStatic/dbStaticRun.c | 10 ++++++---- src/ioc/misc/iocInit.c | 1 - 2 files changed, 6 insertions(+), 5 deletions(-) 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/misc/iocInit.c b/src/ioc/misc/iocInit.c index a61e81dd6..1f418223d 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); From af89b716f470c386e78bce95f8011eb7c9104326 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:14:46 -0400 Subject: [PATCH 02/23] 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 --- src/ioc/db/dbLock.c | 1391 ++++++++++++++++++++++++++-------------- src/ioc/db/dbLock.h | 25 +- src/ioc/db/dbLockPvt.h | 109 ++++ 3 files changed, 1032 insertions(+), 493 deletions(-) create mode 100644 src/ioc/db/dbLockPvt.h diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index 1c59dc068..a2fb4da28 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,947 @@ 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_FREE +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 is incremented whenever + * any lockRecord::plockSet is changed. + * An optimization to avoid a re-sort + * 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. + * Will never exceed the number of records +1 + */ +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_FREE + 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_FREE + 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<=0) { + errlogPrintf("dbLockIncRef(%p) on dead lockSet\n", ls); + 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; + + if(ellCount(&ls->lockRecordList)!=0) { + errlogPrintf("dbLockDecRef(%p) would free lockSet with %d records\n", ls, ellCount(&ls->lockRecordList)); + cantProceed(NULL); + } + + epicsMutexMustLock(lockSetsGuard); + ellDelete(&lockSetsActive, &ls->node); +#ifndef LOCKSET_FREE + ellAdd(&lockSetsFree, &ls->node); +#else + epicsMutexDestroy(ls->lock); + memset(ls, 0, sizeof(*ls)); + 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(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); /* 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); + /* Caller does *not* hold a reference to ls. + * However, the lockRecord does, but can't + * be changed while we hold the lockSet. + */ +#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 + /* no references kept */ } - + 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) { + 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; + ref->plockSet = ref->plr->plockSet; + dbLockIncRef(ref->plockSet); + } + } + epicsSpinUnlock(ref->plr->spin); + if(oldref) + dbLockDecRef(oldref); + if(!update && changed) + return changed; + } +#ifndef LOCKSET_NOCNT + 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) + return NULL; + + dbLockerPrepare(locker, precs, nrecs); + + return locker; +} + +void dbLockerFinalize(dbLocker *locker) +{ + size_t i; + assert(ellCount(&locker->locked)==0); + + 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). + */ + 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; + size_t i, no_links=prec->rdes->no_links; + lockRecord *lrec; + size_t arrsize=(no_links-1)*sizeof(linkRef); + assert(no_links>=1); /* dbCommon has TSEL */ + assert(!prec->lset); + + lrec = callocMustSucceed(1, sizeof(*lrec)+arrsize, "lockRecord"); + if(!lrec) + cantProceed("no memory for lockRecord"); + lrec->spin = epicsSpinCreate(); + if(!lrec->spin) + cantProceed("no memory for spinlock in lockRecord"); + + lrec->precord = prec; + ellInit(&lrec->backlinks); + + for(i=0; ilinks[i].psrc = lrec; + } + + prec->lset = lrec; + return 0; +} + +static int initPVLinks(void* junk, DBENTRY* pdbentry) +{ + size_t i; + dbRecordType *rtype=pdbentry->precordType; + dbCommon *prec = pdbentry->precnode->precord; + lockSet *A=prec->lset->plockSet; + + /* for each link originating from this record */ + for(i=0; ino_links; i++) { + linkRef *bref = &prec->lset->links[i]; + DBADDR *paddr; + dbFldDes *pdesc = rtype->papFldDes[rtype->link_ind[i]]; + DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset); + lockSet *B; + + if(plink->type!=PV_LINK) + continue; + + dbInitLink(prec, plink, pdesc->field_type); + + if(plink->type!=DB_LINK) + continue; + + paddr = (DBADDR*)plink->value.pv_link.pvt; + B = paddr->precord->lset->plockSet; + + + /* Initial population of lockSets happens here. */ + if(!A && !B) { /* neither side has a lockSet */ + A = prec->lset->plockSet = paddr->precord->lset->plockSet = makeSet(); + dbLockIncRef(A); /* ref for psecond */ + ellAdd(&A->lockRecordList, &prec->lset->node); + ellAdd(&A->lockRecordList, &paddr->precord->lset->node); + + } else if(!B) { /* fast merge paddr->precord into A */ + paddr->precord->lset->plockSet = A; + dbLockIncRef(A); + ellAdd(&A->lockRecordList, &paddr->precord->lset->node); + + } else if(!A) { /* fast merge prec into B */ + A = prec->lset->plockSet = B; + dbLockIncRef(B); + ellAdd(&B->lockRecordList, &prec->lset->node); + } + + /* initialize backward link tracking */ + bref->ptarget = paddr->precord->lset; + ellAdd(&bref->ptarget->backlinks, &bref->backlinksnode); + } + return 0; +} + +static int initSingleSets(void* junk, DBENTRY* pdbentry) +{ + dbCommon *prec = pdbentry->precnode->precord; + lockSet *ls; + + if(!prec->lset->plockSet) { + ls = prec->lset->plockSet = makeSet(); + ellAdd(&ls->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 */ + forEachRecord(NULL, pdbbase, &createLockRecord); + /* create lockSets for pairs of records with DB_LINKs */ + forEachRecord(NULL, pdbbase, &initPVLinks); + /* create lockSets for all records with no DB_LINKs */ + forEachRecord(NULL, pdbbase, &initSingleSets); +} - 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; - 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_FREE 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_FREE + 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) +/* update backwards link tracking */ +static void updateBackRefs(dbCommon *prec) { - lockRecord *p1lockRecord = pfirst->lset; - lockRecord *p2lockRecord = psecond->lset; - lockSet *p1lockSet; - lockSet *p2lockSet; - lockRecord *plockRecord; - lockRecord *pnext; + size_t i; + /* for each link */ + for(i=0; irdes->no_links; i++) { + linkRef *bref = &prec->lset->links[i]; + dbFldDes *pdesc = prec->rdes->papFldDes[prec->rdes->link_ind[i]]; + DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset); - 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; - } - if(!p2lockSet) { - p2lockRecord->plockSet = p1lockSet; - ellAdd(&p1lockSet->lockRecordList,&p2lockRecord->node); - goto all_done; - } - /*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; - } - ellDelete(&lockSetList[p2lockSet->type],&p2lockSet->node); - p2lockSet->type = listTypeFree; - ellAdd(&lockSetList[listTypeFree],&p2lockSet->node); -all_done: - epicsMutexUnlock(lockSetModifyLock); - return; -} - -void dbLockSetSplit(dbCommon *psource) -{ - lockSet *plockSet; - lockRecord *plockRecord; - lockRecord *pnext; - dbCommon *precord; - int link; - dbRecordType *pdbRecordType; - dbFldDes *pdbFldDes; - DBLINK *plink; - int indlockRecord,nlockRecords; - lockRecord **paplockRecord; - epicsThreadId idself = epicsThreadGetIdSelf(); - + if(plink->type!=DB_LINK && bref->ptarget) + { + /* removed link */ + ellDelete(&bref->ptarget->backlinks, &bref->backlinksnode); + bref->ptarget = NULL; + } else if(plink->type==DB_LINK) + { + DBADDR *paddr = (DBADDR*)plink->value.pv_link.pvt; - 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; - } - 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; + if(paddr->precord->lset != bref->ptarget) { + /* changed link */ + if(bref->ptarget) { + /* clear old */ + ellDelete(&bref->ptarget->backlinks, &bref->backlinksnode); + bref->ptarget = NULL; + } - 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); + bref->ptarget = paddr->precord->lset; + ellAdd(&bref->ptarget->backlinks, &bref->backlinksnode); + } } } - free(paplockRecord); } - + +/* 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) +{ + ELLNODE *cur; + lockSet *A=pfirst->lset->plockSet, + *B=psecond->lset->plockSet; + int Nb; +#ifdef LOCKSET_DEBUG + const epicsThreadId myself = epicsThreadGetIdSelf(); +#endif + + assert(A && B); + +#ifdef LOCKSET_DEBUG + if(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); + } +#endif + if(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); + } + + if(A==B) + return; /* already in the same lockSet */ + + updateBackRefs(pfirst); /* not required */ + + 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); + epicsSpinLock(lr->spin); + ellAdd(&A->lockRecordList, cur); + lr->plockSet = A; +#ifndef LOCKSET_NOCNT + epicsAtomicIncrSizeT(&recomputeCnt); +#endif + epicsSpinUnlock(lr->spin); + } + + /* there is at least 1 ref for each lockRecord, + * and at least one for the locker's locked list + * (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 ref, 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 from above */ + + assert(A==psecond->lset->plockSet); +} + +/* 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 *ls = pfirst->lset->plockSet; + ELLLIST toInspect, newLS; +#ifdef LOCKSET_DEBUG + const epicsThreadId myself = epicsThreadGetIdSelf(); +#endif + +#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); + } +#endif + + /* 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); + } + + updateBackRefs(pfirst); + + if(pfirst==psecond) + return; + + /* at least 1 ref for each lockRecord, + * and one for the locker + */ + assert(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++) { + linkRef *bref=&lr->links[i]; + lockRecord *lr = bref->ptarget; + + if(!lr) + continue; /* not DB_LINK */ + + 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(&lr->backlinks); bcur; bcur=ellNext(bcur)) + { + linkRef *bref=CONTAINER(bcur, linkRef, backlinksnode); + lockRecord *lr = bref->psrc; + + if(lr->precord==pfirst) { + goto nosplit; + } + + if(lr->compflag) + continue; + + ellAdd(&toInspect, &lr->compnode); + lr->compflag = 1; + } + } + /* All links from 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); + + /* lockset is "live" at this point + * as other threads may find it. + */ + epicsSpinLock(lr->spin); + ellDelete(&ls->lockRecordList, &lr->node); + ellAdd(&splitset->lockRecordList, &lr->node); + lr->plockSet = splitset; +#ifndef LOCKSET_NOCNT + epicsAtomicIncrSizeT(&recomputeCnt); +#endif + epicsSpinUnlock(lr->spin); + } + + /* 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; + } +} + +static char *msstring[4]={"NMS","MS","MSI","MSS"}; + long dblsr(char *recordname,int level) { int link; @@ -524,45 +971,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 +1006,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 +1031,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_FREE + 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 +1054,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..bed0199d0 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,27 @@ extern "C" { struct dbCommon; struct dbBase; +//struct dbLockSet; +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 +54,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..4a5646dd7 --- /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" + +#define LOCKSET_DEBUG +#define LOCKSET_FREE +#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; + +typedef struct { + ELLNODE backlinksnode; + struct lockRecord *psrc; + struct lockRecord *ptarget; +} linkRef; + +/* 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; + /* 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; + dbCommon *precord; + epicsSpinId spin; + + /* temp used during lockset split */ + ELLNODE compnode; + unsigned int compflag; + + ELLLIST backlinks; + linkRef links[1]; /* actual size based on no_links from dbRecordType */ +} 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 3 +/* 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 */ +}; + +lockSet* dbLockGetRef(lockRecord *lr); +void dbLockIncRef(lockSet* ls); +void dbLockDecRef(lockSet *ls); + +/* Optimization used by for 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 From ffd188bea36a7715df80a2add4a1e8d3c6555352 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:14:46 -0400 Subject: [PATCH 03/23] dbLockTest and dbStressLock --- src/ioc/db/test/Makefile | 8 + src/ioc/db/test/dbLockTest.c | 340 +++++++++++++++++++++++++++++++- src/ioc/db/test/dbLockTest.db | 3 + src/ioc/db/test/dbStressLock.c | 311 +++++++++++++++++++++++++++++ src/ioc/db/test/dbStressLock.db | 40 ++++ 5 files changed, 698 insertions(+), 4 deletions(-) create mode 100644 src/ioc/db/test/dbStressLock.c create mode 100644 src/ioc/db/test/dbStressLock.db diff --git a/src/ioc/db/test/Makefile b/src/ioc/db/test/Makefile index c51f1a119..afed4f7b0 100644 --- a/src/ioc/db/test/Makefile +++ b/src/ioc/db/test/Makefile @@ -10,6 +10,9 @@ TOP=../../../.. include $(TOP)/configure/CONFIG +# Allow access to private headers in db/ +USR_CPPFLAGS = -I ../.. + TESTLIBRARY = dbTestIoc dbTestIoc_SRCS += xRecord.c @@ -54,6 +57,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..c4cb5b2fb --- /dev/null +++ b/src/ioc/db/test/dbStressLock.c @@ -0,0 +1,311 @@ +/*************************************************************************\ +* 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" + +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.25) { + 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(0); + + 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; 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") {} From 8af3ffb6539cf66d44638a48ee6616c3f957239e Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 04/23] dbAccess: multi-locking in dbPutFieldLink Use new locking API in dbPutFieldLink() Adjust dbAddLink() and dbRemoveLink() to pass a dbLocker* through to lockSet merge/split --- src/ioc/db/dbAccess.c | 25 +++++++++++++++++-------- src/ioc/db/dbLink.c | 22 +++++++++++++--------- src/ioc/db/dbLink.h | 6 ++++-- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/ioc/db/dbAccess.c b/src/ioc/db/dbAccess.c index b13d7a138..b24739d2c 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[3]; + 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>=3); + switch (dbrType) { case DBR_CHAR: case DBR_UCHAR: @@ -992,10 +997,13 @@ 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; + lockrecs[2] = NULL; + dbLockerPrepare(&locker, lockrecs, 3); + + dbScanLockMany(&locker); scan = precord->scan; @@ -1044,7 +1052,7 @@ static long dbPutFieldLink(DBADDR *paddr, switch (plink->type) { /* Old link type */ case DB_LINK: case CA_LINK: - dbRemoveLink(plink); + dbRemoveLink(&locker, precord, plink); break; case PV_LINK: @@ -1091,7 +1099,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 +1129,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/dbLink.c b/src/ioc/db/dbLink.c index 2ad9c6606..4ab5dcde5 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" @@ -140,18 +141,21 @@ 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); + /* merging into the same lockset is deferred to the caller. + * cf. initPVLinks() + */ 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); + dbLockSetSplit(locker, prec, pdbAddr->precord); + free(pdbAddr); } static int dbDbIsLinkConnected(const struct link *plink) @@ -422,7 +426,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; @@ -436,7 +440,7 @@ void dbAddLink(struct dbCommon *precord, struct link *plink, short dbfType, DBAD plink->value.pv_link.pvt = ptargetaddr; /* 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 +467,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); From b8a7da18d21c83ac159343a08ac1fd07dbafb425 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 05/23] iocInit: links now initialized in dbLockInitRecords() --- src/ioc/misc/iocInit.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ioc/misc/iocInit.c b/src/ioc/misc/iocInit.c index 1f418223d..1c148fbbb 100644 --- a/src/ioc/misc/iocInit.c +++ b/src/ioc/misc/iocInit.c @@ -507,9 +507,6 @@ static void doResolveLinks(dbRecordType *pdbRecordType, dbCommon *precord, } } } - - if (plink->type == PV_LINK) - dbInitLink(precord, plink, pdbFldDes->field_type); } } From 8ce0ba1e542598af35fb9ca1e18165de5505fbf1 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 06/23] dbLink: backward link tracking --- src/ioc/db/dbCommon.dbd | 6 ++++++ src/ioc/db/dbLink.c | 3 +++ src/ioc/dbStatic/link.h | 2 ++ 3 files changed, 11 insertions(+) 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 4ab5dcde5..0a440cfcb 100644 --- a/src/ioc/db/dbLink.c +++ b/src/ioc/db/dbLink.c @@ -141,6 +141,7 @@ static long dbDbInitLink(struct link *plink, short dbfType) pdbAddr = dbCalloc(1, sizeof(struct dbAddr)); *pdbAddr = dbaddr; /* structure copy */ plink->value.pv_link.pvt = pdbAddr; + ellAdd(&dbaddr.precord->bklnk, &plink->value.pv_link.backlinknode); /* merging into the same lockset is deferred to the caller. * cf. initPVLinks() */ @@ -154,6 +155,7 @@ static void dbDbRemoveLink(dbLocker *locker, struct dbCommon *prec, struct link plink->value.pv_link.getCvt = 0; plink->value.pv_link.lastGetdbrType = 0; plink->type = PV_LINK; + ellDelete(&pdbAddr->precord->bklnk, &plink->value.pv_link.backlinknode); dbLockSetSplit(locker, prec, pdbAddr->precord); free(pdbAddr); } @@ -438,6 +440,7 @@ void dbAddLink(dbLocker *locker, struct dbCommon *precord, struct link *plink, s 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(locker, plink->value.pv_link.precord, ptargetaddr->precord); 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 */ From ee297dc55879e71dd963d79fce0d9566df68f07a Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 07/23] dbLock: use new backref tracking --- src/ioc/db/dbAccess.c | 2 +- src/ioc/db/dbLock.c | 80 ++++++++++------------------------ src/ioc/db/dbLockPvt.h | 9 ---- src/ioc/db/test/dbStressLock.c | 33 ++++++++++++-- src/ioc/misc/iocInit.c | 7 ++- 5 files changed, 58 insertions(+), 73 deletions(-) diff --git a/src/ioc/db/dbAccess.c b/src/ioc/db/dbAccess.c index b24739d2c..732e783dc 100644 --- a/src/ioc/db/dbAccess.c +++ b/src/ioc/db/dbAccess.c @@ -1052,7 +1052,7 @@ static long dbPutFieldLink(DBADDR *paddr, switch (plink->type) { /* Old link type */ case DB_LINK: case CA_LINK: - dbRemoveLink(&locker, precord, plink); + dbRemoveLink(&locker, precord, plink); /* link type becomes PV_LINK */ break; case PV_LINK: diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index a2fb4da28..2be1f5f82 100644 --- a/src/ioc/db/dbLock.c +++ b/src/ioc/db/dbLock.c @@ -135,11 +135,18 @@ void dbLockDecRef(lockSet *ls) if(cnt) return; + /* not necessary as no one else holds 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_FREE @@ -491,13 +498,11 @@ done: static int createLockRecord(void* junk, DBENTRY* pdbentry) { dbCommon *prec = pdbentry->precnode->precord; - size_t i, no_links=prec->rdes->no_links; lockRecord *lrec; - size_t arrsize=(no_links-1)*sizeof(linkRef); - assert(no_links>=1); /* dbCommon has TSEL */ assert(!prec->lset); - lrec = callocMustSucceed(1, sizeof(*lrec)+arrsize, "lockRecord"); + /* TODO: one allocation for all records? */ + lrec = callocMustSucceed(1, sizeof(*lrec), "lockRecord"); if(!lrec) cantProceed("no memory for lockRecord"); lrec->spin = epicsSpinCreate(); @@ -505,11 +510,6 @@ static int createLockRecord(void* junk, DBENTRY* pdbentry) cantProceed("no memory for spinlock in lockRecord"); lrec->precord = prec; - ellInit(&lrec->backlinks); - - for(i=0; ilinks[i].psrc = lrec; - } prec->lset = lrec; return 0; @@ -524,7 +524,6 @@ static int initPVLinks(void* junk, DBENTRY* pdbentry) /* for each link originating from this record */ for(i=0; ino_links; i++) { - linkRef *bref = &prec->lset->links[i]; DBADDR *paddr; dbFldDes *pdesc = rtype->papFldDes[rtype->link_ind[i]]; DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset); @@ -559,10 +558,6 @@ static int initPVLinks(void* junk, DBENTRY* pdbentry) dbLockIncRef(B); ellAdd(&B->lockRecordList, &prec->lset->node); } - - /* initialize backward link tracking */ - bref->ptarget = paddr->precord->lset; - ellAdd(&bref->ptarget->backlinks, &bref->backlinksnode); } return 0; } @@ -637,40 +632,6 @@ void dbLockCleanupRecords(dbBase *pdbbase) #endif } -/* update backwards link tracking */ -static void updateBackRefs(dbCommon *prec) -{ - size_t i; - /* for each link */ - for(i=0; irdes->no_links; i++) { - linkRef *bref = &prec->lset->links[i]; - dbFldDes *pdesc = prec->rdes->papFldDes[prec->rdes->link_ind[i]]; - DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset); - - if(plink->type!=DB_LINK && bref->ptarget) - { - /* removed link */ - ellDelete(&bref->ptarget->backlinks, &bref->backlinksnode); - bref->ptarget = NULL; - } else if(plink->type==DB_LINK) - { - DBADDR *paddr = (DBADDR*)plink->value.pv_link.pvt; - - if(paddr->precord->lset != bref->ptarget) { - /* changed link */ - if(bref->ptarget) { - /* clear old */ - ellDelete(&bref->ptarget->backlinks, &bref->backlinksnode); - bref->ptarget = NULL; - } - - bref->ptarget = paddr->precord->lset; - ellAdd(&bref->ptarget->backlinks, &bref->backlinksnode); - } - } - } -} - /* Caller must lock both pfirst and psecond. * Assumes that pfirst has been modified * to link to psecond. @@ -705,8 +666,6 @@ void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) if(A==B) return; /* already in the same lockSet */ - updateBackRefs(pfirst); /* not required */ - Nb = ellCount(&B->lockRecordList); assert(Nb>0); @@ -791,7 +750,6 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) cantProceed(NULL); } - updateBackRefs(pfirst); if(pfirst==psecond) return; @@ -829,8 +787,16 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) /* Visit all the links originating from prec */ for(i=0; ino_links; i++) { - linkRef *bref=&lr->links[i]; - lockRecord *lr = bref->ptarget; + 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; if(!lr) continue; /* not DB_LINK */ @@ -851,10 +817,12 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) } /* Visit all links terminating at prec */ - for(bcur=ellFirst(&lr->backlinks); bcur; bcur=ellNext(bcur)) + for(bcur=ellFirst(&prec->bklnk); bcur; bcur=ellNext(bcur)) { - linkRef *bref=CONTAINER(bcur, linkRef, backlinksnode); - lockRecord *lr = bref->psrc; + 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; if(lr->precord==pfirst) { goto nosplit; diff --git a/src/ioc/db/dbLockPvt.h b/src/ioc/db/dbLockPvt.h index 4a5646dd7..4913ac719 100644 --- a/src/ioc/db/dbLockPvt.h +++ b/src/ioc/db/dbLockPvt.h @@ -37,12 +37,6 @@ typedef struct dbLockSet { struct lockRecord; -typedef struct { - ELLNODE backlinksnode; - struct lockRecord *psrc; - struct lockRecord *ptarget; -} linkRef; - /* dbCommon.LSET is a plockRecord. * Except for spin and plockSet, all members of lockRecord are guarded * by the present lockset lock. @@ -62,9 +56,6 @@ typedef struct lockRecord { /* temp used during lockset split */ ELLNODE compnode; unsigned int compflag; - - ELLLIST backlinks; - linkRef links[1]; /* actual size based on no_links from dbRecordType */ } lockRecord; typedef struct { diff --git a/src/ioc/db/test/dbStressLock.c b/src/ioc/db/test/dbStressLock.c index c4cb5b2fb..7aa33100c 100644 --- a/src/ioc/db/test/dbStressLock.c +++ b/src/ioc/db/test/dbStressLock.c @@ -39,6 +39,9 @@ #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 */ @@ -136,7 +139,7 @@ void doreTarget(workerPriv *p) if(ret) testAbort("bad record name? %ld", ret); - if(action<0.25) { + if(action<=0.6) { scratchdst[0] = '\0'; } else { strcpy(scratchdst, ptarg->name); @@ -197,7 +200,7 @@ MAIN(dbStressTest) char *nwork=getenv("NWORK"); struct timespec seed; - testPlan(0); + testPlan(95); clock_gettime(CLOCK_REALTIME, &seed); srand(seed.tv_nsec); @@ -272,7 +275,7 @@ MAIN(dbStressTest) &worker, &priv[i]); } - testDiag("All started"); + testDiag("All started. Will run for %f sec", runningtime); epicsThreadSleep(runningtime); @@ -289,6 +292,30 @@ MAIN(dbStressTest) testDiag("All stopped"); + testDiag("Validate lockSet ref counts"); + dbInitEntry(pdbbase, &ent); + for(status = dbFirstRecordType(&ent); + !status; + status = dbNextRecordType(&ent)) + { + for(status = dbFirstRecord(&ent); + !status; + status = dbNextRecord(&ent)) + { + dbCommon *prec = ent.precnode->precord; + 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; ino_links; j++) { dbFldDes *pdbFldDes = papFldDes[link_ind[j]]; - DBLINK *plink = (DBLINK *)((char *)precord + pdbFldDes->offset); if (ellCount(&precord->rdes->devList) > 0 && pdbFldDes->isDevLink) { devSup *pdevSup = dbDTYPtoDevSup(pdbRecordType, precord->dtyp); @@ -631,13 +630,14 @@ 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; } + dbFreeLinkContents(plink); } if (precord->dset && @@ -672,7 +672,6 @@ static void doFreeRecord(dbRecordType *pdbRecordType, dbCommon *precord, pdbRecordType->papFldDes[pdbRecordType->link_ind[j]]; DBLINK *plink = (DBLINK *)((char *)precord + pdbFldDes->offset); - dbFreeLinkContents(plink); } } From c26b02c20d5ec2fa649e02ab8988dec599ea451c Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 08/23] dbCaTest: adjust locking in dbcar() --- src/ioc/db/dbCaTest.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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); From a78abd00704dc21f930192982840b813ca1ac0e6 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 09/23] dbLock: describe build options --- src/ioc/db/dbLock.c | 24 ++++++++++++++---------- src/ioc/db/dbLockPvt.h | 5 ++++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index 2be1f5f82..98da7cec3 100644 --- a/src/ioc/db/dbLock.c +++ b/src/ioc/db/dbLock.c @@ -41,7 +41,7 @@ typedef struct dbScanLockNode dbScanLockNode; static epicsThreadOnceId dbLockOnceInit = EPICS_THREAD_ONCE_INIT; static ELLLIST lockSetsActive; /* in use */ -#ifndef LOCKSET_FREE +#ifndef LOCKSET_NOFREE static ELLLIST lockSetsFree; /* free list */ #endif @@ -49,10 +49,10 @@ static ELLLIST lockSetsFree; /* free list */ static epicsMutexId lockSetsGuard; #ifndef LOCKSET_NOCNT -/* Counter which is incremented whenever +/* Counter which we increment whenever * any lockRecord::plockSet is changed. - * An optimization to avoid a re-sort - * when no links have changed. + * An optimization to avoid checking lockSet + * associations when no links have changed. */ static size_t recomputeCnt; #endif @@ -73,7 +73,7 @@ static lockSet* makeSet(void) lockSet *ls; int iref; epicsMutexMustLock(lockSetsGuard); -#ifndef LOCKSET_FREE +#ifndef LOCKSET_NOFREE ls = (lockSet*)ellGet(&lockSetsFree); if(!ls) { epicsMutexUnlock(lockSetsGuard); @@ -84,7 +84,7 @@ static lockSet* makeSet(void) ls->lock = epicsMutexMustCreate(); ls->id = epicsAtomicIncrSizeT(&next_id); -#ifndef LOCKSET_FREE +#ifndef LOCKSET_NOFREE epicsMutexMustLock(lockSetsGuard); } #endif @@ -149,7 +149,7 @@ void dbLockDecRef(lockSet *ls) epicsMutexMustLock(lockSetsGuard); ellDelete(&lockSetsActive, &ls->node); -#ifndef LOCKSET_FREE +#ifndef LOCKSET_NOFREE ellAdd(&lockSetsFree, &ls->node); #else epicsMutexDestroy(ls->lock); @@ -309,6 +309,10 @@ int dbLockUpdateRefs(dbLocker *locker, int update) 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; } @@ -607,7 +611,7 @@ static int freeLockRecord(void* junk, DBENTRY* pdbentry) void dbLockCleanupRecords(dbBase *pdbbase) { -#ifndef LOCKSET_FREE +#ifndef LOCKSET_NOFREE ELLNODE *cur; #endif epicsThreadOnce(&dbLockOnceInit, &dbLockOnce, NULL); @@ -620,7 +624,7 @@ void dbLockCleanupRecords(dbBase *pdbbase) assert(ellCount(&lockSetsActive)==0); -#ifndef LOCKSET_FREE +#ifndef LOCKSET_NOFREE while((cur=ellGet(&lockSetsFree))!=NULL) { lockSet *ls = (lockSet*)cur; @@ -1002,7 +1006,7 @@ long dbLockShowLocked(int level) errlogPrintf("lockSets %d listTypeFree %d\n", ellCount(&lockSetsActive), -#ifndef LOCKSET_FREE +#ifndef LOCKSET_NOFREE ellCount(&lockSetsFree) #else -1 diff --git a/src/ioc/db/dbLockPvt.h b/src/ioc/db/dbLockPvt.h index 4913ac719..d74562d49 100644 --- a/src/ioc/db/dbLockPvt.h +++ b/src/ioc/db/dbLockPvt.h @@ -11,8 +11,11 @@ #include "dbLock.h" +/* enable additional error checking */ #define LOCKSET_DEBUG -#define LOCKSET_FREE +/* 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 From 58a8a07cc481f2c4f3200facc0a89344849f7017 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 10/23] dbAccess.c: dbLocker needs at most two refs --- src/ioc/db/dbAccess.c | 7 +++---- src/ioc/db/dbLockPvt.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ioc/db/dbAccess.c b/src/ioc/db/dbAccess.c index 732e783dc..3da0d627d 100644 --- a/src/ioc/db/dbAccess.c +++ b/src/ioc/db/dbAccess.c @@ -945,7 +945,7 @@ static long dbPutFieldLink(DBADDR *paddr, dbLinkInfo link_info; DBADDR *pdbaddr = NULL; dbCommon *precord = paddr->precord; - dbCommon *lockrecs[3]; + dbCommon *lockrecs[2]; dbLocker locker; dbFldDes *pfldDes = paddr->pfldDes; long special = paddr->special; @@ -959,7 +959,7 @@ static long dbPutFieldLink(DBADDR *paddr, int isDevLink; short scan; - STATIC_ASSERT(DBLOCKER_NALLOC>=3); + STATIC_ASSERT(DBLOCKER_NALLOC>=2); switch (dbrType) { case DBR_CHAR: @@ -1000,8 +1000,7 @@ static long dbPutFieldLink(DBADDR *paddr, memset(&locker, 0, sizeof(locker)); lockrecs[0] = precord; lockrecs[1] = pdbaddr ? pdbaddr->precord : NULL; - lockrecs[2] = NULL; - dbLockerPrepare(&locker, lockrecs, 3); + dbLockerPrepare(&locker, lockrecs, 2); dbScanLockMany(&locker); diff --git a/src/ioc/db/dbLockPvt.h b/src/ioc/db/dbLockPvt.h index d74562d49..d316ea359 100644 --- a/src/ioc/db/dbLockPvt.h +++ b/src/ioc/db/dbLockPvt.h @@ -70,7 +70,7 @@ typedef struct { lockSet *plockSet; } lockRecordRef; -#define DBLOCKER_NALLOC 3 +#define DBLOCKER_NALLOC 2 /* a dbLocker can only be used by a single thread. */ struct dbLocker { ELLLIST locked; From 07bb2fbef7fbf19863f309580ced4374ef7a37ae Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 11/23] dbLock: no c++ comments in c code --- src/ioc/db/dbLock.h | 1 - src/ioc/db/dbLockPvt.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ioc/db/dbLock.h b/src/ioc/db/dbLock.h index bed0199d0..9f2714057 100644 --- a/src/ioc/db/dbLock.h +++ b/src/ioc/db/dbLock.h @@ -22,7 +22,6 @@ extern "C" { struct dbCommon; struct dbBase; -//struct dbLockSet; typedef struct dbLocker dbLocker; epicsShareFunc void dbScanLock(struct dbCommon *precord); diff --git a/src/ioc/db/dbLockPvt.h b/src/ioc/db/dbLockPvt.h index d316ea359..5dd4aa44d 100644 --- a/src/ioc/db/dbLockPvt.h +++ b/src/ioc/db/dbLockPvt.h @@ -100,4 +100,4 @@ void dbLockSetSplit(struct dbLocker *locker, struct dbCommon *psource, struct dbCommon *psecond); -#endif // DBLOCKPVT_H +#endif /* DBLOCKPVT_H */ From 8fea2f5ae686245c9b4f1cca22fc39c889c44a3a Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 12/23] dbLock: default build options Enable extra debugging. Disable lockSet free list. Enable recomputeCnt optimization --- src/ioc/db/dbLockPvt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ioc/db/dbLockPvt.h b/src/ioc/db/dbLockPvt.h index 5dd4aa44d..044be7eff 100644 --- a/src/ioc/db/dbLockPvt.h +++ b/src/ioc/db/dbLockPvt.h @@ -16,7 +16,7 @@ /* disable the free list for lockSets */ #define LOCKSET_NOFREE /* disable use of recomputeCnt optimization */ -#define LOCKSET_NOCNT +/*#define LOCKSET_NOCNT*/ /* except for refcount (and lock), all members of dbLockSet * are guarded by its lock. From 127bdfd9aa5384eba807a51a43fef10ddf60ae41 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 13/23] dbLock: comments --- src/ioc/db/dbLock.c | 14 ++++++++------ src/ioc/db/dbLockPvt.h | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index 98da7cec3..aed9cc44e 100644 --- a/src/ioc/db/dbLock.c +++ b/src/ioc/db/dbLock.c @@ -687,9 +687,9 @@ void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) epicsSpinUnlock(lr->spin); } - /* there is at least 1 ref for each lockRecord, - * and at least one for the locker's locked list - * (perhaps another for its refs cache + /* 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)); @@ -698,7 +698,7 @@ void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) epicsAtomicAddIntT(&B->refcount, -Nb+1); /* drop all but one ref, see below */ if(locker) { - /* at least two ref, possibly three remain. + /* 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, @@ -717,7 +717,7 @@ void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) epicsMutexUnlock(B->lock); - dbLockDecRef(B); /* last ref from above */ + dbLockDecRef(B); /* last ref we hold */ assert(A==psecond->lset->plockSet); } @@ -828,6 +828,8 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) 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; } @@ -839,7 +841,7 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) lr->compflag = 1; } } - /* All links from psecond were traversed without finding + /* 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. diff --git a/src/ioc/db/dbLockPvt.h b/src/ioc/db/dbLockPvt.h index 044be7eff..fd13929c2 100644 --- a/src/ioc/db/dbLockPvt.h +++ b/src/ioc/db/dbLockPvt.h @@ -46,7 +46,7 @@ struct lockRecord; * plockSet is guarded by spin. */ typedef struct lockRecord { - ELLNODE node; + 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. From fa4678798c76c3eb488fb312ab0d41963d24f642 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 14/23] iocInit: remove no-op The work which was done here is moved to dbCloseLinks() --- src/ioc/misc/iocInit.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/ioc/misc/iocInit.c b/src/ioc/misc/iocInit.c index 6237a8cfe..3bede8547 100644 --- a/src/ioc/misc/iocInit.c +++ b/src/ioc/misc/iocInit.c @@ -660,19 +660,7 @@ static void doCloseLinks(dbRecordType *pdbRecordType, dbCommon *precord, 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 = - pdbRecordType->papFldDes[pdbRecordType->link_ind[j]]; - DBLINK *plink = (DBLINK *)((char *)precord + pdbFldDes->offset); - - } } int iocShutdown(void) From 765fb7c63ec0c3d02951d4adc1366652b9d4da51 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 15/23] dbLock: remove some unnecessary code no need to hold spinlock for lockRecordList the lockRecordList is protected by the lockSet::lock --- src/ioc/db/dbLock.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index aed9cc44e..041e8f98b 100644 --- a/src/ioc/db/dbLock.c +++ b/src/ioc/db/dbLock.c @@ -678,8 +678,9 @@ void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) { lockRecord *lr = CONTAINER(cur, lockRecord, node); assert(lr->plockSet==B); - epicsSpinLock(lr->spin); ellAdd(&A->lockRecordList, cur); + + epicsSpinLock(lr->spin); lr->plockSet = A; #ifndef LOCKSET_NOCNT epicsAtomicIncrSizeT(&recomputeCnt); @@ -801,9 +802,7 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) ptarget = plink->value.pv_link.pvt; lr = ptarget->precord->lset; - - if(!lr) - continue; /* not DB_LINK */ + assert(lr); if(lr->precord==pfirst) { /* so pfirst is still reachable from psecond, @@ -877,18 +876,18 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) lr->compflag = 0; /* reset for next time */ assert(lr->plockSet == ls); - - /* lockset is "live" at this point - * as other threads may find it. - */ - epicsSpinLock(lr->spin); 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 From 7a5d4cf6cc9e917b2ccda04ac10f006555c3771e Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 16/23] dbLock: minor --- src/ioc/db/dbLock.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index 041e8f98b..8c36289df 100644 --- a/src/ioc/db/dbLock.c +++ b/src/ioc/db/dbLock.c @@ -118,8 +118,8 @@ unsigned long dbLockCountSets(void) void dbLockIncRef(lockSet* ls) { int cnt = epicsAtomicIncrIntT(&ls->refcount); - if(cnt<=0) { - errlogPrintf("dbLockIncRef(%p) on dead lockSet\n", ls); + if(cnt<=1) { + errlogPrintf("dbLockIncRef(%p) on dead lockSet. refs: %d\n", ls, cnt); cantProceed(NULL); } } @@ -185,7 +185,7 @@ void dbScanLock(dbCommon *precord) lockSet *ls; ls = dbLockGetRef(lr); - assert(ls->refcount>0); + assert(epicsAtomicGetIntT(&ls->refcount)>0); retry: epicsMutexMustLock(ls->lock); @@ -353,10 +353,8 @@ dbLocker *dbLockerAlloc(dbCommon **precs, size_t Nextra = nrecs>DBLOCKER_NALLOC ? nrecs-DBLOCKER_NALLOC : 0; dbLocker *locker = calloc(1, sizeof(*locker)+Nextra*sizeof(lockRecordRef)); - if(!locker) - return NULL; - - dbLockerPrepare(locker, precs, nrecs); + if(locker) + dbLockerPrepare(locker, precs, nrecs); return locker; } @@ -507,8 +505,6 @@ static int createLockRecord(void* junk, DBENTRY* pdbentry) /* TODO: one allocation for all records? */ lrec = callocMustSucceed(1, sizeof(*lrec), "lockRecord"); - if(!lrec) - cantProceed("no memory for lockRecord"); lrec->spin = epicsSpinCreate(); if(!lrec->spin) cantProceed("no memory for spinlock in lockRecord"); @@ -762,7 +758,7 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) /* at least 1 ref for each lockRecord, * and one for the locker */ - assert(ls->refcount>=ellCount(&ls->lockRecordList)+1); + assert(epicsAtomicGetIntT(&ls->refcount)>=ellCount(&ls->lockRecordList)+1); ellInit(&toInspect); ellInit(&newLS); From ff4c88ed0562c5cdb72fb5332e43172676e67bee Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:18:11 -0400 Subject: [PATCH 17/23] dbLock: comments --- src/ioc/db/dbLock.c | 28 ++++++++++++++++------------ src/ioc/db/dbLockPvt.h | 10 +++++++--- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index 8c36289df..df2924a70 100644 --- a/src/ioc/db/dbLock.c +++ b/src/ioc/db/dbLock.c @@ -64,7 +64,10 @@ static void dbLockOnce(void* ignore) } /* global ID number assigned to each lockSet on creation. - * Will never exceed the number of records +1 + * 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; @@ -135,13 +138,16 @@ void dbLockDecRef(lockSet *ls) if(cnt) return; - /* not necessary as no one else holds a reference, + /* 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)); + errlogPrintf("dbLockDecRef(%p) would free lockSet with %d records\n", + ls, ellCount(&ls->lockRecordList)); cantProceed(NULL); } @@ -153,7 +159,7 @@ void dbLockDecRef(lockSet *ls) ellAdd(&lockSetsFree, &ls->node); #else epicsMutexDestroy(ls->lock); - memset(ls, 0, sizeof(*ls)); + memset(ls, 0, sizeof(*ls)); /* paranoia */ free(ls); #endif epicsMutexUnlock(lockSetsGuard); @@ -197,7 +203,7 @@ retry: */ lockSet *ls2 = lr->plockSet; int newcnt = epicsAtomicIncrIntT(&ls2->refcount); - assert(newcnt>=2); /* lockRecord and us */ + assert(newcnt>=2); /* at least lockRecord and us */ epicsSpinUnlock(lr->spin); epicsMutexUnlock(ls->lock); @@ -215,10 +221,7 @@ retry: */ cnt = epicsAtomicDecrIntT(&ls->refcount); assert(cnt>0); - /* Caller does *not* hold a reference to ls. - * However, the lockRecord does, but can't - * be changed while we hold the lockSet. - */ + #ifdef LOCKSET_DEBUG if(ls->owner) { assert(ls->owner==epicsThreadGetIdSelf()); @@ -230,7 +233,6 @@ retry: ls->ownercount = 1; } #endif - /* no references kept */ } void dbScanUnlock(dbCommon *precord) @@ -287,7 +289,7 @@ int dbLockUpdateRefs(dbLocker *locker, int update) for(i=0; irefs[i]; lockSet *oldref = NULL; - if(!ref->plr) { + if(!ref->plr) { /* this lockRecord slot not used */ assert(!ref->plockSet); continue; } @@ -297,7 +299,7 @@ int dbLockUpdateRefs(dbLocker *locker, int update) changed = 1; if(update) { /* exchange saved lockSet reference */ - oldref = ref->plockSet; + oldref = ref->plockSet; /* will be NULL on first iteration */ ref->plockSet = ref->plr->plockSet; dbLockIncRef(ref->plockSet); } @@ -364,6 +366,7 @@ 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); @@ -396,6 +399,7 @@ retry: /* skip duplicates (same lockSet * referenced by more than one lockRecord). + * Sorting will group these together. */ if(!ref->plr || (plock && plock==ref->plockSet)) continue; diff --git a/src/ioc/db/dbLockPvt.h b/src/ioc/db/dbLockPvt.h index fd13929c2..86d186619 100644 --- a/src/ioc/db/dbLockPvt.h +++ b/src/ioc/db/dbLockPvt.h @@ -53,10 +53,13 @@ typedef struct lockRecord { * 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 */ + /* temp used during lockset split. + * lockSet must be locked for access + */ ELLNODE compnode; unsigned int compflag; } lockRecord; @@ -81,11 +84,12 @@ struct dbLocker { lockRecordRef refs[DBLOCKER_NALLOC]; /* actual length is maxrefs */ }; -lockSet* dbLockGetRef(lockRecord *lr); +lockSet* dbLockGetRef(lockRecord *lr); /* lookup lockset and increment ref count */ void dbLockIncRef(lockSet* ls); void dbLockDecRef(lockSet *ls); -/* Optimization used by for dbLocker on the stack. +/* Calling dbLockerPrepare directly is an internal + * optimization used when dbLocker on the stack. * nrecs must be <=DBLOCKER_NALLOC. */ void dbLockerPrepare(struct dbLocker *locker, From 8f3fcc2787c0418d191c28c31c0477b8d8c74aac Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 9 Jul 2015 17:07:49 -0400 Subject: [PATCH 18/23] 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. --- src/ioc/db/dbLock.c | 48 +++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index df2924a70..200fdf280 100644 --- a/src/ioc/db/dbLock.c +++ b/src/ioc/db/dbLock.c @@ -526,6 +526,11 @@ static int initPVLinks(void* junk, DBENTRY* pdbentry) dbCommon *prec = pdbentry->precnode->precord; lockSet *A=prec->lset->plockSet; + if(!A) { + A = prec->lset->plockSet = makeSet(); + ellAdd(&A->lockRecordList, &prec->lset->node); + } + /* for each link originating from this record */ for(i=0; ino_links; i++) { DBADDR *paddr; @@ -544,51 +549,31 @@ static int initPVLinks(void* junk, DBENTRY* pdbentry) paddr = (DBADDR*)plink->value.pv_link.pvt; B = paddr->precord->lset->plockSet; + if(A==B) { + /* these records are already in the same lockset */ + } else if(B) { + assert(A!=B); + dbLockSetMerge(NULL, prec, paddr->precord); + assert(prec->lset->plockSet==paddr->precord->lset->plockSet); - /* Initial population of lockSets happens here. */ - if(!A && !B) { /* neither side has a lockSet */ - A = prec->lset->plockSet = paddr->precord->lset->plockSet = makeSet(); - dbLockIncRef(A); /* ref for psecond */ - ellAdd(&A->lockRecordList, &prec->lset->node); - ellAdd(&A->lockRecordList, &paddr->precord->lset->node); - - } else if(!B) { /* fast merge paddr->precord into A */ + } else { + /* fast merge paddr->precord into A */ paddr->precord->lset->plockSet = A; dbLockIncRef(A); ellAdd(&A->lockRecordList, &paddr->precord->lset->node); - - } else if(!A) { /* fast merge prec into B */ - A = prec->lset->plockSet = B; - dbLockIncRef(B); - ellAdd(&B->lockRecordList, &prec->lset->node); } } return 0; } -static int initSingleSets(void* junk, DBENTRY* pdbentry) -{ - dbCommon *prec = pdbentry->precnode->precord; - lockSet *ls; - - if(!prec->lset->plockSet) { - ls = prec->lset->plockSet = makeSet(); - ellAdd(&ls->lockRecordList, &prec->lset->node); - } - - return 0; -} - void dbLockInitRecords(dbBase *pdbbase) { epicsThreadOnce(&dbLockOnceInit, &dbLockOnce, NULL); /* create all lockRecords */ forEachRecord(NULL, pdbbase, &createLockRecord); - /* create lockSets for pairs of records with DB_LINKs */ + /* create lockSets */ forEachRecord(NULL, pdbbase, &initPVLinks); - /* create lockSets for all records with no DB_LINKs */ - forEachRecord(NULL, pdbbase, &initSingleSets); } static int freeLockRecord(void* junk, DBENTRY* pdbentry) @@ -598,6 +583,7 @@ static int freeLockRecord(void* junk, DBENTRY* pdbentry) lockSet *ls = lr->plockSet; prec->lset = NULL; + lr->precord = NULL; assert(ls->refcount>0); assert(ellCount(&ls->lockRecordList)>0); @@ -639,6 +625,8 @@ void dbLockCleanupRecords(dbBase *pdbbase) /* Caller must lock both pfirst and psecond. * Assumes that pfirst has been modified * to link to psecond. + * + * Note: locker may be NULL (during iocInit) */ void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) { @@ -660,7 +648,7 @@ void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) cantProceed(NULL); } #endif - if(A->ownerlocker!=locker || B->ownerlocker!=locker) { + 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); From 9198428619451d160ff17628f3d31f1121ee5e61 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 9 Jul 2015 17:16:39 -0400 Subject: [PATCH 19/23] dbLock: minor must match following condition --- src/ioc/db/dbLock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index 200fdf280..b248cba46 100644 --- a/src/ioc/db/dbLock.c +++ b/src/ioc/db/dbLock.c @@ -641,7 +641,7 @@ void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) assert(A && B); #ifdef LOCKSET_DEBUG - if(A->owner!=myself || B->owner!=myself) { + 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); From d5832354e8abc129ce4f3c24614dc2cbf94cc5e7 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 18 Aug 2015 11:09:35 -0400 Subject: [PATCH 20/23] iocInit: Don't free LSET until scan tasks have stopped --- src/ioc/misc/iocInit.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ioc/misc/iocInit.c b/src/ioc/misc/iocInit.c index 3bede8547..cce2c7938 100644 --- a/src/ioc/misc/iocInit.c +++ b/src/ioc/misc/iocInit.c @@ -637,7 +637,6 @@ static void doCloseLinks(dbRecordType *pdbRecordType, dbCommon *precord, free(plink->value.pv_link.pvt); plink->type = PV_LINK; } - dbFreeLinkContents(plink); } if (precord->dset && @@ -660,6 +659,16 @@ static void doCloseLinks(dbRecordType *pdbRecordType, dbCommon *precord, static void doFreeRecord(dbRecordType *pdbRecordType, dbCommon *precord, void *user) { + int j; + + for (j = 0; j < pdbRecordType->no_links; j++) { + dbFldDes *pdbFldDes = + pdbRecordType->papFldDes[pdbRecordType->link_ind[j]]; + DBLINK *plink = (DBLINK *)((char *)precord + pdbFldDes->offset); + + dbFreeLinkContents(plink); + } + epicsMutexDestroy(precord->mlok); } From 180f40c1f76b3c02bd38ff73b549f7c309612fe6 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 18 Aug 2015 11:09:35 -0400 Subject: [PATCH 21/23] dbLock: fix unlock w/o lock during iocInit --- src/ioc/db/dbLock.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index b248cba46..56f347abd 100644 --- a/src/ioc/db/dbLock.c +++ b/src/ioc/db/dbLock.c @@ -622,11 +622,13 @@ void dbLockCleanupRecords(dbBase *pdbbase) #endif } -/* Caller must lock both pfirst and 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. - * - * Note: locker may be NULL (during iocInit) */ void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) { @@ -702,9 +704,9 @@ void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) ellDelete(&locker->locked, &B->lockernode); B->ownerlocker = NULL; epicsAtomicDecrIntT(&B->refcount); - } - epicsMutexUnlock(B->lock); + epicsMutexUnlock(B->lock); + } dbLockDecRef(B); /* last ref we hold */ From 536c6e91ff0b35e984e5ea4f07bfb3fcce2b8f8f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 19 Aug 2015 11:26:47 -0500 Subject: [PATCH 22/23] Export private dbLock*Ref() functions for tests --- src/ioc/db/dbLockPvt.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ioc/db/dbLockPvt.h b/src/ioc/db/dbLockPvt.h index 86d186619..f7e6a04c3 100644 --- a/src/ioc/db/dbLockPvt.h +++ b/src/ioc/db/dbLockPvt.h @@ -84,9 +84,10 @@ struct dbLocker { lockRecordRef refs[DBLOCKER_NALLOC]; /* actual length is maxrefs */ }; -lockSet* dbLockGetRef(lockRecord *lr); /* lookup lockset and increment ref count */ -void dbLockIncRef(lockSet* ls); -void dbLockDecRef(lockSet *ls); +/* 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. From d890c8096129989022bc59db5519ff4cada84768 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 20 Aug 2015 15:52:30 -0400 Subject: [PATCH 23/23] dbLock: restore initialization of PV_LINK PV_LINK -> DB_LINK must happen in doResolveLinks after add_record --- src/ioc/db/dbLink.c | 8 ++++--- src/ioc/db/dbLock.c | 52 +++--------------------------------------- src/ioc/db/dbLockPvt.h | 1 + src/ioc/misc/iocInit.c | 4 ++++ 4 files changed, 13 insertions(+), 52 deletions(-) diff --git a/src/ioc/db/dbLink.c b/src/ioc/db/dbLink.c index 0a440cfcb..1d5ca9944 100644 --- a/src/ioc/db/dbLink.c +++ b/src/ioc/db/dbLink.c @@ -127,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; @@ -145,6 +145,8 @@ static long dbDbInitLink(struct link *plink, short dbfType) /* 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; } @@ -386,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, @@ -403,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; } diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index 56f347abd..b30d1a28e 100644 --- a/src/ioc/db/dbLock.c +++ b/src/ioc/db/dbLock.c @@ -516,53 +516,9 @@ static int createLockRecord(void* junk, DBENTRY* pdbentry) lrec->precord = prec; prec->lset = lrec; - return 0; -} -static int initPVLinks(void* junk, DBENTRY* pdbentry) -{ - size_t i; - dbRecordType *rtype=pdbentry->precordType; - dbCommon *prec = pdbentry->precnode->precord; - lockSet *A=prec->lset->plockSet; - - if(!A) { - A = prec->lset->plockSet = makeSet(); - ellAdd(&A->lockRecordList, &prec->lset->node); - } - - /* for each link originating from this record */ - for(i=0; ino_links; i++) { - DBADDR *paddr; - dbFldDes *pdesc = rtype->papFldDes[rtype->link_ind[i]]; - DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset); - lockSet *B; - - if(plink->type!=PV_LINK) - continue; - - dbInitLink(prec, plink, pdesc->field_type); - - if(plink->type!=DB_LINK) - continue; - - paddr = (DBADDR*)plink->value.pv_link.pvt; - B = paddr->precord->lset->plockSet; - - if(A==B) { - /* these records are already in the same lockset */ - } else if(B) { - assert(A!=B); - dbLockSetMerge(NULL, prec, paddr->precord); - assert(prec->lset->plockSet==paddr->precord->lset->plockSet); - - } else { - /* fast merge paddr->precord into A */ - paddr->precord->lset->plockSet = A; - dbLockIncRef(A); - ellAdd(&A->lockRecordList, &paddr->precord->lset->node); - } - } + prec->lset->plockSet = makeSet(); + ellAdd(&prec->lset->plockSet->lockRecordList, &prec->lset->node); return 0; } @@ -570,10 +526,8 @@ void dbLockInitRecords(dbBase *pdbbase) { epicsThreadOnce(&dbLockOnceInit, &dbLockOnce, NULL); - /* create all lockRecords */ + /* create all lockRecords and lockSets */ forEachRecord(NULL, pdbbase, &createLockRecord); - /* create lockSets */ - forEachRecord(NULL, pdbbase, &initPVLinks); } static int freeLockRecord(void* junk, DBENTRY* pdbentry) diff --git a/src/ioc/db/dbLockPvt.h b/src/ioc/db/dbLockPvt.h index f7e6a04c3..58b92f7eb 100644 --- a/src/ioc/db/dbLockPvt.h +++ b/src/ioc/db/dbLockPvt.h @@ -10,6 +10,7 @@ #define DBLOCKPVT_H #include "dbLock.h" +#include "epicsSpin.h" /* enable additional error checking */ #define LOCKSET_DEBUG diff --git a/src/ioc/misc/iocInit.c b/src/ioc/misc/iocInit.c index cce2c7938..0b18c261d 100644 --- a/src/ioc/misc/iocInit.c +++ b/src/ioc/misc/iocInit.c @@ -495,6 +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); if (ellCount(&precord->rdes->devList) > 0 && pdbFldDes->isDevLink) { devSup *pdevSup = dbDTYPtoDevSup(pdbRecordType, precord->dtyp); @@ -506,6 +507,9 @@ static void doResolveLinks(dbRecordType *pdbRecordType, dbCommon *precord, } } } + + if (plink->type == PV_LINK) + dbInitLink(precord, plink, pdbFldDes->field_type); } }