From ffd188bea36a7715df80a2add4a1e8d3c6555352 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Mar 2015 14:14:46 -0400 Subject: [PATCH] dbLockTest and dbStressLock --- src/ioc/db/test/Makefile | 8 + src/ioc/db/test/dbLockTest.c | 340 +++++++++++++++++++++++++++++++- src/ioc/db/test/dbLockTest.db | 3 + src/ioc/db/test/dbStressLock.c | 311 +++++++++++++++++++++++++++++ src/ioc/db/test/dbStressLock.db | 40 ++++ 5 files changed, 698 insertions(+), 4 deletions(-) create mode 100644 src/ioc/db/test/dbStressLock.c create mode 100644 src/ioc/db/test/dbStressLock.db diff --git a/src/ioc/db/test/Makefile b/src/ioc/db/test/Makefile index c51f1a119..afed4f7b0 100644 --- a/src/ioc/db/test/Makefile +++ b/src/ioc/db/test/Makefile @@ -10,6 +10,9 @@ TOP=../../../.. include $(TOP)/configure/CONFIG +# Allow access to private headers in db/ +USR_CPPFLAGS = -I ../.. + TESTLIBRARY = dbTestIoc dbTestIoc_SRCS += xRecord.c @@ -54,6 +57,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 diff --git a/src/ioc/db/test/dbLockTest.c b/src/ioc/db/test/dbLockTest.c index defb92f87..042917546 100644 --- a/src/ioc/db/test/dbLockTest.c +++ b/src/ioc/db/test/dbLockTest.c @@ -9,7 +9,15 @@ * Author: Michael Davidsaver */ -#include "dbLock.h" +#include + +#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(); } diff --git a/src/ioc/db/test/dbLockTest.db b/src/ioc/db/test/dbLockTest.db index 5c8ceb19c..37ff6022e 100644 --- a/src/ioc/db/test/dbLockTest.db +++ b/src/ioc/db/test/dbLockTest.db @@ -19,3 +19,6 @@ record(x, "rece") { record(x, "recf") { } + +record(x, "recg") { +} diff --git a/src/ioc/db/test/dbStressLock.c b/src/ioc/db/test/dbStressLock.c new file mode 100644 index 000000000..c4cb5b2fb --- /dev/null +++ b/src/ioc/db/test/dbStressLock.c @@ -0,0 +1,311 @@ +/*************************************************************************\ +* 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 + */ + +#include +#include +#include + +#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" + +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(nlockprec[i] = precords[nrec]; + } + + locker = dbLockerAlloc(p->prec, nlock, 0); + if(!locker) + testAbort("locker allocation fails"); + + dbScanLockMany(locker); + for(i=0; iprec[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.25) { + 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(0); + + 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; i0); + testOk1(priv[i].N[1]>0); + testOk1(priv[i].N[2]>0); + } + + testIocShutdownOk(); + + testdbCleanup(); + + free(priv); + free(precords); + + return testDone(); +} diff --git a/src/ioc/db/test/dbStressLock.db b/src/ioc/db/test/dbStressLock.db new file mode 100644 index 000000000..5aecf860a --- /dev/null +++ b/src/ioc/db/test/dbStressLock.db @@ -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") {}