Merged lockopt branch
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
#include "epicsThread.h"
|
||||
#include "epicsTime.h"
|
||||
#include "errlog.h"
|
||||
#include "epicsSpin.h"
|
||||
#include "errMdef.h"
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
@@ -50,7 +51,7 @@
|
||||
#include "dbFldTypes.h"
|
||||
#include "dbFldTypes.h"
|
||||
#include "dbLink.h"
|
||||
#include "dbLock.h"
|
||||
#include "dbLockPvt.h"
|
||||
#include "dbNotify.h"
|
||||
#include "dbScan.h"
|
||||
#include "dbServer.h"
|
||||
@@ -944,6 +945,8 @@ static long dbPutFieldLink(DBADDR *paddr,
|
||||
dbLinkInfo link_info;
|
||||
DBADDR *pdbaddr = NULL;
|
||||
dbCommon *precord = paddr->precord;
|
||||
dbCommon *lockrecs[2];
|
||||
dbLocker locker;
|
||||
dbFldDes *pfldDes = paddr->pfldDes;
|
||||
long special = paddr->special;
|
||||
struct link *plink = (struct link *)paddr->pfield;
|
||||
@@ -956,6 +959,8 @@ static long dbPutFieldLink(DBADDR *paddr,
|
||||
int isDevLink;
|
||||
short scan;
|
||||
|
||||
STATIC_ASSERT(DBLOCKER_NALLOC>=2);
|
||||
|
||||
switch (dbrType) {
|
||||
case DBR_CHAR:
|
||||
case DBR_UCHAR:
|
||||
@@ -992,10 +997,12 @@ static long dbPutFieldLink(DBADDR *paddr,
|
||||
isDevLink = ellCount(&precord->rdes->devList) > 0 &&
|
||||
pfldDes->isDevLink;
|
||||
|
||||
dbLockSetGblLock();
|
||||
dbLockSetRecordLock(precord);
|
||||
if (pdbaddr)
|
||||
dbLockSetRecordLock(pdbaddr->precord);
|
||||
memset(&locker, 0, sizeof(locker));
|
||||
lockrecs[0] = precord;
|
||||
lockrecs[1] = pdbaddr ? pdbaddr->precord : NULL;
|
||||
dbLockerPrepare(&locker, lockrecs, 2);
|
||||
|
||||
dbScanLockMany(&locker);
|
||||
|
||||
scan = precord->scan;
|
||||
|
||||
@@ -1044,7 +1051,7 @@ static long dbPutFieldLink(DBADDR *paddr,
|
||||
switch (plink->type) { /* Old link type */
|
||||
case DB_LINK:
|
||||
case CA_LINK:
|
||||
dbRemoveLink(plink);
|
||||
dbRemoveLink(&locker, precord, plink); /* link type becomes PV_LINK */
|
||||
break;
|
||||
|
||||
case PV_LINK:
|
||||
@@ -1091,7 +1098,7 @@ static long dbPutFieldLink(DBADDR *paddr,
|
||||
|
||||
switch (plink->type) { /* New link type */
|
||||
case PV_LINK:
|
||||
dbAddLink(precord, plink, pfldDes->field_type, pdbaddr);
|
||||
dbAddLink(&locker, precord, plink, pfldDes->field_type, pdbaddr);
|
||||
break;
|
||||
|
||||
case CONSTANT:
|
||||
@@ -1121,7 +1128,8 @@ postScanEvent:
|
||||
if (scan != precord->scan)
|
||||
db_post_events(precord, &precord->scan, DBE_VALUE | DBE_LOG);
|
||||
unlock:
|
||||
dbLockSetGblUnlock();
|
||||
dbScanUnlockMany(&locker);
|
||||
dbLockerFinalize(&locker);
|
||||
cleanup:
|
||||
free(link_info.target);
|
||||
return status;
|
||||
|
||||
@@ -87,10 +87,10 @@ long dbcar(char *precordname, int level)
|
||||
!dbIsAlias(pdbentry)) {
|
||||
pdbRecordType = pdbentry->precordType;
|
||||
precord = (dbCommon *)pdbentry->precnode->precord;
|
||||
dbScanLock(precord);
|
||||
for (j=0; j<pdbRecordType->no_links; j++) {
|
||||
pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[j]];
|
||||
plink = (DBLINK *)((char *)precord + pdbFldDes->offset);
|
||||
dbLockSetGblLock();
|
||||
if (plink->type == CA_LINK) {
|
||||
ncalinks++;
|
||||
pca = (caLink *)plink->value.pv_link.pvt;
|
||||
@@ -135,8 +135,8 @@ long dbcar(char *precordname, int level)
|
||||
}
|
||||
}
|
||||
}
|
||||
dbLockSetGblUnlock();
|
||||
}
|
||||
dbScanUnlock(precord);
|
||||
if (precordname) goto done;
|
||||
}
|
||||
status = dbNextRecord(pdbentry);
|
||||
|
||||
@@ -92,6 +92,12 @@
|
||||
interest(4)
|
||||
extra("ELLLIST mlis")
|
||||
}
|
||||
field(BKLNK,DBF_NOACCESS) {
|
||||
prompt("Backwards link tracking")
|
||||
special(SPC_NOMOD)
|
||||
interest(4)
|
||||
extra("ELLLIST bklnk")
|
||||
}
|
||||
field(DISP,DBF_UCHAR) {
|
||||
prompt("Disable putField")
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "cantProceed.h"
|
||||
#include "cvtFast.h"
|
||||
#include "dbDefs.h"
|
||||
#include "epicsSpin.h"
|
||||
#include "ellLib.h"
|
||||
#include "epicsThread.h"
|
||||
#include "epicsTime.h"
|
||||
@@ -45,7 +46,7 @@
|
||||
#include "dbFldTypes.h"
|
||||
#include "dbFldTypes.h"
|
||||
#include "dbLink.h"
|
||||
#include "dbLock.h"
|
||||
#include "dbLockPvt.h"
|
||||
#include "dbNotify.h"
|
||||
#include "dbScan.h"
|
||||
#include "dbStaticLib.h"
|
||||
@@ -126,7 +127,7 @@ static long dbConstGetLink(struct link *plink, short dbrType, void *pbuffer,
|
||||
|
||||
/***************************** Database Links *****************************/
|
||||
|
||||
static long dbDbInitLink(struct link *plink, short dbfType)
|
||||
static long dbDbInitLink(struct dbCommon *precord, struct link *plink, short dbfType)
|
||||
{
|
||||
DBADDR dbaddr;
|
||||
long status;
|
||||
@@ -140,18 +141,25 @@ static long dbDbInitLink(struct link *plink, short dbfType)
|
||||
pdbAddr = dbCalloc(1, sizeof(struct dbAddr));
|
||||
*pdbAddr = dbaddr; /* structure copy */
|
||||
plink->value.pv_link.pvt = pdbAddr;
|
||||
dbLockSetMerge(plink->value.pv_link.precord, pdbAddr->precord);
|
||||
ellAdd(&dbaddr.precord->bklnk, &plink->value.pv_link.backlinknode);
|
||||
/* merging into the same lockset is deferred to the caller.
|
||||
* cf. initPVLinks()
|
||||
*/
|
||||
dbLockSetMerge(NULL, precord, dbaddr.precord);
|
||||
assert(precord->lset->plockSet==dbaddr.precord->lset->plockSet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dbDbRemoveLink(struct link *plink)
|
||||
static void dbDbRemoveLink(dbLocker *locker, struct dbCommon *prec, struct link *plink)
|
||||
{
|
||||
free(plink->value.pv_link.pvt);
|
||||
DBADDR *pdbAddr = (DBADDR *) plink->value.pv_link.pvt;
|
||||
plink->value.pv_link.pvt = 0;
|
||||
plink->value.pv_link.getCvt = 0;
|
||||
plink->value.pv_link.lastGetdbrType = 0;
|
||||
plink->type = PV_LINK;
|
||||
dbLockSetSplit(plink->value.pv_link.precord);
|
||||
ellDelete(&pdbAddr->precord->bklnk, &plink->value.pv_link.backlinknode);
|
||||
dbLockSetSplit(locker, prec, pdbAddr->precord);
|
||||
free(pdbAddr);
|
||||
}
|
||||
|
||||
static int dbDbIsLinkConnected(const struct link *plink)
|
||||
@@ -380,7 +388,7 @@ static void dbDbScanFwdLink(struct link *plink)
|
||||
dbScanPassive(precord, paddr->precord);
|
||||
}
|
||||
|
||||
lset dbDb_lset = { dbDbRemoveLink,
|
||||
lset dbDb_lset = { NULL,
|
||||
dbDbIsLinkConnected, dbDbGetDBFtype, dbDbGetElements, dbDbGetValue,
|
||||
dbDbGetControlLimits, dbDbGetGraphicLimits, dbDbGetAlarmLimits,
|
||||
dbDbGetPrecision, dbDbGetUnits, dbDbGetAlarm, dbDbGetTimeStamp,
|
||||
@@ -397,7 +405,7 @@ void dbInitLink(struct dbCommon *precord, struct link *plink, short dbfType)
|
||||
|
||||
if (!(plink->value.pv_link.pvlMask & (pvlOptCA | pvlOptCP | pvlOptCPP))) {
|
||||
/* Make it a DB link if possible */
|
||||
if (!dbDbInitLink(plink, dbfType))
|
||||
if (!dbDbInitLink(precord, plink, dbfType))
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -422,7 +430,7 @@ void dbInitLink(struct dbCommon *precord, struct link *plink, short dbfType)
|
||||
}
|
||||
}
|
||||
|
||||
void dbAddLink(struct dbCommon *precord, struct link *plink, short dbfType, DBADDR *ptargetaddr)
|
||||
void dbAddLink(dbLocker *locker, struct dbCommon *precord, struct link *plink, short dbfType, DBADDR *ptargetaddr)
|
||||
{
|
||||
plink->value.pv_link.precord = precord;
|
||||
|
||||
@@ -434,9 +442,10 @@ void dbAddLink(struct dbCommon *precord, struct link *plink, short dbfType, DBAD
|
||||
|
||||
plink->type = DB_LINK;
|
||||
plink->value.pv_link.pvt = ptargetaddr;
|
||||
ellAdd(&ptargetaddr->precord->bklnk, &plink->value.pv_link.backlinknode);
|
||||
|
||||
/* target record is already locked in dbPutFieldLink() */
|
||||
dbLockSetMerge(plink->value.pv_link.precord, ptargetaddr->precord);
|
||||
dbLockSetMerge(locker, plink->value.pv_link.precord, ptargetaddr->precord);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -463,11 +472,11 @@ long dbLoadLink(struct link *plink, short dbrType, void *pbuffer)
|
||||
return S_db_notFound;
|
||||
}
|
||||
|
||||
void dbRemoveLink(struct link *plink)
|
||||
void dbRemoveLink(dbLocker *locker, dbCommon *prec, struct link *plink)
|
||||
{
|
||||
switch (plink->type) {
|
||||
case DB_LINK:
|
||||
dbDbRemoveLink(plink);
|
||||
dbDbRemoveLink(locker, prec, plink);
|
||||
break;
|
||||
case CA_LINK:
|
||||
dbCaRemoveLink(plink);
|
||||
|
||||
@@ -50,13 +50,15 @@ typedef struct lset {
|
||||
#define dbGetSevr(PLINK, PSEVERITY) \
|
||||
dbGetAlarm((PLINK), NULL, (PSEVERITY))
|
||||
|
||||
struct dbLocker;
|
||||
|
||||
epicsShareFunc void dbInitLink(struct dbCommon *precord, struct link *plink,
|
||||
short dbfType);
|
||||
epicsShareFunc void dbAddLink(struct dbCommon *precord, struct link *plink,
|
||||
epicsShareFunc void dbAddLink(struct dbLocker *locker, struct dbCommon *precord, struct link *plink,
|
||||
short dbfType, DBADDR *ptargetaddr);
|
||||
epicsShareFunc long dbLoadLink(struct link *plink, short dbrType,
|
||||
void *pbuffer);
|
||||
epicsShareFunc void dbRemoveLink(struct link *plink);
|
||||
epicsShareFunc void dbRemoveLink(struct dbLocker *locker, struct dbCommon *prec, struct link *plink);
|
||||
epicsShareFunc long dbGetNelements(const struct link *plink, long *nelements);
|
||||
epicsShareFunc int dbIsLinkConnected(const struct link *plink);
|
||||
epicsShareFunc int dbGetLinkDBFtype(const struct link *plink);
|
||||
|
||||
1298
src/ioc/db/dbLock.c
1298
src/ioc/db/dbLock.c
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@
|
||||
#ifndef INCdbLockh
|
||||
#define INCdbLockh
|
||||
|
||||
#include "ellLib.h"
|
||||
#include "shareLib.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -21,21 +22,26 @@ extern "C" {
|
||||
|
||||
struct dbCommon;
|
||||
struct dbBase;
|
||||
typedef struct dbLocker dbLocker;
|
||||
|
||||
epicsShareFunc void dbScanLock(struct dbCommon *precord);
|
||||
epicsShareFunc void dbScanUnlock(struct dbCommon *precord);
|
||||
|
||||
epicsShareFunc dbLocker *dbLockerAlloc(struct dbCommon **precs,
|
||||
size_t nrecs,
|
||||
unsigned int flags);
|
||||
|
||||
epicsShareFunc void dbLockerFree(dbLocker *);
|
||||
|
||||
epicsShareFunc void dbScanLockMany(dbLocker*);
|
||||
epicsShareFunc void dbScanUnlockMany(dbLocker*);
|
||||
|
||||
epicsShareFunc unsigned long dbLockGetLockId(
|
||||
struct dbCommon *precord);
|
||||
|
||||
epicsShareFunc void dbLockInitRecords(struct dbBase *pdbbase);
|
||||
epicsShareFunc void dbLockCleanupRecords(struct dbBase *pdbbase);
|
||||
epicsShareFunc void dbLockSetMerge(
|
||||
struct dbCommon *pfirst,struct dbCommon *psecond);
|
||||
epicsShareFunc void dbLockSetSplit(struct dbCommon *psource);
|
||||
/*The following are for code that modifies lock sets*/
|
||||
epicsShareFunc void dbLockSetGblLock(void);
|
||||
epicsShareFunc void dbLockSetGblUnlock(void);
|
||||
epicsShareFunc void dbLockSetRecordLock(struct dbCommon *precord);
|
||||
|
||||
|
||||
/* Lock Set Report */
|
||||
epicsShareFunc long dblsr(char *recordname,int level);
|
||||
@@ -47,6 +53,10 @@ epicsShareFunc long dbLockShowLocked(int level);
|
||||
/*KLUDGE to support field TPRO*/
|
||||
epicsShareFunc int * dbLockSetAddrTrace(struct dbCommon *precord);
|
||||
|
||||
/* debugging */
|
||||
epicsShareFunc unsigned long dbLockGetRefs(struct dbCommon*);
|
||||
epicsShareFunc unsigned long dbLockCountSets(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
109
src/ioc/db/dbLockPvt.h
Normal file
109
src/ioc/db/dbLockPvt.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2014 Brookhaven Science Assoc., as Operator of Brookhaven
|
||||
* National Laboratory.
|
||||
* EPICS BASE Versions 3.13.7
|
||||
* and higher are distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
#ifndef DBLOCKPVT_H
|
||||
#define DBLOCKPVT_H
|
||||
|
||||
#include "dbLock.h"
|
||||
#include "epicsSpin.h"
|
||||
|
||||
/* enable additional error checking */
|
||||
#define LOCKSET_DEBUG
|
||||
/* disable the free list for lockSets */
|
||||
#define LOCKSET_NOFREE
|
||||
/* disable use of recomputeCnt optimization */
|
||||
/*#define LOCKSET_NOCNT*/
|
||||
|
||||
/* except for refcount (and lock), all members of dbLockSet
|
||||
* are guarded by its lock.
|
||||
*/
|
||||
typedef struct dbLockSet {
|
||||
ELLNODE node;
|
||||
ELLLIST lockRecordList; /* holds lockRecord::node */
|
||||
epicsMutexId lock;
|
||||
unsigned long id;
|
||||
|
||||
int refcount;
|
||||
#ifdef LOCKSET_DEBUG
|
||||
int ownercount;
|
||||
epicsThreadId owner;
|
||||
#endif
|
||||
dbLocker *ownerlocker;
|
||||
ELLNODE lockernode;
|
||||
|
||||
int trace; /*For field TPRO*/
|
||||
} lockSet;
|
||||
|
||||
struct lockRecord;
|
||||
|
||||
/* dbCommon.LSET is a plockRecord.
|
||||
* Except for spin and plockSet, all members of lockRecord are guarded
|
||||
* by the present lockset lock.
|
||||
* plockSet is guarded by spin.
|
||||
*/
|
||||
typedef struct lockRecord {
|
||||
ELLNODE node; /* in lockSet::lockRecordList */
|
||||
/* The association between lockRecord and lockSet
|
||||
* can only be changed while the lockSet is held,
|
||||
* and the lockRecord's spinlock is held.
|
||||
* It may be read which either lock is held.
|
||||
*/
|
||||
lockSet *plockSet;
|
||||
/* the association between lockRecord and dbCommon never changes */
|
||||
dbCommon *precord;
|
||||
epicsSpinId spin;
|
||||
|
||||
/* temp used during lockset split.
|
||||
* lockSet must be locked for access
|
||||
*/
|
||||
ELLNODE compnode;
|
||||
unsigned int compflag;
|
||||
} lockRecord;
|
||||
|
||||
typedef struct {
|
||||
lockRecord *plr;
|
||||
/* the last lock found associated with the ref.
|
||||
* not stable unless lock is locked, or ref spin
|
||||
* is locked.
|
||||
*/
|
||||
lockSet *plockSet;
|
||||
} lockRecordRef;
|
||||
|
||||
#define DBLOCKER_NALLOC 2
|
||||
/* a dbLocker can only be used by a single thread. */
|
||||
struct dbLocker {
|
||||
ELLLIST locked;
|
||||
#ifndef LOCKSET_NOCNT
|
||||
size_t recomp; /* snapshot of recomputeCnt when refs[] cache updated */
|
||||
#endif
|
||||
size_t maxrefs;
|
||||
lockRecordRef refs[DBLOCKER_NALLOC]; /* actual length is maxrefs */
|
||||
};
|
||||
|
||||
/* These are exported for testing only */
|
||||
epicsShareFunc lockSet* dbLockGetRef(lockRecord *lr); /* lookup lockset and increment ref count */
|
||||
epicsShareFunc void dbLockIncRef(lockSet* ls);
|
||||
epicsShareFunc void dbLockDecRef(lockSet *ls);
|
||||
|
||||
/* Calling dbLockerPrepare directly is an internal
|
||||
* optimization used when dbLocker on the stack.
|
||||
* nrecs must be <=DBLOCKER_NALLOC.
|
||||
*/
|
||||
void dbLockerPrepare(struct dbLocker *locker,
|
||||
struct dbCommon **precs,
|
||||
size_t nrecs);
|
||||
void dbLockerFinalize(dbLocker *);
|
||||
|
||||
void dbLockSetMerge(struct dbLocker *locker,
|
||||
struct dbCommon *pfirst,
|
||||
struct dbCommon *psecond);
|
||||
void dbLockSetSplit(struct dbLocker *locker,
|
||||
struct dbCommon *psource,
|
||||
struct dbCommon *psecond);
|
||||
|
||||
#endif /* DBLOCKPVT_H */
|
||||
@@ -61,6 +61,11 @@ testHarness_SRCS += dbLockTest.c
|
||||
TESTS += dbLockTest
|
||||
TESTFILES += ../dbLockTest.db
|
||||
|
||||
TESTPROD_HOST += dbStressTest
|
||||
dbStressTest_SRCS += dbStressLock.c
|
||||
dbStressTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
|
||||
TESTS += dbStressTest
|
||||
|
||||
TESTPROD_HOST += testdbConvert
|
||||
testdbConvert_SRCS += testdbConvert.c
|
||||
testHarness_SRCS += testdbConvert.c
|
||||
|
||||
@@ -9,7 +9,15 @@
|
||||
* Author: Michael Davidsaver <mdavidsaver@bnl.gov>
|
||||
*/
|
||||
|
||||
#include "dbLock.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "epicsSpin.h"
|
||||
#include "epicsMutex.h"
|
||||
#include "dbCommon.h"
|
||||
#include "epicsThread.h"
|
||||
|
||||
#include "dbLockPvt.h"
|
||||
#include "dbStaticLib.h"
|
||||
|
||||
#include "dbUnitTest.h"
|
||||
#include "testMain.h"
|
||||
@@ -28,15 +36,19 @@ void compareSets(int match, const char *A, const char *B)
|
||||
rA = testdbRecordPtr(A);
|
||||
rB = testdbRecordPtr(B);
|
||||
|
||||
actual = dbLockGetLockId(rA)==dbLockGetLockId(rB);
|
||||
actual = rA->lset->plockSet==rB->lset->plockSet;
|
||||
|
||||
testOk(match==actual, "dbLockGetLockId(\"%s\")%c=dbLockGetLockId(\"%s\")",
|
||||
A, match?'=':'!', B);
|
||||
}
|
||||
|
||||
#define testIntOk1(A, OP, B) testOk((A) OP (B), "%s (%d) %s %s (%d)", #A, A, #OP, #B, B);
|
||||
#define testPtrOk1(A, OP, B) testOk((A) OP (B), "%s (%p) %s %s (%p)", #A, A, #OP, #B, B);
|
||||
|
||||
static
|
||||
void testSets(void) {
|
||||
testDiag("Check initial creation of DB links");
|
||||
DBENTRY entry;
|
||||
long status;
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
@@ -48,6 +60,27 @@ void testSets(void) {
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
testDiag("Check that all records have initialized lockRecord and lockSet");
|
||||
|
||||
dbInitEntry(pdbbase, &entry);
|
||||
for(status = dbFirstRecordType(&entry);
|
||||
!status;
|
||||
status = dbNextRecordType(&entry)) {
|
||||
for(status = dbFirstRecord(&entry);
|
||||
!status;
|
||||
status = dbNextRecord(&entry)) {
|
||||
dbCommon *prec = entry.precnode->precord;
|
||||
testOk(prec->lset!=NULL, "%s.LSET != NULL", prec->name);
|
||||
if(prec->lset!=NULL)
|
||||
testOk(prec->lset->plockSet!=NULL, "%s.LSET.plockSet != NULL", prec->name);
|
||||
else
|
||||
testSkip(1, "lockRecord missing");
|
||||
}
|
||||
}
|
||||
dbFinishEntry(&entry);
|
||||
|
||||
testDiag("Check initial creation of DB links");
|
||||
|
||||
/* reca is by itself */
|
||||
compareSets(0, "reca", "recb");
|
||||
compareSets(0, "reca", "recc");
|
||||
@@ -71,6 +104,299 @@ void testSets(void) {
|
||||
|
||||
compareSets(1, "rece", "recf");
|
||||
|
||||
testOk1(testdbRecordPtr("reca")->lset->plockSet->refcount==1);
|
||||
testOk1(testdbRecordPtr("recb")->lset->plockSet->refcount==2);
|
||||
testOk1(testdbRecordPtr("recd")->lset->plockSet->refcount==3);
|
||||
|
||||
testIocShutdownOk();
|
||||
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
static void testSingleLock(void)
|
||||
{
|
||||
dbCommon *prec;
|
||||
testDiag("testing dbScanLock()/dbScanUnlock()");
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
|
||||
dbTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("dbLockTest.db", NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
prec = testdbRecordPtr("reca");
|
||||
testOk1(prec->lset->plockSet->refcount==1);
|
||||
dbScanLock(prec);
|
||||
/* scan lock does not keep a reference to the lockSet */
|
||||
testOk1(prec->lset->plockSet->refcount==1);
|
||||
dbScanUnlock(prec);
|
||||
|
||||
dbScanLock(prec);
|
||||
dbScanLock(prec);
|
||||
/* scan lock can be recursive */
|
||||
testOk1(prec->lset->plockSet->refcount==1);
|
||||
dbScanUnlock(prec);
|
||||
dbScanUnlock(prec);
|
||||
|
||||
testIocShutdownOk();
|
||||
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
static void testMultiLock(void)
|
||||
{
|
||||
dbCommon *prec[8];
|
||||
dbLocker *plockA;
|
||||
epicsThreadId myself = epicsThreadGetIdSelf();
|
||||
|
||||
testDiag("Test multi-locker function (lock everything)");
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
|
||||
dbTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("dbLockTest.db", NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
prec[0] = testdbRecordPtr("reca");
|
||||
prec[1] = testdbRecordPtr("recb");
|
||||
prec[2] = testdbRecordPtr("recc");
|
||||
prec[3] = NULL;
|
||||
prec[4] = testdbRecordPtr("recd");
|
||||
prec[5] = testdbRecordPtr("rece");
|
||||
prec[6] = testdbRecordPtr("recf");
|
||||
prec[7] = testdbRecordPtr("recg");
|
||||
|
||||
testDiag("Test init refcounts");
|
||||
testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,1);
|
||||
testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,2);
|
||||
testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,3);
|
||||
testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,1);
|
||||
|
||||
plockA = dbLockerAlloc(prec, 8, 0);
|
||||
if(!plockA)
|
||||
testAbort("dbLockerAlloc() failed");
|
||||
|
||||
|
||||
testDiag("After locker created");
|
||||
/* locker takes 7 references, one for each lockRecord. */
|
||||
testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,2);
|
||||
testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,4);
|
||||
testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,6);
|
||||
testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,2);
|
||||
#ifdef LOCKSET_DEBUG
|
||||
testPtrOk1(testdbRecordPtr("reca")->lset->plockSet->owner,==,NULL);
|
||||
testPtrOk1(testdbRecordPtr("recb")->lset->plockSet->owner,==,NULL);
|
||||
testPtrOk1(testdbRecordPtr("recd")->lset->plockSet->owner,==,NULL);
|
||||
testPtrOk1(testdbRecordPtr("recg")->lset->plockSet->owner,==,NULL);
|
||||
#endif
|
||||
|
||||
dbScanLockMany(plockA);
|
||||
|
||||
testDiag("After locker locked");
|
||||
/* locker takes 4 references, one for each lockSet. */
|
||||
testIntOk1(ellCount(&plockA->locked),==,4);
|
||||
testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,3);
|
||||
testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,5);
|
||||
testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,7);
|
||||
testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,3);
|
||||
#ifdef LOCKSET_DEBUG
|
||||
testPtrOk1(testdbRecordPtr("reca")->lset->plockSet->owner,==,myself);
|
||||
testPtrOk1(testdbRecordPtr("recb")->lset->plockSet->owner,==,myself);
|
||||
testPtrOk1(testdbRecordPtr("recd")->lset->plockSet->owner,==,myself);
|
||||
testPtrOk1(testdbRecordPtr("recg")->lset->plockSet->owner,==,myself);
|
||||
#endif
|
||||
|
||||
dbScanUnlockMany(plockA);
|
||||
|
||||
testDiag("After locker unlocked");
|
||||
testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,2);
|
||||
testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,4);
|
||||
testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,6);
|
||||
testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,2);
|
||||
#ifdef LOCKSET_DEBUG
|
||||
testPtrOk1(testdbRecordPtr("reca")->lset->plockSet->owner,==,NULL);
|
||||
testPtrOk1(testdbRecordPtr("recb")->lset->plockSet->owner,==,NULL);
|
||||
testPtrOk1(testdbRecordPtr("recd")->lset->plockSet->owner,==,NULL);
|
||||
testPtrOk1(testdbRecordPtr("recg")->lset->plockSet->owner,==,NULL);
|
||||
#endif
|
||||
|
||||
dbLockerFree(plockA);
|
||||
|
||||
testDiag("After locker free'd");
|
||||
testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,1);
|
||||
testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,2);
|
||||
testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,3);
|
||||
testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,1);
|
||||
|
||||
testIocShutdownOk();
|
||||
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
static void testLinkBreak(void)
|
||||
{
|
||||
dbCommon *precB, *precC;
|
||||
testDiag("Test break link");
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
|
||||
dbTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("dbLockTest.db", NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
precB = testdbRecordPtr("recb");
|
||||
precC = testdbRecordPtr("recc");
|
||||
|
||||
testOk1(precB->lset->plockSet==precC->lset->plockSet);
|
||||
testOk1(precB->lset->plockSet->refcount==2);
|
||||
|
||||
/* break the link between B and C */
|
||||
testdbPutFieldOk("recb.SDIS", DBR_STRING, "");
|
||||
|
||||
testOk1(precB->lset->plockSet!=precC->lset->plockSet);
|
||||
testIntOk1(precB->lset->plockSet->refcount, ==, 1);
|
||||
testIntOk1(precC->lset->plockSet->refcount, ==, 1);
|
||||
|
||||
testIocShutdownOk();
|
||||
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
static void testLinkMake(void)
|
||||
{
|
||||
dbCommon *precA, *precG;
|
||||
lockSet *lA, *lG;
|
||||
testDiag("Test make link");
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
|
||||
dbTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("dbLockTest.db", NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
precA = testdbRecordPtr("reca");
|
||||
lA = dbLockGetRef(precA->lset);
|
||||
precG = testdbRecordPtr("recg");
|
||||
lG = dbLockGetRef(precG->lset);
|
||||
|
||||
testPtrOk1(precA->lset->plockSet, !=, precG->lset->plockSet);
|
||||
testIntOk1(precA->lset->plockSet->refcount, ==, 2);
|
||||
testIntOk1(precG->lset->plockSet->refcount, ==, 2);
|
||||
|
||||
/* make a link between A and G */
|
||||
testdbPutFieldOk("reca.SDIS", DBR_STRING, "recg");
|
||||
|
||||
testPtrOk1(precA->lset->plockSet, ==, precG->lset->plockSet);
|
||||
testIntOk1(precA->lset->plockSet->refcount, ==, 3);
|
||||
|
||||
if(precA->lset->plockSet==lG) {
|
||||
testIntOk1(lA->refcount, ==, 1);
|
||||
testIntOk1(lG->refcount, ==, 3);
|
||||
} else {
|
||||
testIntOk1(lA->refcount, ==, 3);
|
||||
testIntOk1(lG->refcount, ==, 1);
|
||||
}
|
||||
dbLockDecRef(lG);
|
||||
dbLockDecRef(lA);
|
||||
|
||||
testIocShutdownOk();
|
||||
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
static void testLinkChange(void)
|
||||
{
|
||||
dbCommon *precB, *precC, *precG;
|
||||
lockSet *lB, *lG;
|
||||
testDiag("Test re-target link");
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
|
||||
dbTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("dbLockTest.db", NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
precB = testdbRecordPtr("recb");
|
||||
precC = testdbRecordPtr("recc");
|
||||
precG = testdbRecordPtr("recg");
|
||||
|
||||
lB = dbLockGetRef(precB->lset);
|
||||
lG = dbLockGetRef(precG->lset);
|
||||
|
||||
testPtrOk1(lB,==,precC->lset->plockSet);
|
||||
testPtrOk1(lB,!=,lG);
|
||||
testIntOk1(lB->refcount,==,3);
|
||||
testIntOk1(lG->refcount,==,2);
|
||||
|
||||
/* break the link between B and C and replace it
|
||||
* with a link between B and G
|
||||
*/
|
||||
testdbPutFieldOk("recb.SDIS", DBR_STRING, "recg");
|
||||
|
||||
testPtrOk1(precB->lset->plockSet,==,lB);
|
||||
testPtrOk1(precG->lset->plockSet,==,lB);
|
||||
testPtrOk1(precC->lset->plockSet,!=,lB);
|
||||
testPtrOk1(precC->lset->plockSet,!=,lG);
|
||||
|
||||
testIntOk1(lB->refcount,==,3);
|
||||
testIntOk1(lG->refcount,==,1);
|
||||
testIntOk1(precC->lset->plockSet->refcount,==,1);
|
||||
|
||||
dbLockDecRef(lB);
|
||||
dbLockDecRef(lG);
|
||||
|
||||
testIocShutdownOk();
|
||||
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
static void testLinkNOP(void)
|
||||
{
|
||||
dbCommon *precB, *precC;
|
||||
testDiag("Test re-target link to the same destination");
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
|
||||
dbTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("dbLockTest.db", NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
precB = testdbRecordPtr("recb");
|
||||
precC = testdbRecordPtr("recc");
|
||||
|
||||
testOk1(precB->lset->plockSet==precC->lset->plockSet);
|
||||
testOk1(precB->lset->plockSet->refcount==2);
|
||||
|
||||
/* renew link between B and C */
|
||||
testdbPutFieldOk("recb.SDIS", DBR_STRING, "recc");
|
||||
|
||||
testOk1(precB->lset->plockSet==precC->lset->plockSet);
|
||||
testOk1(precB->lset->plockSet->refcount==2);
|
||||
|
||||
testIocShutdownOk();
|
||||
|
||||
testdbCleanup();
|
||||
@@ -78,7 +404,13 @@ void testSets(void) {
|
||||
|
||||
MAIN(dbLockTest)
|
||||
{
|
||||
testPlan(15);
|
||||
testPlan(99);
|
||||
testSets();
|
||||
testSingleLock();
|
||||
testMultiLock();
|
||||
testLinkBreak();
|
||||
testLinkMake();
|
||||
testLinkChange();
|
||||
testLinkNOP();
|
||||
return testDone();
|
||||
}
|
||||
|
||||
@@ -19,3 +19,6 @@ record(x, "rece") {
|
||||
|
||||
record(x, "recf") {
|
||||
}
|
||||
|
||||
record(x, "recg") {
|
||||
}
|
||||
|
||||
338
src/ioc/db/test/dbStressLock.c
Normal file
338
src/ioc/db/test/dbStressLock.c
Normal file
@@ -0,0 +1,338 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2014 Brookhaven Science Assoc. as operator of Brookhaven
|
||||
* National Laboratory.
|
||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
/*
|
||||
* Lockset stress test.
|
||||
*
|
||||
* The test stratagy is for N threads to contend for M records.
|
||||
* Each thread will perform one of three operations:
|
||||
* 1) Lock a single record.
|
||||
* 2) Lock several records.
|
||||
* 3) Retarget the TSEL link of a record
|
||||
*
|
||||
* Author: Michael Davidsaver <mdavidsaver@bnl.gov>
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "envDefs.h"
|
||||
#include "epicsStdlib.h"
|
||||
#include "epicsSpin.h"
|
||||
#include "epicsThread.h"
|
||||
#include "epicsMutex.h"
|
||||
#include "dbCommon.h"
|
||||
|
||||
#include "dbLockPvt.h"
|
||||
#include "dbStaticLib.h"
|
||||
|
||||
#include "dbUnitTest.h"
|
||||
#include "testMain.h"
|
||||
|
||||
#include "dbAccess.h"
|
||||
#include "errlog.h"
|
||||
|
||||
#include "xRecord.h"
|
||||
|
||||
#define testIntOk1(A, OP, B) testOk((A) OP (B), "%s (%d) %s %s (%d)", #A, A, #OP, #B, B);
|
||||
#define testPtrOk1(A, OP, B) testOk((A) OP (B), "%s (%p) %s %s (%p)", #A, A, #OP, #B, B);
|
||||
|
||||
void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
/* number of seconds for the test to run */
|
||||
static double runningtime = 18.0;
|
||||
|
||||
/* number of worker threads */
|
||||
static unsigned int nworkers = 5;
|
||||
|
||||
static unsigned int nrecords;
|
||||
|
||||
#define MAXLOCK 20
|
||||
|
||||
static dbCommon **precords;
|
||||
|
||||
typedef struct {
|
||||
int id;
|
||||
unsigned long N[3];
|
||||
double X[3];
|
||||
double X2[3];
|
||||
|
||||
unsigned int done;
|
||||
epicsEventId donevent;
|
||||
|
||||
dbCommon *prec[MAXLOCK];
|
||||
} workerPriv;
|
||||
|
||||
/* hopefully a uniform random number in [0.0, 1.0] */
|
||||
static
|
||||
double getRand(void)
|
||||
{
|
||||
return rand()/(double)RAND_MAX;
|
||||
}
|
||||
|
||||
static
|
||||
void doSingle(workerPriv *p)
|
||||
{
|
||||
size_t recn = (size_t)(getRand()*(nrecords-1));
|
||||
dbCommon *prec = precords[recn];
|
||||
xRecord *px = (xRecord*)prec;
|
||||
|
||||
dbScanLock(prec);
|
||||
px->val++;
|
||||
dbScanUnlock(prec);
|
||||
}
|
||||
|
||||
static volatile int bitbucket;
|
||||
|
||||
static
|
||||
void doMulti(workerPriv *p)
|
||||
{
|
||||
int sum = 0;
|
||||
size_t i;
|
||||
size_t nlock = 2 + (size_t)(getRand()*(MAXLOCK-3));
|
||||
size_t nrec = (size_t)(getRand()*(nrecords-1));
|
||||
dbLocker *locker;
|
||||
|
||||
assert(nlock>=2);
|
||||
assert(nlock<nrecords);
|
||||
|
||||
for(i=0; i<nlock; i++, nrec=(nrec+1)%nrecords) {
|
||||
p->prec[i] = precords[nrec];
|
||||
}
|
||||
|
||||
locker = dbLockerAlloc(p->prec, nlock, 0);
|
||||
if(!locker)
|
||||
testAbort("locker allocation fails");
|
||||
|
||||
dbScanLockMany(locker);
|
||||
for(i=0; i<nlock; i++) {
|
||||
xRecord *px = (xRecord*)p->prec[i];
|
||||
sum += px->val;
|
||||
}
|
||||
dbScanUnlockMany(locker);
|
||||
|
||||
dbLockerFree(locker);
|
||||
}
|
||||
|
||||
static
|
||||
void doreTarget(workerPriv *p)
|
||||
{
|
||||
char scratchsrc[60];
|
||||
char scratchdst[MAX_STRING_SIZE];
|
||||
long ret;
|
||||
DBADDR dbaddr;
|
||||
double action = getRand();
|
||||
size_t nsrc = (size_t)(getRand()*(nrecords-1));
|
||||
size_t ntarg = (size_t)(getRand()*(nrecords-1));
|
||||
xRecord *psrc = (xRecord*)precords[nsrc];
|
||||
xRecord *ptarg = (xRecord*)precords[ntarg];
|
||||
|
||||
strcpy(scratchsrc, psrc->name);
|
||||
strcat(scratchsrc, ".TSEL");
|
||||
|
||||
ret = dbNameToAddr(scratchsrc, &dbaddr);
|
||||
if(ret)
|
||||
testAbort("bad record name? %ld", ret);
|
||||
|
||||
if(action<=0.6) {
|
||||
scratchdst[0] = '\0';
|
||||
} else {
|
||||
strcpy(scratchdst, ptarg->name);
|
||||
}
|
||||
|
||||
ret = dbPutField(&dbaddr, DBR_STRING, ptarg->name, 1);
|
||||
if(ret)
|
||||
testAbort("put fails with %ld", ret);
|
||||
}
|
||||
|
||||
static
|
||||
void worker(void *raw)
|
||||
{
|
||||
struct timespec before;
|
||||
workerPriv *priv = raw;
|
||||
|
||||
testDiag("worker %d is %p", priv->id, epicsThreadGetIdSelf());
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &before);
|
||||
|
||||
while(!priv->done) {
|
||||
double sel = getRand();
|
||||
struct timespec after;
|
||||
double duration;
|
||||
|
||||
int act;
|
||||
if(sel<0.33) {
|
||||
doSingle(priv);
|
||||
act = 0;
|
||||
} else if(sel<0.66) {
|
||||
doMulti(priv);
|
||||
act = 1;
|
||||
} else {
|
||||
doreTarget(priv);
|
||||
act = 2;
|
||||
}
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &after);
|
||||
|
||||
duration = (double)((long)after.tv_nsec - (long)before.tv_nsec);
|
||||
duration *= 1e-9;
|
||||
duration += (double)(after.tv_sec - before.tv_sec);
|
||||
|
||||
priv->N[act]++;
|
||||
priv->X[act] += duration;
|
||||
priv->X2[act] += duration*duration;
|
||||
}
|
||||
|
||||
epicsEventMustTrigger(priv->donevent);
|
||||
}
|
||||
|
||||
MAIN(dbStressTest)
|
||||
{
|
||||
DBENTRY ent;
|
||||
long status;
|
||||
unsigned int i;
|
||||
workerPriv *priv;
|
||||
char *nwork=getenv("NWORK");
|
||||
struct timespec seed;
|
||||
|
||||
testPlan(95);
|
||||
|
||||
clock_gettime(CLOCK_REALTIME, &seed);
|
||||
srand(seed.tv_nsec);
|
||||
|
||||
if(nwork) {
|
||||
long val = 0;
|
||||
epicsParseLong(nwork, &val, 0, NULL);
|
||||
if(val>2)
|
||||
nworkers = val;
|
||||
}
|
||||
|
||||
priv = callocMustSucceed(nworkers, sizeof(*priv), "no memory");
|
||||
|
||||
testDiag("lock set stress test");
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
|
||||
dbTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("dbStressLock.db", NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
/* collect an array of all records */
|
||||
dbInitEntry(pdbbase, &ent);
|
||||
for(status = dbFirstRecordType(&ent);
|
||||
!status;
|
||||
status = dbNextRecordType(&ent))
|
||||
{
|
||||
for(status = dbFirstRecord(&ent);
|
||||
!status;
|
||||
status = dbNextRecord(&ent))
|
||||
{
|
||||
if(ent.precnode->flags&DBRN_FLAGS_ISALIAS)
|
||||
continue;
|
||||
nrecords++;
|
||||
}
|
||||
|
||||
}
|
||||
if(nrecords<2)
|
||||
testAbort("where are the records!");
|
||||
precords = callocMustSucceed(nrecords, sizeof(*precords), "no mem");
|
||||
for(status = dbFirstRecordType(&ent), i = 0;
|
||||
!status;
|
||||
status = dbNextRecordType(&ent))
|
||||
{
|
||||
for(status = dbFirstRecord(&ent);
|
||||
!status;
|
||||
status = dbNextRecord(&ent))
|
||||
{
|
||||
if(ent.precnode->flags&DBRN_FLAGS_ISALIAS)
|
||||
continue;
|
||||
precords[i++] = ent.precnode->precord;
|
||||
}
|
||||
|
||||
}
|
||||
dbFinishEntry(&ent);
|
||||
|
||||
testDiag("Running with %u workers and %u records",
|
||||
nworkers, nrecords);
|
||||
|
||||
for(i=0; i<nworkers; i++) {
|
||||
priv[i].id = i;
|
||||
priv[i].donevent = epicsEventMustCreate(epicsEventEmpty);
|
||||
}
|
||||
|
||||
for(i=0; i<nworkers; i++) {
|
||||
epicsThreadMustCreate("runner", epicsThreadPriorityMedium,
|
||||
epicsThreadGetStackSize(epicsThreadStackSmall),
|
||||
&worker, &priv[i]);
|
||||
}
|
||||
|
||||
testDiag("All started. Will run for %f sec", runningtime);
|
||||
|
||||
epicsThreadSleep(runningtime);
|
||||
|
||||
testDiag("Stopping");
|
||||
|
||||
for(i=0; i<nworkers; i++) {
|
||||
priv[i].done = 1;
|
||||
}
|
||||
|
||||
for(i=0; i<nworkers; i++) {
|
||||
epicsEventMustWait(priv[i].donevent);
|
||||
epicsEventDestroy(priv[i].donevent);
|
||||
}
|
||||
|
||||
testDiag("All stopped");
|
||||
|
||||
testDiag("Validate lockSet ref counts");
|
||||
dbInitEntry(pdbbase, &ent);
|
||||
for(status = dbFirstRecordType(&ent);
|
||||
!status;
|
||||
status = dbNextRecordType(&ent))
|
||||
{
|
||||
for(status = dbFirstRecord(&ent);
|
||||
!status;
|
||||
status = dbNextRecord(&ent))
|
||||
{
|
||||
dbCommon *prec = ent.precnode->precord;
|
||||
lockSet *ls;
|
||||
if(ent.precnode->flags&DBRN_FLAGS_ISALIAS)
|
||||
continue;
|
||||
ls = prec->lset->plockSet;
|
||||
testOk(ellCount(&ls->lockRecordList)==ls->refcount, "%s only lockRecords hold refs. %d == %d",
|
||||
prec->name,ellCount(&ls->lockRecordList),ls->refcount);
|
||||
testOk1(ls->ownerlocker==NULL);
|
||||
}
|
||||
|
||||
}
|
||||
dbFinishEntry(&ent);
|
||||
|
||||
testDiag("Statistics");
|
||||
for(i=0; i<nworkers; i++) {
|
||||
testDiag("Worker %u", i);
|
||||
testDiag("N = %lu %lu %lu", priv[i].N[0], priv[i].N[1], priv[i].N[2]);
|
||||
testDiag("X = %g %g %g", priv[i].X[0], priv[i].X[1], priv[i].X[2]);
|
||||
testDiag("X2= %g %g %g", priv[i].X2[0], priv[i].X2[1], priv[i].X2[2]);
|
||||
|
||||
testOk1(priv[i].N[0]>0);
|
||||
testOk1(priv[i].N[1]>0);
|
||||
testOk1(priv[i].N[2]>0);
|
||||
}
|
||||
|
||||
testIocShutdownOk();
|
||||
|
||||
testdbCleanup();
|
||||
|
||||
free(priv);
|
||||
free(precords);
|
||||
|
||||
return testDone();
|
||||
}
|
||||
40
src/ioc/db/test/dbStressLock.db
Normal file
40
src/ioc/db/test/dbStressLock.db
Normal file
@@ -0,0 +1,40 @@
|
||||
record(x, "rec01") {}
|
||||
record(x, "rec02") {}
|
||||
record(x, "rec03") {}
|
||||
record(x, "rec04") {}
|
||||
record(x, "rec05") {}
|
||||
record(x, "rec06") {}
|
||||
record(x, "rec07") {}
|
||||
record(x, "rec08") {}
|
||||
record(x, "rec09") {}
|
||||
record(x, "rec10") {}
|
||||
record(x, "rec11") {}
|
||||
record(x, "rec12") {}
|
||||
record(x, "rec13") {}
|
||||
record(x, "rec14") {}
|
||||
record(x, "rec15") {}
|
||||
record(x, "rec16") {}
|
||||
record(x, "rec17") {}
|
||||
record(x, "rec18") {}
|
||||
record(x, "rec19") {}
|
||||
record(x, "rec20") {}
|
||||
record(x, "rec21") {}
|
||||
record(x, "rec22") {}
|
||||
record(x, "rec23") {}
|
||||
record(x, "rec24") {}
|
||||
record(x, "rec25") {}
|
||||
record(x, "rec26") {}
|
||||
record(x, "rec27") {}
|
||||
record(x, "rec28") {}
|
||||
record(x, "rec29") {}
|
||||
record(x, "rec30") {}
|
||||
record(x, "rec31") {}
|
||||
record(x, "rec32") {}
|
||||
record(x, "rec33") {}
|
||||
record(x, "rec34") {}
|
||||
record(x, "rec35") {}
|
||||
record(x, "rec36") {}
|
||||
record(x, "rec37") {}
|
||||
record(x, "rec38") {}
|
||||
record(x, "rec39") {}
|
||||
record(x, "rec40") {}
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "dbBase.h"
|
||||
#include "dbCommon.h"
|
||||
#include "dbStaticLib.h"
|
||||
#include "dbStaticPvt.h"
|
||||
#include "devSup.h"
|
||||
@@ -198,7 +199,7 @@ long dbAllocRecord(DBENTRY *pdbentry,const char *precordName)
|
||||
dbRecordNode *precnode = pdbentry->precnode;
|
||||
dbFldDes *pflddes;
|
||||
int i;
|
||||
char *precord;
|
||||
dbCommon *precord;
|
||||
char *pfield;
|
||||
|
||||
if(!pdbRecordType) return(S_dbLib_recordTypeNotFound);
|
||||
@@ -210,7 +211,8 @@ long dbAllocRecord(DBENTRY *pdbentry,const char *precordName)
|
||||
return(S_dbLib_noRecSup);
|
||||
}
|
||||
precnode->precord = dbCalloc(1,pdbRecordType->rec_size);
|
||||
precord = (char *)precnode->precord;
|
||||
precord = precnode->precord;
|
||||
precord->rdes = pdbRecordType;
|
||||
pflddes = pdbRecordType->papFldDes[0];
|
||||
if(!pflddes) {
|
||||
epicsPrintf("dbAllocRecord pflddes for NAME not found\n");
|
||||
@@ -220,13 +222,13 @@ long dbAllocRecord(DBENTRY *pdbentry,const char *precordName)
|
||||
epicsPrintf("dbAllocRecord: NAME(%s) too long\n",precordName);
|
||||
return(S_dbLib_nameLength);
|
||||
}
|
||||
pfield = precord + pflddes->offset;
|
||||
pfield = (char*)precord + pflddes->offset;
|
||||
strcpy(pfield,precordName);
|
||||
for(i=1; i<pdbRecordType->no_fields; i++) {
|
||||
|
||||
pflddes = pdbRecordType->papFldDes[i];
|
||||
if(!pflddes) continue;
|
||||
pfield = precord + pflddes->offset;
|
||||
pfield = (char*)precord + pflddes->offset;
|
||||
pdbentry->pfield = (void *)pfield;
|
||||
pdbentry->pflddes = pflddes;
|
||||
pdbentry->indfield = i;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#define INC_link_H
|
||||
|
||||
#include "dbDefs.h"
|
||||
#include "ellLib.h"
|
||||
#include "shareLib.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -79,6 +80,7 @@ struct dbCommon;
|
||||
struct pvlet;
|
||||
|
||||
struct pv_link {
|
||||
ELLNODE backlinknode;
|
||||
char *pvname; /* pvname link points to */
|
||||
struct dbCommon *precord; /* Address of record owning link */
|
||||
void *pvt; /* CA or DB private */
|
||||
|
||||
@@ -467,7 +467,6 @@ static void doInitRecord0(dbRecordType *pdbRecordType, dbCommon *precord,
|
||||
if (!prset) return; /* unlikely */
|
||||
|
||||
precord->rset = prset;
|
||||
precord->rdes = pdbRecordType;
|
||||
precord->mlok = epicsMutexMustCreate();
|
||||
ellInit(&precord->mlis);
|
||||
|
||||
@@ -496,7 +495,7 @@ static void doResolveLinks(dbRecordType *pdbRecordType, dbCommon *precord,
|
||||
/* For all the links in the record type... */
|
||||
for (j = 0; j < pdbRecordType->no_links; j++) {
|
||||
dbFldDes *pdbFldDes = papFldDes[link_ind[j]];
|
||||
DBLINK *plink = (DBLINK *)((char *)precord + pdbFldDes->offset);
|
||||
DBLINK *plink = (DBLINK*)((char*)precord + pdbFldDes->offset);
|
||||
|
||||
if (ellCount(&precord->rdes->devList) > 0 && pdbFldDes->isDevLink) {
|
||||
devSup *pdevSup = dbDTYPtoDevSup(pdbRecordType, precord->dtyp);
|
||||
@@ -636,12 +635,12 @@ static void doCloseLinks(dbRecordType *pdbRecordType, dbCommon *precord,
|
||||
locked = 1;
|
||||
}
|
||||
dbCaRemoveLink(plink);
|
||||
plink->type = CONSTANT;
|
||||
plink->type = PV_LINK;
|
||||
|
||||
} else if (plink->type == DB_LINK) {
|
||||
/* free link, but don't split lockset like dbDbRemoveLink() */
|
||||
free(plink->value.pv_link.pvt);
|
||||
plink->type = CONSTANT;
|
||||
plink->type = PV_LINK;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,11 +665,6 @@ static void doFreeRecord(dbRecordType *pdbRecordType, dbCommon *precord,
|
||||
void *user)
|
||||
{
|
||||
int j;
|
||||
struct rset *prset = pdbRecordType->prset;
|
||||
|
||||
if (!prset) return; /* unlikely */
|
||||
|
||||
epicsMutexDestroy(precord->mlok);
|
||||
|
||||
for (j = 0; j < pdbRecordType->no_links; j++) {
|
||||
dbFldDes *pdbFldDes =
|
||||
@@ -680,6 +674,8 @@ static void doFreeRecord(dbRecordType *pdbRecordType, dbCommon *precord,
|
||||
dbFreeLinkContents(plink);
|
||||
}
|
||||
|
||||
epicsMutexDestroy(precord->mlok);
|
||||
|
||||
// may be allocated in dbNotify.c
|
||||
free(precord->ppnr);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user