dbLockTest and dbStressLock

This commit is contained in:
Michael Davidsaver
2015-03-24 14:14:46 -04:00
parent af89b716f4
commit ffd188bea3
5 changed files with 698 additions and 4 deletions

View File

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

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,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 <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"
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.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; 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");
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");
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") {}