From 4ab9808180b8a4cc59294b2f52687e5311f50635 Mon Sep 17 00:00:00 2001 From: Ben Franksen Date: Mon, 30 Mar 2020 21:46:46 +0200 Subject: [PATCH 01/15] make it clearer what the result of wrapArrayIndices will be --- modules/database/src/std/filters/arr.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/database/src/std/filters/arr.c b/modules/database/src/std/filters/arr.c index 813848076..cfa7118bf 100644 --- a/modules/database/src/std/filters/arr.c +++ b/modules/database/src/std/filters/arr.c @@ -77,8 +77,6 @@ static void freeArray(db_field_log *pfl) static long wrapArrayIndices(long *start, const long increment, long *end, const long no_elements) { - long len = 0; - if (*start < 0) *start = no_elements + *start; if (*start < 0) *start = 0; if (*start > no_elements) *start = no_elements; @@ -87,8 +85,10 @@ static long wrapArrayIndices(long *start, const long increment, long *end, if (*end < 0) *end = 0; if (*end >= no_elements) *end = no_elements - 1; - if (*end - *start >= 0) len = 1 + (*end - *start) / increment; - return len; + if (*end - *start >= 0) + return 1 + (*end - *start) / increment; + else + return 0; } static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) From 27fe3e4468ec62d4d17f775c4aced939a666ba36 Mon Sep 17 00:00:00 2001 From: Ben Franksen Date: Mon, 30 Mar 2020 21:34:32 +0200 Subject: [PATCH 02/15] refactor db_field_log and filters to get rid of dbfl_type_rec This refactor simplifies and streamlines the code associated with server side filters. Apart from immediate benefits (clearer code, less duplication) it is also hoped that this will make it easier to add write filters. The data pointer dbfl_ref.field can now either point to a copy owned by a filter, or it can point to the original data owned by a record. In the latter case, the dbfl_ref.dtor is NULL. The dbExtractArray* functions are unified to the single function dbExtractArray and stripped of conversion functionality. This is redundant because we always call dbGet after applying filters, which takes care of conversion. Accordingly, dbChannelMakeArrayCopy is now obsolete and its single use (in the ts filter) replaced with dbExtractArray. Instead, we add the helper function dbChannelGetArrayInfo to wrap the common boilerplate around calls to the get_array_info method, used in both arr.c and ts.c. --- modules/database/src/ioc/db/dbAccess.c | 39 +++--- modules/database/src/ioc/db/dbChannel.c | 72 +++-------- modules/database/src/ioc/db/dbChannel.h | 8 +- modules/database/src/ioc/db/dbEvent.c | 69 ++++++----- modules/database/src/ioc/db/dbExtractArray.c | 70 +++-------- modules/database/src/ioc/db/dbExtractArray.h | 35 +++++- modules/database/src/ioc/db/db_field_log.h | 53 ++++---- modules/database/src/std/filters/arr.c | 116 +++++++----------- modules/database/src/std/filters/ts.c | 41 +++++-- modules/database/test/ioc/db/dbChArrTest.cpp | 2 +- modules/database/test/std/filters/arrTest.cpp | 6 +- modules/database/test/std/filters/dbndTest.c | 9 +- 12 files changed, 239 insertions(+), 281 deletions(-) diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c index d7e5d0890..39e6e9a98 100644 --- a/modules/database/src/ioc/db/dbAccess.c +++ b/modules/database/src/ioc/db/dbAccess.c @@ -339,7 +339,7 @@ static void getOptions(DBADDR *paddr, char **poriginal, long *options, dbCommon *pcommon; char *pbuffer = *poriginal; - if (!pfl || pfl->type == dbfl_type_rec) + if (!pfl) field_type = paddr->field_type; else field_type = pfl->field_type; @@ -349,7 +349,7 @@ static void getOptions(DBADDR *paddr, char **poriginal, long *options, if( (*options) & DBR_STATUS ) { unsigned short *pushort = (unsigned short *)pbuffer; - if (!pfl || pfl->type == dbfl_type_rec) { + if (!pfl) { *pushort++ = pcommon->stat; *pushort++ = pcommon->sevr; } else { @@ -383,7 +383,7 @@ static void getOptions(DBADDR *paddr, char **poriginal, long *options, if( (*options) & DBR_TIME ) { epicsUInt32 *ptime = (epicsUInt32 *)pbuffer; - if (!pfl || pfl->type == dbfl_type_rec) { + if (!pfl) { *ptime++ = pcommon->time.secPastEpoch; *ptime++ = pcommon->time.nsec; } else { @@ -904,22 +904,23 @@ long dbGet(DBADDR *paddr, short dbrType, if (nRequest && *nRequest == 0) return 0; - if (!pfl || pfl->type == dbfl_type_rec) { + if (!pfl) { field_type = paddr->field_type; no_elements = capacity = paddr->no_elements; - - /* Update field info from record - * may modify paddr->pfield - */ - if (paddr->pfldDes->special == SPC_DBADDR && - (prset = dbGetRset(paddr)) && - prset->get_array_info) { - status = prset->get_array_info(paddr, &no_elements, &offset); - } else - offset = 0; } else { field_type = pfl->field_type; no_elements = capacity = pfl->no_elements; + } + + /* Update field info from record + * may modify paddr->pfield + */ + if ((!pfl || (pfl->type==dbfl_type_ref && !pfl->u.r.dtor)) && + paddr->pfldDes->special == SPC_DBADDR && + (prset = dbGetRset(paddr)) && + prset->get_array_info) { + status = prset->get_array_info(paddr, &no_elements, &offset); + } else { offset = 0; } @@ -951,7 +952,7 @@ long dbGet(DBADDR *paddr, short dbrType, goto done; } - if (!pfl || pfl->type == dbfl_type_rec) { + if (!pfl) { status = dbFastGetConvertRoutine[field_type][dbrType] (paddr->pfield, pbuf, paddr); } else { @@ -964,6 +965,7 @@ long dbGet(DBADDR *paddr, short dbrType, localAddr.field_type = pfl->field_type; localAddr.field_size = pfl->field_size; + /* not used by dbFastConvert: */ localAddr.no_elements = pfl->no_elements; if (pfl->type == dbfl_type_val) localAddr.pfield = (char *) &pfl->u.v.field; @@ -979,6 +981,8 @@ long dbGet(DBADDR *paddr, short dbrType, if (nRequest) { if (no_elements < *nRequest) *nRequest = no_elements; + if (capacity < *nRequest) + *nRequest = capacity; n = *nRequest; } else { n = 1; @@ -995,8 +999,8 @@ long dbGet(DBADDR *paddr, short dbrType, } /* convert data into the caller's buffer */ if (n <= 0) { - ;/*do nothing*/ - } else if (!pfl || pfl->type == dbfl_type_rec) { + ; /*do nothing */ + } else if (!pfl) { status = convert(paddr, pbuf, n, capacity, offset); } else { DBADDR localAddr = *paddr; /* Structure copy */ @@ -1008,6 +1012,7 @@ long dbGet(DBADDR *paddr, short dbrType, localAddr.field_type = pfl->field_type; localAddr.field_size = pfl->field_size; + /* not used by dbConvert, it uses the passed capacity instead: */ localAddr.no_elements = pfl->no_elements; if (pfl->type == dbfl_type_val) localAddr.pfield = (char *) &pfl->u.v.field; diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c index b6f53f797..c71d8426f 100644 --- a/modules/database/src/ioc/db/dbChannel.c +++ b/modules/database/src/ioc/db/dbChannel.c @@ -52,14 +52,12 @@ typedef struct parseContext { static void *dbChannelFreeList; static void *chFilterFreeList; -static void *dbchStringFreeList; void dbChannelExit(void) { freeListCleanup(dbChannelFreeList); freeListCleanup(chFilterFreeList); - freeListCleanup(dbchStringFreeList); - dbChannelFreeList = chFilterFreeList = dbchStringFreeList = NULL; + dbChannelFreeList = chFilterFreeList = NULL; } void dbChannelInit (void) @@ -69,7 +67,6 @@ void dbChannelInit (void) freeListInitPvt(&dbChannelFreeList, sizeof(dbChannel), 128); freeListInitPvt(&chFilterFreeList, sizeof(chFilter), 64); - freeListInitPvt(&dbchStringFreeList, sizeof(epicsOldString), 128); db_init_event_freelists(); } @@ -449,28 +446,6 @@ static long parseArrayRange(dbChannel* chan, const char *pname, const char **ppn return status; } -/* Stolen from dbAccess.c: */ -static short mapDBFToDBR[DBF_NTYPES] = - { - /* DBF_STRING => */DBR_STRING, - /* DBF_CHAR => */DBR_CHAR, - /* DBF_UCHAR => */DBR_UCHAR, - /* DBF_SHORT => */DBR_SHORT, - /* DBF_USHORT => */DBR_USHORT, - /* DBF_LONG => */DBR_LONG, - /* DBF_ULONG => */DBR_ULONG, - /* DBF_INT64 => */DBR_INT64, - /* DBF_UINT64 => */DBR_UINT64, - /* DBF_FLOAT => */DBR_FLOAT, - /* DBF_DOUBLE => */DBR_DOUBLE, - /* DBF_ENUM, => */DBR_ENUM, - /* DBF_MENU, => */DBR_ENUM, - /* DBF_DEVICE => */DBR_ENUM, - /* DBF_INLINK => */DBR_STRING, - /* DBF_OUTLINK => */DBR_STRING, - /* DBF_FWDLINK => */DBR_STRING, - /* DBF_NOACCESS => */DBR_NOACCESS }; - dbChannel * dbChannelCreate(const char *name) { const char *pname = name; @@ -743,37 +718,24 @@ void dbChannelDelete(dbChannel *chan) freeListFree(dbChannelFreeList, chan); } -static void freeArray(db_field_log *pfl) { - if (pfl->field_type == DBF_STRING && pfl->no_elements == 1) { - freeListFree(dbchStringFreeList, pfl->u.r.field); - } else { - free(pfl->u.r.field); - } -} - -void dbChannelMakeArrayCopy(void *pvt, db_field_log *pfl, dbChannel *chan) +/* + * Helper function to adjust no_elements, offset, and pfield + * when copying an array from a record. + */ +void dbChannelGetArrayInfo(dbChannel *chan, + void **pfield, long *no_elements, long *offset) { - void *p; - struct dbCommon *prec = dbChannelRecord(chan); - - if (pfl->type != dbfl_type_rec) return; - - pfl->type = dbfl_type_ref; - pfl->stat = prec->stat; - pfl->sevr = prec->sevr; - pfl->time = prec->time; - pfl->field_type = chan->addr.field_type; - pfl->no_elements = chan->addr.no_elements; - pfl->field_size = chan->addr.field_size; - pfl->u.r.dtor = freeArray; - pfl->u.r.pvt = pvt; - if (pfl->field_type == DBF_STRING && pfl->no_elements == 1) { - p = freeListCalloc(dbchStringFreeList); - } else { - p = calloc(pfl->no_elements, pfl->field_size); + rset *prset; + if (dbChannelSpecial(chan) == SPC_DBADDR && + (prset = dbGetRset(&chan->addr)) && + prset->get_array_info) + { + void *pfieldsave = dbChannelField(chan); + /* it is expected that this call always succeeds */ + prset->get_array_info(&chan->addr, no_elements, offset); + *pfield = dbChannelField(chan); + dbChannelField(chan) = pfieldsave; } - if (p) dbGet(&chan->addr, mapDBFToDBR[pfl->field_type], p, NULL, &pfl->no_elements, NULL); - pfl->u.r.field = p; } /* FIXME: Do these belong in a different file? */ diff --git a/modules/database/src/ioc/db/dbChannel.h b/modules/database/src/ioc/db/dbChannel.h index 1ca02bbb6..063d91aa5 100644 --- a/modules/database/src/ioc/db/dbChannel.h +++ b/modules/database/src/ioc/db/dbChannel.h @@ -65,8 +65,9 @@ typedef struct dbChannel { /* Prototype for the channel event function that is called in filter stacks * * When invoked the scan lock for the record associated with 'chan' _may_ be locked. - * If pLog->type==dbfl_type_rec then dbScanLock() must be called before copying - * data out of the associated record. + * If pLog->type==dbfl_type_ref and pLog->u.r.dtor is NULL, then dbScanLock() must + * be called before accessing the data, as this indicates the data is owned by the + * record. * * This function has ownership of the field log pLog, if it wishes to discard * this update it should free the field log with db_delete_field_log() and @@ -225,7 +226,8 @@ DBCORE_API void dbRegisterFilter(const char *key, const chFilterIf *fif, void *p DBCORE_API db_field_log* dbChannelRunPreChain(dbChannel *chan, db_field_log *pLogIn); DBCORE_API db_field_log* dbChannelRunPostChain(dbChannel *chan, db_field_log *pLogIn); DBCORE_API const chFilterPlugin * dbFindFilter(const char *key, size_t len); -DBCORE_API void dbChannelMakeArrayCopy(void *pvt, db_field_log *pfl, dbChannel *chan); +DBCORE_API void dbChannelGetArrayInfo(dbChannel *chan, + void **pfield, long *no_elements, long *offset); #ifdef __cplusplus } diff --git a/modules/database/src/ioc/db/dbEvent.c b/modules/database/src/ioc/db/dbEvent.c index 437cb6d5d..1db01f2cd 100644 --- a/modules/database/src/ioc/db/dbEvent.c +++ b/modules/database/src/ioc/db/dbEvent.c @@ -668,27 +668,21 @@ int db_post_extra_labor (dbEventCtx ctx) return DB_EVENT_OK; } -/* - * DB_CREATE_EVENT_LOG() - * - * NOTE: This assumes that the db scan lock is already applied - * (as it copies data from the record) - */ -db_field_log* db_create_event_log (struct evSubscrip *pevent) +static db_field_log* db_create_field_log (struct dbChannel *chan, int use_val) { db_field_log *pLog = (db_field_log *) freeListCalloc(dbevFieldLogFreeList); if (pLog) { - struct dbChannel *chan = pevent->chan; struct dbCommon *prec = dbChannelRecord(chan); - pLog->ctx = dbfl_context_event; - if (pevent->useValque) { + pLog->stat = prec->stat; + pLog->sevr = prec->sevr; + pLog->time = prec->time; + pLog->field_type = dbChannelFieldType(chan); + pLog->field_size = dbChannelFieldSize(chan); + pLog->no_elements = dbChannelElements(chan); + + if (use_val) { pLog->type = dbfl_type_val; - pLog->stat = prec->stat; - pLog->sevr = prec->sevr; - pLog->time = prec->time; - pLog->field_type = dbChannelFieldType(chan); - pLog->no_elements = dbChannelElements(chan); /* * use memcpy to avoid a bus error on * union copy of char in the db at an odd @@ -698,23 +692,46 @@ db_field_log* db_create_event_log (struct evSubscrip *pevent) dbChannelField(chan), dbChannelFieldSize(chan)); } else { - pLog->type = dbfl_type_rec; + pLog->type = dbfl_type_ref; + + /* don't make a copy yet, just reference the field value */ + pLog->u.r.field = dbChannelField(chan); + /* indicate field value still owned by record */ + pLog->u.r.dtor = NULL; + /* no private data yet, may be set by a filter */ + pLog->u.r.pvt = NULL; } } return pLog; } +/* + * DB_CREATE_EVENT_LOG() + * + * NOTE: This assumes that the db scan lock is already applied + * (as it calls rset->get_array_info) + */ +db_field_log* db_create_event_log (struct evSubscrip *pevent) +{ + db_field_log *pLog = db_create_field_log(pevent->chan, pevent->useValque); + if (pLog) { + pLog->ctx = dbfl_context_event; + } + return pLog; +} + /* * DB_CREATE_READ_LOG() * */ db_field_log* db_create_read_log (struct dbChannel *chan) { - db_field_log *pLog = (db_field_log *) freeListCalloc(dbevFieldLogFreeList); - + db_field_log *pLog = db_create_field_log(chan, + dbChannelElements(chan) == 1 && + dbChannelSpecial(chan) != SPC_DBADDR && + dbChannelFieldSize(chan) <= sizeof(union native_value)); if (pLog) { pLog->ctx = dbfl_context_read; - pLog->type = dbfl_type_rec; } return pLog; } @@ -737,20 +754,6 @@ static void db_queue_event_log (evSubscrip *pevent, db_field_log *pLog) LOCKEVQUE (ev_que); - /* - * if we have an event on the queue and both the last - * event on the queue and the current event are emtpy - * (i.e. of type dbfl_type_rec), simply ignore duplicate - * events (saving empty events serves no purpose) - */ - if (pevent->npend > 0u && - (*pevent->pLastLog)->type == dbfl_type_rec && - pLog->type == dbfl_type_rec) { - db_delete_field_log(pLog); - UNLOCKEVQUE (ev_que); - return; - } - /* * add to task local event que */ diff --git a/modules/database/src/ioc/db/dbExtractArray.c b/modules/database/src/ioc/db/dbExtractArray.c index a7dcf4d0c..197e66c3a 100644 --- a/modules/database/src/ioc/db/dbExtractArray.c +++ b/modules/database/src/ioc/db/dbExtractArray.c @@ -14,11 +14,12 @@ /* * Author: Ralph Lange * - * based on dbConvert.c + * based on dbConvert.c, see copyNoConvert * written by: Bob Dalesio, Marty Kraimer */ #include +#include #include "epicsTypes.h" @@ -26,61 +27,30 @@ #include "dbAddr.h" #include "dbExtractArray.h" -void dbExtractArrayFromRec(const dbAddr *paddr, void *pto, - long nRequest, long no_elements, long offset, long increment) +void dbExtractArray(const void *pfrom, void *pto, short field_size, + long nRequest, long no_elements, long offset, long increment) { char *pdst = (char *) pto; - char *psrc = (char *) paddr->pfield; - long nUpperPart; - int i; - short srcSize = paddr->field_size; - short dstSize = srcSize; - char isString = (paddr->field_type == DBF_STRING); + const char *psrc = (char *) pfrom; - if (nRequest > no_elements) nRequest = no_elements; - if (isString && srcSize > MAX_STRING_SIZE) dstSize = MAX_STRING_SIZE; - - if (increment == 1 && dstSize == srcSize) { - nUpperPart = nRequest < no_elements - offset ? nRequest : no_elements - offset; - memcpy(pdst, &psrc[offset * srcSize], dstSize * nUpperPart); - if (nRequest > nUpperPart) - memcpy(&pdst[dstSize * nUpperPart], psrc, dstSize * (nRequest - nUpperPart)); - if (isString) - for (i = 1; i <= nRequest; i++) - pdst[dstSize*i-1] = '\0'; - } else { - for (; nRequest > 0; nRequest--, pdst += dstSize, offset += increment) { - offset %= no_elements; - memcpy(pdst, &psrc[offset*srcSize], dstSize); - if (isString) pdst[dstSize-1] = '\0'; - } - } -} - -void dbExtractArrayFromBuf(const void *pfrom, void *pto, - short field_size, short field_type, - long nRequest, long no_elements, long offset, long increment) -{ - char *pdst = (char *) pto; - char *psrc = (char *) pfrom; - int i; - short srcSize = field_size; - short dstSize = srcSize; - char isString = (field_type == DBF_STRING); - - if (nRequest > no_elements) nRequest = no_elements; - if (offset > no_elements - 1) offset = no_elements - 1; - if (isString && dstSize >= MAX_STRING_SIZE) dstSize = MAX_STRING_SIZE - 1; + /* assert preconditions */ + assert(nRequest >= 0); + assert(no_elements >= 0); + assert(increment > 0); + assert(0 <= offset); + assert(offset < no_elements); if (increment == 1) { - memcpy(pdst, &psrc[offset * srcSize], dstSize * nRequest); - if (isString) - for (i = 1; i <= nRequest; i++) - pdst[dstSize*i] = '\0'; + long nUpperPart = + nRequest < no_elements - offset ? nRequest : no_elements - offset; + memcpy(pdst, psrc + (offset * field_size), field_size * nUpperPart); + if (nRequest > nUpperPart) + memcpy(pdst + (field_size * nUpperPart), psrc, + field_size * (nRequest - nUpperPart)); } else { - for (; nRequest > 0; nRequest--, pdst += srcSize, offset += increment) { - memcpy(pdst, &psrc[offset*srcSize], dstSize); - if (isString) pdst[dstSize] = '\0'; + for (; nRequest > 0; nRequest--, pdst += field_size, offset += increment) { + offset %= no_elements; + memcpy(pdst, psrc + (offset * field_size), field_size); } } } diff --git a/modules/database/src/ioc/db/dbExtractArray.h b/modules/database/src/ioc/db/dbExtractArray.h index b44bd15fc..9755ac570 100644 --- a/modules/database/src/ioc/db/dbExtractArray.h +++ b/modules/database/src/ioc/db/dbExtractArray.h @@ -22,11 +22,36 @@ extern "C" { #endif -epicsShareFunc void dbExtractArrayFromRec(const DBADDR *paddr, void *pto, - long nRequest, long no_elements, long offset, long increment); -epicsShareFunc void dbExtractArrayFromBuf(const void *pfrom, void *pto, - short field_size, short field_type, - long nRequest, long no_elements, long offset, long increment); +/** @brief Make a copy of parts of an array. + * + * The source array may or may not be a record field. + * + * The increment parameter is used to support array filters; it + * means: copy only every increment'th element, starting at offset. + * + * The offset and no_elements parameters are used to support the + * circular buffer feature of record fields: elements before offset + * are treated as if they came right after no_elements. + * + * This function does not do any conversion on the array elements. + * + * Preconditions: + * nRequest >= 0, no_elements >= 0, increment > 0 + * 0 <= offset < no_elements + * pto points to a buffer with at least field_size*nRequest bytes + * pfrom points to a buffer with exactly field_size*no_elements bytes + * + * @param pfrom Pointer to source array. + * @param pto Pointer to target array. + * @param field_size Size of an array element. + * @param nRequest Number of elements to copy. + * @param no_elements Number of elements in source array. + * @param offset Wrap-around point in source array. + * @param increment Copy only every increment'th element. + */ +epicsShareFunc void dbExtractArray(const void *pfrom, void *pto, + short field_size, long nRequest, long no_elements, long offset, + long increment); #ifdef __cplusplus } diff --git a/modules/database/src/ioc/db/db_field_log.h b/modules/database/src/ioc/db/db_field_log.h index 5a40f5fad..a043899ae 100644 --- a/modules/database/src/ioc/db/db_field_log.h +++ b/modules/database/src/ioc/db/db_field_log.h @@ -57,20 +57,31 @@ union native_value { struct db_field_log; typedef void (dbfl_freeFunc)(struct db_field_log *pfl); -/* Types of db_field_log: rec = use record, val = val inside, ref = reference inside */ +/* + * A db_field_log has one of two types: + * + * dbfl_type_ref - Reference to value + * Used for variable size (array) data types. Meta-data + * is stored in the field log, but value data is stored externally. + * Only the dbfl_ref side of the data union is valid. + * + * dbfl_type_val - Internal value + * Used to store small scalar data. Meta-data and value are + * present in this structure and no external references are used. + * Only the dbfl_val side of the data union is valid. + */ typedef enum dbfl_type { - dbfl_type_rec = 0, dbfl_type_val, dbfl_type_ref } dbfl_type; /* Context of db_field_log: event = subscription update, read = read reply */ typedef enum dbfl_context { - dbfl_context_read = 0, + dbfl_context_read, dbfl_context_event } dbfl_context; -#define dbflTypeStr(t) (t==dbfl_type_val?"val":t==dbfl_type_rec?"rec":"ref") +#define dbflTypeStr(t) (t==dbfl_type_val?"val":"ref") struct dbfl_val { union native_value field; /* Field value */ @@ -82,6 +93,8 @@ struct dbfl_val { * db_delete_field_log(). Any code which changes a dbfl_type_ref * field log to another type, or to reference different data, * must explicitly call the dtor function. + * If the dtor is NULL, then this means the array data is still owned + * by a record. */ struct dbfl_ref { dbfl_freeFunc *dtor; /* Callback to free filter-allocated resources */ @@ -89,8 +102,11 @@ struct dbfl_ref { void *field; /* Field value */ }; +/* + * Note that field_size may be larger than MAX_STRING_SIZE. + */ typedef struct db_field_log { - unsigned int type:2; /* type (union) selector */ + unsigned int type:1; /* type (union) selector */ /* ctx is used for all types */ unsigned int ctx:1; /* context (operation type) */ /* the following are used for value and reference types */ @@ -98,37 +114,14 @@ typedef struct db_field_log { unsigned short stat; /* Alarm Status */ unsigned short sevr; /* Alarm Severity */ short field_type; /* DBF type of data */ - short field_size; /* Data size */ - long no_elements; /* No of array elements */ + short field_size; /* Size of a single element */ + long no_elements; /* No of valid array elements */ union { struct dbfl_val v; struct dbfl_ref r; } u; } db_field_log; -/* - * A db_field_log will in one of three types: - * - * dbfl_type_rec - Reference to record - * The field log stores no data itself. Data must instead be taken - * via the dbChannel* which must always be provided when along - * with the field log. - * For this type only the 'type' and 'ctx' members are used. - * - * dbfl_type_ref - Reference to outside value - * Used for variable size (array) data types. Meta-data - * is stored in the field log, but value data is stored externally - * (see struct dbfl_ref). - * For this type all meta-data members are used. The dbfl_ref side of the - * data union is used. - * - * dbfl_type_val - Internal value - * Used to store small scalar data. Meta-data and value are - * present in this structure and no external references are used. - * For this type all meta-data members are used. The dbfl_val side of the - * data union is used. - */ - #ifdef __cplusplus } #endif diff --git a/modules/database/src/std/filters/arr.c b/modules/database/src/std/filters/arr.c index cfa7118bf..5f251844a 100644 --- a/modules/database/src/std/filters/arr.c +++ b/modules/database/src/std/filters/arr.c @@ -13,16 +13,14 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "chfPlugin.h" +#include "dbAccessDefs.h" +#include "dbExtractArray.h" +#include "db_field_log.h" +#include "dbLock.h" +#include "epicsExit.h" +#include "freeList.h" +#include "epicsExport.h" typedef struct myStruct { epicsInt32 start; @@ -46,6 +44,8 @@ static void * allocPvt(void) myStruct *my = (myStruct*) freeListCalloc(myStructFreeList); if (!my) return NULL; + /* defaults */ + my->start = 0; my->incr = 1; my->end = -1; return (void *) my; @@ -94,78 +94,56 @@ static long wrapArrayIndices(long *start, const long increment, long *end, static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { myStruct *my = (myStruct*) pvt; - struct dbCommon *prec; - rset *prset; + int must_lock; long start = my->start; long end = my->end; - long nTarget = 0; + long nTarget; + void *pTarget; long offset = 0; - long nSource = dbChannelElements(chan); - long capacity = nSource; - void *pdst; + long nSource = pfl->no_elements; + void *pSource = pfl->u.r.field; switch (pfl->type) { case dbfl_type_val: - /* Only filter arrays */ + /* TODO Treat scalars as arrays with 1 element */ break; - case dbfl_type_rec: - /* Extract from record */ - if (dbChannelSpecial(chan) == SPC_DBADDR && - nSource > 1 && - (prset = dbGetRset(&chan->addr)) && - prset->get_array_info) - { - void *pfieldsave = dbChannelField(chan); - prec = dbChannelRecord(chan); - dbScanLock(prec); - prset->get_array_info(&chan->addr, &nSource, &offset); - nTarget = wrapArrayIndices(&start, my->incr, &end, nSource); - pfl->type = dbfl_type_ref; - pfl->stat = prec->stat; - pfl->sevr = prec->sevr; - pfl->time = prec->time; - pfl->field_type = dbChannelFieldType(chan); - pfl->field_size = dbChannelFieldSize(chan); - pfl->no_elements = nTarget; - if (nTarget) { - pdst = freeListCalloc(my->arrayFreeList); - if (pdst) { - pfl->u.r.dtor = freeArray; - pfl->u.r.pvt = my->arrayFreeList; - offset = (offset + start) % dbChannelElements(chan); - dbExtractArrayFromRec(&chan->addr, pdst, nTarget, capacity, - offset, my->incr); - pfl->u.r.field = pdst; - } - } - dbScanUnlock(prec); - dbChannelField(chan) = pfieldsave; - } - break; - - /* Extract from buffer */ case dbfl_type_ref: - pdst = NULL; - nSource = pfl->no_elements; - nTarget = wrapArrayIndices(&start, my->incr, &end, nSource); - pfl->no_elements = nTarget; - if (nTarget) { - /* Copy the data out */ - void *psrc = pfl->u.r.field; - - pdst = freeListCalloc(my->arrayFreeList); - if (!pdst) break; - offset = start; - dbExtractArrayFromBuf(psrc, pdst, pfl->field_size, pfl->field_type, - nTarget, nSource, offset, my->incr); + must_lock = !pfl->u.r.dtor; + if (must_lock) { + dbScanLock(dbChannelRecord(chan)); + dbChannelGetArrayInfo(chan, &pSource, &nSource, &offset); } - if (pfl->u.r.dtor) pfl->u.r.dtor(pfl); - if (nTarget) { + nTarget = wrapArrayIndices(&start, my->incr, &end, nSource); + if (nTarget > 0) { + /* copy the data */ + pTarget = freeListCalloc(my->arrayFreeList); + if (!pTarget) break; + /* must do the wrap-around with the original no_elements */ + offset = (offset + start) % pfl->no_elements; + dbExtractArray(pSource, pTarget, pfl->field_size, + nTarget, pfl->no_elements, offset, my->incr); + if (pfl->u.r.dtor) pfl->u.r.dtor(pfl); + pfl->u.r.field = pTarget; pfl->u.r.dtor = freeArray; pfl->u.r.pvt = my->arrayFreeList; - pfl->u.r.field = pdst; } + /* Adjust no_elements to refer to the new pTarget. + * + * Setting pfl->no_elements outside of the "if" clause above is + * done to make requests fail if nTarget is zero, that is, if all + * elements selected by the filter are outside the array bounds. + * TODO: + * It would be possible to lift this restriction by interpreting + * a request with *no* number of elements (NULL pointer) as scalar + * (meaning: fail if we get less than one element); in contrast, + * a request that explicitly specifies one element would be + * interpreted as an array request, for which zero elements would + * be a normal expected result. + */ + pfl->no_elements = nTarget; + if (must_lock) + dbScanUnlock(dbChannelRecord(chan)); break; } return pfl; diff --git a/modules/database/src/std/filters/ts.c b/modules/database/src/std/filters/ts.c index 8a07c3f2f..0feadb0f5 100644 --- a/modules/database/src/std/filters/ts.c +++ b/modules/database/src/std/filters/ts.c @@ -12,21 +12,44 @@ */ #include +#include +#include -#include -#include -#include -#include +#include "chfPlugin.h" +#include "db_field_log.h" +#include "dbExtractArray.h" +#include "dbLock.h" +#include "epicsExport.h" + +/* + * The size of the data is different for each channel, and can even + * change at runtime, so a freeList doesn't make much sense here. + */ +static void freeArray(db_field_log *pfl) { + free(pfl->u.r.field); +} static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { epicsTimeStamp now; epicsTimeGetCurrent(&now); - /* If string or array, must make a copy (to ensure coherence between time and data) */ - if (pfl->type == dbfl_type_rec) { - dbScanLock(dbChannelRecord(chan)); - dbChannelMakeArrayCopy(pvt, pfl, chan); - dbScanUnlock(dbChannelRecord(chan)); + /* If reference and not already copied, + must make a copy (to ensure coherence between time and data) */ + if (pfl->type == dbfl_type_ref && !pfl->u.r.dtor) { + void *pTarget = calloc(pfl->no_elements, pfl->field_size); + void *pSource = pfl->u.r.field; + if (pTarget) { + long offset = 0; + long nSource = pfl->no_elements; + dbScanLock(dbChannelRecord(chan)); + dbChannelGetArrayInfo(chan, &pSource, &nSource, &offset); + dbExtractArray(pSource, pTarget, pfl->field_size, + nSource, pfl->no_elements, offset, 1); + pfl->u.r.field = pTarget; + pfl->u.r.dtor = freeArray; + pfl->u.r.pvt = pvt; + dbScanUnlock(dbChannelRecord(chan)); + } } pfl->time = now; diff --git a/modules/database/test/ioc/db/dbChArrTest.cpp b/modules/database/test/ioc/db/dbChArrTest.cpp index 90702b52e..9965852a8 100644 --- a/modules/database/test/ioc/db/dbChArrTest.cpp +++ b/modules/database/test/ioc/db/dbChArrTest.cpp @@ -131,7 +131,7 @@ static void check(short dbr_type) { memset(buf, 0, sizeof(buf)); \ (void) dbPutField(&offaddr, DBR_LONG, &off, 1); \ pfl = db_create_read_log(pch); \ - testOk(pfl && pfl->type == dbfl_type_rec, "Valid pfl, type = rec"); \ + testOk(pfl && pfl->type == dbfl_type_ref, "Valid pfl, type = ref"); \ testOk(!dbChannelGetField(pch, DBR_LONG, buf, NULL, &req, pfl), "Got Field value"); \ testOk(req == Size, "Got %ld elements (expected %d)", req, Size); \ if (!testOk(!memcmp(buf, Expected, sizeof(Expected)), "Data correct")) \ diff --git a/modules/database/test/std/filters/arrTest.cpp b/modules/database/test/std/filters/arrTest.cpp index bd83bd8ab..dfbbf463f 100644 --- a/modules/database/test/std/filters/arrTest.cpp +++ b/modules/database/test/std/filters/arrTest.cpp @@ -73,9 +73,9 @@ static int fl_equals_array(short type, const db_field_log *pfl1, void *p2) { } break; case DBR_STRING: - if (strtol(&((const char*)pfl1->u.r.field)[i*MAX_STRING_SIZE], NULL, 0) != ((epicsInt32*)p2)[i]) { + if (strtol(&((const char*)pfl1->u.r.field)[i*pfl1->field_size], NULL, 0) != ((epicsInt32*)p2)[i]) { testDiag("at index=%d: field log has '%s', should be '%d'", - i, &((const char*)pfl1->u.r.field)[i*MAX_STRING_SIZE], ((epicsInt32*)p2)[i]); + i, &((const char*)pfl1->u.r.field)[i*pfl1->field_size], ((epicsInt32*)p2)[i]); return 0; } break; @@ -120,7 +120,7 @@ static void testHead (const char *title, const char *typ = "") { off = Offset; \ (void) dbPutField(&offaddr, DBR_LONG, &off, 1); \ pfl = db_create_read_log(pch); \ - testOk(pfl->type == dbfl_type_rec, "original field log has type rec"); \ + testOk(pfl->type == dbfl_type_ref, "original field log has type ref"); \ pfl2 = dbChannelRunPostChain(pch, pfl); \ testOk(pfl2 == pfl, "call does not drop or replace field_log"); \ testOk(pfl->type == dbfl_type_ref, "filtered field log has type ref"); \ diff --git a/modules/database/test/std/filters/dbndTest.c b/modules/database/test/std/filters/dbndTest.c index e7897e28a..0206865fb 100644 --- a/modules/database/test/std/filters/dbndTest.c +++ b/modules/database/test/std/filters/dbndTest.c @@ -130,7 +130,7 @@ MAIN(dbndTest) dbEventCtx evtctx; int logsFree, logsFinal; - testPlan(77); + testPlan(72); testdbPrepare(); @@ -171,12 +171,9 @@ MAIN(dbndTest) "dbnd has one filter with argument in pre chain"); testOk((ellCount(&pch->post_chain) == 0), "dbnd has no filter in post chain"); - /* Field logs of type ref and rec: pass any update */ - - testHead("Field logs of type ref and rec"); - fl1.type = dbfl_type_rec; - mustPassTwice(pch, &fl1, "abs field_log=rec", 0., 0); + /* Field logs of type ref: pass any update */ + testHead("Field logs of type ref"); fl1.type = dbfl_type_ref; mustPassTwice(pch, &fl1, "abs field_log=ref", 0., 0); From 85822f3051d2236144bb46dc2c24b7e38143e531 Mon Sep 17 00:00:00 2001 From: Ben Franksen Date: Wed, 1 Apr 2020 10:42:22 +0200 Subject: [PATCH 03/15] add macro dbfl_has_copy to db_field_log.h and use it in dbAccess.c It encapsulates the slightly tricky logic to decide whether a pointer to a db_field_log has ownership of the data or not. --- modules/database/src/ioc/db/dbAccess.c | 6 +++--- modules/database/src/ioc/db/dbChannel.h | 5 ++--- modules/database/src/ioc/db/db_field_log.h | 16 ++++++++++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c index 39e6e9a98..3f7554a58 100644 --- a/modules/database/src/ioc/db/dbAccess.c +++ b/modules/database/src/ioc/db/dbAccess.c @@ -912,10 +912,10 @@ long dbGet(DBADDR *paddr, short dbrType, no_elements = capacity = pfl->no_elements; } - /* Update field info from record - * may modify paddr->pfield + /* Update field info from record (if neccessary); + * may modify paddr->pfield. */ - if ((!pfl || (pfl->type==dbfl_type_ref && !pfl->u.r.dtor)) && + if (!dbfl_has_copy(pfl) && paddr->pfldDes->special == SPC_DBADDR && (prset = dbGetRset(paddr)) && prset->get_array_info) { diff --git a/modules/database/src/ioc/db/dbChannel.h b/modules/database/src/ioc/db/dbChannel.h index 063d91aa5..ec86e9e28 100644 --- a/modules/database/src/ioc/db/dbChannel.h +++ b/modules/database/src/ioc/db/dbChannel.h @@ -65,9 +65,8 @@ typedef struct dbChannel { /* Prototype for the channel event function that is called in filter stacks * * When invoked the scan lock for the record associated with 'chan' _may_ be locked. - * If pLog->type==dbfl_type_ref and pLog->u.r.dtor is NULL, then dbScanLock() must - * be called before accessing the data, as this indicates the data is owned by the - * record. + * Unless dbfl_has_copy(pLog), it must call dbScanLock before accessing the data, + * as this indicates the data is still owned by the record. * * This function has ownership of the field log pLog, if it wishes to discard * this update it should free the field log with db_delete_field_log() and diff --git a/modules/database/src/ioc/db/db_field_log.h b/modules/database/src/ioc/db/db_field_log.h index a043899ae..222bad0c1 100644 --- a/modules/database/src/ioc/db/db_field_log.h +++ b/modules/database/src/ioc/db/db_field_log.h @@ -93,8 +93,8 @@ struct dbfl_val { * db_delete_field_log(). Any code which changes a dbfl_type_ref * field log to another type, or to reference different data, * must explicitly call the dtor function. - * If the dtor is NULL, then this means the array data is still owned - * by a record. + * If the dtor is NULL and no_elements > 0, then this means the array + * data is still owned by a record. See the macro dbfl_has_copy below. */ struct dbfl_ref { dbfl_freeFunc *dtor; /* Callback to free filter-allocated resources */ @@ -122,6 +122,18 @@ typedef struct db_field_log { } u; } db_field_log; +/* + * Whether a db_field_log* owns the field data. If this is the case, then the + * db_field_log is fully decoupled from the record: there is no need to lock + * the record when accessing the data, nor to call any rset methods (like + * get_array_info) because this has already been done when we made the copy. A + * special case here is that of no (remaining) data (i.e. no_elements==0). In + * this case, making a copy is redundant, so we have no dtor. But conceptually + * the db_field_log still owns the (empty) data. + */ +#define dbfl_has_copy(p)\ + ((p) && ((p)->type==dbfl_type_val || (p)->u.r.dtor || (p)->no_elements==0)) + #ifdef __cplusplus } #endif From 56f05d722dee4b8ca2968b8bface2737a3a9b185 Mon Sep 17 00:00:00 2001 From: Ben Franksen Date: Thu, 14 Jan 2021 17:38:58 +0100 Subject: [PATCH 04/15] fix in dbGet: decide use of db_field_log based on whether it has copy or not --- modules/database/src/ioc/db/dbAccess.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c index 3f7554a58..d50b2561d 100644 --- a/modules/database/src/ioc/db/dbAccess.c +++ b/modules/database/src/ioc/db/dbAccess.c @@ -952,7 +952,7 @@ long dbGet(DBADDR *paddr, short dbrType, goto done; } - if (!pfl) { + if (!dbfl_has_copy(pfl)) { status = dbFastGetConvertRoutine[field_type][dbrType] (paddr->pfield, pbuf, paddr); } else { @@ -1000,7 +1000,7 @@ long dbGet(DBADDR *paddr, short dbrType, /* convert data into the caller's buffer */ if (n <= 0) { ; /*do nothing */ - } else if (!pfl) { + } else if (!dbfl_has_copy(pfl)) { status = convert(paddr, pbuf, n, capacity, offset); } else { DBADDR localAddr = *paddr; /* Structure copy */ From 372e937717af65b903d7b9885b7c34e151c9bd86 Mon Sep 17 00:00:00 2001 From: Ben Franksen Date: Thu, 14 Jan 2021 17:45:25 +0100 Subject: [PATCH 05/15] add macro dbfl_pfield to db_field_log.h and use it in dbGet --- modules/database/src/ioc/db/dbAccess.c | 10 ++-------- modules/database/src/ioc/db/db_field_log.h | 3 +++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c index d50b2561d..bac208f02 100644 --- a/modules/database/src/ioc/db/dbAccess.c +++ b/modules/database/src/ioc/db/dbAccess.c @@ -967,10 +967,7 @@ long dbGet(DBADDR *paddr, short dbrType, localAddr.field_size = pfl->field_size; /* not used by dbFastConvert: */ localAddr.no_elements = pfl->no_elements; - if (pfl->type == dbfl_type_val) - localAddr.pfield = (char *) &pfl->u.v.field; - else - localAddr.pfield = (char *) pfl->u.r.field; + localAddr.pfield = dbfl_pfield(pfl); status = dbFastGetConvertRoutine[field_type][dbrType] (localAddr.pfield, pbuf, &localAddr); } @@ -1014,10 +1011,7 @@ long dbGet(DBADDR *paddr, short dbrType, localAddr.field_size = pfl->field_size; /* not used by dbConvert, it uses the passed capacity instead: */ localAddr.no_elements = pfl->no_elements; - if (pfl->type == dbfl_type_val) - localAddr.pfield = (char *) &pfl->u.v.field; - else - localAddr.pfield = (char *) pfl->u.r.field; + localAddr.pfield = dbfl_pfield(pfl); status = convert(&localAddr, pbuf, n, capacity, offset); } diff --git a/modules/database/src/ioc/db/db_field_log.h b/modules/database/src/ioc/db/db_field_log.h index 222bad0c1..e517d529f 100644 --- a/modules/database/src/ioc/db/db_field_log.h +++ b/modules/database/src/ioc/db/db_field_log.h @@ -134,6 +134,9 @@ typedef struct db_field_log { #define dbfl_has_copy(p)\ ((p) && ((p)->type==dbfl_type_val || (p)->u.r.dtor || (p)->no_elements==0)) +#define dbfl_pfield(p)\ + ((p)->type==dbfl_type_val ? &p->u.v.field : p->u.r.field) + #ifdef __cplusplus } #endif From 236bb2c671b00891d7d010a2a38e22894819987d Mon Sep 17 00:00:00 2001 From: Ben Franksen Date: Fri, 15 Jan 2021 14:59:33 +0100 Subject: [PATCH 06/15] fix an out-dated comment in the array filter code --- modules/database/src/std/filters/arr.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/modules/database/src/std/filters/arr.c b/modules/database/src/std/filters/arr.c index 5f251844a..ffe3fce8f 100644 --- a/modules/database/src/std/filters/arr.c +++ b/modules/database/src/std/filters/arr.c @@ -128,19 +128,7 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) pfl->u.r.dtor = freeArray; pfl->u.r.pvt = my->arrayFreeList; } - /* Adjust no_elements to refer to the new pTarget. - * - * Setting pfl->no_elements outside of the "if" clause above is - * done to make requests fail if nTarget is zero, that is, if all - * elements selected by the filter are outside the array bounds. - * TODO: - * It would be possible to lift this restriction by interpreting - * a request with *no* number of elements (NULL pointer) as scalar - * (meaning: fail if we get less than one element); in contrast, - * a request that explicitly specifies one element would be - * interpreted as an array request, for which zero elements would - * be a normal expected result. - */ + /* adjust no_elements (even if zero elements remain) */ pfl->no_elements = nTarget; if (must_lock) dbScanUnlock(dbChannelRecord(chan)); From ff5df5fbf3b2cfdf6ca9f2e5b454674546bafdec Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sun, 28 Feb 2021 20:21:41 -0600 Subject: [PATCH 07/15] Update version numbers after tagging --- configure/CONFIG_BASE_VERSION | 4 ++-- configure/CONFIG_CA_VERSION | 4 ++-- configure/CONFIG_DATABASE_VERSION | 4 ++-- configure/CONFIG_LIBCOM_VERSION | 4 ++-- documentation/RELEASE_NOTES.md | 9 +++++++++ 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/configure/CONFIG_BASE_VERSION b/configure/CONFIG_BASE_VERSION index a32f28c54..b0c57b763 100644 --- a/configure/CONFIG_BASE_VERSION +++ b/configure/CONFIG_BASE_VERSION @@ -52,11 +52,11 @@ EPICS_MODIFICATION = 5 # EPICS_PATCH_LEVEL must be a number (win32 resource file requirement) # Not included in the official EPICS version number if zero -EPICS_PATCH_LEVEL = 0 +EPICS_PATCH_LEVEL = 1 # Immediately after an official release the EPICS_PATCH_LEVEL is incremented # and the -DEV suffix is added (similar to the Maven -SNAPSHOT versions) -EPICS_DEV_SNAPSHOT= +EPICS_DEV_SNAPSHOT=-DEV # No changes should be needed below here diff --git a/configure/CONFIG_CA_VERSION b/configure/CONFIG_CA_VERSION index 3f8780e25..af570b0e7 100644 --- a/configure/CONFIG_CA_VERSION +++ b/configure/CONFIG_CA_VERSION @@ -2,11 +2,11 @@ EPICS_CA_MAJOR_VERSION = 4 EPICS_CA_MINOR_VERSION = 13 -EPICS_CA_MAINTENANCE_VERSION = 8 +EPICS_CA_MAINTENANCE_VERSION = 9 # Development flag, set to zero for release versions -EPICS_CA_DEVELOPMENT_FLAG = 0 +EPICS_CA_DEVELOPMENT_FLAG = 1 # Immediately after a release the MAINTENANCE_VERSION # will be incremented and the DEVELOPMENT_FLAG set to 1 diff --git a/configure/CONFIG_DATABASE_VERSION b/configure/CONFIG_DATABASE_VERSION index c62f11daa..2205a5386 100644 --- a/configure/CONFIG_DATABASE_VERSION +++ b/configure/CONFIG_DATABASE_VERSION @@ -2,11 +2,11 @@ EPICS_DATABASE_MAJOR_VERSION = 3 EPICS_DATABASE_MINOR_VERSION = 19 -EPICS_DATABASE_MAINTENANCE_VERSION = 0 +EPICS_DATABASE_MAINTENANCE_VERSION = 1 # Development flag, set to zero for release versions -EPICS_DATABASE_DEVELOPMENT_FLAG = 0 +EPICS_DATABASE_DEVELOPMENT_FLAG = 1 # Immediately after a release the MAINTENANCE_VERSION # will be incremented and the DEVELOPMENT_FLAG set to 1 diff --git a/configure/CONFIG_LIBCOM_VERSION b/configure/CONFIG_LIBCOM_VERSION index 27dc8d943..96f25ebaa 100644 --- a/configure/CONFIG_LIBCOM_VERSION +++ b/configure/CONFIG_LIBCOM_VERSION @@ -2,11 +2,11 @@ EPICS_LIBCOM_MAJOR_VERSION = 3 EPICS_LIBCOM_MINOR_VERSION = 19 -EPICS_LIBCOM_MAINTENANCE_VERSION = 0 +EPICS_LIBCOM_MAINTENANCE_VERSION = 1 # Development flag, set to zero for release versions -EPICS_LIBCOM_DEVELOPMENT_FLAG = 0 +EPICS_LIBCOM_DEVELOPMENT_FLAG = 1 # Immediately after a release the MAINTENANCE_VERSION # will be incremented and the DEVELOPMENT_FLAG set to 1 diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index dfb93e613..587b52448 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -11,6 +11,15 @@ release. The PVA submodules each have their own individual sets of release notes which should also be read to understand what has changed since earlier releases. +**This version of EPICS has not been released yet.** + +## Changes made on the 7.0 branch since 7.0.5 + + + + +----- + ## EPICS Release 7.0.5 ### Fix aai's Device Support Initialization From f8eb0be7a411ca689648dc994aaf423ce4f19052 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sun, 28 Feb 2021 21:39:28 -0600 Subject: [PATCH 08/15] Update submodules after release --- modules/normativeTypes | 2 +- modules/pvAccess | 2 +- modules/pvData | 2 +- modules/pvDatabase | 2 +- modules/pva2pva | 2 +- modules/pvaClient | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/normativeTypes b/modules/normativeTypes index 1250a3c23..7a2d264f2 160000 --- a/modules/normativeTypes +++ b/modules/normativeTypes @@ -1 +1 @@ -Subproject commit 1250a3c236f0aa92e0b5bd73647fd71d8a09360d +Subproject commit 7a2d264f2cb107bfd10adb23bc2b73d8323a79e4 diff --git a/modules/pvAccess b/modules/pvAccess index 64c284cd4..e1c1a4bc1 160000 --- a/modules/pvAccess +++ b/modules/pvAccess @@ -1 +1 @@ -Subproject commit 64c284cd41f114ee07e999a94ff55ae53a87c7e0 +Subproject commit e1c1a4bc1bad6933e57b199d58f74468401218b3 diff --git a/modules/pvData b/modules/pvData index b1c830387..d3b4976ea 160000 --- a/modules/pvData +++ b/modules/pvData @@ -1 +1 @@ -Subproject commit b1c8303870a04f1c3ee5a01a84aad2b2596e918c +Subproject commit d3b4976ea2b0d78075511f14d7f7bf9620dd4d14 diff --git a/modules/pvDatabase b/modules/pvDatabase index 09423edea..93a259cbd 160000 --- a/modules/pvDatabase +++ b/modules/pvDatabase @@ -1 +1 @@ -Subproject commit 09423edeabc4b46c0ff3a6a09c8c1268e3de291f +Subproject commit 93a259cbde56668c1bbe495b15cc3ede8b42ce30 diff --git a/modules/pva2pva b/modules/pva2pva index b8389ac6a..ad8b77e19 160000 --- a/modules/pva2pva +++ b/modules/pva2pva @@ -1 +1 @@ -Subproject commit b8389ac6a19679fe515fd74cddeb1e02467b1007 +Subproject commit ad8b77e19f7e2ae50c48b5d871bdbe7b0ee23b61 diff --git a/modules/pvaClient b/modules/pvaClient index bc9ac8422..efb263190 160000 --- a/modules/pvaClient +++ b/modules/pvaClient @@ -1 +1 @@ -Subproject commit bc9ac8422cb94a38c27385c3c6781aea30c2c8b8 +Subproject commit efb2631905cd61cc06041f5aac5be9017383a004 From 3c46542630823a272001aaab4e6fc265c7e03046 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 2 Mar 2021 06:47:43 -0800 Subject: [PATCH 09/15] posix: epicsMutexOsdShowAll check for PI support --- modules/libcom/src/osi/os/posix/osdMutex.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/libcom/src/osi/os/posix/osdMutex.c b/modules/libcom/src/osi/os/posix/osdMutex.c index 83833f85c..18cd08d95 100644 --- a/modules/libcom/src/osi/os/posix/osdMutex.c +++ b/modules/libcom/src/osi/os/posix/osdMutex.c @@ -194,6 +194,7 @@ void epicsMutexOsdShow(struct epicsMutexOSD * pmutex, unsigned int level) void epicsMutexOsdShowAll(void) { +#if defined _POSIX_THREAD_PRIO_INHERIT int proto = -1; int ret = pthread_mutexattr_getprotocol(&globalAttrRecursive, &proto); if(ret) { @@ -201,4 +202,7 @@ void epicsMutexOsdShowAll(void) } else { printf("PI is%s enabled\n", proto==PTHREAD_PRIO_INHERIT ? "" : " not"); } +#else + printf("PI not supported\n"); +#endif } From f9e3e86401be19daf74744ca74622c02a97a7d1e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 2 Mar 2021 11:54:17 -0600 Subject: [PATCH 10/15] Support VxWorks 6.9.x before taskWait() was added We don't know exactly which version this was added in, but it is present in 6.9.4.1 so use that. Fixes lp: #1913699 --- modules/libcom/src/osi/os/vxWorks/osdThread.h | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.h b/modules/libcom/src/osi/os/vxWorks/osdThread.h index 259d358e5..25cce37ac 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.h +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.h @@ -1,5 +1,5 @@ /*************************************************************************\ -* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* Copyright (c) 2021 The University of Chicago, as Operator of Argonne * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. @@ -8,13 +8,22 @@ * in file LICENSE that is included with this distribution. \*************************************************************************/ -#ifndef osdThreadh -#define osdThreadh +#ifndef INC_osdThread_H +#define INC_osdThread_H -/* VxWorks 6.9 and later can support joining threads */ +#include -#if (_WRS_VXWORKS_MAJOR == 6 && _WRS_VXWORKS_MINOR < 9) -#undef EPICS_THREAD_CAN_JOIN +#ifdef _WRS_VXWORKS_MAJOR +# define VXWORKS_VERSION_INT VERSION_INT(_WRS_VXWORKS_MAJOR, \ + _WRS_VXWORKS_MINOR, _WRS_VXWORKS_MAINT, _WRS_VXWORKS_SVCPK) +#else +/* Version not available at compile-time, assume... */ +# define VXWORKS_VERSION_INT VERSION_INT(5, 5, 0, 0) #endif -#endif /* osdThreadh */ +#if VXWORKS_VERSION_INT < VERSION_INT(6, 9, 4, 1) +/* VxWorks 6.9.4.1 and later can support joining threads */ +# undef EPICS_THREAD_CAN_JOIN +#endif + +#endif /* INC_osdThread_H */ From f41276bef8e1a98da18f146428ad2c1ada6a9b6b Mon Sep 17 00:00:00 2001 From: Brendan Chandler Date: Sun, 28 Feb 2021 20:07:33 -0600 Subject: [PATCH 11/15] epicPosicMutexInit: avoid calling with 0 which is platform dependent Different platforms (RTEMS5) can define different values for PTHREAD_MUTEX_DEFAULT, so we shouldn't pass 0 assuming its PTHREAD_MUTEX_DEFAULT. --- modules/libcom/src/osi/os/posix/osdEvent.c | 2 +- modules/libcom/src/osi/os/posix/osdPosixMutexPriv.h | 2 +- modules/libcom/src/osi/os/posix/osdSpin.c | 2 +- modules/libcom/src/osi/os/posix/osdThread.c | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/libcom/src/osi/os/posix/osdEvent.c b/modules/libcom/src/osi/os/posix/osdEvent.c index db61c240b..b22a3430b 100644 --- a/modules/libcom/src/osi/os/posix/osdEvent.c +++ b/modules/libcom/src/osi/os/posix/osdEvent.c @@ -51,7 +51,7 @@ LIBCOM_API epicsEventId epicsEventCreate(epicsEventInitialState init) epicsEventId pevent = malloc(sizeof(*pevent)); if (pevent) { - int status = osdPosixMutexInit(&pevent->mutex, 0); + int status = osdPosixMutexInit(&pevent->mutex, PTHREAD_MUTEX_DEFAULT); pevent->isFull = (init == epicsEventFull); if (status) { diff --git a/modules/libcom/src/osi/os/posix/osdPosixMutexPriv.h b/modules/libcom/src/osi/os/posix/osdPosixMutexPriv.h index 2b6846c91..87289be5f 100644 --- a/modules/libcom/src/osi/os/posix/osdPosixMutexPriv.h +++ b/modules/libcom/src/osi/os/posix/osdPosixMutexPriv.h @@ -18,7 +18,7 @@ extern "C" { #endif /* Returns ENOTSUP if requested mutextype is not supported */ -/* At the moment, only 0 (default non recursive mutex) and PTHREAD_MUTEX_RECURSIVE are supported */ +/* At the moment, only PTHREAD_MUTEX_DEFAULT and PTHREAD_MUTEX_RECURSIVE are supported */ int osdPosixMutexInit(pthread_mutex_t *,int mutextype); #ifdef __cplusplus diff --git a/modules/libcom/src/osi/os/posix/osdSpin.c b/modules/libcom/src/osi/os/posix/osdSpin.c index f039e7c34..0b924ee3a 100644 --- a/modules/libcom/src/osi/os/posix/osdSpin.c +++ b/modules/libcom/src/osi/os/posix/osdSpin.c @@ -123,7 +123,7 @@ epicsSpinId epicsSpinCreate(void) { if (!spin) goto fail; - status = osdPosixMutexInit(&spin->lock, 0); + status = osdPosixMutexInit(&spin->lock, PTHREAD_MUTEX_DEFAULT); checkStatus(status, "osdPosixMutexInit"); if (status) goto fail; diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c index 45255881f..0eb5f5fd2 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.c +++ b/modules/libcom/src/osi/os/posix/osdThread.c @@ -328,9 +328,9 @@ static void once(void) int status; pthread_key_create(&getpthreadInfo,0); - status = osdPosixMutexInit(&onceLock,0); + status = osdPosixMutexInit(&onceLock,PTHREAD_MUTEX_DEFAULT); checkStatusOnceQuit(status,"osdPosixMutexInit","epicsThreadInit"); - status = osdPosixMutexInit(&listLock,0); + status = osdPosixMutexInit(&listLock,PTHREAD_MUTEX_DEFAULT); checkStatusOnceQuit(status,"osdPosixMutexInit","epicsThreadInit"); pcommonAttr = calloc(1,sizeof(commonAttr)); if(!pcommonAttr) checkStatusOnceQuit(errno,"calloc","epicsThreadInit"); From bbb4d86f787b74579062aa77db9a98aee8e7ba80 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 25 Sep 2020 18:10:56 -0500 Subject: [PATCH 12/15] Enable RTEMS testing in modules/database/test/std/link --- modules/database/test/std/link/Makefile | 9 +++++++++ modules/database/test/std/link/lnkCalcTest.c | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/database/test/std/link/Makefile b/modules/database/test/std/link/Makefile index 2563fd591..fca089966 100644 --- a/modules/database/test/std/link/Makefile +++ b/modules/database/test/std/link/Makefile @@ -47,6 +47,8 @@ testHarness_SRCS += epicsRunLinkTests.c linkTestHarness_SRCS += $(testHarness_SRCS) linkTestHarness_SRCS_RTEMS += rtemsTestHarness.c +PROD_SRCS_RTEMS += rtemsTestData.c + PROD_vxWorks = linkTestHarness PROD_RTEMS = linkTestHarness @@ -54,9 +56,16 @@ TESTSPEC_vxWorks = linkTestHarness.munch; epicsRunLinkTests TESTSPEC_RTEMS = linkTestHarness.boot; epicsRunLinkTests TESTSCRIPTS_HOST += $(TESTS:%=%.t) +ifneq ($(filter $(T_A),$(CROSS_COMPILER_RUNTEST_ARCHS)),) + TESTPROD = $(TESTPROD_HOST) + TESTSCRIPTS_RTEMS += $(TESTS:%=%.t) +endif include $(TOP)/configure/RULES ioRecord$(DEP): $(COMMON_DIR)/ioRecord.h lnkStateTest$(DEP): $(COMMON_DIR)/ioRecord.h lnkCalcTest$(DEP): $(COMMON_DIR)/ioRecord.h + +rtemsTestData.c : $(TESTFILES) $(TOOLS)/epicsMakeMemFs.pl + $(PERL) $(TOOLS)/epicsMakeMemFs.pl $@ epicsRtemsFSImage $(TESTFILES) diff --git a/modules/database/test/std/link/lnkCalcTest.c b/modules/database/test/std/link/lnkCalcTest.c index 5297a53c5..b573e345d 100644 --- a/modules/database/test/std/link/lnkCalcTest.c +++ b/modules/database/test/std/link/lnkCalcTest.c @@ -157,7 +157,7 @@ static void testCalc() MAIN(lnkCalcTest) { - testPlan(0); + testPlan(30); testCalc(); From 4baf7912e1ba9cfdb5f88fe911020ffa2d418fcf Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sun, 7 Mar 2021 20:23:19 -0600 Subject: [PATCH 13/15] Tidying up in documentation directory --- documentation/Doxyfile@ | 21 --------------------- documentation/Makefile | 4 ++-- documentation/mainpage.dox | 1 - 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/documentation/Doxyfile@ b/documentation/Doxyfile@ index 482564264..442731a30 100644 --- a/documentation/Doxyfile@ +++ b/documentation/Doxyfile@ @@ -241,12 +241,6 @@ TAB_SIZE = 4 ALIASES = -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all @@ -2094,12 +2088,6 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of 'which perl'). -# The default file (with absolute path) is: /usr/bin/perl. - -PERL_PATH = /usr/bin/perl - #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- @@ -2113,15 +2101,6 @@ PERL_PATH = /usr/bin/perl CLASS_DIAGRAMS = YES -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. diff --git a/documentation/Makefile b/documentation/Makefile index 8148687cb..f94cd9d5f 100644 --- a/documentation/Makefile +++ b/documentation/Makefile @@ -5,7 +5,7 @@ ifdef T_A DOXYGEN=doxygen -EXPAND = Doxyfile +EXPAND = Doxyfile@ EXPAND_ME += EPICS_VERSION EXPAND_ME += EPICS_REVISION @@ -16,7 +16,7 @@ ME = documentation/O.$(T_A)/html install: doxygen -doxygen: Doxyfile +doxygen: Doxyfile ../mainpage.dox $(DOXYGEN) rsync -av $(TOP)/html/ html/ diff --git a/documentation/mainpage.dox b/documentation/mainpage.dox index f13fdcf97..472f1d999 100644 --- a/documentation/mainpage.dox +++ b/documentation/mainpage.dox @@ -3,7 +3,6 @@ Documentation index -@ul @li @ref releasenotes @li @ref install @li @ref recordrefmanual From 1fbbae73de69c15ae6389f1d029814ab2d8be26b Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sun, 7 Mar 2021 20:27:45 -0600 Subject: [PATCH 14/15] Modify documentation/Doxyfile to parse include directory Excludes the include/pv and include/pva directories, which are processed separately in their own modules. --- documentation/Doxyfile@ | 53 +++++++---------------------------------- documentation/Makefile | 1 + 2 files changed, 10 insertions(+), 44 deletions(-) diff --git a/documentation/Doxyfile@ b/documentation/Doxyfile@ index 442731a30..44e09f69e 100644 --- a/documentation/Doxyfile@ +++ b/documentation/Doxyfile@ @@ -162,7 +162,7 @@ FULL_PATH_NAMES = YES # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = \ +STRIP_FROM_PATH = @TOP@/include \ ../ # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the @@ -172,7 +172,8 @@ STRIP_FROM_PATH = \ # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = ../ +STRIP_FROM_INC_PATH = @TOP@/include \ + ../ # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -761,7 +762,8 @@ WARN_LOGFILE = INPUT = ../mainpage.dox \ ../RELEASE_NOTES.md \ ../README.md \ - ../RecordReference.md + ../RecordReference.md \ + @TOP@/include # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -781,48 +783,10 @@ INPUT_ENCODING = UTF-8 # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.idl \ - *.ddl \ - *.odl \ - *.h \ - *.hh \ - *.hxx \ +FILE_PATTERNS = *.h \ *.hpp \ - *.h++ \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ - *.markdown \ *.md \ - *.mm \ - *.dox \ - *.py \ - *.f90 \ - *.f \ - *.for \ - *.tcl \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf \ - *.as \ - *.js + *.dox # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -837,7 +801,8 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = +EXCLUDE = @TOP@/include/pv \ + @TOP@/include/pva # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/documentation/Makefile b/documentation/Makefile index f94cd9d5f..d8e3b6a1d 100644 --- a/documentation/Makefile +++ b/documentation/Makefile @@ -11,6 +11,7 @@ EXPAND_ME += EPICS_VERSION EXPAND_ME += EPICS_REVISION EXPAND_ME += EPICS_MODIFICATION EXPAND_ME += EPICS_PATCH_LEVEL +EXPAND_ME += OS_CLASS CMPLR_CLASS ME = documentation/O.$(T_A)/html From 3ba778c08bfbf6364bdbefa41f16022b4c0f41d5 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sun, 7 Mar 2021 21:35:07 -0600 Subject: [PATCH 15/15] documentation/Makefile tweaks --- documentation/Makefile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/documentation/Makefile b/documentation/Makefile index d8e3b6a1d..bd527e7db 100644 --- a/documentation/Makefile +++ b/documentation/Makefile @@ -1,7 +1,7 @@ TOP = .. include $(TOP)/configure/CONFIG -ifdef T_A +ifeq ($(T_A),$(EPICS_HOST_ARCH)) DOXYGEN=doxygen @@ -14,6 +14,7 @@ EXPAND_ME += EPICS_PATCH_LEVEL EXPAND_ME += OS_CLASS CMPLR_CLASS ME = documentation/O.$(T_A)/html +GH_FILES = $(ME)/ $(ME)/.nojekyll $(ME)/*.* $(ME)/*/*.* install: doxygen @@ -25,10 +26,10 @@ doxygen: Doxyfile ../mainpage.dox commit: doxygen $(TOUCH) html/.nojekyll - (cd $(TOP) && $(CURDIR)/../commit-gh.sh $(ME)/ $(ME)/.nojekyll $(ME)/*.* $(ME)/*/*.*) + (cd $(TOP) && $(CURDIR)/../commit-gh.sh $(GH_FILES)) .PHONY: commit -endif # T_A +endif # EPICS_HOST_ARCH include $(TOP)/configure/RULES