diff --git a/src/ioc/as/asDbLib.c b/src/ioc/as/asDbLib.c index 4c295018c..d0322bfd1 100644 --- a/src/ioc/as/asDbLib.c +++ b/src/ioc/as/asDbLib.c @@ -152,6 +152,15 @@ int asInit(void) return(asInitCommon()); } +int asShutdown(void) { + volatile ASBASE *pbase = pasbase; + pasbase = NULL; + firstTime = TRUE; + if(pbase) + asFreeAll((ASBASE*)pbase); + return 0; +} + static void wdCallback(void *arg) { ASDBCALLBACK *pcallback = (ASDBCALLBACK *)arg; diff --git a/src/ioc/as/asDbLib.h b/src/ioc/as/asDbLib.h index 04e35611f..b0c3516f6 100644 --- a/src/ioc/as/asDbLib.h +++ b/src/ioc/as/asDbLib.h @@ -32,6 +32,7 @@ epicsShareFunc int asSetFilename(const char *acf); epicsShareFunc int asSetSubstitutions(const char *substitutions); epicsShareFunc int asInit(void); epicsShareFunc int asInitAsyn(ASDBCALLBACK *pcallback); +epicsShareFunc int asShutdown(void); epicsShareFunc int asDbGetAsl(struct dbChannel *chan); epicsShareFunc void * asDbGetMemberPvt(struct dbChannel *chan); epicsShareFunc int asdbdump(void); diff --git a/src/ioc/db/Makefile b/src/ioc/db/Makefile index c957842cb..8788207fe 100644 --- a/src/ioc/db/Makefile +++ b/src/ioc/db/Makefile @@ -37,6 +37,7 @@ INC += chfPlugin.h INC += dbState.h INC += db_access_routines.h INC += db_convert.h +INC += dbUnitTest.h # Generate menuGlobal.dbd automatically DBD += menuGlobal.dbd @@ -86,4 +87,4 @@ dbCore_SRCS += templateInstances.cpp dbCore_SRCS += dbIocRegister.c dbCore_SRCS += chfPlugin.c dbCore_SRCS += dbState.c - +dbCore_SRCS += dbUnitTest.c diff --git a/src/ioc/db/callback.c b/src/ioc/db/callback.c index 4e364b181..bd20304a3 100644 --- a/src/ioc/db/callback.c +++ b/src/ioc/db/callback.c @@ -43,7 +43,6 @@ #include "callback.h" -static epicsThreadOnceId callbackOnceFlag = EPICS_THREAD_ONCE_INIT; static int callbackQueueSize = 2000; static epicsEventId callbackSem[NUM_CALLBACK_PRIORITIES]; static epicsRingPointerId callbackQ[NUM_CALLBACK_PRIORITIES]; @@ -53,6 +52,8 @@ static volatile int ringOverflow[NUM_CALLBACK_PRIORITIES]; static epicsTimerQueueId timerQueue; /* Shutdown handling */ +enum ctl {ctlInit, ctlRun, ctlPause, ctlExit}; +static volatile enum ctl cbCtl; static epicsEventId startStopEvent; static void *exitCallback; @@ -70,7 +71,7 @@ static int priorityValue[NUM_CALLBACK_PRIORITIES] = {0, 1, 2}; int callbackSetQueueSize(int size) { - if (callbackOnceFlag != EPICS_THREAD_ONCE_INIT) { + if (startStopEvent) { errlogPrintf("Callback system already initialized\n"); return -1; } @@ -101,24 +102,36 @@ shutdown: epicsEventSignal(startStopEvent); } -static void callbackShutdown(void *arg) +void callbackShutdown(void) { int i; + if (cbCtl == ctlExit) return; + cbCtl = ctlExit; + for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) { int lockKey = epicsInterruptLock(); int ok = epicsRingPointerPush(callbackQ[i], &exitCallback); epicsInterruptUnlock(lockKey); epicsEventSignal(callbackSem[i]); if (ok) epicsEventWait(startStopEvent); + epicsEventDestroy(callbackSem[i]); + epicsRingPointerDelete(callbackQ[i]); } + epicsTimerQueueRelease(timerQueue); + epicsEventDestroy(startStopEvent); + startStopEvent = NULL; } -static void callbackInitOnce(void *arg) +void callbackInit(void) { int i; + if(startStopEvent) + return; + startStopEvent = epicsEventMustCreate(epicsEventEmpty); + cbCtl = ctlRun; timerQueue = epicsTimerQueueAllocate(0,epicsThreadPriorityScanHigh); for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) { epicsThreadId tid; @@ -137,12 +150,6 @@ static void callbackInitOnce(void *arg) else epicsEventWait(startStopEvent); } - epicsAtExit(callbackShutdown, NULL); -} - -void callbackInit(void) -{ - epicsThreadOnce(&callbackOnceFlag, callbackInitOnce, NULL); } /* This routine can be called from interrupt context */ diff --git a/src/ioc/db/callback.h b/src/ioc/db/callback.h index 1b539c13e..6ce0b1adb 100644 --- a/src/ioc/db/callback.h +++ b/src/ioc/db/callback.h @@ -57,6 +57,7 @@ typedef void (*CALLBACKFUNC)(struct callbackPvt*); epicsShareFunc void callbackInit(void); epicsShareFunc void callbackRequest(CALLBACK *pCallback); +epicsShareFunc void callbackShutdown(void); epicsShareFunc void callbackSetProcess( CALLBACK *pcallback, int Priority, void *pRec); epicsShareFunc void callbackRequestProcessCallback( diff --git a/src/ioc/db/dbBkpt.c b/src/ioc/db/dbBkpt.c index dba5136b9..86a198ace 100644 --- a/src/ioc/db/dbBkpt.c +++ b/src/ioc/db/dbBkpt.c @@ -61,6 +61,7 @@ #include "errMdef.h" #include "recSup.h" #include "special.h" +#include "epicsExit.h" #define epicsExportSharedSymbols #include "dbAddr.h" #include "dbAccessDefs.h" @@ -250,7 +251,11 @@ static long FIND_CONT_NODE( return(0); } - +static void dbBkptExit(void *junk) { + epicsMutexDestroy(bkpt_stack_sem); + bkpt_stack_sem = NULL; +} + /* * Initialise the breakpoint stack */ @@ -259,6 +264,7 @@ void dbBkptInit(void) if (! bkpt_stack_sem) { bkpt_stack_sem = epicsMutexMustCreate(); lset_stack_count = 0; + epicsAtExit(dbBkptExit, NULL); } } diff --git a/src/ioc/db/dbCa.c b/src/ioc/db/dbCa.c index e329b4323..c64f5713f 100644 --- a/src/ioc/db/dbCa.c +++ b/src/ioc/db/dbCa.c @@ -173,15 +173,22 @@ void dbCaCallbackProcess(void *usrPvt) dbScanUnlock(pdbCommon); } -static void dbCaShutdown(void *arg) +void dbCaShutdown(void) { - if (dbCaCtl == ctlRun) { + if (dbCaCtl == ctlRun || dbCaCtl == ctlPause) { dbCaCtl = ctlExit; epicsEventSignal(workListEvent); epicsEventMustWait(startStopEvent); + epicsEventDestroy(startStopEvent); + epicsEventDestroy(workListEvent); } } +static void dbCaExit(void *arg) +{ + dbCaShutdown(); +} + void dbCaLinkInit(void) { dbServiceIOInit(); @@ -194,19 +201,23 @@ void dbCaLinkInit(void) epicsThreadGetStackSize(epicsThreadStackBig), dbCaTask, NULL); epicsEventMustWait(startStopEvent); - epicsAtExit(dbCaShutdown, NULL); + epicsAtExit(dbCaExit, NULL); } void dbCaRun(void) { - dbCaCtl = ctlRun; - epicsEventSignal(workListEvent); + if (dbCaCtl == ctlPause) { + dbCaCtl = ctlRun; + epicsEventSignal(workListEvent); + } } void dbCaPause(void) { - dbCaCtl = ctlPause; - epicsEventSignal(workListEvent); + if (dbCaCtl == ctlRun) { + dbCaCtl = ctlPause; + epicsEventSignal(workListEvent); + } } void dbCaAddLinkCallback(struct link *plink, diff --git a/src/ioc/db/dbCa.h b/src/ioc/db/dbCa.h index bf6f2edd9..a147307bf 100644 --- a/src/ioc/db/dbCa.h +++ b/src/ioc/db/dbCa.h @@ -26,6 +26,7 @@ epicsShareFunc void dbCaCallbackProcess(void *usrPvt); epicsShareFunc void dbCaLinkInit(void); epicsShareFunc void dbCaRun(void); epicsShareFunc void dbCaPause(void); +epicsShareFunc void dbCaShutdown(void); epicsShareFunc void dbCaAddLinkCallback(struct link *plink, dbCaCallback connect, dbCaCallback monitor, void *userPvt); diff --git a/src/ioc/db/dbChannel.c b/src/ioc/db/dbChannel.c index d555c5f2e..86f23627b 100644 --- a/src/ioc/db/dbChannel.c +++ b/src/ioc/db/dbChannel.c @@ -20,6 +20,7 @@ #include "cantProceed.h" #include "epicsAssert.h" #include "epicsString.h" +#include "epicsExit.h" #include "errlog.h" #include "freeList.h" #include "gpHash.h" @@ -52,16 +53,23 @@ static void *dbChannelFreeList; static void *chFilterFreeList; static void *dbchStringFreeList; +static void dbChannelExit(void* junk) +{ + freeListCleanup(dbChannelFreeList); + freeListCleanup(chFilterFreeList); + freeListCleanup(dbchStringFreeList); + dbChannelFreeList = chFilterFreeList = dbchStringFreeList = NULL; +} + void dbChannelInit (void) { - static int done = 0; + if(dbChannelFreeList) + return; - if (!done) { - done = 1; - freeListInitPvt(&dbChannelFreeList, sizeof(dbChannel), 128); - freeListInitPvt(&chFilterFreeList, sizeof(chFilter), 64); - freeListInitPvt(&dbchStringFreeList, sizeof(epicsOldString), 128); - } + freeListInitPvt(&dbChannelFreeList, sizeof(dbChannel), 128); + freeListInitPvt(&chFilterFreeList, sizeof(chFilter), 64); + freeListInitPvt(&dbchStringFreeList, sizeof(epicsOldString), 128); + epicsAtExit(dbChannelExit, NULL); } static void chf_value(parseContext *parser, parse_result *presult) diff --git a/src/ioc/db/dbLock.c b/src/ioc/db/dbLock.c index eb2e113f7..5774f7ae1 100644 --- a/src/ioc/db/dbLock.c +++ b/src/ioc/db/dbLock.c @@ -54,6 +54,7 @@ since this will delay all other threads. #include "epicsMutex.h" #include "epicsThread.h" #include "epicsAssert.h" +#include "epicsExit.h" #include "cantProceed.h" #include "ellLib.h" #define epicsExportSharedSymbols @@ -107,7 +108,16 @@ typedef struct lockRecord { lockSet *plockSet; dbCommon *precord; } lockRecord; - + +static void dbLockExit(void *junk) +{ + epicsMutexDestroy(globalLock); + epicsMutexDestroy(lockSetModifyLock); + globalLock = NULL; + lockSetModifyLock = NULL; + dbLockIsInitialized = FALSE; +} + /*private routines */ static void dbLockInitialize(void) { @@ -118,6 +128,7 @@ static void dbLockInitialize(void) globalLock = epicsMutexMustCreate(); lockSetModifyLock = epicsMutexMustCreate(); dbLockIsInitialized = TRUE; + epicsAtExit(dbLockExit,NULL); } static lockSet * allocLockSet( @@ -315,7 +326,9 @@ void dbScanUnlock(dbCommon *precord) epicsMutexUnlock(lockSetModifyLock); return; } - + +static lockRecord *lockRecordAlloc; + void dbLockInitRecords(dbBase *pdbbase) { int link; @@ -336,7 +349,7 @@ void dbLockInitRecords(dbBase *pdbbase) - pdbRecordType->no_aliases; } /*Allocate all of them at once */ - plockRecord = dbCalloc(nrecords,sizeof(lockRecord)); + lockRecordAlloc = plockRecord = dbCalloc(nrecords,sizeof(lockRecord)); for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { @@ -377,7 +390,34 @@ void dbLockInitRecords(dbBase *pdbbase) } } } - + +void dbLockCleanupRecords(dbBase *pdbbase) +{ + ELLNODE *cur; + + free(lockRecordAlloc); + lockRecordAlloc = NULL; + + /* free lockSets */ + /* ensure no lockSets are locked for re-compute */ + assert(ellCount(&lockSetList[listTypeRecordLock])==0); + /* move allocated locks back to the free list */ + while((cur=ellGet(&lockSetList[listTypeScanLock]))!=NULL) + { + lockSet *pset = CONTAINER(cur, lockSet, node); + assert(pset->state == lockSetStateFree); /* lock not held */ + pset->type = listTypeFree; + ellAdd(&lockSetList[listTypeFree],&pset->node); + } + /* clean up free list */ + while((cur=ellGet(&lockSetList[listTypeFree]))!=NULL) + { + lockSet *pset = CONTAINER(cur, lockSet, node); + epicsMutexDestroy(pset->lock); + free(pset); + } +} + void dbLockSetMerge(dbCommon *pfirst,dbCommon *psecond) { lockRecord *p1lockRecord = pfirst->lset; diff --git a/src/ioc/db/dbLock.h b/src/ioc/db/dbLock.h index 062996601..e13ce38b1 100644 --- a/src/ioc/db/dbLock.h +++ b/src/ioc/db/dbLock.h @@ -28,6 +28,7 @@ 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); diff --git a/src/ioc/db/dbNotify.c b/src/ioc/db/dbNotify.c index 020f6d7c7..66253736c 100644 --- a/src/ioc/db/dbNotify.c +++ b/src/ioc/db/dbNotify.c @@ -44,6 +44,7 @@ #include "recGbl.h" #include "dbNotify.h" #include "epicsTime.h" +#include "epicsExit.h" #include "cantProceed.h" /*notify state values */ @@ -298,6 +299,14 @@ static void notifyCallback(CALLBACK *pcallback) callDone(precord, ppn); } +static void dbProcessNotifyExit(void* junk) +{ + assert(ellCount(&pnotifyGlobal->freeList)==0); + epicsMutexDestroy(pnotifyGlobal->lock); + free(pnotifyGlobal); + pnotifyGlobal = NULL; +} + void dbProcessNotifyInit(void) { if (pnotifyGlobal) @@ -305,6 +314,7 @@ void dbProcessNotifyInit(void) pnotifyGlobal = dbCalloc(1,sizeof(notifyGlobal)); pnotifyGlobal->lock = epicsMutexMustCreate(); ellInit(&pnotifyGlobal->freeList); + epicsAtExit(dbProcessNotifyExit, NULL); } void dbProcessNotify(processNotify *ppn) diff --git a/src/ioc/db/dbScan.c b/src/ioc/db/dbScan.c index 10dc9c146..a1ba12d9a 100644 --- a/src/ioc/db/dbScan.c +++ b/src/ioc/db/dbScan.c @@ -3,6 +3,8 @@ * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. +* Copyright (c) 2013 Helmholtz-Zentrum Berlin +* für Materialien und Energie GmbH. * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ @@ -132,6 +134,7 @@ static void onceTask(void *); static void initOnce(void); static void periodicTask(void *arg); static void initPeriodic(void); +static void deletePeriodic(void); static void spawnPeriodic(int ind); static void initEvent(void); static void eventCallback(CALLBACK *pcallback); @@ -142,10 +145,13 @@ static void buildScanLists(void); static void addToList(struct dbCommon *precord, scan_list *psl); static void deleteFromList(struct dbCommon *precord, scan_list *psl); -static void scanShutdown(void *arg) +void scanShutdown(void) { int i; + if (scanCtl == ctlExit) return; + scanCtl = ctlExit; + interruptAccept = FALSE; for (i = 0; i < nPeriodic; i++) { @@ -156,6 +162,18 @@ static void scanShutdown(void *arg) scanOnce((dbCommon *)&exitOnce); epicsEventWait(startStopEvent); + + deletePeriodic(); + + epicsRingPointerDelete(onceQ); + + epicsEventDestroy(startStopEvent); + epicsEventDestroy(onceSem); + onceSem = startStopEvent = NULL; + + free(periodicTaskId); + papPeriodic = NULL; + periodicTaskId = NULL; } long scanInit(void) @@ -172,7 +190,6 @@ long scanInit(void) for (i = 0; i < nPeriodic; i++) spawnPeriodic(i); - epicsAtExit(scanShutdown, NULL); return 0; } @@ -696,6 +713,22 @@ static void initPeriodic(void) } } +static void deletePeriodic(void) +{ + int i; + + for (i = 0; i < nPeriodic; i++) { + periodic_scan_list *ppsl = papPeriodic[i]; + ellFree(&ppsl->scan_list.list); + epicsEventDestroy(ppsl->loopEvent); + epicsMutexDestroy(ppsl->scan_list.lock); + free(ppsl); + } + + free(papPeriodic); + papPeriodic = NULL; +} + static void spawnPeriodic(int ind) { periodic_scan_list *ppsl; @@ -802,23 +835,25 @@ static void buildScanLists(void) { dbRecordType *pdbRecordType; - /*Look for first record*/ for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { dbRecordNode *pdbRecordNode; + for (pdbRecordNode = (dbRecordNode *)ellFirst(&pdbRecordType->recList); pdbRecordNode; pdbRecordNode = (dbRecordNode *)ellNext(&pdbRecordNode->node)) { dbCommon *precord = pdbRecordNode->precord; + if (!precord->name[0] || pdbRecordNode->flags & DBRN_FLAGS_ISALIAS) continue; + scanAdd(precord); } } } - + static void addToList(struct dbCommon *precord, scan_list *psl) { scan_element *pse, *ptemp; diff --git a/src/ioc/db/dbScan.h b/src/ioc/db/dbScan.h index eca2c889d..03117f769 100644 --- a/src/ioc/db/dbScan.h +++ b/src/ioc/db/dbScan.h @@ -44,6 +44,7 @@ struct dbCommon; epicsShareFunc long scanInit(void); epicsShareFunc void scanRun(void); epicsShareFunc void scanPause(void); +epicsShareFunc void scanShutdown(void); epicsShareFunc EVENTPVT eventNameToHandle(const char* event); epicsShareFunc void postEvent(EVENTPVT epvt); diff --git a/src/ioc/db/dbUnitTest.c b/src/ioc/db/dbUnitTest.c new file mode 100644 index 000000000..634993e20 --- /dev/null +++ b/src/ioc/db/dbUnitTest.c @@ -0,0 +1,201 @@ +/*************************************************************************\ +* Copyright (c) 2013 Brookhaven National Laboratory. +* Copyright (c) 2013 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + * Ralph Lange + */ + +#include + +#include "epicsUnitTest.h" +#include "osiFileName.h" +#include "dbmf.h" +#include "registry.h" +#define epicsExportSharedSymbols +#include "iocInit.h" +#include "initHooks.h" +#include "dbBase.h" +#include "dbAccess.h" +#include "dbStaticLib.h" + +#include "dbUnitTest.h" + +void testdbPrepare(void) +{ + /* No-op at the moment */ +} + +void testdbReadDatabase(const char* file, + const char* path, + const char* substitutions) +{ + if(!path) + path = "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR + "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common"; + if(dbReadDatabase(&pdbbase, file, path, substitutions)) + testAbort("Failed to load test database\ndbReadDatabase(%s,%s,%s)", + file, path, substitutions); +} + +void testIocInitOk(void) +{ + if(iocBuildIsolated() || iocRun()) + testAbort("Failed to start up test database"); +} + +void testIocShutdownOk(void) +{ + if(iocShutdown()) + testAbort("Failed to shutdown test database"); +} + +void testdbCleanup(void) +{ + dbFreeBase(pdbbase); + initHookFree(); + registryFree(); + pdbbase = NULL; + dbmfFreeChunks(); +} + +union anybuf { + epicsAny val; + char valStr[MAX_STRING_SIZE]; + char bytes[sizeof(epicsAny)]; +}; + +long testdbVPutField(const char* pv, short dbrType, va_list ap) +{ + DBADDR addr; + union anybuf pod; + + if(dbNameToAddr(pv, &addr)) { + testFail("Missing PV %s", pv); + return S_dbLib_recNotFound; + } + + switch(dbrType) { + case DBR_STRING: { + const char *uarg = va_arg(ap,char*); + strncpy(pod.valStr, uarg, sizeof(pod.valStr)); + pod.valStr[sizeof(pod.valStr)-1] = '\0'; + return dbPutField(&addr, dbrType, pod.valStr, 1); + } + + /* The Type parameter takes into consideration + * the C language rules for promotion of argument types + * in variadic functions. + */ +#define OP(DBR,Type,mem) case DBR: {pod.val.mem = va_arg(ap,Type); break;} + OP(DBR_CHAR, int, int8); + OP(DBR_UCHAR, int, uInt8); + OP(DBR_SHORT, int, int16); + OP(DBR_USHORT, int, uInt16); + OP(DBR_LONG, int, int32); + OP(DBR_ULONG, unsigned int, uInt32); + OP(DBR_FLOAT, double, float32); + OP(DBR_DOUBLE, double, float64); + OP(DBR_ENUM, int, enum16); +#undef OP + default: + testFail("invalid DBR: dbPutField(\"%s\", %d, ...)", + addr.precord->name, dbrType); + return S_db_badDbrtype; + } + + return dbPutField(&addr, dbrType, pod.bytes, 1); +} + +void testdbPutFieldOk(const char* pv, short dbrType, ...) +{ + long ret; + va_list ap; + + va_start(ap, dbrType); + ret = testdbVPutField(pv, dbrType, ap); + va_end(ap); + + testOk(ret==0, "dbPutField(%s, %d, ...) == %ld", pv, dbrType, ret); +} + +void testdbPutFieldFail(long status, const char* pv, short dbrType, ...) +{ + long ret; + va_list ap; + + va_start(ap, dbrType); + ret = testdbVPutField(pv, dbrType, ap); + va_end(ap); + + if(ret==status) + testPass("dbPutField(\"%s\", %d, ...) == %ld", pv, dbrType, status); + else + testFail("dbPutField(\"%s\", %d, ...) != %ld (%ld)", pv, dbrType, status, ret); +} + +void testdbGetFieldEqual(const char* pv, short dbrType, ...) +{ + va_list ap; + + va_start(ap, dbrType); + testdbVGetFieldEqual(pv, dbrType, ap); + va_end(ap); +} + +void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap) +{ + DBADDR addr; + long nReq = 1; + union anybuf pod; + long status; + + if(dbNameToAddr(pv, &addr)) { + testFail("Missing PV %s", pv); + return; + } + + status = dbGetField(&addr, dbrType, pod.bytes, NULL, &nReq, NULL); + if(status) { + testFail("dbGetField(\"%s\",%d,...) returns %ld", pv, dbrType, status); + return; + } + + switch(dbrType) { + case DBR_STRING: { + const char *expect = va_arg(ap, char*); + testOk(strcmp(expect, pod.valStr)==0, + "dbGetField(\"%s\", %d) -> \"%s\" == \"%s\"", + pv, dbrType, expect, pod.valStr); + break; + } +#define OP(DBR,Type,mem,pat) case DBR: {Type expect = va_arg(ap,Type); \ + testOk(expect==pod.val.mem, "dbGetField(\"%s\", %d) -> " pat " == " pat, \ + pv, dbrType, expect, (Type)pod.val.mem); break;} + + OP(DBR_CHAR, int, int8, "%d"); + OP(DBR_UCHAR, int, uInt8, "%d"); + OP(DBR_SHORT, int, int16, "%d"); + OP(DBR_USHORT, int, uInt16, "%d"); + OP(DBR_LONG, int, int32, "%d"); + OP(DBR_ULONG, unsigned int, uInt32, "%u"); + OP(DBR_FLOAT, double, float32, "%e"); + OP(DBR_DOUBLE, double, float64, "%e"); + OP(DBR_ENUM, int, enum16, "%d"); +#undef OP + } +} + +dbCommon* testdbRecordPtr(const char* pv) +{ + DBADDR addr; + + if(dbNameToAddr(pv, &addr)) + testAbort("Missing record %s", pv); + + return addr.precord; +} diff --git a/src/ioc/db/dbUnitTest.h b/src/ioc/db/dbUnitTest.h new file mode 100644 index 000000000..80867dc9d --- /dev/null +++ b/src/ioc/db/dbUnitTest.h @@ -0,0 +1,64 @@ +/*************************************************************************\ +* Copyright (c) 2013 Brookhaven National Laboratory. +* Copyright (c) 2013 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + * Ralph Lange + */ + +#ifndef EPICSUNITTESTDB_H +#define EPICSUNITTESTDB_H + +#include + +#include "epicsUnitTest.h" +#include "dbAddr.h" +#include "dbCommon.h" + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void testdbPrepare(void); +epicsShareFunc void testdbReadDatabase(const char* file, + const char* path, + const char* substitutions); +epicsShareFunc void testIocInitOk(void); +epicsShareFunc void testIocShutdownOk(void); +epicsShareFunc void testdbCleanup(void); + +/* Correct argument types must be used with this var-arg function! + * Doing otherwise will result in corruption of argument values! + * + * int for DBR_UCHAR, DBR_CHAR, DBR_USHORT, DBR_SHORT, DBR_LONG + * unsigned int for DBR_ULONG + * double for DBR_FLOAT and DBR_DOUBLE + * const char* for DBR_STRING + * + * eg. + * testdbPutFieldOk("pvname", DBF_ULONG, (unsigned int)5); + * testdbPutFieldOk("pvname", DBF_FLOAT, (double)4.1); + * testdbPutFieldOk("pvname", DBF_STRING, "hello world"); + */ +epicsShareFunc void testdbPutFieldOk(const char* pv, short dbrType, ...); +/* Tests for put failure */ +epicsShareFunc void testdbPutFieldFail(long status, const char* pv, short dbrType, ...); + +epicsShareFunc long testdbVPutField(const char* pv, short dbrType, va_list ap); + +epicsShareFunc void testdbGetFieldEqual(const char* pv, short dbrType, ...); +epicsShareFunc void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap); + +epicsShareFunc dbCommon* testdbRecordPtr(const char* pv); + +#ifdef __cplusplus +} +#endif + +#endif // EPICSUNITTESTDB_H diff --git a/src/ioc/db/initHooks.c b/src/ioc/db/initHooks.c index 5dc7f88e7..a6803f56b 100644 --- a/src/ioc/db/initHooks.c +++ b/src/ioc/db/initHooks.c @@ -93,6 +93,14 @@ void initHookAnnounce(initHookState state) } } +void initHookFree(void) +{ + initHookInit(); + epicsMutexMustLock(listLock); + ellFree(&functionList); + epicsMutexUnlock(listLock); +} + /* * Call any time you want to print out a state name. */ diff --git a/src/ioc/db/initHooks.h b/src/ioc/db/initHooks.h index f8606e314..c4c84b7b9 100644 --- a/src/ioc/db/initHooks.h +++ b/src/ioc/db/initHooks.h @@ -60,6 +60,7 @@ typedef void (*initHookFunction)(initHookState state); epicsShareFunc int initHookRegister(initHookFunction func); epicsShareFunc void initHookAnnounce(initHookState state); epicsShareFunc const char *initHookName(int state); +epicsShareFunc void initHookFree(void); #ifdef __cplusplus } diff --git a/src/ioc/db/test/Makefile b/src/ioc/db/test/Makefile index 9d5aa6e37..373207fcf 100644 --- a/src/ioc/db/test/Makefile +++ b/src/ioc/db/test/Makefile @@ -10,11 +10,31 @@ TOP=../../../.. include $(TOP)/configure/CONFIG -TESTLIBRARY = xRec +TESTLIBRARY = dbTestIoc -xRec_SRCS = xRecord.c +dbTestIoc_SRCS = xRecord.c -PROD_LIBS = xRec dbCore ca Com +TARGETS += $(COMMON_DIR)/dbTestIoc.dbd +dbTestIoc_DBD += menuGlobal.dbd +dbTestIoc_DBD += menuConvert.dbd +dbTestIoc_DBD += xRecord.dbd +TESTFILES += $(COMMON_DIR)/dbTestIoc.dbd ../xRecord.db + +testHarness_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp + +PROD_LIBS = dbTestIoc dbCore ca Com + +TESTPROD_HOST += dbShutdownTest +dbShutdownTest_SRCS += dbShutdownTest.c +dbShutdownTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbShutdownTest.c +TESTS += dbShutdownTest + +TESTPROD_HOST += dbPutLinkTest +dbPutLinkTest_SRCS += dbPutLinkTest.c +dbPutLinkTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbPutLinkTest.c +TESTS += dbPutLinkTest TESTPROD_HOST += testdbConvert testdbConvert_SRCS += testdbConvert.c @@ -31,34 +51,22 @@ dbStateTest_SRCS += dbStateTest.c testHarness_SRCS += dbStateTest.c TESTS += dbStateTest -TARGETS += $(COMMON_DIR)/dbChannelTest.dbd -dbChannelTest_DBD += xRecord.dbd TESTPROD_HOST += dbChannelTest dbChannelTest_SRCS += dbChannelTest.c -dbChannelTest_SRCS += dbChannelTest_registerRecordDeviceDriver.cpp +dbChannelTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp testHarness_SRCS += dbChannelTest.c -testHarness_SRCS += dbChannelTest_registerRecordDeviceDriver.cpp -TESTFILES += $(COMMON_DIR)/dbChannelTest.dbd ../xRecord.db TESTS += dbChannelTest -TARGETS += $(COMMON_DIR)/chfPluginTest.dbd -chfPluginTest_DBD += xRecord.dbd TESTPROD_HOST += chfPluginTest chfPluginTest_SRCS += chfPluginTest.c -chfPluginTest_SRCS += chfPluginTest_registerRecordDeviceDriver.cpp +chfPluginTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp testHarness_SRCS += chfPluginTest.c -testHarness_SRCS += chfPluginTest_registerRecordDeviceDriver.cpp -TESTFILES += $(COMMON_DIR)/chfPluginTest.dbd TESTS += chfPluginTest -TARGETS += $(COMMON_DIR)/arrShorthandTest.dbd -arrShorthandTest_DBD += xRecord.dbd TESTPROD_HOST += arrShorthandTest arrShorthandTest_SRCS += arrShorthandTest.c -arrShorthandTest_SRCS += arrShorthandTest_registerRecordDeviceDriver.cpp +arrShorthandTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp testHarness_SRCS += arrShorthandTest.c -testHarness_SRCS += arrShorthandTest_registerRecordDeviceDriver.cpp -TESTFILES += $(COMMON_DIR)/arrShorthandTest.dbd TESTS += arrShorthandTest TESTPROD_HOST += benchdbConvert diff --git a/src/ioc/db/test/arrShorthandTest.c b/src/ioc/db/test/arrShorthandTest.c index dafa9e6e8..afb37e577 100644 --- a/src/ioc/db/test/arrShorthandTest.c +++ b/src/ioc/db/test/arrShorthandTest.c @@ -77,7 +77,7 @@ static void testHead (char* title) { testDiag("--------------------------------------------------------"); } -void arrShorthandTest_registerRecordDeviceDriver(struct dbBase *); +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); MAIN(arrShorthandTest) { @@ -88,12 +88,12 @@ MAIN(arrShorthandTest) db_init_events(); dbChannelInit(); - if (dbReadDatabase(&pdbbase, "arrShorthandTest.dbd", + if (dbReadDatabase(&pdbbase, "dbTestIoc.dbd", "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL)) - testAbort("Database description 'arrShorthandTest.dbd' not found"); + testAbort("Database description 'dbTestIoc.dbd' not found"); - arrShorthandTest_registerRecordDeviceDriver(pdbbase); + dbTestIoc_registerRecordDeviceDriver(pdbbase); if (dbReadDatabase(&pdbbase, "xRecord.db", "." OSI_PATH_LIST_SEPARATOR "..", NULL)) testAbort("Test database 'xRecord.db' not found"); diff --git a/src/ioc/db/test/chfPluginTest.c b/src/ioc/db/test/chfPluginTest.c index de34b989a..028ecceb2 100644 --- a/src/ioc/db/test/chfPluginTest.c +++ b/src/ioc/db/test/chfPluginTest.c @@ -481,7 +481,7 @@ static void testHead (char* title) { testDiag("--------------------------------------------------------"); } -void chfPluginTest_registerRecordDeviceDriver(struct dbBase *); +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); MAIN(chfPluginTest) { @@ -508,12 +508,12 @@ MAIN(chfPluginTest) testOk(strcmp(chfPluginEnumString(colorEnum, 3, "-"), "-") == 0, "Enum to string: invalid index"); - if (dbReadDatabase(&pdbbase, "chfPluginTest.dbd", + if (dbReadDatabase(&pdbbase, "dbTestIoc.dbd", "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL)) - testAbort("Database description 'chfPluginTest.dbd' not found"); + testAbort("Database description 'dbTestIoc.dbd' not found"); - chfPluginTest_registerRecordDeviceDriver(pdbbase); + dbTestIoc_registerRecordDeviceDriver(pdbbase); if (dbReadDatabase(&pdbbase, "xRecord.db", "." OSI_PATH_LIST_SEPARATOR "..", NULL)) testAbort("Test database 'xRecord.db' not found"); diff --git a/src/ioc/db/test/dbChannelTest.c b/src/ioc/db/test/dbChannelTest.c index a4011f646..75d3185aa 100644 --- a/src/ioc/db/test/dbChannelTest.c +++ b/src/ioc/db/test/dbChannelTest.c @@ -21,6 +21,7 @@ #include "epicsUnitTest.h" #include "testMain.h" #include "osiFileName.h" +#include "errlog.h" /* Expected call bit definitions */ #define e_start 0x00000001 @@ -149,7 +150,7 @@ chFilterIf testIf = p_string, p_start_map, p_map_key, p_end_map, p_start_array, p_end_array, c_open, c_reg_pre, c_reg_post, c_report, c_close }; -void dbChannelTest_registerRecordDeviceDriver(struct dbBase *); +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); MAIN(testDbChannel) /* dbChannelTest is an API routine... */ { @@ -157,12 +158,12 @@ MAIN(testDbChannel) /* dbChannelTest is an API routine... */ testPlan(66); - if (dbReadDatabase(&pdbbase, "dbChannelTest.dbd", + if (dbReadDatabase(&pdbbase, "dbTestIoc.dbd", "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL)) - testAbort("Database description 'dbChannelTest.dbd' not found"); + testAbort("Database description 'dbTestIoc.dbd' not found"); - dbChannelTest_registerRecordDeviceDriver(pdbbase); + dbTestIoc_registerRecordDeviceDriver(pdbbase); if (dbReadDatabase(&pdbbase, "xRecord.db", "." OSI_PATH_LIST_SEPARATOR "..", NULL)) testAbort("Test database 'xRecord.db' not found"); @@ -199,7 +200,9 @@ MAIN(testDbChannel) /* dbChannelTest is an API routine... */ testOk(!dbChannelCreate("y"), "Create, bad record"); testOk(!dbChannelCreate("x.NOFIELD"), "Create, bad field"); testOk(!dbChannelCreate("x.{not-json}"), "Create, bad JSON"); + eltc(0); testOk(!dbChannelCreate("x.{\"none\":null}"), "Create, bad filter"); + eltc(1); dbRegisterFilter("any", &testIf, NULL); diff --git a/src/ioc/db/test/dbPutLinkTest.c b/src/ioc/db/test/dbPutLinkTest.c new file mode 100644 index 000000000..a5a4e8084 --- /dev/null +++ b/src/ioc/db/test/dbPutLinkTest.c @@ -0,0 +1,110 @@ +/*************************************************************************\ +* 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. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + */ + +#include "string.h" + +#include "epicsString.h" +#include "dbUnitTest.h" +#include "epicsThread.h" +#include "iocInit.h" +#include "dbBase.h" +#include "link.h" +#include "dbAccess.h" +#include "registry.h" +#include "dbStaticLib.h" +#include "osiFileName.h" +#include "dbmf.h" +#include "errlog.h" + +#include "xRecord.h" + +#include "testMain.h" + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static const struct testDataT { + const char *linkstring; + short linkType; + unsigned int pvlMask; + const char *linkback; +} testSetData[] = { + {"", CONSTANT, 0}, + {"0", CONSTANT, 0}, + {"42", CONSTANT, 0}, + {"x1", DB_LINK, 0, "x1 NPP NMS"}, + {"x1.VAL", DB_LINK, 0, "x1.VAL NPP NMS"}, + {"x1.TIME", DB_LINK, 0, "x1.TIME NPP NMS"}, + {"x1 PP", DB_LINK, pvlOptPP, "x1 PP NMS"}, + {"x1 PP MSS", DB_LINK, pvlOptPP|pvlOptMSS, "x1 PP MSS"}, + {"x1 PPMSS", DB_LINK, pvlOptPP|pvlOptMSS, "x1 PP MSS"}, + {"x1 PPMSI", DB_LINK, pvlOptPP|pvlOptMSI, "x1 PP MSI"}, + /*TODO: testing doesn't support CA_LINK yet */ + {NULL} +}; + +static void testSet(void) +{ + const struct testDataT *td = testSetData; + xRecord *prec; + DBLINK *plink; + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + prec = (xRecord*)testdbRecordPtr("x1"); + plink = &prec->lnk; + + for(;td->linkstring;td++) { + + testdbPutFieldOk("x1.LNK", DBF_STRING, td->linkstring); + if(td->linkback) + testdbGetFieldEqual("x1.LNK", DBF_STRING, td->linkback); + else + testdbGetFieldEqual("x1.LNK", DBF_STRING, td->linkstring); + testOk1(plink->type==td->linkType); + + if(plink->type==td->linkType) { + switch(td->linkType) { + case CONSTANT: + if(plink->value.constantStr) + testOk1(strcmp(plink->value.constantStr,td->linkstring)==0); + else if(td->linkstring[0]=='\0') + testPass("Empty String"); + else + testFail("oops"); + break; + + case DB_LINK: + testOk1(plink->value.pv_link.pvlMask==td->pvlMask); + break; + } + } + } + + testIocShutdownOk(); + + testdbCleanup(); +} + +MAIN(dbPutLinkTest) +{ + testPlan(40); + testSet(); + return testDone(); +} diff --git a/src/ioc/db/test/dbPutLinkTest.db b/src/ioc/db/test/dbPutLinkTest.db new file mode 100644 index 000000000..940ea2e7f --- /dev/null +++ b/src/ioc/db/test/dbPutLinkTest.db @@ -0,0 +1,4 @@ +record(x, "x1") {} +record(x, "x2") {} +record(x, "x3") {} +record(x, "x4") {} diff --git a/src/ioc/db/test/dbShutdownTest.c b/src/ioc/db/test/dbShutdownTest.c new file mode 100644 index 000000000..55a8f3e0c --- /dev/null +++ b/src/ioc/db/test/dbShutdownTest.c @@ -0,0 +1,96 @@ +/*************************************************************************\ +* Copyright (c) 2013 Brookhaven National Laboratory. +* Copyright (c) 2013 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + * Ralph Lange + */ + +#include "epicsString.h" +#include "dbUnitTest.h" +#include "epicsThread.h" +#include "iocInit.h" +#include "dbBase.h" +#include "dbAccess.h" +#include "registry.h" +#include "dbStaticLib.h" +#include "osiFileName.h" +#include "dbmf.h" +#include "errlog.h" + +#include "testMain.h" + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static struct threadItem { + char *name; + char found; +} commonThreads[] = { + { "errlog", 0 }, + { "taskwd", 0 }, + { "timerQueue", 0 }, + { "cbLow", 0 }, + { "scanOnce", 0 }, + { NULL, 0 } +}; + +static +void findCommonThread (epicsThreadId id) { + struct threadItem *thr; + char name[32]; + + epicsThreadGetName(id, name, 32); + + for (thr = commonThreads; thr->name; thr++) { + if (epicsStrCaseCmp(thr->name, name) == 0) { + thr->found = 1; + } + } +} + +static +void checkCommonThreads (void) { + struct threadItem *thr; + + for (thr = commonThreads; thr->name; thr++) { + testOk(thr->found, "Thread %s is running", thr->name); + thr->found = 0; + } +} + +static +void cycle(void) { + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("xRecord.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + epicsThreadMap(findCommonThread); + checkCommonThreads(); + + testIocShutdownOk(); + + testdbCleanup(); +} + +MAIN(dbShutdownTest) +{ + testPlan(10); + + cycle(); + cycle(); + + return testDone(); +} diff --git a/src/ioc/db/test/epicsRunDbTests.c b/src/ioc/db/test/epicsRunDbTests.c index 98b0b417a..51f0b3db1 100644 --- a/src/ioc/db/test/epicsRunDbTests.c +++ b/src/ioc/db/test/epicsRunDbTests.c @@ -19,6 +19,8 @@ int testdbConvert(void); int callbackTest(void); int dbStateTest(void); +int dbShutdownTest(void); +int dbPutLinkTest(void); int testDbChannel(void); int chfPluginTest(void); int arrShorthandTest(void); @@ -30,9 +32,11 @@ void epicsRunDbTests(void) runTest(testdbConvert); runTest(callbackTest); runTest(dbStateTest); + runTest(dbShutdownTest); + runTest(dbPutLinkTest); runTest(testDbChannel); - runTest(chfPluginTest); runTest(arrShorthandTest); + runTest(chfPluginTest); dbmfFreeChunks(); diff --git a/src/ioc/db/test/sRecord.db b/src/ioc/db/test/sRecord.db new file mode 100644 index 000000000..790a7df7c --- /dev/null +++ b/src/ioc/db/test/sRecord.db @@ -0,0 +1 @@ +record(ai, "somename") {} diff --git a/src/ioc/db/test/xRecord.c b/src/ioc/db/test/xRecord.c index 568fbb838..dcc1f776c 100644 --- a/src/ioc/db/test/xRecord.c +++ b/src/ioc/db/test/xRecord.c @@ -14,12 +14,24 @@ */ #include "dbAccessDefs.h" -#include +#include "recSup.h" +#include "recGbl.h" #define GEN_SIZE_OFFSET #include "xRecord.h" #include -static rset xRSET; +static long process(xRecord *prec) +{ + prec->pact = TRUE; + recGblGetTimeStamp(prec); + recGblFwdLink(prec); + prec->pact = FALSE; + return 0; +} + +static rset xRSET = { + RSETNUMBER, NULL, NULL, NULL, process +}; epicsExportAddress(rset,xRSET); diff --git a/src/ioc/db/test/xRecord.dbd b/src/ioc/db/test/xRecord.dbd index 4837871a4..8f4b2156c 100644 --- a/src/ioc/db/test/xRecord.dbd +++ b/src/ioc/db/test/xRecord.dbd @@ -1,12 +1,11 @@ -# This is a combined minimal DBD and DB file +# This is a minimal record definition recordtype(x) { - field(NAME, DBF_STRING) { - prompt("Record Name") - special(SPC_NOMOD) - size(61) - } + include "dbCommon.dbd" field(VAL, DBF_LONG) { prompt("Value") } + field(LNK, DBF_INLINK) { + prompt("Link") + } } diff --git a/src/ioc/misc/dbCore.dbd b/src/ioc/misc/dbCore.dbd index fac0fd674..3b22f4663 100644 --- a/src/ioc/misc/dbCore.dbd +++ b/src/ioc/misc/dbCore.dbd @@ -5,6 +5,9 @@ # This file provides iocsh access to variables that control some lesser-used # and debugging features of the IOC database code. +# show epicsAtExit callbacks as they are run +variable(atExitDebug,int) + # Access security subroutines variable(asCaDebug,int) diff --git a/src/ioc/misc/iocInit.c b/src/ioc/misc/iocInit.c index f13cf6bb3..5f3d505d5 100644 --- a/src/ioc/misc/iocInit.c +++ b/src/ioc/misc/iocInit.c @@ -3,6 +3,8 @@ * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. +* Copyright (c) 2013 Helmholtz-Zentrum Berlin +* für Materialien und Energie GmbH. * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ @@ -31,6 +33,7 @@ #include "errMdef.h" #include "taskwd.h" #include "caeventmask.h" +#include "iocsh.h" #define epicsExportSharedSymbols #include "alarm.h" @@ -88,12 +91,13 @@ int iocInit(void) return iocBuild() || iocRun(); } -int iocBuild(void) +static int iocBuild_1(void) { - if (iocState != iocVirgin) { - errlogPrintf("iocBuild: IOC can only be initialized once\n"); + if (iocState != iocVirgin && iocState != iocStopped) { + errlogPrintf("iocBuild: IOC can only be initialized from uninitialized or stopped state\n"); return -1; } + errlogInit(0); initHookAnnounce(initHookAtIocBuild); if (!epicsThreadIsOkToBlock()) { @@ -109,14 +113,17 @@ int iocBuild(void) initHookAnnounce(initHookAtBeginning); coreRelease(); - /* After this point, further calls to iocInit() are disallowed. */ iocState = iocBuilding; taskwdInit(); callbackInit(); initHookAnnounce(initHookAfterCallbackInit); - dbCaLinkInit(); + return 0; +} + +static int iocBuild_2(void) +{ initHookAnnounce(initHookAfterCaLinkInit); initDrvSup(); @@ -147,9 +154,11 @@ int iocBuild(void) initialProcess(); initHookAnnounce(initHookAfterInitialProcess); + return 0; +} - /* Start CA server threads */ - rsrv_init(); +static int iocBuild_3(void) +{ initHookAnnounce(initHookAfterCaServerInit); iocState = iocBuilt; @@ -157,6 +166,39 @@ int iocBuild(void) return 0; } +int iocBuild(void) +{ + int status; + + status = iocBuild_1(); + if (status) return status; + + dbCaLinkInit(); + + status = iocBuild_2(); + if (status) return status; + + /* Start CA server threads */ + rsrv_init(); + + status = iocBuild_3(); + return status; +} + +int iocBuildIsolated(void) +{ + int status; + + status = iocBuild_1(); + if (status) return status; + + status = iocBuild_2(); + if (status) return status; + + status = iocBuild_3(); + return status; +} + int iocRun(void) { if (iocState != iocPaused && iocState != iocBuilt) { @@ -599,8 +641,31 @@ static void doCloseLinks(dbRecordType *pdbRecordType, dbCommon *precord, } } +static void doFreeRecord(dbRecordType *pdbRecordType, dbCommon *precord, + void *user) +{ + struct rset *prset = pdbRecordType->prset; + + if (!prset) return; /* unlikely */ + + epicsMutexDestroy(precord->mlok); +} + +int iocShutdown(void) +{ + if (iocState == iocVirgin || iocState == iocStopped) return 0; + iterateRecords(doCloseLinks, NULL); + scanShutdown(); + callbackShutdown(); + iterateRecords(doFreeRecord, NULL); + dbLockCleanupRecords(pdbbase); + asShutdown(); + iocshFree(); + iocState = iocStopped; + return 0; +} + static void exitDatabase(void *dummy) { - iterateRecords(doCloseLinks, NULL); - iocState = iocStopped; + iocShutdown(); } diff --git a/src/ioc/misc/iocInit.h b/src/ioc/misc/iocInit.h index 6a2f5682c..24ae45e06 100644 --- a/src/ioc/misc/iocInit.h +++ b/src/ioc/misc/iocInit.h @@ -19,8 +19,10 @@ extern "C" { epicsShareFunc int iocInit(void); epicsShareFunc int iocBuild(void); +epicsShareFunc int iocBuildIsolated(void); epicsShareFunc int iocRun(void); epicsShareFunc int iocPause(void); +epicsShareFunc int iocShutdown(void); #ifdef __cplusplus } diff --git a/src/libCom/as/asLib.h b/src/libCom/as/asLib.h index b3da2f836..af8e0dc30 100644 --- a/src/libCom/as/asLib.h +++ b/src/libCom/as/asLib.h @@ -222,6 +222,7 @@ epicsShareFunc long epicsShareAPI asComputeAsg(ASG *pasg); /*following is "friend" function*/ epicsShareFunc void * epicsShareAPI asCalloc(size_t nobj,size_t size); epicsShareFunc char * epicsShareAPI asStrdup(unsigned char *str); +epicsShareFunc void asFreeAll(ASBASE *pasbase); #ifdef __cplusplus } #endif diff --git a/src/libCom/as/asLibRoutines.c b/src/libCom/as/asLibRoutines.c index 83f6629e6..c8046ebce 100644 --- a/src/libCom/as/asLibRoutines.c +++ b/src/libCom/as/asLibRoutines.c @@ -51,7 +51,6 @@ static long asAddMemberPvt(ASMEMBERPVT *pasMemberPvt,const char *asgName); static long asComputeAllAsgPvt(void); static long asComputeAsgPvt(ASG *pasg); static long asComputePvt(ASCLIENTPVT asClientPvt); -static void asFreeAll(ASBASE *pasbase); static UAG *asUagAdd(const char *uagName); static long asUagAddUser(UAG *puag,const char *user); static HAG *asHagAdd(const char *hagName); @@ -1015,7 +1014,7 @@ next_rule: return(0); } -static void asFreeAll(ASBASE *pasbase) +void asFreeAll(ASBASE *pasbase) { UAG *puag; UAGNAME *puagname; diff --git a/src/libCom/error/errlog.c b/src/libCom/error/errlog.c index b2714d7a9..680a1316a 100644 --- a/src/libCom/error/errlog.c +++ b/src/libCom/error/errlog.c @@ -41,7 +41,7 @@ /*Declare storage for errVerbose */ epicsShareDef int errVerbose = 0; -static void exitHandler(void *); +static void errlogExitHandler(void *); static void errlogThread(void); static char *msgbufGetFree(int noConsoleMessage); @@ -70,8 +70,8 @@ static struct { epicsEventId waitForFlush; /*errlogFlush waits for this*/ epicsEventId flush; /*errlogFlush sets errlogThread does a Try*/ epicsMutexId flushLock; - epicsEventId waitForExit; /*exitHandler waits for this*/ - int atExit; /*TRUE when exitHandler is active*/ + epicsEventId waitForExit; /*errlogExitHandler waits for this*/ + int atExit; /*TRUE when errlogExitHandler is active*/ ELLLIST listenerList; ELLLIST msgQueue; msgNode *pnextSend; @@ -368,6 +368,7 @@ epicsShareFunc int epicsShareAPI errlogRemoveListeners( epicsShareFunc int epicsShareAPI eltc(int yesno) { errlogInit(0); + errlogFlush(); pvtData.toConsole = yesno; return 0; } @@ -447,7 +448,7 @@ epicsShareFunc void errPrintf(long status, const char *pFileName, } -static void exitHandler(void *pvt) +static void errlogExitHandler(void *pvt) { pvtData.atExit = 1; epicsEventSignal(pvtData.waitForWork); @@ -561,7 +562,7 @@ static void errlogThread(void) int noConsoleMessage; char *pmessage; - epicsAtExit(exitHandler,0); + epicsAtExit(errlogExitHandler,0); while (TRUE) { epicsEventMustWait(pvtData.waitForWork); while ((pmessage = msgbufGetSend(&noConsoleMessage))) { diff --git a/src/libCom/iocsh/iocsh.cpp b/src/libCom/iocsh/iocsh.cpp index 81727696b..671239781 100644 --- a/src/libCom/iocsh/iocsh.cpp +++ b/src/libCom/iocsh/iocsh.cpp @@ -203,20 +203,22 @@ void epicsShareAPI iocshRegisterVariable (const iocshVarDef *piocshVarDef) */ void epicsShareAPI iocshFree(void) { - struct iocshCommand *pc, *nc; - struct iocshVariable *pv, *nv; + struct iocshCommand *pc; + struct iocshVariable *pv; iocshTableLock (); for (pc = iocshCommandHead ; pc != NULL ; ) { - nc = pc->next; + struct iocshCommand * nc = pc->next; free (pc); pc = nc; } for (pv = iocshVariableHead ; pv != NULL ; ) { - nv = pv->next; + struct iocshVariable *nv = pv->next; free (pv); pv = nv; } + iocshCommandHead = NULL; + iocshVariableHead = NULL; iocshTableUnlock (); } diff --git a/src/libCom/misc/epicsExit.c b/src/libCom/misc/epicsExit.c index 453378d36..0fbed224b 100644 --- a/src/libCom/misc/epicsExit.c +++ b/src/libCom/misc/epicsExit.c @@ -23,7 +23,9 @@ */ #include +#include #include +#include #define epicsExportSharedSymbols #include "ellLib.h" @@ -36,12 +38,15 @@ typedef struct exitNode { ELLNODE node; epicsExitFunc func; void *arg; + char name[1]; }exitNode; typedef struct exitPvt { ELLLIST list; } exitPvt; +int atExitDebug = 0; + static epicsThreadOnceId exitPvtOnce = EPICS_THREAD_ONCE_INIT; static exitPvt * pExitPvtPerProcess = 0; static epicsMutexId exitPvtLock = 0; @@ -66,15 +71,23 @@ static void exitPvtOnceFunc(void *pParm) { exitPvtPerThread = epicsThreadPrivateCreate (); assert ( exitPvtPerThread ); - pExitPvtPerProcess = createExitPvt (); - assert ( pExitPvtPerProcess ); exitPvtLock = epicsMutexMustCreate (); } +static void epicsExitInit(void) +{ + epicsThreadOnce ( & exitPvtOnce, exitPvtOnceFunc, 0 ); +} + static void epicsExitCallAtExitsPvt(exitPvt *pep) { exitNode *pexitNode; + while ( ( pexitNode = (exitNode *) ellLast ( & pep->list ) ) ) { + if (atExitDebug && pexitNode->name[0]) + fprintf(stderr, "atExit %s(%p)\n", pexitNode->name, pexitNode->arg); + else if(atExitDebug) + fprintf(stderr, "atExit %p(%p)\n", pexitNode->func, pexitNode->arg); pexitNode->func ( pexitNode->arg ); ellDelete ( & pep->list, & pexitNode->node ); free ( pexitNode ); @@ -84,7 +97,8 @@ static void epicsExitCallAtExitsPvt(exitPvt *pep) epicsShareFunc void epicsExitCallAtExits(void) { exitPvt * pep = 0; - epicsThreadOnce ( & exitPvtOnce, exitPvtOnceFunc, 0 ); + + epicsExitInit (); epicsMutexMustLock ( exitPvtLock ); if ( pExitPvtPerProcess ) { pep = pExitPvtPerProcess; @@ -100,7 +114,8 @@ epicsShareFunc void epicsExitCallAtExits(void) epicsShareFunc void epicsExitCallAtThreadExits(void) { exitPvt * pep; - epicsThreadOnce ( & exitPvtOnce, exitPvtOnceFunc, 0 ); + + epicsExitInit (); pep = epicsThreadPrivateGet ( exitPvtPerThread ); if ( pep ) { epicsExitCallAtExitsPvt ( pep ); @@ -109,14 +124,16 @@ epicsShareFunc void epicsExitCallAtThreadExits(void) } } -static int epicsAtExitPvt(exitPvt *pep, epicsExitFunc func, void *arg) +static int epicsAtExitPvt(exitPvt *pep, epicsExitFunc func, void *arg, const char *name) { int status = -1; - exitNode * pExitNode - = calloc ( 1, sizeof( *pExitNode ) ); + exitNode * pExitNode = calloc ( 1, sizeof( *pExitNode ) + (name?strlen(name):0) ); + if ( pExitNode ) { pExitNode->func = func; pExitNode->arg = arg; + if(name) + strcpy(pExitNode->name, name); ellAdd ( & pep->list, & pExitNode->node ); status = 0; } @@ -126,7 +143,8 @@ static int epicsAtExitPvt(exitPvt *pep, epicsExitFunc func, void *arg) epicsShareFunc int epicsAtThreadExit(epicsExitFunc func, void *arg) { exitPvt * pep; - epicsThreadOnce ( & exitPvtOnce, exitPvtOnceFunc, 0 ); + + epicsExitInit (); pep = epicsThreadPrivateGet ( exitPvtPerThread ); if ( ! pep ) { pep = createExitPvt (); @@ -135,16 +153,20 @@ epicsShareFunc int epicsAtThreadExit(epicsExitFunc func, void *arg) } epicsThreadPrivateSet ( exitPvtPerThread, pep ); } - return epicsAtExitPvt ( pep, func, arg ); + return epicsAtExitPvt ( pep, func, arg, NULL ); } -epicsShareFunc int epicsAtExit(epicsExitFunc func, void *arg) +epicsShareFunc int epicsAtExit3(epicsExitFunc func, void *arg, const char* name) { int status = -1; - epicsThreadOnce ( & exitPvtOnce, exitPvtOnceFunc, 0 ); + + epicsExitInit (); epicsMutexMustLock ( exitPvtLock ); + if ( !pExitPvtPerProcess ) { + pExitPvtPerProcess = createExitPvt (); + } if ( pExitPvtPerProcess ) { - status = epicsAtExitPvt ( pExitPvtPerProcess, func, arg ); + status = epicsAtExitPvt ( pExitPvtPerProcess, func, arg, name ); } epicsMutexUnlock ( exitPvtLock ); return status; @@ -153,6 +175,10 @@ epicsShareFunc int epicsAtExit(epicsExitFunc func, void *arg) epicsShareFunc void epicsExit(int status) { epicsExitCallAtExits(); - epicsThreadSleep(1.0); + epicsThreadSleep(0.1); exit(status); } + +#include "epicsExport.h" + +epicsExportAddress(int,atExitDebug); diff --git a/src/libCom/misc/epicsExit.h b/src/libCom/misc/epicsExit.h index 2fb33f129..e64316010 100644 --- a/src/libCom/misc/epicsExit.h +++ b/src/libCom/misc/epicsExit.h @@ -19,7 +19,8 @@ typedef void (*epicsExitFunc)(void *arg); epicsShareFunc void epicsExit(int status); epicsShareFunc void epicsExitCallAtExits(void); -epicsShareFunc int epicsAtExit(epicsExitFunc func, void *arg); +epicsShareFunc int epicsAtExit3(epicsExitFunc func, void *arg, const char* name); +#define epicsAtExit(F,A) epicsAtExit3(F,A,#F) epicsShareFunc void epicsExitCallAtThreadExits(void); epicsShareFunc int epicsAtThreadExit(epicsExitFunc func, void *arg); diff --git a/src/libCom/misc/epicsUnitTest.h b/src/libCom/misc/epicsUnitTest.h index c1f0a5939..0489752a1 100644 --- a/src/libCom/misc/epicsUnitTest.h +++ b/src/libCom/misc/epicsUnitTest.h @@ -9,6 +9,9 @@ * Author: Andrew Johnson */ +#ifndef INC_epicsUnitTest_H +#define INC_epicsUnitTest_H + #include #include "compilerDependencies.h" @@ -47,3 +50,5 @@ epicsShareFunc void runTestFunc(const char *name, TESTFUNC func); #ifdef __cplusplus } #endif + +#endif /* INC_epicsUnitTest_H */ diff --git a/src/libCom/test/epicsExitTest.c b/src/libCom/test/epicsExitTest.c index f464b707c..dc901b5ac 100644 --- a/src/libCom/test/epicsExitTest.c +++ b/src/libCom/test/epicsExitTest.c @@ -59,12 +59,20 @@ static void thread(void *arg) testDiag("%s starting", pinfo->name); pinfo->terminate = epicsEventMustCreate(epicsEventEmpty); pinfo->terminated = epicsEventMustCreate(epicsEventEmpty); - epicsAtExit(atExit, pinfo); - epicsAtThreadExit(atThreadExit, pinfo); + testOk(!epicsAtExit(atExit, pinfo), "Registered atExit(%p)", pinfo); + testOk(!epicsAtThreadExit(atThreadExit, pinfo), + "Registered atThreadExit(%p)", pinfo); testDiag("%s waiting for atExit", pinfo->name); epicsEventMustWait(pinfo->terminate); } +int count; + +static void counter(void *pvt) +{ + count++; +} + static void mainExit(void *pvt) { testPass("Reached mainExit"); @@ -77,16 +85,23 @@ MAIN(epicsExitTest) info *pinfoA = (info *)calloc(1, sizeof(info)); info *pinfoB = (info *)calloc(1, sizeof(info)); - testPlan(7); + testPlan(15); - epicsAtExit(mainExit, NULL); + testOk(!epicsAtExit(counter, NULL), "Registered counter()"); + count = 0; + epicsExitCallAtExits(); + testOk(count == 1, "counter() called once"); + epicsExitCallAtExits(); + testOk(count == 1, "unregistered counter() not called"); + + testOk(!epicsAtExit(mainExit, NULL), "Registered mainExit()"); epicsThreadCreate("threadA", 50, stackSize, thread, pinfoA); epicsThreadSleep(0.1); epicsThreadCreate("threadB", 50, stackSize, thread, pinfoB); epicsThreadSleep(1.0); - testDiag("Calling epicsExit\n"); + testDiag("Calling epicsExit"); epicsExit(0); return 0; }