diff --git a/.ci b/.ci index aea790683..1e0e326f7 160000 --- a/.ci +++ b/.ci @@ -1 +1 @@ -Subproject commit aea79068391fe407fcb817c036bc54f26342bdaa +Subproject commit 1e0e326f74ffac4154ce80b5d41c410c754cf5d8 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..b6a8712f6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +# Documentation for this file: https://EditorConfig.org + +root = true + +# Unix-style newlines ending every file, +# as some compilers complain about files not ending in newline +[*] +insert_final_newline = true diff --git a/.github/workflows/check-editorconfig.yml b/.github/workflows/check-editorconfig.yml new file mode 100644 index 000000000..07f6d6c94 --- /dev/null +++ b/.github/workflows/check-editorconfig.yml @@ -0,0 +1,13 @@ +name: Check EditorConfig + +on: + push: + pull_request: + +jobs: + editorconfig: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: EditorConfig-Action + uses: greut/eclint-action@v0 diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index 945fea713..acd8b63cb 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -43,11 +43,8 @@ jobs: env: CMP: ${{ matrix.cmp }} BCFG: ${{ matrix.configuration }} - WINE: ${{ matrix.wine }} - RTEMS: ${{ matrix.rtems }} - RTEMS_TARGET: ${{ matrix.rtems_target }} + CI_CROSS_TARGETS: ${{ matrix.cross }} EXTRA: ${{ matrix.extra }} - EXTRA1: ${{ matrix.extra1 }} TEST: ${{ matrix.test }} strategy: fail-fast: false @@ -57,13 +54,13 @@ jobs: - os: ubuntu-20.04 cmp: gcc configuration: default - wine: "64" + cross: "windows-x64-mingw" name: "Ub-20 gcc-9 + MinGW" - os: ubuntu-20.04 cmp: gcc configuration: static - wine: "64" + cross: "windows-x64-mingw" name: "Ub-20 gcc-9 + MinGW, static" - os: ubuntu-20.04 @@ -75,8 +72,7 @@ jobs: - os: ubuntu-20.04 cmp: gcc configuration: static - extra: "CMD_CFLAGS=-funsigned-char" - extra1: "CMD_CXXFLAGS=-funsigned-char" + extra: "CMD_CFLAGS=-funsigned-char CMD_CXXFLAGS=-funsigned-char" name: "Ub-20 gcc-9 unsigned char" - os: ubuntu-20.04 @@ -93,67 +89,42 @@ jobs: - os: ubuntu-20.04 cmp: gcc configuration: default - rtems: "5" - rtems_target: RTEMS-pc686-qemu + cross: "RTEMS-pc686-qemu@5" name: "Ub-20 gcc-9 + RT-5.1 pc686" - os: ubuntu-20.04 cmp: gcc configuration: default - rtems: "5" - rtems_target: RTEMS-beatnik + cross: "RTEMS-beatnik@5" test: NO name: "Ub-20 gcc-9 + RT-5.1 beatnik" - # Only build one RTEMS target per CPU family - # unless it's running the tests - # - # - os: ubuntu-20.04 - # cmp: gcc - # configuration: default - # rtems: "5" - # rtems_target: RTEMS-mvme3100 - # test: NO - # name: "Ub-20 gcc-9 + RT-5.1 mvme3100" - # - # - os: ubuntu-20.04 - # cmp: gcc - # configuration: default - # rtems: "5" - # rtems_target: RTEMS-qoriq_e500 - # test: NO - # name: "Ub-20 gcc-9 + RT-5.1 qoriq_e500" - - os: ubuntu-20.04 cmp: gcc configuration: default - rtems: "5" - rtems_target: RTEMS-xilinx_zynq_a9_qemu + cross: "RTEMS-xilinx_zynq_a9_qemu@5" test: NO name: "Ub-20 gcc-9 + RT-5.1 xilinx_zynq_a9_qemu" - os: ubuntu-20.04 cmp: gcc configuration: default - rtems: "5" - rtems_target: RTEMS-uC5282 + cross: "RTEMS-uC5282@5" test: NO name: "Ub-20 gcc-9 + RT-5.1 uC5282" - os: ubuntu-20.04 cmp: gcc configuration: default - rtems: "4.10" name: "Ub-20 gcc-9 + RT-4.10" - rtems_target: RTEMS-pc386-qemu + cross: "RTEMS-pc386-qemu@4.10" test: NO - os: ubuntu-20.04 cmp: gcc configuration: default - rtems: "4.9" name: "Ub-20 gcc-9 + RT-4.9" - rtems_target: RTEMS-pc386-qemu + cross: "RTEMS-pc386-qemu@4.9" - os: macos-latest cmp: clang diff --git a/.gitignore b/.gitignore index 94e36205a..b7a25673a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ /modules/Makefile.local O.*/ /QtC-* +/.qtc_* /.vscode/ *.orig *.log diff --git a/configure/os/CONFIG_SITE.darwinCommon.darwinCommon b/configure/os/CONFIG_SITE.darwinCommon.darwinCommon index 6cc9a9de0..d45422401 100644 --- a/configure/os/CONFIG_SITE.darwinCommon.darwinCommon +++ b/configure/os/CONFIG_SITE.darwinCommon.darwinCommon @@ -12,6 +12,9 @@ ifneq (,$(wildcard /opt/homebrew)) else ifneq (,$(wildcard /usr/local/Homebrew)) # Default location on x86_64 HOMEBREW_DIR = /usr/local +else ifneq (,$(wildcard /opt/local/include/readline)) + # MacPorts + READLINE_DIR = /opt/local endif # Look for Homebrew's readline diff --git a/configure/toolchain.c b/configure/toolchain.c index 2f76b475b..da4e8bb2c 100644 --- a/configure/toolchain.c +++ b/configure/toolchain.c @@ -45,5 +45,5 @@ COMMANDLINE_LIBRARY ?= READLINE COMMANDLINE_LIBRARY ?= EPICS # endif #else -COMMANDLINE_LIBRARY ?= EPICS +COMMANDLINE_LIBRARY ?= $(strip $(if $(wildcard $(if $(GNU_DIR),$(GNU_DIR)/include/readline/readline.h)), READLINE, EPICS)) #endif diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index b0133b1ba..162abc3a8 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -15,7 +15,38 @@ should also be read to understand what has changed since earlier releases. ## Changes made on the 7.0 branch since 7.0.7 - +### dbEvent eventsRemaining missed on cancel + +In some cases, RSRV may queue a subscription update, but not flush it. +This partially addresses this issue. + +### subRecord on bad INP links + +Previously, if a subRecord has an invalid `INP*` link, it was silently failing +(and not running the proc function). Now the the status code returned by the +subroutine is returned from `dbProcess()`. + +### COMMANDLINE_LIBRARY fallback to GNU_DIR + +Fall back to the previous behavior when searching for `readline.h` with older compilers. + +### Search for readline installed via HomeBrew. + +Look for `/opt/local/include/readline` on OSX. + +### Always stop worker threads + +The SCAN and callback threads are now stopped during normal IOC shutdown. + +### Allow runtime bypass of free list allocator + +The environment variable `$EPICS_FREELIST_BYPASS` may be set to `YES` to cause the `freeListLib` functions to always call directly to `malloc()`/`free()`. May be useful when troubleshooting some kinds of memory allocation bugs which would otherwise be "hidden". eg. use-after-free data races. This may also improve the results of dynamic analysis tools which are not aware of this internal free list. + +### `compress` record enhancement + +The compress record now supports the use of partially-filled buffers when using +any of the N-to-one algorithms. This is achieved by setting the new field `PBUF` +to `YES`. ### Add conditional output (OOPT) to the longout record diff --git a/modules/ca/src/client/test_event.cpp b/modules/ca/src/client/test_event.cpp index 1d285ed12..6db6a4a56 100644 --- a/modules/ca/src/client/test_event.cpp +++ b/modules/ca/src/client/test_event.cpp @@ -56,6 +56,7 @@ extern "C" void epicsStdCall ca_dump_dbr ( if ( INVALID_DB_REQ ( type ) ) { printf ( "bad DBR type %ld\n", type ); + return; } printf ( "%s\t", dbr_text[type] ); diff --git a/modules/database/src/ioc/db/callback.c b/modules/database/src/ioc/db/callback.c index d58b8fc6f..556da37b9 100644 --- a/modules/database/src/ioc/db/callback.c +++ b/modules/database/src/ioc/db/callback.c @@ -58,6 +58,7 @@ typedef struct cbQueueSet { int shutdown; // use atomic int threadsConfigured; int threadsRunning; + epicsThreadId *threads; } cbQueueSet; static cbQueueSet callbackQueue[NUM_CALLBACK_PRIORITIES]; @@ -242,11 +243,15 @@ void callbackStop(void) for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) { cbQueueSet *mySet = &callbackQueue[i]; + int j; while (epicsAtomicGetIntT(&mySet->threadsRunning)) { epicsEventSignal(mySet->semWakeUp); epicsEventWaitWithTimeout(startStopEvent, 0.1); } + for(j=0; jthreadsConfigured; j++) { + epicsThreadMustJoin(mySet->threads[j]); + } } } @@ -266,6 +271,8 @@ void callbackCleanup(void) mySet->semWakeUp = NULL; epicsRingPointerDelete(mySet->queue); mySet->queue = NULL; + free(mySet->threads); + mySet->threads = NULL; } epicsTimerQueueRelease(timerQueue); @@ -297,17 +304,25 @@ void callbackInit(void) cantProceed("epicsRingPointerLockedCreate failed for %s\n", threadNamePrefix[i]); callbackQueue[i].queueOverflow = FALSE; + if (callbackQueue[i].threadsConfigured == 0) callbackQueue[i].threadsConfigured = callbackThreadsDefault; + callbackQueue[i].threads = callocMustSucceed(callbackQueue[i].threadsConfigured, + sizeof(*callbackQueue[i].threads), + "callbackInit"); + for (j = 0; j < callbackQueue[i].threadsConfigured; j++) { + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; + opts.joinable = 1; + opts.priority = threadPriority[i]; + opts.stackSize = epicsThreadStackBig; if (callbackQueue[i].threadsConfigured > 1 ) sprintf(threadName, "%s-%d", threadNamePrefix[i], j); else strcpy(threadName, threadNamePrefix[i]); - tid = epicsThreadCreate(threadName, threadPriority[i], - epicsThreadGetStackSize(epicsThreadStackBig), - (EPICSTHREADFUNC)callbackTask, &priorityValue[i]); + callbackQueue[i].threads[j] = tid = epicsThreadCreateOpt(threadName, + (EPICSTHREADFUNC)callbackTask, &priorityValue[i], &opts); if (tid == 0) { cantProceed("Failed to spawn callback thread %s\n", threadName); } else { diff --git a/modules/database/src/ioc/db/dbConstLink.c b/modules/database/src/ioc/db/dbConstLink.c index ec2d3184f..46ffd92b2 100644 --- a/modules/database/src/ioc/db/dbConstLink.c +++ b/modules/database/src/ioc/db/dbConstLink.c @@ -154,7 +154,7 @@ static long dbConstLoadScalar(struct link *plink, short dbrType, void *pbuffer) const char *pstr = plink->value.constantStr; size_t len; - if (!pstr) + if (!pstr || !pstr[0]) return S_db_badField; len = strlen(pstr); @@ -181,7 +181,7 @@ static long dbConstLoadLS(struct link *plink, char *pbuffer, epicsUInt32 size, const char *pstr = plink->value.constantStr; long status; - if (!pstr) + if (!pstr || !pstr[0]) return S_db_badField; status = dbLSConvertJSON(pstr, pbuffer, size, plen); @@ -197,7 +197,7 @@ static long dbConstLoadArray(struct link *plink, short dbrType, void *pbuffer, const char *pstr = plink->value.constantStr; long status; - if (!pstr) + if (!pstr || !pstr[0]) return S_db_badField; /* Choice values must be numeric */ diff --git a/modules/database/src/ioc/db/dbEvent.c b/modules/database/src/ioc/db/dbEvent.c index 5891c0afe..d5c930dfc 100644 --- a/modules/database/src/ioc/db/dbEvent.c +++ b/modules/database/src/ioc/db/dbEvent.c @@ -76,6 +76,7 @@ struct event_que { unsigned short quota; /* the number of assigned entries*/ unsigned short nDuplicates; /* N events duplicated on this q */ unsigned short nCanceled; /* the number of canceled entries */ + unsigned possibleStall; }; struct event_user { @@ -934,6 +935,7 @@ void db_post_single_event (dbEventSubscription event) static int event_read ( struct event_que *ev_que ) { db_field_log *pfl; + int notifiedRemaining = 0; void ( *user_sub ) ( void *user_arg, struct dbChannel *chan, int eventsRemaining, db_field_log *pfl ); @@ -955,6 +957,7 @@ static int event_read ( struct event_que *ev_que ) while ( ev_que->evque[ev_que->getix] != EVENTQEMPTY ) { struct evSubscrip *pevent = ev_que->evque[ev_que->getix]; + int eventsRemaining; pfl = ev_que->valque[ev_que->getix]; if ( pevent == &canceledEvent ) { @@ -977,6 +980,7 @@ static int event_read ( struct event_que *ev_que ) event_remove ( ev_que, ev_que->getix, EVENTQEMPTY ); ev_que->getix = RNGINC ( ev_que->getix ); + eventsRemaining = ev_que->evque[ev_que->getix] != EVENTQEMPTY && !ev_que->nCanceled; /* * create a local copy of the call back parameters while @@ -1009,7 +1013,8 @@ static int event_read ( struct event_que *ev_que ) if (pfl) { /* Issue user callback */ ( *user_sub ) ( pevent->user_arg, pevent->chan, - ev_que->evque[ev_que->getix] != EVENTQEMPTY, pfl ); + eventsRemaining, pfl ); + notifiedRemaining = eventsRemaining; } LOCKEVQUE (ev_que); @@ -1036,6 +1041,11 @@ static int event_read ( struct event_que *ev_que ) db_delete_field_log(pfl); } + if(notifiedRemaining && !ev_que->possibleStall) { + ev_que->possibleStall = 1; + errlogPrintf(ERL_WARNING " dbEvent possible queue stall\n"); + } + UNLOCKEVQUE (ev_que); return DB_EVENT_OK; diff --git a/modules/database/src/ioc/db/dbScan.c b/modules/database/src/ioc/db/dbScan.c index f3e354e78..6e8799784 100644 --- a/modules/database/src/ioc/db/dbScan.c +++ b/modules/database/src/ioc/db/dbScan.c @@ -168,9 +168,13 @@ void scanStop(void) epicsEventSignal(ppsl->loopEvent); epicsEventWait(startStopEvent); } + for (i = 0; i < nPeriodic; i++) { + epicsThreadMustJoin(periodicTaskId[i]); + } scanOnce((dbCommon *)&exitOnce); epicsEventWait(startStopEvent); + epicsThreadMustJoin(onceTaskId); } void scanCleanup(void) @@ -761,14 +765,16 @@ void scanOnceQueueShow(const int reset) static void initOnce(void) { + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; + opts.joinable = 1; + opts.priority = epicsThreadPriorityScanLow + nPeriodic; + opts.stackSize = epicsThreadStackBig; if ((onceQ = epicsRingBytesLockedCreate(sizeof(onceEntry)*onceQueueSize)) == NULL) { cantProceed("initOnce: Ring buffer create failed\n"); } if(!onceSem) onceSem = epicsEventMustCreate(epicsEventEmpty); - onceTaskId = epicsThreadCreate("scanOnce", - epicsThreadPriorityScanLow + nPeriodic, - epicsThreadGetStackSize(epicsThreadStackBig), onceTask, 0); + onceTaskId = epicsThreadCreateOpt("scanOnce", onceTask, 0, &opts); epicsEventWait(startStopEvent); } @@ -932,14 +938,16 @@ static void spawnPeriodic(int ind) { periodic_scan_list *ppsl = papPeriodic[ind]; char taskName[20]; + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; + opts.joinable = 1; + opts.priority = epicsThreadPriorityScanLow + ind; + opts.stackSize = epicsThreadStackBig; if (!ppsl) return; sprintf(taskName, "scan-%g", ppsl->period); - periodicTaskId[ind] = epicsThreadCreate( - taskName, epicsThreadPriorityScanLow + ind, - epicsThreadGetStackSize(epicsThreadStackBig), - periodicTask, (void *)ppsl); + periodicTaskId[ind] = epicsThreadCreateOpt( + taskName, periodicTask, (void *)ppsl, &opts); epicsEventWait(startStopEvent); } diff --git a/modules/database/src/ioc/db/dbUnitTest.c b/modules/database/src/ioc/db/dbUnitTest.c index 805031bc2..2c7efd8a9 100644 --- a/modules/database/src/ioc/db/dbUnitTest.c +++ b/modules/database/src/ioc/db/dbUnitTest.c @@ -339,7 +339,7 @@ void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsign break; } #define OP(DBR,Type,pat) case DBR: {Type expect = *(Type*)pbuf, actual = *(Type*)gbuf; assert(vSize==sizeof(Type)); match &= expect==actual; \ - if(expect!=actual) testDiag("[%lu] expected=" pat " actual=" pat, n, expect, actual); break;} + if(expect!=actual) {testDiag("[%lu] expected=" pat " actual=" pat, n, expect, actual);} break;} OP(DBR_CHAR, char, "%c"); OP(DBR_UCHAR, unsigned char, "%u"); diff --git a/modules/database/src/ioc/dbStatic/dbLexRoutines.c b/modules/database/src/ioc/dbStatic/dbLexRoutines.c index 719ffc9db..0fd250826 100644 --- a/modules/database/src/ioc/dbStatic/dbLexRoutines.c +++ b/modules/database/src/ioc/dbStatic/dbLexRoutines.c @@ -111,7 +111,8 @@ typedef struct inputFile{ static ELLLIST inputFileList = ELLLIST_INIT; static inputFile *pinputFileNow = NULL; -static DBBASE *pdbbase = NULL; +/* The DBBASE most recently allocated/used by dbReadCOM() */ +static DBBASE *savedPdbbase = NULL; typedef struct tempListNode { ELLNODE node; @@ -233,15 +234,15 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp, } if(*ppdbbase == 0) *ppdbbase = dbAllocBase(); - pdbbase = *ppdbbase; + savedPdbbase = *ppdbbase; if(path && strlen(path)>0) { - dbPath(pdbbase,path); + dbPath(savedPdbbase,path); } else { penv = getenv("EPICS_DB_INCLUDE_PATH"); if(penv) { - dbPath(pdbbase,penv); + dbPath(savedPdbbase,penv); } else { - dbPath(pdbbase,"."); + dbPath(savedPdbbase,"."); } } my_buffer = dbCalloc(MY_BUFFER_SIZE,sizeof(char)); @@ -271,7 +272,7 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp, FILE *fp1 = 0; if (pinputFile->filename) - pinputFile->path = dbOpenFile(pdbbase, pinputFile->filename, &fp1); + pinputFile->path = dbOpenFile(savedPdbbase, pinputFile->filename, &fp1); if (!pinputFile->filename || !fp1) { errPrintf(0, __FILE__, __LINE__, "dbRead opening file %s\n",pinputFile->filename); @@ -297,13 +298,13 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp, while (ellCount(&tempList)) popFirstTemp(); /* Memory leak on parser failure */ - dbFreePath(pdbbase); + dbFreePath(savedPdbbase); if(!status) { /*add RTYP and VERS as an attribute */ DBENTRY dbEntry; DBENTRY *pdbEntry = &dbEntry; long localStatus; - dbInitEntry(pdbbase,pdbEntry); + dbInitEntry(savedPdbbase,pdbEntry); localStatus = dbFirstRecordType(pdbEntry); while(!localStatus) { localStatus = dbPutRecordAttribute(pdbEntry,"RTYP", @@ -323,7 +324,7 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp, cleanup: if(dbRecordsAbcSorted) { ELLNODE *cur; - for(cur = ellFirst(&pdbbase->recordTypeList); cur; cur=ellNext(cur)) + for(cur = ellFirst(&savedPdbbase->recordTypeList); cur; cur=ellNext(cur)) { dbRecordType *rtype = CONTAINER(cur, dbRecordType, node); @@ -416,12 +417,12 @@ static void dbIncludePrint(void) static void dbPathCmd(char *path) { - dbPath(pdbbase,path); + dbPath(savedPdbbase,path); } static void dbAddPathCmd(char *path) { - dbAddPath(pdbbase,path); + dbAddPath(savedPdbbase,path); } static void dbIncludeNew(char *filename) @@ -431,7 +432,7 @@ static void dbIncludeNew(char *filename) pinputFile = dbCalloc(1,sizeof(inputFile)); pinputFile->filename = macEnvExpand(filename); - pinputFile->path = dbOpenFile(pdbbase, pinputFile->filename, &fp); + pinputFile->path = dbOpenFile(savedPdbbase, pinputFile->filename, &fp); if (!fp) { epicsPrintf("Can't open include file \"%s\"\n", filename); yyerror(NULL); @@ -453,7 +454,7 @@ static void dbMenuHead(char *name) yyerrorAbort("dbMenuHead: Menu name can't be empty"); return; } - pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->menuList); + pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->menuList); if(pgphentry) { duplicate = TRUE; return; @@ -501,14 +502,14 @@ static void dbMenuBody(void) } if(ellCount(&tempList)) yyerrorAbort("dbMenuBody: tempList not empty"); /* Add menu in sorted order */ - pMenu = (dbMenu *)ellFirst(&pdbbase->menuList); + pMenu = (dbMenu *)ellFirst(&savedPdbbase->menuList); while(pMenu && strcmp(pMenu->name,pnewMenu->name) >0 ) pMenu = (dbMenu *)ellNext(&pMenu->node); if(pMenu) - ellInsert(&pdbbase->menuList,ellPrevious(&pMenu->node),&pnewMenu->node); + ellInsert(&savedPdbbase->menuList,ellPrevious(&pMenu->node),&pnewMenu->node); else - ellAdd(&pdbbase->menuList,&pnewMenu->node); - pgphentry = gphAdd(pdbbase->pgpHash,pnewMenu->name,&pdbbase->menuList); + ellAdd(&savedPdbbase->menuList,&pnewMenu->node); + pgphentry = gphAdd(savedPdbbase->pgpHash,pnewMenu->name,&savedPdbbase->menuList); if(!pgphentry) { yyerrorAbort("gphAdd failed"); } else { @@ -525,14 +526,14 @@ static void dbRecordtypeHead(char *name) yyerrorAbort("dbRecordtypeHead: Recordtype name can't be empty"); return; } - pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->recordTypeList); + pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->recordTypeList); if(pgphentry) { duplicate = TRUE; return; } pdbRecordType = dbCalloc(1,sizeof(dbRecordType)); pdbRecordType->name = epicsStrDup(name); - if (pdbbase->loadCdefs) ellInit(&pdbRecordType->cdefList); + if (savedPdbbase->loadCdefs) ellInit(&pdbRecordType->cdefList); if(ellCount(&tempList)) yyerrorAbort("dbRecordtypeHead tempList not empty"); allocTemp(pdbRecordType); @@ -564,13 +565,13 @@ static short findOrAddGuiGroup(const char *name) { dbGuiGroup *pdbGuiGroup; GPHENTRY *pgphentry; - pgphentry = gphFind(pdbbase->pgpHash, name, &pdbbase->guiGroupList); + pgphentry = gphFind(savedPdbbase->pgpHash, name, &savedPdbbase->guiGroupList); if (!pgphentry) { pdbGuiGroup = dbCalloc(1,sizeof(dbGuiGroup)); pdbGuiGroup->name = epicsStrDup(name); - ellAdd(&pdbbase->guiGroupList, &pdbGuiGroup->node); - pdbGuiGroup->key = ellCount(&pdbbase->guiGroupList); - pgphentry = gphAdd(pdbbase->pgpHash, pdbGuiGroup->name, &pdbbase->guiGroupList); + ellAdd(&savedPdbbase->guiGroupList, &pdbGuiGroup->node); + pdbGuiGroup->key = ellCount(&savedPdbbase->guiGroupList); + pgphentry = gphAdd(savedPdbbase->pgpHash, pdbGuiGroup->name, &savedPdbbase->guiGroupList); pgphentry->userPvt = pdbGuiGroup; } return ((dbGuiGroup *)pgphentry->userPvt)->key; @@ -653,8 +654,8 @@ static void dbRecordtypeFieldItem(char *name,char *value) return; } if(strcmp(name,"menu")==0) { - pdbFldDes->ftPvt = (dbMenu *)dbFindMenu(pdbbase,value); - if(!pdbbase->ignoreMissingMenus && !pdbFldDes->ftPvt) + pdbFldDes->ftPvt = (dbMenu *)dbFindMenu(savedPdbbase,value); + if(!savedPdbbase->ignoreMissingMenus && !pdbFldDes->ftPvt) yyerrorAbort("menu not found"); return; } @@ -672,7 +673,7 @@ static void dbRecordtypeCdef(char *text) { tempListNode *ptempListNode; dbRecordType *pdbRecordType; - if (!pdbbase->loadCdefs || duplicate) return; + if (!savedPdbbase->loadCdefs || duplicate) return; ptempListNode = (tempListNode *)ellFirst(&tempList); pdbRecordType = ptempListNode->item; @@ -781,14 +782,14 @@ static void dbRecordtypeBody(void) ellInit(&pdbRecordType->attributeList); ellInit(&pdbRecordType->recList); ellInit(&pdbRecordType->devList); - pgphentry = gphAdd(pdbbase->pgpHash,pdbRecordType->name, - &pdbbase->recordTypeList); + pgphentry = gphAdd(savedPdbbase->pgpHash,pdbRecordType->name, + &savedPdbbase->recordTypeList); if(!pgphentry) { yyerrorAbort("gphAdd failed"); } else { pgphentry->userPvt = pdbRecordType; } - ellAdd(&pdbbase->recordTypeList,&pdbRecordType->node); + ellAdd(&savedPdbbase->recordTypeList,&pdbRecordType->node); } static void dbDevice(char *recordtype,char *linktype, @@ -798,7 +799,7 @@ static void dbDevice(char *recordtype,char *linktype, dbRecordType *pdbRecordType; GPHENTRY *pgphentry; int i,link_type; - pgphentry = gphFind(pdbbase->pgpHash,recordtype,&pdbbase->recordTypeList); + pgphentry = gphFind(savedPdbbase->pgpHash,recordtype,&savedPdbbase->recordTypeList); if(!pgphentry) { epicsPrintf("Record type \"%s\" not found for device \"%s\"\n", recordtype, choicestring); @@ -819,7 +820,7 @@ static void dbDevice(char *recordtype,char *linktype, return; } pdbRecordType = (dbRecordType *)pgphentry->userPvt; - pgphentry = gphFind(pdbbase->pgpHash,choicestring,&pdbRecordType->devList); + pgphentry = gphFind(savedPdbbase->pgpHash,choicestring,&pdbRecordType->devList); if(pgphentry) { return; } @@ -827,7 +828,7 @@ static void dbDevice(char *recordtype,char *linktype, pdevSup->name = epicsStrDup(dsetname); pdevSup->choice = epicsStrDup(choicestring); pdevSup->link_type = link_type; - pgphentry = gphAdd(pdbbase->pgpHash,pdevSup->choice,&pdbRecordType->devList); + pgphentry = gphAdd(savedPdbbase->pgpHash,pdevSup->choice,&pdbRecordType->devList); if(!pgphentry) { yyerrorAbort("gphAdd failed"); } else { @@ -845,18 +846,18 @@ static void dbDriver(char *name) yyerrorAbort("dbDriver: Driver name can't be empty"); return; } - pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->drvList); + pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->drvList); if(pgphentry) { return; } pdrvSup = dbCalloc(1,sizeof(drvSup)); pdrvSup->name = epicsStrDup(name); - pgphentry = gphAdd(pdbbase->pgpHash,pdrvSup->name,&pdbbase->drvList); + pgphentry = gphAdd(savedPdbbase->pgpHash,pdrvSup->name,&savedPdbbase->drvList); if(!pgphentry) { yyerrorAbort("gphAdd failed"); } pgphentry->userPvt = pdrvSup; - ellAdd(&pdbbase->drvList,&pdrvSup->node); + ellAdd(&savedPdbbase->drvList,&pdrvSup->node); } static void dbLinkType(char *name, char *jlif_name) @@ -864,19 +865,19 @@ static void dbLinkType(char *name, char *jlif_name) linkSup *pLinkSup; GPHENTRY *pgphentry; - pgphentry = gphFind(pdbbase->pgpHash, name, &pdbbase->linkList); + pgphentry = gphFind(savedPdbbase->pgpHash, name, &savedPdbbase->linkList); if (pgphentry) { return; } pLinkSup = dbCalloc(1,sizeof(linkSup)); pLinkSup->name = epicsStrDup(name); pLinkSup->jlif_name = epicsStrDup(jlif_name); - pgphentry = gphAdd(pdbbase->pgpHash, pLinkSup->name, &pdbbase->linkList); + pgphentry = gphAdd(savedPdbbase->pgpHash, pLinkSup->name, &savedPdbbase->linkList); if (!pgphentry) { yyerrorAbort("gphAdd failed"); } pgphentry->userPvt = pLinkSup; - ellAdd(&pdbbase->linkList, &pLinkSup->node); + ellAdd(&savedPdbbase->linkList, &pLinkSup->node); } static void dbRegistrar(char *name) @@ -888,18 +889,18 @@ static void dbRegistrar(char *name) yyerrorAbort("dbRegistrar: Registrar name can't be empty"); return; } - pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->registrarList); + pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->registrarList); if(pgphentry) { return; } ptext = dbCalloc(1,sizeof(dbText)); ptext->text = epicsStrDup(name); - pgphentry = gphAdd(pdbbase->pgpHash,ptext->text,&pdbbase->registrarList); + pgphentry = gphAdd(savedPdbbase->pgpHash,ptext->text,&savedPdbbase->registrarList); if(!pgphentry) { yyerrorAbort("gphAdd failed"); } pgphentry->userPvt = ptext; - ellAdd(&pdbbase->registrarList,&ptext->node); + ellAdd(&savedPdbbase->registrarList,&ptext->node); } static void dbFunction(char *name) @@ -911,18 +912,18 @@ static void dbFunction(char *name) yyerrorAbort("dbFunction: Function name can't be empty"); return; } - pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->functionList); + pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->functionList); if(pgphentry) { return; } ptext = dbCalloc(1,sizeof(dbText)); ptext->text = epicsStrDup(name); - pgphentry = gphAdd(pdbbase->pgpHash,ptext->text,&pdbbase->functionList); + pgphentry = gphAdd(savedPdbbase->pgpHash,ptext->text,&savedPdbbase->functionList); if(!pgphentry) { yyerrorAbort("gphAdd failed"); } pgphentry->userPvt = ptext; - ellAdd(&pdbbase->functionList,&ptext->node); + ellAdd(&savedPdbbase->functionList,&ptext->node); } static void dbVariable(char *name, char *type) @@ -934,19 +935,19 @@ static void dbVariable(char *name, char *type) yyerrorAbort("dbVariable: Variable name can't be empty"); return; } - pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->variableList); + pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->variableList); if(pgphentry) { return; } pvar = dbCalloc(1,sizeof(dbVariableDef)); pvar->name = epicsStrDup(name); pvar->type = epicsStrDup(type); - pgphentry = gphAdd(pdbbase->pgpHash,pvar->name,&pdbbase->variableList); + pgphentry = gphAdd(savedPdbbase->pgpHash,pvar->name,&savedPdbbase->variableList); if(!pgphentry) { yyerrorAbort("gphAdd failed"); } pgphentry->userPvt = pvar; - ellAdd(&pdbbase->variableList,&pvar->node); + ellAdd(&savedPdbbase->variableList,&pvar->node); } static void dbBreakHead(char *name) @@ -958,7 +959,7 @@ static void dbBreakHead(char *name) yyerrorAbort("dbBreakHead: Breaktable name can't be empty"); return; } - pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->bptList); + pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->bptList); if(pgphentry) { duplicate = TRUE; return; @@ -1042,17 +1043,17 @@ static void dbBreakBody(void) /* Continue with last slope beyond the final point */ paBrkInt[number-1].slope = paBrkInt[number-2].slope; /* Add brkTable in sorted order */ - pbrkTable = (brkTable *)ellFirst(&pdbbase->bptList); + pbrkTable = (brkTable *)ellFirst(&savedPdbbase->bptList); while (pbrkTable) { if (strcmp(pbrkTable->name, pnewbrkTable->name) > 0) { - ellInsert(&pdbbase->bptList, ellPrevious((ELLNODE *)pbrkTable), + ellInsert(&savedPdbbase->bptList, ellPrevious((ELLNODE *)pbrkTable), (ELLNODE *)pnewbrkTable); break; } pbrkTable = (brkTable *)ellNext(&pbrkTable->node); } - if (!pbrkTable) ellAdd(&pdbbase->bptList, &pnewbrkTable->node); - pgphentry = gphAdd(pdbbase->pgpHash,pnewbrkTable->name,&pdbbase->bptList); + if (!pbrkTable) ellAdd(&savedPdbbase->bptList, &pnewbrkTable->node); + pgphentry = gphAdd(savedPdbbase->pgpHash,pnewbrkTable->name,&savedPdbbase->bptList); if (!pgphentry) { yyerrorAbort("dbBreakBody: gphAdd failed"); return; @@ -1103,7 +1104,7 @@ static void dbRecordHead(char *recordType, char *name, int visible) if(dbRecordNameValidate(name)) return; - pdbentry = dbAllocEntry(pdbbase); + pdbentry = dbAllocEntry(savedPdbbase); if (ellCount(&tempList)) yyerrorAbort("dbRecordHead: tempList not empty"); allocTemp(pdbentry); @@ -1260,7 +1261,7 @@ static void dbAlias(char *name, char *alias) if(dbRecordNameValidate(alias)) return; - dbInitEntry(pdbbase, pdbEntry); + dbInitEntry(savedPdbbase, pdbEntry); if (dbFindRecord(pdbEntry, name)) { epicsPrintf("Alias \"%s\" refers to unknown record \"%s\"\n", alias, name); diff --git a/modules/database/src/ioc/misc/iocInit.c b/modules/database/src/ioc/misc/iocInit.c index c26fd0e95..3be83adbe 100644 --- a/modules/database/src/ioc/misc/iocInit.c +++ b/modules/database/src/ioc/misc/iocInit.c @@ -716,13 +716,13 @@ int iocShutdown(void) iterateRecords(doCloseLinks, NULL); initHookAnnounce(initHookAfterCloseLinks); - if (iocBuildMode == buildIsolated) { - /* stop and "join" threads */ - scanStop(); - initHookAnnounce(initHookAfterStopScan); - callbackStop(); - initHookAnnounce(initHookAfterStopCallback); - } else { + /* stop and "join" threads */ + scanStop(); + initHookAnnounce(initHookAfterStopScan); + callbackStop(); + initHookAnnounce(initHookAfterStopCallback); + + if (iocBuildMode != buildIsolated) { dbStopServers(); } diff --git a/modules/database/src/std/rec/compressRecord.c b/modules/database/src/std/rec/compressRecord.c index d7da7f9b6..b6fafb4ab 100644 --- a/modules/database/src/std/rec/compressRecord.c +++ b/modules/database/src/std/rec/compressRecord.c @@ -28,6 +28,7 @@ #include "dbEvent.h" #include "dbFldTypes.h" #include "errMdef.h" +#include "menuYesNo.h" #include "special.h" #include "recSup.h" #include "recGbl.h" @@ -166,9 +167,9 @@ static int compress_array(compressRecord *prec, } if (prec->n <= 0) prec->n = 1; - n = prec->n; - if (no_elements < n) + if (no_elements < prec->n && prec->pbuf != menuYesNoYES) return 1; /*dont do anything*/ + n = no_elements; /* determine number of samples to take */ if (no_elements < nsam * n) @@ -272,7 +273,7 @@ static int array_average(compressRecord *prec, prec->inx = 0; return 0; } - + static int compress_scalar(struct compressRecord *prec,double *psource) { double value = *psource; @@ -292,19 +293,13 @@ static int compress_scalar(struct compressRecord *prec,double *psource) /* for scalars, Median not implemented => use average */ case (compressALG_N_to_1_Average): case (compressALG_N_to_1_Median): - if (inx == 0) - *pdest = value; - else { - *pdest += value; - if (inx + 1 >= prec->n) - *pdest = *pdest / (inx + 1); - } + *pdest = (inx * (*pdest) + value) / (inx + 1); break; } inx++; - if (inx >= prec->n) { + if ((inx >= prec->n) || (prec->pbuf == menuYesNoYES)) { put_value(prec,pdest,1); - prec->inx = 0; + prec->inx = (inx >= prec->n) ? 0 : inx; return 0; } else { prec->inx = inx; diff --git a/modules/database/src/std/rec/compressRecord.dbd.pod b/modules/database/src/std/rec/compressRecord.dbd.pod index 87ef67c00..20c52f496 100644 --- a/modules/database/src/std/rec/compressRecord.dbd.pod +++ b/modules/database/src/std/rec/compressRecord.dbd.pod @@ -40,7 +40,7 @@ the beginning or the end of the VAL array. =head2 Parameter Fields -The record-specific fields are described below. +The record-specific fields are described below, grouped by functionality. =recordtype compress @@ -60,10 +60,6 @@ menu(bufferingALG) { } recordtype(compress) { -=head2 Parameter Fields - -The record-specific fields are described below, grouped by functionality. - =head3 Scanning Parameters The compression record has the standard fields for specifying under what @@ -85,7 +81,7 @@ algorithms which can be specified as follows: The following fields determine what channel to read and how to compress the data: -=fields ALG, INP, NSAM, N, ILIL, IHIL, OFF, RES +=fields ALG, INP, NSAM, N, ILIL, IHIL, OFF, RES, PBUF As stated above, the ALG field specifies which algorithm to be performed on the data. @@ -167,6 +163,23 @@ Compress N to 1 samples, taking the median value. =back +The behaviour of the record for partially filled buffers depends on the field PBUF. +If PBUF is set to NO, then the record will wait until the buffer is completely full +before processing. If PBUF is set to YES, then it will start processing immediately. + +For example, if ALG is set to C<<< N to 1 Average >>> with NSAM equal to 4, N equal +to 1, and PBUF set to NO, then the first three times that the compress record is +processed it will remain in an undefined state. On the fourth process, the average +of all four records will be calculated and placed into the VAL field. + +If PBUF is set to YES, then after each process the average of the first several +elements will be calculated. + +Note that PBUF has no impact on the C<<< Average >>> method. If one wishes to have a +rolling average computed, then the best way to achieve that is with two compress +records: a C<<< Circular buffer >>> which is linked to an C<<< N to 1 Average >>> +record with PBUF set to YES. + The compression record keeps NSAM data samples. The field N determines the number of elements to compress into each result. @@ -393,7 +406,15 @@ Scan forward link if necessary, set PACT FALSE, and return. interest(1) menu(compressALG) } - field(BALG,DBF_MENU) { + field(PBUF,DBF_MENU) { + prompt("Use Partial buffers") + promptgroup("30 - Action") + special(SPC_RESET) + interest(1) + menu(menuYesNo) + initial("NO") + } + field(BALG,DBF_MENU) { prompt("Buffering Algorithm") promptgroup("30 - Action") special(SPC_RESET) diff --git a/modules/database/src/std/rec/mbboDirectRecord.c b/modules/database/src/std/rec/mbboDirectRecord.c index c41cc6d7a..6d3347abb 100644 --- a/modules/database/src/std/rec/mbboDirectRecord.c +++ b/modules/database/src/std/rec/mbboDirectRecord.c @@ -271,7 +271,7 @@ static long special(DBADDR *paddr, int after) } else if(after==1 && fieldIndex >= mbboDirectRecordB0 && fieldIndex <= mbboDirectRecordB1F) { /* Adjust VAL corresponding to the bit changed */ epicsUInt8 *pBn = (epicsUInt8 *) paddr->pfield; - epicsUInt32 bit = 1 << (pBn - &prec->b0); + epicsUInt32 bit = 1u << (pBn - &prec->b0); /* Because this is !(VAL and PP), dbPut() will always post a monitor on this B* field * after we return. We must keep track of this change separately from MLST to handle diff --git a/modules/database/src/std/rec/subRecord.c b/modules/database/src/std/rec/subRecord.c index 91f964123..1ef3af3de 100644 --- a/modules/database/src/std/rec/subRecord.c +++ b/modules/database/src/std/rec/subRecord.c @@ -162,7 +162,7 @@ static long process(struct dbCommon *pcommon) recGblFwdLink(prec); prec->pact = FALSE; - return 0; + return status; } static long special(DBADDR *paddr, int after) diff --git a/modules/database/src/tools/dbdToRecordtypeH.pl b/modules/database/src/tools/dbdToRecordtypeH.pl index 0bb3839fe..bfb14aa2e 100644 --- a/modules/database/src/tools/dbdToRecordtypeH.pl +++ b/modules/database/src/tools/dbdToRecordtypeH.pl @@ -146,7 +146,7 @@ __EOF__ " prt->papFldDes[${rn}Record${fn}]->size = " . "sizeof(prec->${cn});\n" . " prt->papFldDes[${rn}Record${fn}]->offset = " . - "(unsigned short)((char *)&prec->${cn} - (char *)prec);" + "(unsigned short)offsetof(${rn}Record, ${cn});" } @fields), << "__EOF__"; prt->rec_size = sizeof(*prec); diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile index effbbd1cc..816164ebe 100644 --- a/modules/database/test/std/rec/Makefile +++ b/modules/database/test/std/rec/Makefile @@ -167,6 +167,16 @@ asyncproctest_SRCS += asyncproctest_registerRecordDeviceDriver.cpp TESTFILES += $(COMMON_DIR)/asyncproctest.dbd ../asyncproctest.db TESTS += asyncproctest +TARGETS += $(COMMON_DIR)/subproctest.dbd +DBDDEPENDS_FILES += subproctest.dbd$(DEP) +subproctest_DBD += base.dbd +TESTPROD_HOST += subproctest +subproctest_SRCS += subproctest.c +subproctest_SRCS += subproctest_registerRecordDeviceDriver.cpp +TESTFILES += $(COMMON_DIR)/subproctest.dbd ../subproctest.db +TESTS += subproctest + + TESTPROD_HOST += linkFilterTest linkFilterTest_SRCS += linkFilterTest.c linkFilterTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp diff --git a/modules/database/test/std/rec/compressTest.c b/modules/database/test/std/rec/compressTest.c index d57b6b0a8..ed5f31e63 100644 --- a/modules/database/test/std/rec/compressTest.c +++ b/modules/database/test/std/rec/compressTest.c @@ -5,17 +5,24 @@ * in file LICENSE that is included with this distribution. \*************************************************************************/ +#include + +#include "cantProceed.h" #include "dbUnitTest.h" #include "testMain.h" #include "dbLock.h" #include "errlog.h" #include "dbAccess.h" #include "epicsMath.h" +#include "menuYesNo.h" #include "aiRecord.h" +#include "waveformRecord.h" #include "compressRecord.h" #define testDEq(A,B,D) testOk(fabs((A)-(B))<(D), #A " (%f) ~= " #B " (%f)", A, B) +#define fetchRecordOrDie(recname, addr) if (dbNameToAddr(recname, &addr)) {testAbort("Unknown PV '%s'", recname);} + void recTestIoc_registerRecordDeviceDriver(struct dbBase *); @@ -33,8 +40,7 @@ void checkArrD(const char *pv, long elen, double a, double b, double c, double d expect[2] = c; expect[3] = d; - if (dbNameToAddr(pv, &addr)) - testAbort("Unknown PV '%s'", pv); + fetchRecordOrDie(pv, addr); if (dbGet(&addr, DBR_DOUBLE, buf, NULL, &nReq, NULL)) testAbort("Failed to get '%s'", pv); @@ -66,8 +72,7 @@ void checkArrI(const char *pv, long elen, epicsInt32 a, epicsInt32 b, epicsInt32 expect[2] = c; expect[3] = d; - if (dbNameToAddr(pv, &addr)) - testAbort("Unknown PV '%s'", pv); + fetchRecordOrDie(pv, addr); if (dbGet(&addr, DBR_LONG, buf, NULL, &nReq, NULL)) testAbort("Failed to get '%s'", pv); @@ -85,6 +90,24 @@ void checkArrI(const char *pv, long elen, epicsInt32 a, epicsInt32 b, epicsInt32 } } +void +writeToWaveform(DBADDR *addr, long count, ...) { + va_list args; + long i; + double *values = (double *)callocMustSucceed(count, sizeof(double), "writeToWaveform"); + + va_start(args, count); + for (i=0; i< count; i++) { + values[i] = va_arg(args, double); + } + va_end(args); + + dbScanLock(addr->precord); + dbPut(addr, DBR_DOUBLE, values, count); + dbScanUnlock(addr->precord); + free(values); +} + static void testFIFOCirc(void) { @@ -100,9 +123,9 @@ void testFIFOCirc(void) recTestIoc_registerRecordDeviceDriver(pdbbase); - testdbReadDatabase("compressTest.db", NULL, "ALG=Circular Buffer,BALG=FIFO Buffer,NSAM=4"); + testdbReadDatabase("compressTest.db", NULL, "INP=ai,ALG=Circular Buffer,BALG=FIFO Buffer,NSAM=4"); - vrec = (aiRecord*)testdbRecordPtr("val"); + vrec = (aiRecord*)testdbRecordPtr("ai"); crec = (compressRecord*)testdbRecordPtr("comp"); eltc(0); @@ -230,9 +253,9 @@ void testLIFOCirc(void) recTestIoc_registerRecordDeviceDriver(pdbbase); testdbReadDatabase("compressTest.db", NULL, - "ALG=Circular Buffer,BALG=LIFO Buffer,NSAM=4"); + "INP=ai,ALG=Circular Buffer,BALG=LIFO Buffer,NSAM=4"); - vrec = (aiRecord*)testdbRecordPtr("val"); + vrec = (aiRecord*)testdbRecordPtr("ai"); crec = (compressRecord*)testdbRecordPtr("comp"); eltc(0); @@ -346,10 +369,278 @@ void testLIFOCirc(void) testdbCleanup(); } +void +testArrayAverage(void) { + DBADDR wfaddr, caddr; + + testDiag("Test Array Average"); + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=Average,BALG=FIFO Buffer,NSAM=4,N=2"); + + eltc(0); + testIocInitOk(); + eltc(1); + + fetchRecordOrDie("wf", wfaddr); + fetchRecordOrDie("comp", caddr); + + writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.); + + dbScanLock(caddr.precord); + dbProcess(caddr.precord); + + writeToWaveform(&wfaddr, 4, 2., 4., 6., 8.); + + dbProcess(caddr.precord); + + checkArrD("comp", 4, 1.5, 3., 4.5, 6.); + dbScanUnlock(caddr.precord); + + testIocShutdownOk(); + testdbCleanup(); +} + +void +testNto1Average(void) { + double buf = 0.0; + long nReq = 1; + DBADDR wfaddr, caddr; + + testDiag("Test Average"); + + testdbPrepare(); + + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=1,N=4"); + + eltc(0); + testIocInitOk(); + eltc(1); + + fetchRecordOrDie("wf", wfaddr); + fetchRecordOrDie("comp", caddr); + + testDiag("Test incomplete input data"); + + writeToWaveform(&wfaddr, 3, 1., 2., 3.); + + dbScanLock(caddr.precord); + dbProcess(caddr.precord); + if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL)) + testAbort("dbGet failed on compress record"); + + testOk1(nReq == 0); + testDEq(buf, 0., 0.01); + dbScanUnlock(caddr.precord); + + testDiag("Test complete input data"); + + writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.); + + dbScanLock(caddr.precord); + dbProcess(caddr.precord); + nReq = 1; + if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL)) + testAbort("dbGet failed on compress record"); + + testDEq(buf, 2.5, 0.01); + dbScanUnlock(caddr.precord); + + testDiag("Test single input data"); + + writeToWaveform(&wfaddr, 1, 5.); + + dbScanLock(caddr.precord); + dbProcess(caddr.precord); + nReq = 1; + if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL)) + testAbort("dbGet failed on compress record"); + + // Assert that nothing has changed from before + testDEq(buf, 2.5, 0.01); + dbScanUnlock(caddr.precord); + + testIocShutdownOk(); + testdbCleanup(); +} + +void +testNto1AveragePartial(void) { + double buf = 0.0; + long nReq = 1; + DBADDR wfaddr, caddr; + + testDiag("Test Average, Partial"); + + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=1,N=4,PBUF=YES"); + + eltc(0); + testIocInitOk(); + eltc(1); + + testDiag("Test incomplete input data"); + + fetchRecordOrDie("wf", wfaddr); + fetchRecordOrDie("comp", caddr); + + writeToWaveform(&wfaddr, 3, 1., 2., 3.); + + dbScanLock(caddr.precord); + dbProcess(caddr.precord); + if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL)) + testAbort("dbGet failed on compress record"); + + testDEq(buf, 2.0, 0.01); + dbScanUnlock(caddr.precord); + + testDiag("Test single entry from wf record"); + + writeToWaveform(&wfaddr, 1, 6.); + + dbScanLock(caddr.precord); + dbProcess(caddr.precord); + if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL)) + testAbort("dbGet failed on compress record"); + + testDEq(buf, 6.0, 0.01); + dbScanUnlock(caddr.precord); + + testIocShutdownOk(); + testdbCleanup(); +} + +void +testNto1LowValue(void) { + double buf = 0.0; + long nReq = 1; + DBADDR wfaddr, caddr; + + testDiag("Test 'N to 1 Low Value'"); + + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Low Value,BALG=FIFO Buffer,NSAM=1,N=4"); + + eltc(0); + testIocInitOk(); + eltc(1); + + fetchRecordOrDie("wf", wfaddr); + fetchRecordOrDie("comp", caddr); + + testDiag("Test full array"); + + writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.); + + dbScanLock(caddr.precord); + dbProcess(caddr.precord); + if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL)) + testAbort("dbGet failed on compress record"); + + testDEq(buf, 1.0, 0.01); + dbScanUnlock(caddr.precord); + + writeToWaveform(&wfaddr, 4, 4., 3., 2., 1.); + + dbScanLock(caddr.precord); + dbProcess(caddr.precord); + if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL)) + testAbort("dbGet failed on compress record"); + + testDEq(buf, 1.0, 0.01); + dbScanUnlock(caddr.precord); + + testDiag("Test partial data with PBUF set to NO"); + + writeToWaveform(&wfaddr, 3, 5., 6., 7.); + + dbScanLock(caddr.precord); + dbProcess(caddr.precord); + if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL)) + testAbort("dbGet failed on compress record"); + + // We confirm that this hasn't changed i.e. the dbProcess above did nothing + testDEq(buf, 1.0, 0.01); + + testDiag("Test partial data with PBUF set to YES"); + + ((compressRecord *)caddr.precord)->pbuf = menuYesNoYES; + + dbProcess(caddr.precord); + if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL)) + testAbort("dbGet failed on compress record"); + + testDEq(buf, 5.0, 0.01); + dbScanUnlock(caddr.precord); + + + testIocShutdownOk(); + testdbCleanup(); +} + +void +testAIAveragePartial(void) { + double buf = 0.; + double data[5] = {1., 2., 3., 4., 5.}; + /* + * Note that the fifth dbPut essentially resets the circular buffer, so the + * average is once again the average of the _first_ entry alone. + */ + double expected[5] = {1., 1.5, 2., 2.5, 5.}; + long nReq = 1; + int i; + DBADDR aiaddr, caddr; + + testDiag("Test 'N to 1 Average' with analog in, PBUF=YES"); + + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("compressTest.db", NULL, "INP=ai,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=1,N=4,PBUF=YES"); + + eltc(0); + testIocInitOk(); + eltc(1); + + fetchRecordOrDie("ai", aiaddr); + fetchRecordOrDie("comp", caddr); + + for (i = 0; i < 5; i++) { + dbScanLock(aiaddr.precord); + dbPut(&aiaddr, DBR_DOUBLE, &data[i], 1); + dbScanUnlock(aiaddr.precord); + + dbScanLock(caddr.precord); + dbProcess(caddr.precord); + if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL)) + testAbort("dbGet failed on compress record"); + dbScanUnlock(caddr.precord); + + testDEq(buf, expected[i], 0.01); + } + + testIocShutdownOk(); + testdbCleanup(); +} + MAIN(compressTest) { - testPlan(116); + testPlan(132); testFIFOCirc(); testLIFOCirc(); + testArrayAverage(); + testNto1Average(); + testNto1AveragePartial(); + testAIAveragePartial(); + testNto1LowValue(); return testDone(); } diff --git a/modules/database/test/std/rec/compressTest.db b/modules/database/test/std/rec/compressTest.db index 59fc620ba..168bad03b 100644 --- a/modules/database/test/std/rec/compressTest.db +++ b/modules/database/test/std/rec/compressTest.db @@ -1,7 +1,13 @@ -record(ai, "val") {} +record(ai, "ai") {} +record(waveform, "wf") { + field(FTVL, "DOUBLE") + field(NELM, "4") +} record(compress, "comp") { - field(INP, "val NPP") + field(INP, "$(INP) NPP") field(ALG, "$(ALG)") + field(PBUF,"$(PBUF=NO)") field(BALG,"$(BALG)") field(NSAM,"$(NSAM)") + field(N, "$(N=1)") } diff --git a/modules/database/test/std/rec/mbbioDirectTest.c b/modules/database/test/std/rec/mbbioDirectTest.c index 304217d37..ed2c5c39d 100644 --- a/modules/database/test/std/rec/mbbioDirectTest.c +++ b/modules/database/test/std/rec/mbbioDirectTest.c @@ -123,7 +123,7 @@ MAIN(mbbioDirectTest) testDiag("##### clear bit 31 (0x1f) #####"); putN("do%u.B1F", N, 0); - value &= ~(1<<31); + value &= ~(1u<<31u); testN("val%d", N, value); testmbbioRecords(N, value); diff --git a/modules/database/test/std/rec/subproctest.c b/modules/database/test/std/rec/subproctest.c new file mode 100644 index 000000000..213a6e88d --- /dev/null +++ b/modules/database/test/std/rec/subproctest.c @@ -0,0 +1,55 @@ +/*************************************************************************\ +* Copyright (c) 2022 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* SPDX-License-Identifier: EPICS +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* This test covers tests related to invoking subrecords + */ + +#include +#include +#include +#include +#include "registryFunction.h" +#include + +static +long subproc(subRecord *prec) +{ + prec->proc = 77; + return 0; +} + +void subproctest_registerRecordDeviceDriver(struct dbBase *); + +MAIN(subproctest) +{ + testPlan(2); + + testdbPrepare(); + + testdbReadDatabase("subproctest.dbd", NULL, NULL); + subproctest_registerRecordDeviceDriver(pdbbase); + registryFunctionAdd("subproc", (REGISTRYFUNCTION) subproc); + testdbReadDatabase("subproctest.db", NULL, "TPRO=0"); + + testIocInitOk(); + testDiag("===== Test that invalid link in INPA field fails a put request ======"); + + testdbPutFieldFail(-1, "InvalidINPARec.PROC", DBF_LONG, 1); + + /* Since the put to PROC above fails, subproc() never runs + * and the value of PROC will not be set by subproc(). However, + * the testdbPutField call above goes through, so we get a partial + * result of the PROC field being left as 1. */ + testdbGetFieldEqual("InvalidINPARec.PROC", DBF_LONG, 1); + + testIocShutdownOk(); + + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/rec/subproctest.db b/modules/database/test/std/rec/subproctest.db new file mode 100644 index 000000000..126d3fea5 --- /dev/null +++ b/modules/database/test/std/rec/subproctest.db @@ -0,0 +1,4 @@ +record(sub, "InvalidINPARec") { + field(SNAM, "subproc") + field(INPA, "nonexistent") +} diff --git a/modules/libcom/RTEMS/rtems_netconfig.c b/modules/libcom/RTEMS/rtems_netconfig.c index 6272d81a6..d9267ccaa 100644 --- a/modules/libcom/RTEMS/rtems_netconfig.c +++ b/modules/libcom/RTEMS/rtems_netconfig.c @@ -54,10 +54,10 @@ static struct rtems_bsdnet_ifconfig loopback_config = { */ #if defined(__i386__) extern int -rtems_ne2kpci_driver_attach (struct rtems_bsdnet_ifconfig *config, int attach); +rtems_ne2kpci_driver_attach (struct rtems_bsdnet_ifconfig *config); static struct rtems_bsdnet_ifconfig ne2k_driver_config = { "ne2", /* name */ - rtems_ne2kpci_driver_attach, /* attach function */ + (void*)&rtems_ne2kpci_driver_attach, /* attach function */ #if RTEMS_VERSION_INT= 0 && c < 32) || c >= 127 ) { diff --git a/modules/libcom/src/freeList/freeList.h b/modules/libcom/src/freeList/freeList.h index 778c1e1a6..c8b4680b9 100644 --- a/modules/libcom/src/freeList/freeList.h +++ b/modules/libcom/src/freeList/freeList.h @@ -29,6 +29,8 @@ extern "C" { #endif +LIBCOM_API extern int freeListBypass; + LIBCOM_API void epicsStdCall freeListInitPvt(void **ppvt, int size, int malloc); LIBCOM_API void * epicsStdCall freeListCalloc(void *pvt); LIBCOM_API void * epicsStdCall freeListMalloc(void *pvt); diff --git a/modules/libcom/src/freeList/freeListLib.c b/modules/libcom/src/freeList/freeListLib.c index 3dfecb5ee..ad0321b26 100644 --- a/modules/libcom/src/freeList/freeListLib.c +++ b/modules/libcom/src/freeList/freeListLib.c @@ -26,6 +26,20 @@ #include "epicsMutex.h" #include "freeList.h" #include "adjustment.h" +#include "errlog.h" +#include "epicsString.h" +#include "epicsAtomic.h" +#include "epicsExport.h" + +/* Bypass free list and directly call malloc() every time? */ +int freeListBypass +#ifdef EPICS_FREELIST_DEBUG + = 1; +#else + = 2; /* checks environment $EPICS_FREELIST_BYPASS */ +#endif + +epicsExportAddress(int, freeListBypass); typedef struct allocMem { struct allocMem *next; @@ -44,10 +58,25 @@ LIBCOM_API void epicsStdCall freeListInitPvt(void **ppvt,int size,int nmalloc) { FREELISTPVT *pfl; + int bypass = epicsAtomicGetIntT(&freeListBypass); + + if(bypass==2) { + const char *str = getenv("EPICS_FREELIST_BYPASS"); + + if(str && epicsStrCaseCmp(str, "YES")==0) { + bypass = 1; + } else if(!str || str[0]=='\0' || epicsStrCaseCmp(str, "NO")==0) { + bypass = 0; + } else { + errlogPrintf(ERL_WARNING " EPICS_FREELIST_BYPASS expected to be YES, NO, or empty. Not \"%s\"\n", str); + } + epicsAtomicSetIntT(&freeListBypass, bypass); + } pfl = callocMustSucceed(1,sizeof(FREELISTPVT), "freeListInitPvt"); pfl->size = adjustToWorstCaseAlignment(size); - pfl->nmalloc = nmalloc; + if(!bypass) + pfl->nmalloc = nmalloc; /* nmalloc==0 to bypass */ pfl->head = NULL; pfl->mallochead = NULL; pfl->nBlocksAvailable = 0u; @@ -60,28 +89,26 @@ LIBCOM_API void epicsStdCall LIBCOM_API void * epicsStdCall freeListCalloc(void *pvt) { FREELISTPVT *pfl = pvt; -# ifdef EPICS_FREELIST_DEBUG - return callocMustSucceed(1,pfl->size,"freeList Debug Calloc"); -# else void *ptemp; - ptemp = freeListMalloc(pvt); - if(ptemp) memset((char *)ptemp,0,pfl->size); + if(!pfl->nmalloc) + ptemp = calloc(1u, pfl->size); + else if(!!(ptemp = freeListMalloc(pvt))) + memset((char *)ptemp,0,pfl->size); return(ptemp); -# endif } LIBCOM_API void * epicsStdCall freeListMalloc(void *pvt) { FREELISTPVT *pfl = pvt; -# ifdef EPICS_FREELIST_DEBUG - return callocMustSucceed(1,pfl->size,"freeList Debug Malloc"); -# else void *ptemp; void **ppnext; allocMem *pallocmem; int i; + if(!pfl->nmalloc) + return malloc(pfl->size); + epicsMutexMustLock(pfl->lock); ptemp = pfl->head; if(ptemp==0) { @@ -125,18 +152,18 @@ LIBCOM_API void * epicsStdCall freeListMalloc(void *pvt) VALGRIND_MEMPOOL_FREE(pfl, ptemp); VALGRIND_MEMPOOL_ALLOC(pfl, ptemp, pfl->size); return(ptemp); -# endif } LIBCOM_API void epicsStdCall freeListFree(void *pvt,void*pmem) { FREELISTPVT *pfl = pvt; -# ifdef EPICS_FREELIST_DEBUG - memset ( pmem, 0xdd, pfl->size ); - free(pmem); -# else void **ppnext; + if(!pfl->nmalloc) { + free(pmem); + return; + } + VALGRIND_MEMPOOL_FREE(pvt, pmem); VALGRIND_MEMPOOL_ALLOC(pvt, pmem, sizeof(void*)); @@ -146,7 +173,6 @@ LIBCOM_API void epicsStdCall freeListFree(void *pvt,void*pmem) pfl->head = pmem; pfl->nBlocksAvailable++; epicsMutexUnlock(pfl->lock); -# endif } LIBCOM_API void epicsStdCall freeListCleanup(void *pvt) diff --git a/modules/libcom/src/iocsh/initHooks.h b/modules/libcom/src/iocsh/initHooks.h index 15faf1384..9afc0090a 100644 --- a/modules/libcom/src/iocsh/initHooks.h +++ b/modules/libcom/src/iocsh/initHooks.h @@ -95,9 +95,9 @@ typedef enum { initHookAtShutdown, /**< Start of iocShutdown() (unit tests only) */ initHookAfterCloseLinks, /**< Links disabled/deleted */ - initHookAfterStopScan, /**< Scan tasks stopped */ + initHookAfterStopScan, /**< Scan tasks stopped. Prior to UNRELEASED, triggered only by unittest code. */ initHookAfterStopCallback, /**< Callback tasks stopped */ - initHookAfterStopLinks, /**< CA links stopped */ + initHookAfterStopLinks, /**< CA links stopped. Prior to UNRELEASED, triggered only by unittest code. */ initHookBeforeFree, /**< Resource cleanup about to happen */ initHookAfterShutdown, /**< End of iocShutdown() */ diff --git a/modules/libcom/src/iocsh/libComRegister.c b/modules/libcom/src/iocsh/libComRegister.c index f09d5ca48..079bdc7ac 100644 --- a/modules/libcom/src/iocsh/libComRegister.c +++ b/modules/libcom/src/iocsh/libComRegister.c @@ -25,6 +25,7 @@ #include "taskwd.h" #include "registry.h" #include "epicsGeneralTime.h" +#include "freeList.h" #include "libComRegister.h" /* Register the PWD environment variable when the cd IOC shell function is @@ -447,7 +448,7 @@ static void epicsThreadResumeCallFunc(const iocshArgBuf *args) } /* generalTimeReport */ -static const iocshArg generalTimeReportArg0 = { "interest_level", iocshArgArgv}; +static const iocshArg generalTimeReportArg0 = { "interest_level", iocshArgInt}; static const iocshArg * const generalTimeReportArgs[1] = { &generalTimeReportArg0 }; static const iocshFuncDef generalTimeReportFuncDef = {"generalTimeReport",1,generalTimeReportArgs, "Display time providers and their priority levels" @@ -467,7 +468,11 @@ static void installLastResortEventProviderCallFunc(const iocshArgBuf *args) installLastResortEventProvider(); } -static iocshVarDef asCheckClientIPDef[] = { { "asCheckClientIP", iocshArgInt, 0 }, { NULL, iocshArgInt, NULL } }; +static iocshVarDef comDefs[] = { + { "asCheckClientIP", iocshArgInt, 0 }, + { "freeListBypass", iocshArgInt, 0 }, + { NULL, iocshArgInt, NULL } +}; void epicsStdCall libComRegister(void) { @@ -504,6 +509,7 @@ void epicsStdCall libComRegister(void) iocshRegister(&generalTimeReportFuncDef,generalTimeReportCallFunc); iocshRegister(&installLastResortEventProviderFuncDef, installLastResortEventProviderCallFunc); - asCheckClientIPDef[0].pval = &asCheckClientIP; - iocshRegisterVariable(asCheckClientIPDef); + comDefs[0].pval = &asCheckClientIP; + comDefs[1].pval = &freeListBypass; + iocshRegisterVariable(comDefs); } diff --git a/modules/libcom/src/osi/os/RTEMS-score/osdThread.c b/modules/libcom/src/osi/os/RTEMS-score/osdThread.c index ac157f445..110951445 100644 --- a/modules/libcom/src/osi/os/RTEMS-score/osdThread.c +++ b/modules/libcom/src/osi/os/RTEMS-score/osdThread.c @@ -371,6 +371,9 @@ void epicsThreadMustJoin(epicsThreadId id) rtems_id target_tid = (rtems_id)id, self_tid; struct taskVar *v = 0; + if(!id) + return; + rtems_task_ident (RTEMS_SELF, 0, &self_tid); { diff --git a/modules/libcom/src/yacc/closure.c b/modules/libcom/src/yacc/closure.c index 548b93889..5d7c0f5d4 100644 --- a/modules/libcom/src/yacc/closure.c +++ b/modules/libcom/src/yacc/closure.c @@ -85,7 +85,7 @@ set_first_derives(void) k = 0; } - if (cword & (1 << k)) + if (cword & (1u << k)) { rp = derives[j]; while ((rule = *rp++) >= 0) @@ -152,7 +152,7 @@ closure(short int *nucleus, int n) { for (i = 0; i < BITS_PER_WORD; ++i) { - if (word & (1 << i)) + if (word & (1u << i)) { itemno = rrhs[ruleno+i]; while (csp < csend && *csp < itemno) diff --git a/modules/libcom/src/yacc/warshall.c b/modules/libcom/src/yacc/warshall.c index 186fdbce9..1362e49cb 100644 --- a/modules/libcom/src/yacc/warshall.c +++ b/modules/libcom/src/yacc/warshall.c @@ -26,7 +26,7 @@ transitive_closure(unsigned int *R, int n) while (rowj < relend) { - if (*ccol & (1 << i)) + if (*ccol & (1u << i)) { rp = rowi; rend = rowj + rowsize; @@ -68,7 +68,7 @@ reflexive_transitive_closure(unsigned int *R, int n) rp = R; while (rp < relend) { - *rp |= (1 << i); + *rp |= (1u << i); if (++i >= BITS_PER_WORD) { i = 0;