/*************************************************************************\ * Copyright (c) 2002 The University of Chicago, as Operator of Argonne * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. * SPDX-License-Identifier: EPICS * EPICS Base is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ #include #include #include #include #include "cantProceed.h" #include "dbDefs.h" #include "ellLib.h" #include "epicsAssert.h" #include "epicsAtomic.h" #include "epicsMutex.h" #include "epicsPrint.h" #include "epicsSpin.h" #include "epicsStdio.h" #include "epicsThread.h" #include "errMdef.h" #include "dbAccessDefs.h" #include "dbAddr.h" #include "dbBase.h" #include "dbLink.h" #include "dbCommon.h" #include "dbFldTypes.h" #include "dbLockPvt.h" #include "dbStaticLib.h" #include "link.h" typedef struct dbScanLockNode dbScanLockNode; static epicsThreadOnceId dbLockOnceInit = EPICS_THREAD_ONCE_INIT; static ELLLIST lockSetsActive; /* in use */ #ifndef LOCKSET_NOFREE static ELLLIST lockSetsFree; /* free list */ #endif /* Guard the global list */ static epicsMutexId lockSetsGuard; #ifndef LOCKSET_NOCNT /* Counter which we increment whenever * any lockRecord::plockSet is changed. * An optimization to avoid checking lockSet * associations when no links have changed. */ static size_t recomputeCnt; #endif /*private routines */ static void dbLockOnce(void* ignore) { lockSetsGuard = epicsMutexMustCreate(); } /* global ID number assigned to each lockSet on creation. * When the free-list is in use will never exceed * the number of records +1. * Without the free-list it can roll over, potentially * leading to duplicate IDs. */ static size_t next_id = 1; static lockSet* makeSet(void) { lockSet *ls; int iref; epicsMutexMustLock(lockSetsGuard); #ifndef LOCKSET_NOFREE ls = (lockSet*)ellGet(&lockSetsFree); if(!ls) { epicsMutexUnlock(lockSetsGuard); #endif ls=dbCalloc(1,sizeof(*ls)); ellInit(&ls->lockRecordList); ls->lock = epicsMutexMustCreate(); ls->id = epicsAtomicIncrSizeT(&next_id); #ifndef LOCKSET_NOFREE epicsMutexMustLock(lockSetsGuard); } #endif /* the initial reference for the first lockRecord */ iref = epicsAtomicIncrIntT(&ls->refcount); ellAdd(&lockSetsActive, &ls->node); epicsMutexUnlock(lockSetsGuard); assert(ls->id>0); assert(iref>0); assert(ellCount(&ls->lockRecordList)==0); return ls; } unsigned long dbLockGetRefs(struct dbCommon* prec) { return (unsigned long)epicsAtomicGetIntT(&prec->lset->plockSet->refcount); } unsigned long dbLockCountSets(void) { unsigned long count; epicsMutexMustLock(lockSetsGuard); count = (unsigned long)ellCount(&lockSetsActive); epicsMutexUnlock(lockSetsGuard); return count; } /* caller must lock accessLock.*/ void dbLockIncRef(lockSet* ls) { int cnt = epicsAtomicIncrIntT(&ls->refcount); if(cnt<=1) { cantProceed("dbLockIncRef(%p) on dead lockSet. refs: %d\n", ls, cnt); } } /* caller must lock accessLock. * lockSet must *not* be locked */ void dbLockDecRef(lockSet *ls) { int cnt = epicsAtomicDecrIntT(&ls->refcount); assert(cnt>=0); if(cnt) return; /* lockSet is unused and will be free'd */ /* not necessary as no one else (should) hold a reference, * but lock anyway to quiet valgrind */ epicsMutexMustLock(ls->lock); if(ellCount(&ls->lockRecordList)!=0) { cantProceed("dbLockDecRef(%p) would free lockSet with %d records\n", ls, ellCount(&ls->lockRecordList)); } epicsMutexUnlock(ls->lock); epicsMutexMustLock(lockSetsGuard); ellDelete(&lockSetsActive, &ls->node); #ifndef LOCKSET_NOFREE ellAdd(&lockSetsFree, &ls->node); #else epicsMutexDestroy(ls->lock); memset(ls, 0, sizeof(*ls)); /* paranoia */ free(ls); #endif epicsMutexUnlock(lockSetsGuard); } lockSet* dbLockGetRef(lockRecord *lr) { lockSet *ls; epicsSpinLock(lr->spin); ls = lr->plockSet; dbLockIncRef(ls); epicsSpinUnlock(lr->spin); return ls; } unsigned long dbLockGetLockId(dbCommon *precord) { unsigned long id=0; epicsSpinLock(precord->lset->spin); id = precord->lset->plockSet->id; epicsSpinUnlock(precord->lset->spin); return id; } void dbScanLock(dbCommon *precord) { int cnt; lockRecord * const lr = precord->lset; lockSet *ls; assert(lr); ls = dbLockGetRef(lr); assert(epicsAtomicGetIntT(&ls->refcount)>0); retry: epicsMutexMustLock(ls->lock); epicsSpinLock(lr->spin); if(ls!=lr->plockSet) { /* oops, collided with recompute. * take a reference to the new lockSet. */ lockSet *ls2 = lr->plockSet; int newcnt = epicsAtomicIncrIntT(&ls2->refcount); assert(newcnt>=2); /* at least lockRecord and us */ epicsSpinUnlock(lr->spin); epicsMutexUnlock(ls->lock); dbLockDecRef(ls); ls = ls2; goto retry; } epicsSpinUnlock(lr->spin); /* Release reference taken within this * function. The count will *never* fall to zero * as the lockRecords can't be changed while * we hold the lock. */ cnt = epicsAtomicDecrIntT(&ls->refcount); assert(cnt>0); #ifdef LOCKSET_DEBUG if(ls->owner) { assert(ls->owner==epicsThreadGetIdSelf()); assert(ls->ownercount>=1); ls->ownercount++; } else { assert(ls->ownercount==0); ls->owner = epicsThreadGetIdSelf(); ls->ownercount = 1; } #endif } void dbScanUnlock(dbCommon *precord) { 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 int lrrcompare(const void *rawA, const void *rawB) { const lockRecordRef *refA=rawA, *refB=rawB; const lockSet *A=refA->plockSet, *B=refB->plockSet; if(!A && !B) return 0; /* NULL == NULL */ else if(!A) return 1; /* NULL > !NULL */ else if(!B) return -1; /* !NULL < NULL */ else if(A < B) return -1; else if(A > B) return 1; else return 0; } /* Call w/ update=1 before locking to update cached lockSet entries. * Call w/ update=0 after locking to verify that lockRecord weren't updated */ static int dbLockUpdateRefs(dbLocker *locker, int update) { int changed = 0; size_t i, nlock = locker->maxrefs; #ifndef LOCKSET_NOCNT const size_t recomp = epicsAtomicGetSizeT(&recomputeCnt); if(locker->recomp!=recomp) { #endif /* some lockset recompute happened. * must re-check our references. */ for(i=0; irefs[i]; lockSet *oldref = NULL; if(!ref->plr) { /* this lockRecord slot not used */ assert(!ref->plockSet); continue; } epicsSpinLock(ref->plr->spin); if(ref->plockSet!=ref->plr->plockSet) { changed = 1; if(update) { /* exchange saved lockSet reference */ oldref = ref->plockSet; /* will be NULL on first iteration */ ref->plockSet = ref->plr->plockSet; dbLockIncRef(ref->plockSet); } } epicsSpinUnlock(ref->plr->spin); if(oldref) dbLockDecRef(oldref); if(!update && changed) return changed; } #ifndef LOCKSET_NOCNT /* Use the value captured before we started. * If it has changed in the intrim we will catch this later * during the update==0 pass (which triggers a re-try) */ if(update) locker->recomp = recomp; } #endif if(changed && update) { qsort(locker->refs, nlock, sizeof(lockRecordRef), &lrrcompare); } return changed; } void dbLockerPrepare(struct dbLocker *locker, struct dbCommon * const *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 * const *precs, size_t nrecs, unsigned int flags) { size_t Nextra = nrecs>DBLOCKER_NALLOC ? nrecs-DBLOCKER_NALLOC : 0; dbLocker *locker = calloc(1, sizeof(*locker)+Nextra*sizeof(lockRecordRef)); if(locker) dbLockerPrepare(locker, precs, nrecs); return locker; } void dbLockerFinalize(dbLocker *locker) { size_t i; assert(ellCount(&locker->locked)==0); /* release references taken in dbLockUpdateRefs() */ for(i=0; imaxrefs; i++) { if(locker->refs[i].plockSet) dbLockDecRef(locker->refs[i].plockSet); } } void dbLockerFree(dbLocker *locker) { dbLockerFinalize(locker); free(locker); } /* Lock the given list of records. * This function modifies its arguments. */ void dbScanLockMany(dbLocker* locker) { size_t i, nlock = locker->maxrefs; lockSet *plock; #ifdef LOCKSET_DEBUG const epicsThreadId myself = epicsThreadGetIdSelf(); #endif if(ellCount(&locker->locked)!=0) { cantProceed("dbScanLockMany(%p) already locked. Recursive locking not allowed", locker); return; } retry: assert(ellCount(&locker->locked)==0); dbLockUpdateRefs(locker, 1); for(i=0, plock=NULL; irefs[i]; /* skip duplicates (same lockSet * referenced by more than one lockRecord). * Sorting will group these together. */ if(!ref->plr || (plock && plock==ref->plockSet)) continue; plock = ref->plockSet; epicsMutexMustLock(plock->lock); assert(plock->ownerlocker==NULL); plock->ownerlocker = locker; ellAdd(&locker->locked, &plock->lockernode); /* An extra ref for the locked list */ dbLockIncRef(plock); #ifdef LOCKSET_DEBUG if(plock->owner) { if(plock->owner!=myself || plock->ownercount<1) { cantProceed("dbScanLockMany(%p) ownership violation %p (%p) %u\n", locker, plock->owner, myself, plock->ownercount); } 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 */ cantProceed("dbScanLockMany(%p) didn't lock anything\n", locker); } } void dbScanUnlockMany(dbLocker* locker) { ELLNODE *cur; #ifdef LOCKSET_DEBUG const epicsThreadId myself = epicsThreadGetIdSelf(); #endif while((cur=ellGet(&locker->locked))!=NULL) { lockSet *plock = CONTAINER(cur, lockSet, lockernode); assert(plock->ownerlocker==locker); plock->ownerlocker = NULL; #ifdef LOCKSET_DEBUG assert(plock->owner==myself); assert(plock->ownercount>=1); plock->ownercount--; if(plock->ownercount==0) plock->owner = NULL; #endif epicsMutexUnlock(plock->lock); /* release ref for locked list */ dbLockDecRef(plock); } } typedef int (*reciter)(void*, DBENTRY*); static int forEachRecord(void *priv, dbBase *pdbbase, reciter fn) { long status; int ret = 0; DBENTRY dbentry; dbInitEntry(pdbbase,&dbentry); status = dbFirstRecordType(&dbentry); while(!status) { status = dbFirstRecord(&dbentry); while(!status) { /* skip alias names */ if(!dbentry.precnode->recordname[0] || dbentry.precnode->flags & DBRN_FLAGS_ISALIAS) { /* skip */ } else { ret = fn(priv, &dbentry); if(ret) goto done; } status = dbNextRecord(&dbentry); } status = dbNextRecordType(&dbentry); } done: dbFinishEntry(&dbentry); return ret; } static int createLockRecord(void* junk, DBENTRY* pdbentry) { dbCommon *prec = pdbentry->precnode->precord; lockRecord *lrec; assert(!prec->lset); /* TODO: one allocation for all records? */ lrec = callocMustSucceed(1, sizeof(*lrec), "lockRecord"); lrec->spin = epicsSpinCreate(); if(!lrec->spin) cantProceed("no memory for spinlock in lockRecord"); lrec->precord = prec; prec->lset = lrec; prec->lset->plockSet = makeSet(); ellAdd(&prec->lset->plockSet->lockRecordList, &prec->lset->node); return 0; } void dbLockInitRecords(dbBase *pdbbase) { epicsThreadOnce(&dbLockOnceInit, &dbLockOnce, NULL); /* create all lockRecords and lockSets */ forEachRecord(NULL, pdbbase, &createLockRecord); } static int freeLockRecord(void* junk, DBENTRY* pdbentry) { dbCommon *prec = pdbentry->precnode->precord; lockRecord *lr = prec->lset; lockSet *ls = lr->plockSet; prec->lset = NULL; lr->precord = NULL; assert(ls->refcount>0); assert(ellCount(&ls->lockRecordList)>0); ellDelete(&ls->lockRecordList, &lr->node); dbLockDecRef(ls); epicsSpinDestroy(lr->spin); free(lr); return 0; } void dbLockCleanupRecords(dbBase *pdbbase) { #ifndef LOCKSET_NOFREE ELLNODE *cur; #endif epicsThreadOnce(&dbLockOnceInit, &dbLockOnce, NULL); forEachRecord(NULL, pdbbase, &freeLockRecord); if(ellCount(&lockSetsActive)) { printf("Warning: dbLockCleanupRecords() leaking lockSets\n"); dblsr(NULL,2); } #ifndef LOCKSET_NOFREE while((cur=ellGet(&lockSetsFree))!=NULL) { lockSet *ls = (lockSet*)cur; assert(ls->refcount==0); assert(ellCount(&ls->lockRecordList)==0); epicsMutexDestroy(ls->lock); free(ls); } #endif } /* Called in two modes. * During dbLockInitRecords w/ locker==NULL, then no mutex are locked. * After dbLockInitRecords w/ locker!=NULL, then * the caller must lock both pfirst and psecond. * * Assumes that pfirst has been modified * to link to psecond. */ void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) { 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(locker && (A->owner!=myself || B->owner!=myself)) { cantProceed("dbLockSetMerge(%p,\"%s\",\"%s\") ownership violation %p %p (%p)\n", locker, pfirst->name, psecond->name, A->owner, B->owner, myself); } #endif if(locker && (A->ownerlocker!=locker || B->ownerlocker!=locker)) { cantProceed("dbLockSetMerge(%p,\"%s\",\"%s\") locker ownership violation %p %p (%p)\n", locker, pfirst->name, psecond->name, A->ownerlocker, B->ownerlocker, locker); } if(A==B) return; /* already in the same lockSet */ Nb = ellCount(&B->lockRecordList); assert(Nb>0); /* move all records from B to A */ while((cur=ellGet(&B->lockRecordList))!=NULL) { lockRecord *lr = CONTAINER(cur, lockRecord, node); assert(lr->plockSet==B); ellAdd(&A->lockRecordList, cur); epicsSpinLock(lr->spin); lr->plockSet = A; #ifndef LOCKSET_NOCNT epicsAtomicIncrSizeT(&recomputeCnt); #endif epicsSpinUnlock(lr->spin); } /* there are at minimum, 1 ref for each lockRecord, * and one for the locker's locked list * (and perhaps another for its refs cache) */ assert(epicsAtomicGetIntT(&B->refcount)>=Nb+(locker?1:0)); /* update ref counters. for lockRecords */ epicsAtomicAddIntT(&A->refcount, Nb); epicsAtomicAddIntT(&B->refcount, -Nb+1); /* drop all but one ref, see below */ if(locker) { /* at least two refs, possibly three, remain. * # One ref from above * # locker->locked list, which is released now. * # locker->refs array, assuming it is directly referenced, * and not added as the result of a dbLockSetSplit, * which will be cleaned when the locker is free'd (not here). */ #ifdef LOCKSET_DEBUG B->owner = NULL; B->ownercount = 0; #endif assert(B->ownerlocker==locker); ellDelete(&locker->locked, &B->lockernode); B->ownerlocker = NULL; epicsAtomicDecrIntT(&B->refcount); epicsMutexUnlock(B->lock); } dbLockDecRef(B); /* last ref we hold */ assert(A==psecond->lset->plockSet); } /* 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) { cantProceed("dbLockSetSplit(%p,\"%s\",\"%s\") ownership violation %p %p (%p)\n", locker, pfirst->name, psecond->name, ls->owner, psecond->lset->plockSet->owner, myself); } #endif /* lockset consistency violation */ if(ls!=psecond->lset->plockSet) { cantProceed("dbLockSetSplit(%p,\"%s\",\"%s\") consistency violation %p %p\n", locker, pfirst->name, psecond->name, pfirst->lset->plockSet, psecond->lset->plockSet); } if(pfirst==psecond) return; /* at least 1 ref for each lockRecord, * and one for the locker */ assert(epicsAtomicGetIntT(&ls->refcount)>=ellCount(&ls->lockRecordList)+1); ellInit(&toInspect); ellInit(&newLS); /* strategy is to start with psecond and do * a breadth first traversal until all records are * visited. If we encounter pfirst, then there * is no need to create a new lockset so we abort * early. */ ellAdd(&toInspect, &psecond->lset->compnode); psecond->lset->compflag = 1; { lockSet *splitset; ELLNODE *cur; while((cur=ellGet(&toInspect))!=NULL) { lockRecord *lr=CONTAINER(cur,lockRecord,compnode); dbCommon *prec=lr->precord; dbRecordType *rtype = prec->rdes; size_t i; ELLNODE *bcur; ellAdd(&newLS, cur); prec->lset->compflag = 2; /* Visit all the links originating from prec */ for(i=0; ino_links; i++) { dbFldDes *pdesc = rtype->papFldDes[rtype->link_ind[i]]; DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset); dbChannel *chan; lockRecord *lr; if(plink->type!=DB_LINK) continue; chan = plink->value.pv_link.pvt; lr = dbChannelRecord(chan)->lset; assert(lr); if(lr->precord==pfirst) { /* so pfirst is still reachable from psecond, * no new lock set should be created. */ goto nosplit; } /* have we already visited this record? */ if(lr->compflag) continue; ellAdd(&toInspect, &lr->compnode); lr->compflag = 1; } /* Visit all links terminating at prec */ for(bcur=ellFirst(&prec->bklnk); bcur; bcur=ellNext(bcur)) { struct pv_link *plink1 = CONTAINER(bcur, struct pv_link, backlinknode); union value *plink2 = CONTAINER(plink1, union value, pv_link); DBLINK *plink = CONTAINER(plink2, DBLINK, value); lockRecord *lr = plink->precord->lset; /* plink->type==DB_LINK is implied. Only DB_LINKs are tracked from BKLNK */ if(lr->precord==pfirst) { goto nosplit; } if(lr->compflag) continue; ellAdd(&toInspect, &lr->compnode); lr->compflag = 1; } } /* All links involving psecond were traversed without finding * pfirst. So we must create a new lockset. * newLS contains the nodes which will * make up this new lockset. */ /* newLS will have at least psecond in it */ assert(ellCount(&newLS) > 0); /* If we didn't find pfirst, then it must be in the * original lockset, and not the new one */ assert(ellCount(&newLS) < ellCount(&ls->lockRecordList)); assert(ellCount(&newLS) < ls->refcount); splitset = makeSet(); /* reference for locker->locked */ epicsMutexMustLock(splitset->lock); assert(splitset->ownerlocker==NULL); ellAdd(&locker->locked, &splitset->lockernode); splitset->ownerlocker = locker; assert(splitset->refcount==1); #ifdef LOCKSET_DEBUG splitset->owner = ls->owner; splitset->ownercount = 1; assert(ls->ownercount==1); #endif while((cur=ellGet(&newLS))!=NULL) { lockRecord *lr=CONTAINER(cur,lockRecord,compnode); lr->compflag = 0; /* reset for next time */ assert(lr->plockSet == ls); ellDelete(&ls->lockRecordList, &lr->node); ellAdd(&splitset->lockRecordList, &lr->node); epicsSpinLock(lr->spin); lr->plockSet = splitset; #ifndef LOCKSET_NOCNT epicsAtomicIncrSizeT(&recomputeCnt); #endif epicsSpinUnlock(lr->spin); /* new lockSet is "live" at this point * as other threads may find it. */ } /* refcount of ls can't go to zero as the locker * holds at least one reference (its locked list) */ epicsAtomicAddIntT(&ls->refcount, -ellCount(&splitset->lockRecordList)); assert(ls->refcount>0); epicsAtomicAddIntT(&splitset->refcount, ellCount(&splitset->lockRecordList)); assert(splitset->refcount>=ellCount(&splitset->lockRecordList)+1); assert(psecond->lset->plockSet==splitset); /* must have refs from pfirst lockRecord, * and the locked list. */ assert(epicsAtomicGetIntT(&ls->refcount)>=2); return; } nosplit: { /* reset compflag for all nodes visited * during the aborted search */ ELLNODE *cur; while((cur=ellGet(&toInspect))!=NULL) { lockRecord *lr=CONTAINER(cur,lockRecord,compnode); lr->compflag = 0; } while((cur=ellGet(&newLS))!=NULL) { lockRecord *lr=CONTAINER(cur,lockRecord,compnode); lr->compflag = 0; } return; } } static const char *msstring[4]={"NMS","MS","MSI","MSS"}; long dblsr(char *recordname,int level) { int link; DBENTRY dbentry; DBENTRY *pdbentry=&dbentry; long status; dbCommon *precord; lockSet *plockSet; lockRecord *plockRecord; dbRecordType *pdbRecordType; dbFldDes *pdbFldDes; DBLINK *plink; if (recordname && ((*recordname == '\0') || !strcmp(recordname,"*"))) recordname = NULL; if(recordname) { dbInitEntry(pdbbase,pdbentry); status = dbFindRecord(pdbentry,recordname); if(status) { printf("Record not found\n"); dbFinishEntry(pdbentry); return 0; } precord = pdbentry->precnode->precord; dbFinishEntry(pdbentry); plockRecord = precord->lset; if (!plockRecord) return 0; /* before iocInit */ plockSet = plockRecord->plockSet; } else { plockSet = (lockSet *)ellFirst(&lockSetsActive); } for( ; plockSet; plockSet = (lockSet *)ellNext(&plockSet->node)) { printf("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); if(level<=1) continue; for(link=0; (linkno_links) ; link++) { DBADDR *pdbAddr; 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); printf("\t%s",pdbFldDes->name); if(pdbFldDes->field_type==DBF_INLINK) { printf("\t INLINK"); } else if(pdbFldDes->field_type==DBF_OUTLINK) { printf("\tOUTLINK"); } else if(pdbFldDes->field_type==DBF_FWDLINK) { printf("\tFWDLINK"); } printf(" %s %s", ((plink->value.pv_link.pvlMask&pvlOptPP)?" PP":"NPP"), msstring[plink->value.pv_link.pvlMask&pvlOptMsMode]); printf(" %s\n",pdbAddr->precord->name); } } if(recordname) break; } return 0; } long dbLockShowLocked(int level) { int indListType; lockSet *plockSet; printf("Active lockSets: %d\n", ellCount(&lockSetsActive)); #ifndef LOCKSET_NOFREE printf("Free lockSets: %d\n", ellCount(&lockSetsFree)); #endif /*Even if failure on lockSetModifyLock will continue */ for(indListType=0; indListType <= 1; ++indListType) { plockSet = (lockSet *)ellFirst(&lockSetsActive); if(plockSet) { if (indListType==0) printf("listTypeScanLock\n"); else printf("listTypeRecordLock\n"); } while(plockSet) { epicsMutexLockStatus status; status = epicsMutexTryLock(plockSet->lock); if(status==epicsMutexLockOK) epicsMutexUnlock(plockSet->lock); if(status!=epicsMutexLockOK || indListType==1) { epicsMutexShow(plockSet->lock,level); } plockSet = (lockSet *)ellNext(&plockSet->node); } } return 0; } int * dbLockSetAddrTrace(dbCommon *precord) { lockRecord *plockRecord = precord->lset; lockSet *plockSet = plockRecord->plockSet; return(&plockSet->trace); }