Merged lockopt branch

This commit is contained in:
Andrew Johnson
2015-08-21 22:44:11 -05:00
16 changed files with 1727 additions and 529 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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")
}

View File

@@ -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);

View File

@@ -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);

File diff suppressed because it is too large Load Diff

View File

@@ -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
View 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 */

View File

@@ -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

View File

@@ -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();
}

View File

@@ -19,3 +19,6 @@ record(x, "rece") {
record(x, "recf") {
}
record(x, "recg") {
}

View 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();
}

View 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") {}

View File

@@ -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;

View File

@@ -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 */

View File

@@ -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);
}