982 lines
28 KiB
C
982 lines
28 KiB
C
/*************************************************************************\
|
|
* 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 <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#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; i<nlock; i++) {
|
|
lockRecordRef *ref = &locker->refs[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; i<nrecs; i++) {
|
|
locker->refs[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; i<locker->maxrefs; 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; i<nlock; i++) {
|
|
lockRecordRef *ref = &locker->refs[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; i<rtype->no_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 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; (link<pdbRecordType->no_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);
|
|
}
|