From af89b716f470c386e78bce95f8011eb7c9104326 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:14:46 -0400 Subject: [PATCH] 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