Merge branch '7.0' into PSI-7.0
This commit is contained in:
26
.github/workflows/ci-scripts-build.yml
vendored
26
.github/workflows/ci-scripts-build.yml
vendored
@@ -133,13 +133,15 @@ jobs:
|
||||
|
||||
- os: windows-2019
|
||||
cmp: vs2019
|
||||
configuration: default
|
||||
configuration: debug
|
||||
name: "Win2019 MSC-19"
|
||||
extra: "CMD_CXXFLAGS=-analysis"
|
||||
|
||||
- os: windows-2019
|
||||
cmp: vs2019
|
||||
configuration: static
|
||||
configuration: static-debug
|
||||
name: "Win2019 MSC-19, static"
|
||||
extra: "CMD_CXXFLAGS=-analysis"
|
||||
|
||||
- os: windows-2019
|
||||
cmp: vs2019
|
||||
@@ -151,6 +153,26 @@ jobs:
|
||||
configuration: default
|
||||
name: "Win2019 mingw"
|
||||
|
||||
# Cross builds
|
||||
|
||||
- os: ubuntu-latest
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
name: "Cross linux-aarch64"
|
||||
cross: linux-aarch64
|
||||
|
||||
- os: ubuntu-latest
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
name: "Cross linux-arm gnueabi"
|
||||
cross: linux-arm@arm-linux-gnueabi
|
||||
|
||||
- os: ubuntu-latest
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
name: "Cross linux-arm gnueabihf"
|
||||
cross: linux-arm@arm-linux-gnueabihf
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
|
||||
# Include definitions common to all Unix targets
|
||||
include $(CONFIG)/os/CONFIG.Common.UnixCommon
|
||||
GNU = NO
|
||||
CMPLR_CLASS = clang
|
||||
CC = clang
|
||||
CCC = clang++
|
||||
|
||||
OS_CLASS = freebsd
|
||||
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
|
||||
#Include definitions common to unix hosts
|
||||
include $(CONFIG)/os/CONFIG.UnixCommon.Common
|
||||
CMPLR_CLASS = clang
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Definitions for freebsd-x86 host - freebsd-x86 target builds
|
||||
# Sites may override these definitions in CONFIG_SITE.freebsd-x86.freebsd-x86
|
||||
#-------------------------------------------------------
|
||||
GNU_DIR=/usr/local
|
||||
|
||||
# Include common gnu compiler definitions
|
||||
include $(CONFIG)/CONFIG.gnuCommon
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Definitions for freebsd-x86_64 host - freebsd-x86_64 target builds
|
||||
# Sites may override these definitions in CONFIG_SITE.freebsd-x86_64.freebsd-x86_64
|
||||
#-------------------------------------------------------
|
||||
GNU_DIR=/usr/local
|
||||
|
||||
# Include common gnu compiler definitions
|
||||
include $(CONFIG)/CONFIG.gnuCommon
|
||||
|
||||
@@ -15,6 +15,30 @@ should also be read to understand what has changed since earlier releases.
|
||||
|
||||
## Changes made on the 7.0 branch since 7.0.7
|
||||
|
||||
### Fixed leak from a non-EPICS thread on WIN32
|
||||
|
||||
On Windows targets, if a thread not created by `epicsThreadCreate*()` directly
|
||||
or indirectly calls an `epicsThread*()` function, a specific tracking struct
|
||||
is allocated. Prior to this release the allocation would not be `free()`d,
|
||||
resulting in a memory leak.
|
||||
|
||||
A similar issue on POSIX targets was previously fixed.
|
||||
|
||||
### Change compiler for FreeBSD to clang
|
||||
|
||||
The default compiler for FreeBSD targets changes from GCC to clang.
|
||||
|
||||
### Expose `dbCreateAlias` in IOC shell
|
||||
|
||||
Add a new IOC shell command `dbCreateAlias` allow record aliases to be added.
|
||||
Intended for use before `iocInit`. eg. to add an alias "bar" for a record "foo".
|
||||
|
||||
```
|
||||
dbLoadRecords("some.db") # includes: record(ai, "foo") { ...
|
||||
dbCreateAlias("foo", "bar")
|
||||
iocInit()
|
||||
```
|
||||
|
||||
### dbEvent eventsRemaining missed on cancel
|
||||
|
||||
In some cases, RSRV may queue a subscription update, but not flush it.
|
||||
@@ -48,6 +72,23 @@ 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`.
|
||||
|
||||
### Extended timestamp channel filter
|
||||
|
||||
The `"ts"` filter can now retrieve the record's timestamp in several numeric
|
||||
and string formats, some of which support full nanosecond precision.
|
||||
|
||||
Hal$ caget -a test:channel
|
||||
test:channel 2021-03-11 18:23:48.265386 42
|
||||
Hal$ caget -f9 'test:channel.{"ts": {"num": "dbl"}}'
|
||||
test:channel.{"ts": {"num": "dbl"}} 984331428.265386105
|
||||
Hal$ caget 'test:channel.{"ts": {"str": "iso"}}'
|
||||
test:channel.{"ts": {"str": "iso"}} 2021-03-11T18:23:48.265386+0100
|
||||
Hal$ caget -f1 'test:channel.{"ts": {"num": "ts"}}'
|
||||
test:channel.{"ts": {"num": "ts"}} 2 984331428.0 265386163.0
|
||||
|
||||
More information is included in the filters documentation, which can be found in
|
||||
the `html/filters.html` document that is generated during the build
|
||||
|
||||
### Add conditional output (OOPT) to the longout record
|
||||
|
||||
The longout record can now be configured using its new OOPT and OOCH fields
|
||||
@@ -227,6 +268,32 @@ This release fixed the leak on POSIX targets.
|
||||
See the associated github [issue 241](https://github.com/epics-base/epics-base/issues/241)
|
||||
for WIN32 status.
|
||||
|
||||
### Fixed leak from a non-EPICS thread
|
||||
|
||||
On some targets, if a thread not created by `epicsThreadCreate*()` directly
|
||||
or indirectly calls an `epicsThread*()` function, a specific tracking struct
|
||||
is allocated.
|
||||
|
||||
Prior to this release, on POSIX and WIN32 targets, this
|
||||
allocation would not be `free()`d, resulting in a memory leak.
|
||||
|
||||
This release fixed the leak on POSIX and WIN32 targets (excluding
|
||||
MSVC before vs2012, and the WINE runtime).
|
||||
|
||||
### Fixed leak from a non-EPICS thread
|
||||
|
||||
On some targets, if a thread not created by `epicsThreadCreate*()` directly
|
||||
or indirectly calls an `epicsThread*()` function, a specific tracking struct
|
||||
is allocated.
|
||||
|
||||
Prior to this release, on POSIX and WIN32 targets, this
|
||||
struct would not be `free()`d, resulting in a memory leak.
|
||||
|
||||
This release fixed the leak on POSIX targets.
|
||||
|
||||
See the associated github [issue 241](https://github.com/epics-base/epics-base/issues/241)
|
||||
for WIN32 status.
|
||||
|
||||
### Fix `CHECK_RELEASE = WARN`
|
||||
|
||||
This now works again, it was broken in 2019 (7.0.3.1) by an errant commit.
|
||||
|
||||
@@ -73,7 +73,7 @@ epicsExportAddress(int, dbAccessDebugPUTF);
|
||||
|
||||
DB_LOAD_RECORDS_HOOK_ROUTINE dbLoadRecordsHook = NULL;
|
||||
|
||||
static short mapDBFToDBR[DBF_NTYPES] = {
|
||||
static const short mapDBFToDBR[DBF_NTYPES] = {
|
||||
/* DBF_STRING => */ DBR_STRING,
|
||||
/* DBF_CHAR => */ DBR_CHAR,
|
||||
/* DBF_UCHAR => */ DBR_UCHAR,
|
||||
|
||||
@@ -410,8 +410,21 @@ DBCORE_API long dbLoadLinkArray(struct link *, short dbrType, void *pbuffer,
|
||||
DBCORE_API long dbGetNelements(const struct link *plink, long *pnElements);
|
||||
DBCORE_API int dbIsLinkConnected(const struct link *plink); /* 0 or 1 */
|
||||
DBCORE_API int dbGetLinkDBFtype(const struct link *plink);
|
||||
/** \brief Fetch current value from link.
|
||||
* \param dbrType Database DBR code
|
||||
* \param pbuffer Destination buffer
|
||||
* \param nRequest If !NULL. Caller initializes with number of elements requested,
|
||||
* On success, set to number of elements written to pbuffer.
|
||||
* \return 0 on success
|
||||
*
|
||||
* When called with `nRequest==NULL`, treated as a request for one (1)
|
||||
* element.
|
||||
*
|
||||
* see lset::getValue
|
||||
*/
|
||||
DBCORE_API long dbTryGetLink(struct link *, short dbrType, void *pbuffer,
|
||||
long *nRequest);
|
||||
/** see dbTryGetLink() */
|
||||
DBCORE_API long dbGetLink(struct link *, short dbrType, void *pbuffer,
|
||||
long *options, long *nRequest);
|
||||
DBCORE_API long dbGetControlLimits(const struct link *plink, double *low,
|
||||
|
||||
@@ -39,12 +39,17 @@ extern "C" {
|
||||
* will adjust automatically, it just compares field sizes.
|
||||
*/
|
||||
union native_value {
|
||||
epicsInt8 dbf_char;
|
||||
epicsInt16 dbf_short;
|
||||
epicsEnum16 dbf_enum;
|
||||
epicsInt32 dbf_long;
|
||||
epicsFloat32 dbf_float;
|
||||
epicsFloat64 dbf_double;
|
||||
epicsInt8 dbf_char;
|
||||
epicsUInt8 dbf_uchar;
|
||||
epicsInt16 dbf_short;
|
||||
epicsUInt16 dbf_ushort;
|
||||
epicsEnum16 dbf_enum;
|
||||
epicsInt32 dbf_long;
|
||||
epicsUInt32 dbf_ulong;
|
||||
epicsInt64 dbf_int64;
|
||||
epicsUInt64 dbf_uint64;
|
||||
epicsFloat32 dbf_float;
|
||||
epicsFloat64 dbf_double;
|
||||
#ifdef DB_EVENT_LOG_STRINGS
|
||||
char dbf_string[MAX_STRING_SIZE];
|
||||
#endif
|
||||
|
||||
@@ -75,7 +75,7 @@ DBCORE_API void recGblInheritSevr(int msMode, void *precord, epicsEnum16 stat,
|
||||
epicsEnum16 sevr);
|
||||
DBCORE_API int recGblSetSevrMsg(void *precord, epicsEnum16 new_stat,
|
||||
epicsEnum16 new_sevr,
|
||||
const char *msg, ...) EPICS_PRINTF_STYLE(4,5);
|
||||
EPICS_PRINTF_FMT(const char *msg), ...) EPICS_PRINTF_STYLE(4,5);
|
||||
DBCORE_API int recGblSetSevrVMsg(void *precord, epicsEnum16 new_stat,
|
||||
epicsEnum16 new_sevr,
|
||||
const char *msg, va_list args);
|
||||
|
||||
@@ -139,14 +139,16 @@ char** dbCompleteRecord(const char *cword)
|
||||
|
||||
if(!prefix.empty() || !suggestions.empty()) {
|
||||
ret = (char**)malloc(sizeof(*ret)*(2u + suggestions.size()));
|
||||
ret[0] = prefix.dup();
|
||||
size_t n=1u;
|
||||
for(suggestions_t::iterator it(suggestions.begin()), end(suggestions.end());
|
||||
it!=end; ++it)
|
||||
{
|
||||
ret[n++] = it->dup();
|
||||
if(ret) {
|
||||
ret[0] = prefix.dup();
|
||||
size_t n=1u;
|
||||
for(suggestions_t::iterator it(suggestions.begin()), end(suggestions.end());
|
||||
it!=end; ++it)
|
||||
{
|
||||
ret[n++] = it->dup();
|
||||
}
|
||||
ret[n] = NULL;
|
||||
}
|
||||
ret[n] = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
\*************************************************************************/
|
||||
|
||||
#include "iocsh.h"
|
||||
#include "errSymTbl.h"
|
||||
|
||||
#include "dbStaticIocRegister.h"
|
||||
#include "dbStaticLib.h"
|
||||
@@ -217,6 +218,40 @@ static void dbReportDeviceConfigCallFunc(const iocshArgBuf *args)
|
||||
dbReportDeviceConfig(*iocshPpdbbase,stdout);
|
||||
}
|
||||
|
||||
|
||||
static const iocshArg dbCreateAliasArg0 = { "record",iocshArgStringRecord};
|
||||
static const iocshArg dbCreateAliasArg1 = { "alias",iocshArgStringRecord};
|
||||
static const iocshArg * const dbCreateAliasArgs[] = {&argPdbbase,&dbCreateAliasArg0, &dbCreateAliasArg1};
|
||||
static const iocshFuncDef dbCreateAliasFuncDef = {
|
||||
"dbCreateAlias",
|
||||
3,
|
||||
dbCreateAliasArgs,
|
||||
"Add a new record alias.\n"
|
||||
"\n"
|
||||
"Example: dbCreateAlias pdbbase record:name new:alias\n",
|
||||
};
|
||||
static void dbCreateAliasCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
DBENTRY ent;
|
||||
long status;
|
||||
|
||||
dbInitEntry(*iocshPpdbbase, &ent);
|
||||
if(!args[1].sval || !args[2].sval) {
|
||||
status = S_dbLib_recNotFound;
|
||||
|
||||
} else {
|
||||
status = dbFindRecord(&ent, args[1].sval);
|
||||
if(!status) {
|
||||
status = dbCreateAlias(&ent, args[2].sval);
|
||||
}
|
||||
}
|
||||
dbFinishEntry(&ent);
|
||||
if(status) {
|
||||
fprintf(stderr, "Error: %ld %s\n", status, errSymMsg(status));
|
||||
iocshSetError(1);
|
||||
}
|
||||
}
|
||||
|
||||
void dbStaticIocRegister(void)
|
||||
{
|
||||
iocshRegister(&dbDumpPathFuncDef, dbDumpPathCallFunc);
|
||||
@@ -234,4 +269,5 @@ void dbStaticIocRegister(void)
|
||||
iocshRegister(&dbPvdDumpFuncDef, dbPvdDumpCallFunc);
|
||||
iocshRegister(&dbPvdTableSizeFuncDef,dbPvdTableSizeCallFunc);
|
||||
iocshRegister(&dbReportDeviceConfigFuncDef, dbReportDeviceConfigCallFunc);
|
||||
iocshRegister(&dbCreateAliasFuncDef, dbCreateAliasCallFunc);
|
||||
}
|
||||
|
||||
@@ -1652,6 +1652,7 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
|
||||
dbRecordNode *pnewnode;
|
||||
DBENTRY tempEntry;
|
||||
PVDENTRY *ppvd;
|
||||
long status;
|
||||
|
||||
if (!precordType)
|
||||
return S_dbLib_recordTypeNotFound;
|
||||
@@ -1664,9 +1665,10 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
|
||||
return S_dbLib_recNotFound;
|
||||
|
||||
dbInitEntry(pdbentry->pdbbase, &tempEntry);
|
||||
if (!dbFindRecord(&tempEntry, alias))
|
||||
return S_dbLib_recExists;
|
||||
status = dbFindRecord(&tempEntry, alias);
|
||||
dbFinishEntry(&tempEntry);
|
||||
if (!status)
|
||||
return S_dbLib_recExists;
|
||||
|
||||
pnewnode = dbCalloc(1, sizeof(dbRecordNode));
|
||||
pnewnode->recordname = epicsStrDup(alias);
|
||||
@@ -1676,15 +1678,16 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
|
||||
precnode->flags |= DBRN_FLAGS_HASALIAS;
|
||||
ellInit(&pnewnode->infoList);
|
||||
|
||||
ellAdd(&precordType->recList, &pnewnode->node);
|
||||
precordType->no_aliases++;
|
||||
|
||||
ppvd = dbPvdAdd(pdbentry->pdbbase, precordType, pnewnode);
|
||||
if (!ppvd) {
|
||||
errMessage(-1, "dbCreateAlias: Add to PVD failed");
|
||||
free(pnewnode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ellAdd(&precordType->recList, &pnewnode->node);
|
||||
precordType->no_aliases++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ char *dbRecordName(DBENTRY *pdbentry);
|
||||
char *dbGetStringNum(DBENTRY *pdbentry);
|
||||
long dbPutStringNum(DBENTRY *pdbentry,const char *pstring);
|
||||
|
||||
void dbMsgPrint(DBENTRY *pdbentry, const char *fmt, ...) EPICS_PRINTF_STYLE(2,3);
|
||||
void dbMsgPrint(
|
||||
DBENTRY *pdbentry, EPICS_PRINTF_FMT(const char *fmt), ...
|
||||
) EPICS_PRINTF_STYLE(2,3);
|
||||
|
||||
void dbPutStringSuggest(DBENTRY *pdbentry, const char *pstring);
|
||||
|
||||
|
||||
@@ -216,22 +216,96 @@ registrar(tsInitialize)
|
||||
|
||||
=head3 TimeStamp Filter C<"ts">
|
||||
|
||||
This filter replaces the timestamp in the data fetched through the channel with
|
||||
the time the value was fetched (or an update was sent). The record's timestamp
|
||||
This filter is used for two purposes:
|
||||
|
||||
=over
|
||||
|
||||
=item * to retrieve the timestamp of the record as a value in several different
|
||||
formats;
|
||||
|
||||
=item * to retrieve the record value as normal, but replace the timestamp with
|
||||
the time the value was fetched.
|
||||
|
||||
=back
|
||||
|
||||
=head4 Parameters
|
||||
|
||||
=head4 No parameters (an empty pair of braces)
|
||||
|
||||
Retrieve the record value as normal, but replace the timestamp with the time the
|
||||
value was fetched (or an update was sent). This is useful for clients that can't
|
||||
handle timestamps that are far in the past. Normally, the record's timestamp
|
||||
indicates when the record last processed, which could have been days or even
|
||||
weeks ago for some records, or set to the EPICS epoch if the record has never
|
||||
processed.
|
||||
|
||||
=head4 Parameters
|
||||
=head4 Numeric type C<"num">
|
||||
|
||||
None, use an empty pair of braces.
|
||||
The following values are accepted for this parameter:
|
||||
|
||||
=head4 Example
|
||||
=over
|
||||
|
||||
Hal$ caget -a 'test:channel.{"ts":{}}'
|
||||
test:channel.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID
|
||||
Hal$ caget -a 'test:channel'
|
||||
test:channel <undefined> 0 UDF INVALID
|
||||
=item * C<"dbl"> requests the timestamp as C<epicsFloat64> representing the
|
||||
non-integral number of seconds since epoch. This format is convenient,
|
||||
but loses precision, depending on which epoch is used.
|
||||
|
||||
=item * C<"sec"> requests the number of seconds since epoch as C<epicsUInt32>.
|
||||
|
||||
=item * C<"nsec"> requests the number of nanoseconds since epoch as
|
||||
C<epicsUInt32>.
|
||||
|
||||
=item * C<"ts"> requests the entire timestamp. It is provided as a two-element
|
||||
array of C<epicsUInt32> representing seconds and nanoseconds.
|
||||
|
||||
=back
|
||||
|
||||
Note that C<epicsUInt32> cannot be transferred over Channel Access; in that
|
||||
case, the value will be converted to C<epicsFloat64>.
|
||||
|
||||
=head4 String type C<"str">
|
||||
|
||||
The following values are accepted for this parameter:
|
||||
|
||||
=over
|
||||
|
||||
=item * C<"epics"> requests the timestamp as a string in the format used by
|
||||
tools such as C<caget>.
|
||||
|
||||
=item * C<"iso"> requests the timestamp as a string in the ISO8601 format.
|
||||
|
||||
=back
|
||||
|
||||
=head4 Epoch adjustment C<"epoch">
|
||||
|
||||
The following values are accepted for this parameter:
|
||||
|
||||
=over
|
||||
|
||||
=item * C<"epics"> keeps the EPICS epoch (1990-01-01) and is the default if the
|
||||
C<"epoch"> parameter is not specified.
|
||||
|
||||
=item * C<"unix"> converts the timestamp to the UNIX/POSIX epoch (1970-01-01).
|
||||
|
||||
=back
|
||||
|
||||
=head4 Examples
|
||||
|
||||
Hal$ caget -a 'test:invalid_ts.{"ts":{}}'
|
||||
test:invalid_ts.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID
|
||||
Hal$ caget -a 'test:invalid_ts'
|
||||
test:invalid_ts <undefined> 0 UDF INVALID
|
||||
Hal$ caget -a test:channel
|
||||
test:channel 2021-03-11 18:23:48.265386 42
|
||||
Hal$ caget 'test:channel.{"ts": {"str": "epics"}}'
|
||||
test:channel.{"ts": {"str": "epics"}} 2021-03-11 18:23:48.265386
|
||||
Hal$ caget 'test:channel.{"ts": {"str": "iso"}}'
|
||||
test:channel.{"ts": {"str": "iso"}} 2021-03-11T18:23:48.265386+0100
|
||||
Hal$ caget -f9 'test:channel.{"ts": {"num": "dbl"}}'
|
||||
test:channel.{"ts": {"num": "dbl"}} 984331428.265386105
|
||||
Hal$ caget -f1 'test:channel.{"ts": {"num": "ts"}}'
|
||||
test:channel.{"ts": {"num": "ts"}} 2 984331428.0 265386163.0
|
||||
Hal$ caget -f1 'test:channel.{"ts": {"num": "ts", "epoch": "unix"}}'
|
||||
test:channel.{"ts": {"num": "ts", "epoch": "unix"}} 2 1615483428.0 265386163.0
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2021 Cosylab d.d
|
||||
* Copyright (c) 2010 Brookhaven National Laboratory.
|
||||
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
|
||||
* fuer Materialien und Energie GmbH.
|
||||
@@ -9,6 +10,7 @@
|
||||
|
||||
/*
|
||||
* Author: Ralph Lange <Ralph.Lange@bessy.de>
|
||||
* Author: Jure Varlec <jure.varlec@cosylab.com>
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -16,20 +18,124 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "chfPlugin.h"
|
||||
#include "db_field_log.h"
|
||||
#include "dbExtractArray.h"
|
||||
#include "dbLock.h"
|
||||
#include "db_field_log.h"
|
||||
#include "epicsExport.h"
|
||||
#include "freeList.h"
|
||||
#include "errlog.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.
|
||||
*/
|
||||
/* Allocation size for freelists */
|
||||
#define ALLOC_NUM_ELEMENTS 32
|
||||
|
||||
#define logicErrorMessage() \
|
||||
errMessage(-1, "Logic error: invalid state encountered in ts filter")
|
||||
|
||||
/* Filter settings */
|
||||
|
||||
enum tsMode {
|
||||
tsModeInvalid = 0,
|
||||
tsModeGenerate = 1,
|
||||
tsModeDouble = 2,
|
||||
tsModeSec = 3,
|
||||
tsModeNsec = 4,
|
||||
tsModeArray = 5,
|
||||
tsModeString = 6,
|
||||
};
|
||||
|
||||
static const chfPluginEnumType ts_numeric_enum[] = {
|
||||
{"dbl", 2}, {"sec", 3}, {"nsec", 4}, {"ts", 5}};
|
||||
|
||||
enum tsEpoch {
|
||||
tsEpochEpics = 0,
|
||||
tsEpochUnix = 1,
|
||||
};
|
||||
|
||||
static const chfPluginEnumType ts_epoch_enum[] = {{"epics", 0}, {"unix", 1}};
|
||||
|
||||
enum tsString {
|
||||
tsStringInvalid = 0,
|
||||
tsStringEpics = 1,
|
||||
tsStringIso = 2,
|
||||
};
|
||||
|
||||
static const chfPluginEnumType ts_string_enum[] = {{"epics", 1}, {"iso", 2}};
|
||||
|
||||
typedef struct tsPrivate {
|
||||
enum tsMode mode;
|
||||
enum tsEpoch epoch;
|
||||
enum tsString str;
|
||||
} tsPrivate;
|
||||
|
||||
static const chfPluginArgDef ts_args[] = {
|
||||
chfEnum(tsPrivate, mode, "num", 0, 0, ts_numeric_enum),
|
||||
chfEnum(tsPrivate, epoch, "epoch", 0, 0, ts_epoch_enum),
|
||||
chfEnum(tsPrivate, str, "str", 0, 0, ts_string_enum),
|
||||
chfPluginArgEnd
|
||||
};
|
||||
|
||||
static int parse_finished(void *pvt) {
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
if (settings->str != tsStringInvalid) {
|
||||
settings->mode = tsModeString;
|
||||
#if defined _MSC_VER && _MSC_VER <= 1700
|
||||
// VS 2012 crashes in ISO mode, doesn't support timezones
|
||||
if (settings->str == tsStringIso) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
} else if (settings->mode == tsModeInvalid) {
|
||||
settings->mode = tsModeGenerate;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Allocation of filter settings */
|
||||
static void *private_free_list;
|
||||
|
||||
static void * allocPvt() {
|
||||
return freeListCalloc(private_free_list);
|
||||
}
|
||||
|
||||
static void freePvt(void *pvt) {
|
||||
freeListFree(private_free_list, pvt);
|
||||
}
|
||||
|
||||
|
||||
/* Allocation of two-element arrays for second+nanosecond pairs */
|
||||
static void *ts_array_free_list;
|
||||
|
||||
static void *allocTsArray() {
|
||||
return freeListCalloc(ts_array_free_list);
|
||||
}
|
||||
|
||||
static void freeTsArray(db_field_log *pfl) {
|
||||
freeListFree(ts_array_free_list, pfl->u.r.field);
|
||||
}
|
||||
|
||||
/* Allocation of strings */
|
||||
static void *string_free_list;
|
||||
|
||||
static void *allocString() {
|
||||
return freeListCalloc(string_free_list);
|
||||
}
|
||||
|
||||
static void freeString(db_field_log *pfl) {
|
||||
freeListFree(string_free_list, pfl->u.r.field);
|
||||
}
|
||||
|
||||
|
||||
/* The dtor for waveform data for the case when we have to copy it. */
|
||||
static void freeArray(db_field_log *pfl) {
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
free(pfl->u.r.field);
|
||||
}
|
||||
|
||||
static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
static db_field_log* generate(void* pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
epicsTimeStamp now;
|
||||
epicsTimeGetCurrent(&now);
|
||||
|
||||
@@ -44,7 +150,7 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
dbScanLock(dbChannelRecord(chan));
|
||||
dbChannelGetArrayInfo(chan, &pSource, &nSource, &offset);
|
||||
dbExtractArray(pSource, pTarget, pfl->field_size,
|
||||
nSource, pfl->no_elements, offset, 1);
|
||||
nSource, pfl->no_elements, offset, 1);
|
||||
pfl->u.r.field = pTarget;
|
||||
pfl->dtor = freeArray;
|
||||
pfl->u.r.pvt = pvt;
|
||||
@@ -56,34 +162,246 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
return pfl;
|
||||
}
|
||||
|
||||
static void channelRegisterPre(dbChannel *chan, void *pvt,
|
||||
chPostEventFunc **cb_out, void **arg_out, db_field_log *probe)
|
||||
{
|
||||
static db_field_log *replace_fl_value(tsPrivate const *pvt,
|
||||
db_field_log *pfl,
|
||||
int (*func)(tsPrivate const *,
|
||||
db_field_log *)) {
|
||||
/* Get rid of the old value */
|
||||
if (pfl->type == dbfl_type_ref && pfl->dtor) {
|
||||
pfl->dtor(pfl);
|
||||
pfl->dtor = NULL;
|
||||
}
|
||||
pfl->no_elements = 1;
|
||||
pfl->type = dbfl_type_val;
|
||||
|
||||
if (func(pvt, pfl)) {
|
||||
db_delete_field_log(pfl);
|
||||
pfl = NULL;
|
||||
}
|
||||
|
||||
return pfl;
|
||||
}
|
||||
|
||||
static void ts_to_array(tsPrivate const *settings,
|
||||
epicsTimeStamp const *ts,
|
||||
epicsUInt32 arr[2]) {
|
||||
arr[0] = ts->secPastEpoch;
|
||||
arr[1] = ts->nsec;
|
||||
if (settings->epoch == tsEpochUnix) {
|
||||
/* Cannot use epicsTimeToWhatever because Whatever uses signed ints */
|
||||
arr[0] += POSIX_TIME_AT_EPICS_EPOCH;
|
||||
}
|
||||
}
|
||||
|
||||
static int ts_seconds(tsPrivate const *settings, db_field_log *pfl) {
|
||||
epicsUInt32 arr[2];
|
||||
ts_to_array(settings, &pfl->time, arr);
|
||||
pfl->field_type = DBF_ULONG;
|
||||
pfl->field_size = sizeof(epicsUInt32);
|
||||
pfl->u.v.field.dbf_ulong = arr[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts_nanos(tsPrivate const *settings, db_field_log *pfl) {
|
||||
epicsUInt32 arr[2];
|
||||
ts_to_array(settings, &pfl->time, arr);
|
||||
pfl->field_type = DBF_ULONG;
|
||||
pfl->field_size = sizeof(epicsUInt32);
|
||||
pfl->u.v.field.dbf_ulong = arr[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts_double(tsPrivate const *settings, db_field_log *pfl) {
|
||||
epicsUInt32 arr[2];
|
||||
ts_to_array(settings, &pfl->time, arr);
|
||||
pfl->field_type = DBF_DOUBLE;
|
||||
pfl->field_size = sizeof(epicsFloat64);
|
||||
pfl->u.v.field.dbf_double = arr[0] + arr[1] * 1e-9;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts_array(tsPrivate const *settings, db_field_log *pfl) {
|
||||
pfl->field_type = DBF_ULONG;
|
||||
pfl->field_size = sizeof(epicsUInt32);
|
||||
pfl->type = dbfl_type_ref;
|
||||
pfl->u.r.pvt = NULL;
|
||||
pfl->u.r.field = allocTsArray();
|
||||
if (pfl->u.r.field) {
|
||||
pfl->no_elements = 2;
|
||||
pfl->dtor = freeTsArray;
|
||||
ts_to_array(settings, &pfl->time, (epicsUInt32*)pfl->u.r.field);
|
||||
} else {
|
||||
pfl->no_elements = 0;
|
||||
pfl->dtor = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts_string(tsPrivate const *settings, db_field_log *pfl) {
|
||||
char const *fmt;
|
||||
char *field;
|
||||
size_t n;
|
||||
|
||||
switch (settings->str) {
|
||||
case tsStringEpics:
|
||||
fmt = "%Y-%m-%d %H:%M:%S.%06f";
|
||||
break;
|
||||
case tsStringIso:
|
||||
fmt = "%Y-%m-%dT%H:%M:%S.%06f%z";
|
||||
break;
|
||||
case tsStringInvalid:
|
||||
default:
|
||||
logicErrorMessage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
pfl->field_type = DBF_STRING;
|
||||
pfl->field_size = MAX_STRING_SIZE;
|
||||
pfl->type = dbfl_type_ref;
|
||||
pfl->u.r.pvt = NULL;
|
||||
pfl->u.r.field = allocString();
|
||||
|
||||
if (!pfl->u.r.field) {
|
||||
pfl->no_elements = 0;
|
||||
pfl->dtor = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
pfl->dtor = freeString;
|
||||
|
||||
field = (char *)pfl->u.r.field;
|
||||
n = epicsTimeToStrftime(field, MAX_STRING_SIZE, fmt, &pfl->time);
|
||||
if (!n) {
|
||||
field[0] = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static db_field_log *filter(void *pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
(void)chan;
|
||||
|
||||
switch (settings->mode) {
|
||||
case tsModeDouble:
|
||||
return replace_fl_value(pvt, pfl, ts_double);
|
||||
case tsModeSec:
|
||||
return replace_fl_value(pvt, pfl, ts_seconds);
|
||||
case tsModeNsec:
|
||||
return replace_fl_value(pvt, pfl, ts_nanos);
|
||||
case tsModeArray:
|
||||
return replace_fl_value(pvt, pfl, ts_array);
|
||||
case tsModeString:
|
||||
return replace_fl_value(pvt, pfl, ts_string);
|
||||
case tsModeGenerate:
|
||||
case tsModeInvalid:
|
||||
default:
|
||||
logicErrorMessage();
|
||||
db_delete_field_log(pfl);
|
||||
pfl = NULL;
|
||||
}
|
||||
|
||||
return pfl;
|
||||
}
|
||||
|
||||
|
||||
/* Only the "generate" mode is registered for the pre-queue chain as it creates
|
||||
it's own timestamp which should be as close to the event as possible */
|
||||
static void channelRegisterPre(dbChannel * chan, void *pvt,
|
||||
chPostEventFunc **cb_out, void **arg_out,
|
||||
db_field_log *probe) {
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
(void)chan;
|
||||
(void)arg_out;
|
||||
(void)probe;
|
||||
|
||||
*cb_out = settings->mode == tsModeGenerate ? generate : NULL;
|
||||
}
|
||||
|
||||
/* For other modes, the post-chain is fine as they only manipulate existing
|
||||
timestamps */
|
||||
static void channelRegisterPost(dbChannel *chan, void *pvt,
|
||||
chPostEventFunc **cb_out, void **arg_out,
|
||||
db_field_log *probe) {
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
(void)chan;
|
||||
|
||||
if (settings->mode == tsModeGenerate || settings->mode == tsModeInvalid) {
|
||||
*cb_out = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
*cb_out = filter;
|
||||
*arg_out = pvt;
|
||||
|
||||
/* Get rid of the value of the probe because we will be changing the
|
||||
datatype */
|
||||
if (probe->type == dbfl_type_ref && probe->dtor) {
|
||||
probe->dtor(probe);
|
||||
probe->dtor = NULL;
|
||||
}
|
||||
probe->no_elements = 1;
|
||||
probe->type = dbfl_type_val;
|
||||
|
||||
switch (settings->mode) {
|
||||
case tsModeArray:
|
||||
probe->no_elements = 2;
|
||||
/* fallthrough */
|
||||
case tsModeSec:
|
||||
case tsModeNsec:
|
||||
probe->field_type = DBF_ULONG;
|
||||
probe->field_size = sizeof(epicsUInt32);
|
||||
break;
|
||||
case tsModeDouble:
|
||||
probe->field_type = DBF_DOUBLE;
|
||||
probe->field_size = sizeof(epicsFloat64);
|
||||
break;
|
||||
case tsModeString:
|
||||
probe->field_type = DBF_STRING;
|
||||
probe->field_size = MAX_STRING_SIZE;
|
||||
break;
|
||||
case tsModeGenerate:
|
||||
case tsModeInvalid:
|
||||
// Already handled above, added here for completeness.
|
||||
default:
|
||||
logicErrorMessage();
|
||||
*cb_out = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void channel_report(dbChannel *chan, void *pvt, int level, const unsigned short indent)
|
||||
{
|
||||
printf("%*sTimestamp (ts)\n", indent, "");
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
(void)chan;
|
||||
(void)level;
|
||||
|
||||
printf("%*sTimestamp (ts): mode: %d, epoch: %d, str: %d\n",
|
||||
indent, "", settings->mode, settings->epoch, settings->str);
|
||||
}
|
||||
|
||||
static chfPluginIf pif = {
|
||||
NULL, /* allocPvt, */
|
||||
NULL, /* freePvt, */
|
||||
allocPvt,
|
||||
freePvt,
|
||||
|
||||
NULL, /* parse_error, */
|
||||
NULL, /* parse_ok, */
|
||||
NULL, /* parse_error, */
|
||||
parse_finished,
|
||||
|
||||
NULL, /* channel_open, */
|
||||
channelRegisterPre,
|
||||
NULL, /* channelRegisterPost, */
|
||||
channelRegisterPost,
|
||||
channel_report,
|
||||
NULL /* channel_close */
|
||||
};
|
||||
|
||||
static void tsInitialize(void)
|
||||
{
|
||||
chfPluginRegister("ts", &pif, NULL);
|
||||
freeListInitPvt(&private_free_list, sizeof(tsPrivate),
|
||||
ALLOC_NUM_ELEMENTS);
|
||||
freeListInitPvt(&ts_array_free_list, 2 * sizeof(epicsUInt32),
|
||||
ALLOC_NUM_ELEMENTS);
|
||||
freeListInitPvt(&string_free_list, MAX_STRING_SIZE,
|
||||
ALLOC_NUM_ELEMENTS);
|
||||
chfPluginRegister("ts", &pif, ts_args);
|
||||
}
|
||||
|
||||
epicsExportRegistrar(tsInitialize);
|
||||
|
||||
@@ -42,6 +42,8 @@ value is non-zero.
|
||||
|
||||
=back
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.14.10 .
|
||||
|
||||
=head2 Record-specific Menus
|
||||
|
||||
=head3 Menu aSubLFLG
|
||||
|
||||
@@ -13,6 +13,8 @@ The histogram record is used to store frequency counts of a signal into an array
|
||||
of arbitrary length. The user can configure the range of the signal value that
|
||||
the array will store. Anything outside this range will be ignored.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.15.0.1 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below.
|
||||
|
||||
@@ -14,6 +14,8 @@ from a hardware input.
|
||||
The record supports alarm limits, alarm filtering, graphics and control
|
||||
limits.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.16.1 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below.
|
||||
|
||||
@@ -13,6 +13,8 @@ This record type is normally used to send an integer value of up to 64 bits
|
||||
to an output device.
|
||||
The record supports alarm, drive, graphics and control limits.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.16.1 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below.
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
The long string input record is used to retrieve an arbitrary ASCII string with
|
||||
a maximum length of 65535 characters.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.15.0.2 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
The long string output record is used to write an arbitrary ASCII string with a
|
||||
maximum length of 65535 characters.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.15.0.2 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
The printf record is used to generate and write a string using a format
|
||||
specification and parameters, analogous to the C C<printf()> function.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.15.0.2 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
@@ -3,7 +3,7 @@ To start the ioc from this directory execute the command
|
||||
|
||||
Alternatively make the st.cmd file directly executable with
|
||||
chmod +x st.cmd
|
||||
and check the executable name on the first line of the st.cmd file
|
||||
and check the executable name on the first line of the st.cmd file
|
||||
|
||||
You may need to change the name of the .dbd file given in the
|
||||
st.cmd's dbLoadDatabase() command before starting the ioc.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2021 Cosylab d.d
|
||||
* Copyright (c) 2010 Brookhaven National Laboratory.
|
||||
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
|
||||
* fuer Materialien und Energie GmbH.
|
||||
@@ -9,9 +10,11 @@
|
||||
|
||||
/*
|
||||
* Author: Ralph Lange <Ralph.Lange@bessy.de>
|
||||
* Author: Jure Varlec <jure.varlec@cosylab.com>
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "dbStaticLib.h"
|
||||
#include "dbAccessDefs.h"
|
||||
@@ -25,11 +28,24 @@
|
||||
#include "testMain.h"
|
||||
#include "osiFileName.h"
|
||||
|
||||
/* A fill pattern for setting a field log to something "random". */
|
||||
#define PATTERN 0x55
|
||||
|
||||
void filterTest_registerRecordDeviceDriver(struct dbBase *);
|
||||
/* Use a "normal" timestamp for testing. What results from filling the field log
|
||||
with the above pattern is a timestamp that causes problems with
|
||||
epicsTimeToStrftime() on some platforms. */
|
||||
static epicsTimeStamp const test_ts = { 616600420, 998425354 };
|
||||
|
||||
static db_field_log fl;
|
||||
typedef int (*TypeCheck)(const db_field_log *pfl);
|
||||
typedef int (*ValueCheck)(const db_field_log *pfl, const epicsTimeStamp *ts);
|
||||
|
||||
typedef struct {
|
||||
char const *channel;
|
||||
TypeCheck type_check;
|
||||
ValueCheck value_check;
|
||||
} TestSpec;
|
||||
|
||||
void filterTest_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) {
|
||||
return !(memcmp(pfl1, pfl2, sizeof(db_field_log)));
|
||||
@@ -42,21 +58,210 @@ static int fl_equal_ex_ts(const db_field_log *pfl1, const db_field_log *pfl2) {
|
||||
return fl_equal(&fl1, pfl2);
|
||||
}
|
||||
|
||||
MAIN(tsTest)
|
||||
{
|
||||
static void fl_reset(db_field_log *pfl) {
|
||||
memset(pfl, PATTERN, sizeof(*pfl));
|
||||
pfl->time = test_ts;
|
||||
}
|
||||
|
||||
static void test_generate_filter(const chFilterPlugin *plug) {
|
||||
dbChannel *pch;
|
||||
chFilter *filter;
|
||||
const chFilterPlugin *plug;
|
||||
char ts[] = "ts";
|
||||
ELLNODE *node;
|
||||
chPostEventFunc *cb_out = NULL;
|
||||
void *arg_out = NULL;
|
||||
db_field_log fl;
|
||||
db_field_log fl1;
|
||||
db_field_log *pfl2;
|
||||
epicsTimeStamp stamp, now;
|
||||
dbEventCtx evtctx;
|
||||
ELLNODE *node;
|
||||
chPostEventFunc *cb_out = NULL;
|
||||
void *arg_out = NULL;
|
||||
char const *chan_name = "x.VAL{ts:{}}";
|
||||
|
||||
testPlan(12);
|
||||
testOk(!!(pch = dbChannelCreate(chan_name)),
|
||||
"dbChannel with plugin ts created");
|
||||
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
|
||||
|
||||
fl_reset(&fl);
|
||||
fl1 = fl;
|
||||
node = ellFirst(&pch->filters);
|
||||
filter = CONTAINER(node, chFilter, list_node);
|
||||
plug->fif->channel_register_post(filter, &cb_out, &arg_out, &fl1);
|
||||
plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1);
|
||||
testOk(!!(cb_out) && !(arg_out),
|
||||
"register_pre registers one filter w/o argument");
|
||||
testOk(fl_equal(&fl1, &fl),
|
||||
"register_pre does not change field_log data type");
|
||||
|
||||
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened");
|
||||
node = ellFirst(&pch->pre_chain);
|
||||
filter = CONTAINER(node, chFilter, pre_node);
|
||||
testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg == NULL),
|
||||
"ts has one filter w/o argument in pre chain");
|
||||
testOk((ellCount(&pch->post_chain) == 0), "ts has no filter in post chain");
|
||||
|
||||
fl_reset(&fl);
|
||||
fl1 = fl;
|
||||
pfl2 = dbChannelRunPreChain(pch, &fl1);
|
||||
testOk(pfl2 == &fl1, "ts filter does not drop or replace field_log");
|
||||
testOk(fl_equal_ex_ts(&fl1, pfl2),
|
||||
"ts filter does not change field_log data");
|
||||
|
||||
testOk(!!(pfl2 = db_create_read_log(pch)), "create field log from channel");
|
||||
stamp = pfl2->time;
|
||||
db_delete_field_log(pfl2);
|
||||
|
||||
pfl2 = dbChannelRunPreChain(pch, &fl1);
|
||||
epicsTimeGetCurrent(&now);
|
||||
testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) >= 0 &&
|
||||
epicsTimeDiffInSeconds(&now, &pfl2->time) >= 0,
|
||||
"ts filter sets time stamp to \"now\"");
|
||||
|
||||
dbChannelDelete(pch);
|
||||
}
|
||||
|
||||
static void test_value_filter(const chFilterPlugin *plug, const char *chan_name,
|
||||
TypeCheck tc_func, ValueCheck vc_func) {
|
||||
dbChannel *pch;
|
||||
chFilter *filter;
|
||||
db_field_log fl;
|
||||
db_field_log fl2;
|
||||
db_field_log *pfl;
|
||||
epicsTimeStamp ts;
|
||||
ELLNODE *node;
|
||||
chPostEventFunc *cb_out = NULL;
|
||||
void *arg_out = NULL;
|
||||
|
||||
testDiag("Channel %s", chan_name);
|
||||
|
||||
testOk(!!(pch = dbChannelCreate(chan_name)),
|
||||
"dbChannel with plugin ts created");
|
||||
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
|
||||
|
||||
fl_reset(&fl);
|
||||
fl.type = dbfl_type_val;
|
||||
node = ellFirst(&pch->filters);
|
||||
filter = CONTAINER(node, chFilter, list_node);
|
||||
plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl);
|
||||
plug->fif->channel_register_post(filter, &cb_out, &arg_out, &fl);
|
||||
testOk(!!(cb_out) && arg_out,
|
||||
"register_post registers one filter with argument");
|
||||
testOk(tc_func(&fl), "register_post gives correct field type");
|
||||
|
||||
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened");
|
||||
node = ellFirst(&pch->post_chain);
|
||||
filter = CONTAINER(node, chFilter, post_node);
|
||||
testOk((ellCount(&pch->post_chain) == 1 && filter->post_arg != NULL),
|
||||
"ts has one filter with argument in post chain");
|
||||
testOk((ellCount(&pch->pre_chain) == 0), "ts has no filter in pre chain");
|
||||
|
||||
fl_reset(&fl);
|
||||
fl.type = dbfl_type_val;
|
||||
ts = fl.time;
|
||||
fl2 = fl;
|
||||
pfl = dbChannelRunPostChain(pch, &fl);
|
||||
testOk(pfl == &fl, "ts filter does not drop or replace field_log");
|
||||
testOk(tc_func(pfl), "ts filter gives correct field type");
|
||||
testOk((pfl->time.secPastEpoch == fl2.time.secPastEpoch &&
|
||||
pfl->time.nsec == fl2.time.nsec && pfl->stat == fl2.stat &&
|
||||
pfl->sevr == fl2.sevr),
|
||||
"ts filter does not touch non-value fields of field_log");
|
||||
testOk(vc_func(pfl, &ts), "ts filter gives correct field value");
|
||||
|
||||
dbChannelDelete(pch);
|
||||
}
|
||||
|
||||
static int type_check_double(const db_field_log *pfl) {
|
||||
return pfl->type == dbfl_type_val
|
||||
&& pfl->field_type == DBR_DOUBLE
|
||||
&& pfl->field_size == sizeof(epicsFloat64)
|
||||
&& pfl->no_elements == 1;
|
||||
}
|
||||
|
||||
static int value_check_double(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
epicsFloat64 flt = pfl->u.v.field.dbf_double;
|
||||
epicsFloat64 nsec = (flt - (epicsUInt32)(flt)) * 1e9;
|
||||
return ts->secPastEpoch == (epicsUInt32)(flt)
|
||||
&& fabs(ts->nsec - nsec) < 1000.; /* allow loss of precision */
|
||||
}
|
||||
|
||||
static int type_check_sec_nsec(const db_field_log *pfl) {
|
||||
return pfl->type == dbfl_type_val
|
||||
&& pfl->field_type == DBR_ULONG
|
||||
&& pfl->field_size == sizeof(epicsUInt32)
|
||||
&& pfl->no_elements == 1;
|
||||
}
|
||||
|
||||
static int value_check_sec(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
return ts->secPastEpoch == pfl->u.v.field.dbf_ulong;
|
||||
}
|
||||
|
||||
static int value_check_nsec(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
return ts->nsec == pfl->u.v.field.dbf_ulong;
|
||||
}
|
||||
|
||||
static int type_check_array(const db_field_log *pfl) {
|
||||
return pfl->field_type == DBR_ULONG
|
||||
&& pfl->field_size == sizeof(epicsUInt32)
|
||||
&& pfl->no_elements == 2;
|
||||
}
|
||||
|
||||
static int value_check_array(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
epicsUInt32 *arr = (epicsUInt32*)pfl->u.r.field;
|
||||
return pfl->type == dbfl_type_ref
|
||||
&& pfl->u.r.field != NULL
|
||||
&& pfl->dtor != NULL
|
||||
&& pfl->u.r.pvt == NULL
|
||||
&& ts->secPastEpoch == arr[0]
|
||||
&& ts->nsec == arr[1];
|
||||
}
|
||||
|
||||
static int value_check_unix(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
epicsUInt32 *arr = (epicsUInt32 *)pfl->u.r.field;
|
||||
return pfl->type == dbfl_type_ref
|
||||
&& pfl->u.r.field != NULL
|
||||
&& pfl->dtor != NULL
|
||||
&& pfl->u.r.pvt == NULL
|
||||
&& ts->secPastEpoch == arr[0] - POSIX_TIME_AT_EPICS_EPOCH
|
||||
&& ts->nsec == arr[1];
|
||||
}
|
||||
|
||||
static int type_check_string(const db_field_log *pfl) {
|
||||
return pfl->field_type == DBR_STRING
|
||||
&& pfl->field_size == MAX_STRING_SIZE
|
||||
&& pfl->no_elements == 1;
|
||||
}
|
||||
|
||||
static int value_check_string(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
/* We can only verify the type, not the value, because using strptime()
|
||||
might be problematic. */
|
||||
(void)ts;
|
||||
return pfl->type == dbfl_type_ref
|
||||
&& pfl->u.r.field != NULL
|
||||
&& pfl->dtor != NULL
|
||||
&& pfl->u.r.pvt == NULL;
|
||||
}
|
||||
|
||||
MAIN(tsTest) {
|
||||
int i;
|
||||
char ts[] = "ts";
|
||||
dbEventCtx evtctx;
|
||||
const chFilterPlugin *plug;
|
||||
|
||||
static TestSpec const tests[] = {
|
||||
{"x.VAL{ts:{\"num\": \"dbl\"}}", type_check_double, value_check_double},
|
||||
{"x.VAL{ts:{\"num\": \"sec\"}}", type_check_sec_nsec, value_check_sec},
|
||||
{"x.VAL{ts:{\"num\": \"nsec\"}}", type_check_sec_nsec, value_check_nsec},
|
||||
{"x.VAL{ts:{\"num\": \"ts\"}}", type_check_array, value_check_array},
|
||||
{"x.VAL{ts:{\"num\": \"ts\", \"epoch\": \"epics\"}}", type_check_array, value_check_array},
|
||||
{"x.VAL{ts:{\"num\": \"ts\", \"epoch\": \"unix\"}}", type_check_array, value_check_unix},
|
||||
{"x.VAL{ts:{\"str\": \"epics\"}}", type_check_string, value_check_string},
|
||||
#if !(defined _MSC_VER && _MSC_VER <= 1700)
|
||||
{"x.VAL{ts:{\"str\": \"iso\"}}", type_check_string, value_check_string},
|
||||
#endif
|
||||
};
|
||||
|
||||
static int const num_value_tests = sizeof(tests) / sizeof(tests[0]);
|
||||
|
||||
testPlan(12 /* test_generate_filter() */
|
||||
+ num_value_tests * 11 /* test_value_filter() */);
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
@@ -77,41 +282,12 @@ MAIN(tsTest)
|
||||
testAbort("plugin '%s' not registered", ts);
|
||||
testPass("plugin '%s' registered correctly", ts);
|
||||
|
||||
testOk(!!(pch = dbChannelCreate("x.VAL{ts:{}}")), "dbChannel with plugin ts created");
|
||||
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
|
||||
test_generate_filter(plug);
|
||||
|
||||
memset(&fl, PATTERN, sizeof(fl));
|
||||
fl1 = fl;
|
||||
node = ellFirst(&pch->filters);
|
||||
filter = CONTAINER(node, chFilter, list_node);
|
||||
plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1);
|
||||
testOk(!!(cb_out) && !(arg_out), "register_pre registers one filter w/o argument");
|
||||
testOk(fl_equal(&fl1, &fl), "register_pre does not change field_log data type");
|
||||
|
||||
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened");
|
||||
node = ellFirst(&pch->pre_chain);
|
||||
filter = CONTAINER(node, chFilter, pre_node);
|
||||
testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg == NULL),
|
||||
"ts has one filter w/o argument in pre chain");
|
||||
testOk((ellCount(&pch->post_chain) == 0), "ts has no filter in post chain");
|
||||
|
||||
memset(&fl, PATTERN, sizeof(fl));
|
||||
fl1 = fl;
|
||||
pfl2 = dbChannelRunPreChain(pch, &fl1);
|
||||
testOk(pfl2 == &fl1, "ts filter does not drop or replace field_log");
|
||||
testOk(fl_equal_ex_ts(&fl1, pfl2), "ts filter does not change field_log data");
|
||||
|
||||
testOk(!!(pfl2 = db_create_read_log(pch)), "create field log from channel");
|
||||
stamp = pfl2->time;
|
||||
db_delete_field_log(pfl2);
|
||||
|
||||
pfl2 = dbChannelRunPreChain(pch, &fl1);
|
||||
epicsTimeGetCurrent(&now);
|
||||
testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) >= 0 &&
|
||||
epicsTimeDiffInSeconds(&now, &pfl2->time) >= 0,
|
||||
"ts filter sets time stamp to \"now\"");
|
||||
|
||||
dbChannelDelete(pch);
|
||||
for (i = 0; i < num_value_tests; ++i) {
|
||||
TestSpec const *t = &tests[i];
|
||||
test_value_filter(plug, t->channel, t->type_check, t->value_check);
|
||||
}
|
||||
|
||||
db_close_events(evtctx);
|
||||
|
||||
|
||||
@@ -60,7 +60,19 @@ extern void *POSIX_Init(void *argument);
|
||||
#define CONFIGURE_APPLICATION_NEEDS_STUB_DRIVER
|
||||
#define CONFIGURE_APPLICATION_NEEDS_ZERO_DRIVER
|
||||
|
||||
/* Max FDs cannot exceed FD_SETSIZE from newlib (64) */
|
||||
/* Note: The select() system call can only be used with the first FD_SETSIZE
|
||||
* File Descriptors (newlib default is 64). Beginning RTEMS 5.1, FDs are
|
||||
* allocated sequentially. So changing this CONFIGURE parameter such
|
||||
* that CONFIGURE_MAXIMUM_FILE_DESCRIPTORS >= FD_SETSIZE will likely
|
||||
* cause applications making select() calls to fault at some point.
|
||||
*
|
||||
* IOC core components (libca and RSRV) do not make select() calls.
|
||||
*
|
||||
* Applications and driver code using poll() or other socket
|
||||
* multiplexers do not share this limitation.
|
||||
*
|
||||
* cf. https://github.com/epics-base/epics-base/issues/300
|
||||
*/
|
||||
#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 64
|
||||
#define CONFIGURE_IMFS_ENABLE_MKFIFO 2
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#include "postfix.h"
|
||||
#include "asLib.h"
|
||||
|
||||
#undef ECHO /* from termios.h */
|
||||
|
||||
int asCheckClientIP;
|
||||
|
||||
static epicsMutexId asLock;
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
|
||||
class SingletonUntyped {
|
||||
public:
|
||||
#if __cplusplus>=201103L
|
||||
constexpr
|
||||
#endif
|
||||
SingletonUntyped () :_pInstance ( 0 ), _refCount ( 0 ) {}
|
||||
# if 0
|
||||
~SingletonUntyped () {
|
||||
@@ -112,6 +115,9 @@ public:
|
||||
epicsSingleton * _pSingleton;
|
||||
};
|
||||
friend class reference;
|
||||
#if __cplusplus>=201103L
|
||||
constexpr
|
||||
#endif
|
||||
epicsSingleton () {}
|
||||
// mutex lock/unlock pair overhead incurred
|
||||
// when either of these are called
|
||||
|
||||
@@ -97,7 +97,7 @@ LIBCOM_API extern int errVerbose;
|
||||
* that the output is sent to the errlog task. Unless configured not to, the output
|
||||
* will appear on the console as well.
|
||||
*/
|
||||
LIBCOM_API int errlogPrintf(const char *pformat, ...)
|
||||
LIBCOM_API int errlogPrintf(EPICS_PRINTF_FMT(const char *pformat), ...)
|
||||
EPICS_PRINTF_STYLE(1,2);
|
||||
|
||||
/**
|
||||
@@ -118,7 +118,8 @@ LIBCOM_API int errlogVprintf(const char *pformat, va_list pvar);
|
||||
* \return int Consult printf documentation in C standard library
|
||||
*/
|
||||
LIBCOM_API int errlogSevPrintf(const errlogSevEnum severity,
|
||||
const char *pformat, ...) EPICS_PRINTF_STYLE(2,3);
|
||||
EPICS_PRINTF_FMT(const char *pformat), ...
|
||||
) EPICS_PRINTF_STYLE(2,3);
|
||||
|
||||
/**
|
||||
* This function is like ::errlogVprintf except that it adds the severity to the beginning
|
||||
@@ -239,11 +240,14 @@ LIBCOM_API void errlogFlush(void);
|
||||
* The remaining arguments are just like the arguments to the C printf routine.
|
||||
* ::errVerbose determines if the filename and line number are shown.
|
||||
*/
|
||||
LIBCOM_API void errPrintf(long status, const char *pFileName, int lineno,
|
||||
const char *pformat, ...) EPICS_PRINTF_STYLE(4,5);
|
||||
LIBCOM_API void errPrintf(
|
||||
long status, const char *pFileName, int lineno,
|
||||
EPICS_PRINTF_FMT(const char *pformat), ...
|
||||
) EPICS_PRINTF_STYLE(4,5);
|
||||
|
||||
LIBCOM_API int errlogPrintfNoConsole(const char *pformat, ...)
|
||||
EPICS_PRINTF_STYLE(1,2);
|
||||
LIBCOM_API int errlogPrintfNoConsole(
|
||||
EPICS_PRINTF_FMT(const char *pformat), ...
|
||||
) EPICS_PRINTF_STYLE(1,2);
|
||||
LIBCOM_API int errlogVprintfNoConsole(const char *pformat,va_list pvar);
|
||||
|
||||
/**
|
||||
|
||||
@@ -559,7 +559,17 @@ char** iocsh_attempt_completion(const char* word, int start, int end)
|
||||
break;
|
||||
}
|
||||
}
|
||||
err = arg-1u >= size_t(def->pFuncDef->nargs);
|
||||
if(arg-1u >= size_t(def->pFuncDef->nargs)) {
|
||||
if(def->pFuncDef->arg
|
||||
&& def->pFuncDef->nargs
|
||||
&& def->pFuncDef->arg[def->pFuncDef->nargs-1u]->type == iocshArgArgv)
|
||||
{
|
||||
// last argument is variable length
|
||||
arg = def->pFuncDef->nargs;
|
||||
} else {
|
||||
err = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!err) {
|
||||
|
||||
@@ -287,6 +287,7 @@ LIBCOM_API int epicsStdCall iocshRun(const char *cmd, const char* macros);
|
||||
*
|
||||
* @param err 0 - success (no op), !=0 - error
|
||||
* @return The err argument value.
|
||||
* @since 7.0.3.1
|
||||
*/
|
||||
LIBCOM_API int iocshSetError(int err);
|
||||
|
||||
|
||||
@@ -43,8 +43,9 @@ extern "C" {
|
||||
* \param errorMessage A printf-style error message describing the error.
|
||||
* \param ... Any parameters required for the error message.
|
||||
*/
|
||||
LIBCOM_API void cantProceed(const char *errorMessage, ...)
|
||||
EPICS_PRINTF_STYLE(1,2);
|
||||
LIBCOM_API void cantProceed(
|
||||
EPICS_PRINTF_FMT(const char *errorMessage), ...
|
||||
) EPICS_PRINTF_STYLE(1,2);
|
||||
|
||||
/** \name Memory Allocation Functions
|
||||
* These versions of calloc() and malloc() never fail, they suspend the
|
||||
|
||||
@@ -14,16 +14,22 @@
|
||||
/** \file epicsExport.h
|
||||
* \brief Exporting IOC objects.
|
||||
*
|
||||
* This header is unique, as it defines epicsExportSharedSymbols and thus
|
||||
* triggers a transition between importing declarations from other libraries,
|
||||
* to exporting symbols from our own library. The comments in shareLib.h
|
||||
* provide more information.
|
||||
* This header defines macros that allow registering IOC shell commands,
|
||||
* subroutines, device support, etc.
|
||||
*
|
||||
* This header should be included with a trailing comment to make it stand
|
||||
* out from other includes, something like this:
|
||||
\code
|
||||
#include <epicsExport.h> // defines epicsExportSharedSymbols
|
||||
\endcode
|
||||
* Because this header defines the `epicsExportSharedSymbols` macro, it triggers
|
||||
* a transition between importing declarations from other libraries, to
|
||||
* exporting symbols from our own library. For this reason, it must be included
|
||||
* *last*, after all other EPICS-related includes. The comments in shareLib.h
|
||||
* provide more information. It is recommended to mark this with a comment, e.g.
|
||||
* \code
|
||||
* #include <epicsExport.h> // defines epicsExportSharedSymbols, do not move
|
||||
* \endcode
|
||||
*
|
||||
* \note Do not use this header solely to enable exporting of symbols. If you
|
||||
* are implementing a library and need to handle the differences in shared
|
||||
* libraries between Linux and Windows, refer to the documentation within
|
||||
* [**makeAPIheader.pl**](makeAPIheader.html) or run `makeAPIheader.pl -h`.
|
||||
*/
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
|
||||
@@ -174,7 +174,7 @@ LIBCOM_API void testPlan(int tests);
|
||||
* \param ... Any parameters required for the format string.
|
||||
* \return The value of \p pass.
|
||||
*/
|
||||
LIBCOM_API int testOk(int pass, const char *fmt, ...)
|
||||
LIBCOM_API int testOk(int pass, EPICS_PRINTF_FMT(const char *fmt), ...)
|
||||
EPICS_PRINTF_STYLE(2, 3);
|
||||
/** \brief Test result using expression as description
|
||||
* \param cond Expression to be evaluated and displayed.
|
||||
@@ -192,13 +192,13 @@ LIBCOM_API int testOkV(int pass, const char *fmt, va_list pvar);
|
||||
* \param fmt A printf-style format string describing the test.
|
||||
* \param ... Any parameters required for the format string.
|
||||
*/
|
||||
LIBCOM_API void testPass(const char *fmt, ...)
|
||||
LIBCOM_API void testPass(EPICS_PRINTF_FMT(const char *fmt), ...)
|
||||
EPICS_PRINTF_STYLE(1, 2);
|
||||
/** \brief Failing test result with printf-style description.
|
||||
* \param fmt A printf-style format string describing the test.
|
||||
* \param ... Any parameters required for the format string.
|
||||
*/
|
||||
LIBCOM_API void testFail(const char *fmt, ...)
|
||||
LIBCOM_API void testFail(EPICS_PRINTF_FMT(const char *fmt), ...)
|
||||
EPICS_PRINTF_STYLE(1, 2);
|
||||
/** @} */
|
||||
|
||||
@@ -223,7 +223,7 @@ LIBCOM_API void testTodoEnd(void);
|
||||
* \param fmt A printf-style format string giving the reason for stopping.
|
||||
* \param ... Any parameters required for the format string.
|
||||
*/
|
||||
LIBCOM_API void testAbort(const char *fmt, ...)
|
||||
LIBCOM_API void testAbort(EPICS_PRINTF_FMT(const char *fmt), ...)
|
||||
EPICS_PRINTF_STYLE(1, 2);
|
||||
/** @} */
|
||||
|
||||
@@ -231,7 +231,7 @@ LIBCOM_API void testAbort(const char *fmt, ...)
|
||||
* \param fmt A printf-style format string containing diagnostic information.
|
||||
* \param ... Any parameters required for the format string.
|
||||
*/
|
||||
LIBCOM_API int testDiag(const char *fmt, ...)
|
||||
LIBCOM_API int testDiag(EPICS_PRINTF_FMT(const char *fmt), ...)
|
||||
EPICS_PRINTF_STYLE(1, 2);
|
||||
/** \brief Mark the end of testing.
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
*
|
||||
* These are needed to properly create DLLs on MS Windows.
|
||||
*
|
||||
* \note This header file is deprecated. A newer mechanism is available that
|
||||
* automatically handles the differences in shared libraries between Linux and
|
||||
* Windows while avoiding the pitfalls of the `epicsExportSharedSymbols` macro.
|
||||
* If you are implementing a library, refer to the documentation within
|
||||
* [**makeAPIheader.pl**](makeAPIheader.html) or run `makeAPIheader.pl -h`.
|
||||
*
|
||||
* ### USAGE
|
||||
*
|
||||
* There are two distinct classes of keywords in this file:
|
||||
|
||||
@@ -21,10 +21,6 @@
|
||||
# error compiler/gcc/compilerSpecific.h is only for use with the gnu compiler
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
# error compiler/gcc/compilerSpecific.h is not for use with the clang compiler
|
||||
#endif
|
||||
|
||||
#if __GNUC__ > 2
|
||||
# define EPICS_ALWAYS_INLINE __inline__ __attribute__((always_inline))
|
||||
#else
|
||||
|
||||
@@ -44,5 +44,11 @@
|
||||
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Enable format-string checking if compiler supports it (if msvc is 2015 or newer)
|
||||
*/
|
||||
#if _MSC_VER >= 1900
|
||||
# define EPICS_PRINTF_FMT(a) _Printf_format_string_ a
|
||||
#endif
|
||||
|
||||
#endif /* ifndef compilerSpecific_h */
|
||||
|
||||
@@ -61,4 +61,11 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef EPICS_PRINTF_FMT
|
||||
/*
|
||||
* No format-string checking annotation
|
||||
*/
|
||||
# define EPICS_PRINTF_FMT(a) a
|
||||
#endif
|
||||
|
||||
#endif /* ifndef compilerDependencies_h */
|
||||
|
||||
@@ -130,7 +130,8 @@ extern "C" {
|
||||
* output has been truncated if the return value is `size` or more.
|
||||
*/
|
||||
LIBCOM_API int epicsStdCall epicsSnprintf(
|
||||
char *str, size_t size, const char *format, ...) EPICS_PRINTF_STYLE(3,4);
|
||||
char *str, size_t size, EPICS_PRINTF_FMT(const char *format), ...
|
||||
) EPICS_PRINTF_STYLE(3,4);
|
||||
/**
|
||||
* \brief epicsVsnprintf() is meant to have the same semantics as the C99
|
||||
* function vsnprintf()
|
||||
|
||||
@@ -22,25 +22,10 @@
|
||||
|
||||
#define VC_EXTRALEAN
|
||||
#define STRICT
|
||||
/*
|
||||
* Defining this allows the *much* faster critical
|
||||
* section mutex primitive to be used. Unfortunately,
|
||||
* using certain of these functions drops support for W95\W98\WME
|
||||
* unless we specify "delay loading" when we link with the
|
||||
* DLL so that DLL entry points are not resolved until they
|
||||
* are used. The code does have run time switches so
|
||||
* that the more advanced calls are not called unless
|
||||
* they are available in the windows OS, but this feature
|
||||
* isn't going to be very useful unless we specify "delay
|
||||
* loading" when we link with the DLL.
|
||||
*
|
||||
* It appears that the only entry point used here that causes
|
||||
* portability problems with W95\W98\WME is TryEnterCriticalSection.
|
||||
*/
|
||||
#ifndef _WIN32_WINNT
|
||||
# define _WIN32_WINNT 0x0400
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#if _WIN32_WINNT < 0x0501
|
||||
# error Minimum supported is Windows XP
|
||||
#endif
|
||||
|
||||
#define EPICS_PRIVATE_API
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
* Author: Jeff Hill
|
||||
*/
|
||||
|
||||
/* pull in _WIN32_WINNT* definitions, if _WIN32_WINNT
|
||||
* is not already defined it is set to max supported by SDK
|
||||
*/
|
||||
#include <sdkddkver.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
@@ -32,14 +37,58 @@
|
||||
#include "epicsAtomic.h"
|
||||
#include "osdThreadPvt.h"
|
||||
|
||||
/* Windows Vista and higher supports fibre functions, but the
|
||||
* prototypes only appear in the windows SDK 8 and above.
|
||||
* VS2010 supplies sdk 7, but can be upgraded to later SDK
|
||||
|
||||
* To accomodate this we suuply prototypes on, for XP
|
||||
* fall back to Tls*() which will build and run
|
||||
* correctly for epicsThreads, but means that TLS allocations from
|
||||
* epicsThreadImplicitCreate() will continue to leak (for non-EPICS threads).
|
||||
*
|
||||
* Also, WINE circa 5.0.3 provides the FLS storage functions, but doesn't
|
||||
* actually run the dtor function.
|
||||
*
|
||||
* we check for existance of _WIN32_WINNT_WIN8 which will only be defined
|
||||
* in SDK 8 and above. If Visa is detected and SDK < 8 we will supply
|
||||
* the missing prototypes
|
||||
*/
|
||||
|
||||
#if _WIN32_WINNT >= 0x0600 /* VISTA */
|
||||
# ifdef _WIN32_WINNT_WIN8 /* Existance means using SDK 8 or higher */
|
||||
# include <fibersapi.h>
|
||||
# else
|
||||
# include <winnt.h> /* for PFLS_CALLBACK_FUNCTION */
|
||||
/* the fibers api exists in vista and above, but no header is provided until SDK 8 */
|
||||
WINBASEAPI DWORD WINAPI FlsAlloc(PFLS_CALLBACK_FUNCTION);
|
||||
WINBASEAPI PVOID WINAPI FlsGetValue(DWORD);
|
||||
WINBASEAPI BOOL WINAPI FlsSetValue(DWORD , PVOID);
|
||||
WINBASEAPI BOOL WINAPI FlsFree(DWORD);
|
||||
# endif
|
||||
#elif _WIN32_WINNT >= 0x0501 /* Windows XP */
|
||||
typedef void (WINAPI *xPFLS_CALLBACK_FUNCTION) (void*);
|
||||
static
|
||||
DWORD xFlsAlloc(xPFLS_CALLBACK_FUNCTION dtor) {
|
||||
(void)dtor;
|
||||
return TlsAlloc();
|
||||
}
|
||||
# define FlsAlloc xFlsAlloc
|
||||
# define FlsSetValue TlsSetValue
|
||||
# define FlsGetValue TlsGetValue
|
||||
# define USE_TLSALLOC_FALLBACK
|
||||
#else
|
||||
# error Minimum supported is Windows XP
|
||||
#endif
|
||||
|
||||
LIBCOM_API void osdThreadHooksRun(epicsThreadId id);
|
||||
|
||||
void setThreadName ( DWORD dwThreadID, LPCSTR szThreadName );
|
||||
static void WINAPI epicsParmCleanupWIN32 ( void * praw );
|
||||
|
||||
typedef struct win32ThreadGlobal {
|
||||
CRITICAL_SECTION mutex;
|
||||
ELLLIST threadList;
|
||||
DWORD tlsIndexThreadLibraryEPICS;
|
||||
DWORD flsIndexThreadLibraryEPICS;
|
||||
} win32ThreadGlobal;
|
||||
|
||||
typedef struct epicsThreadOSD {
|
||||
@@ -119,7 +168,6 @@ BOOL WINAPI DllMain (
|
||||
* Don't allow user's explicitly calling FreeLibrary for Com.dll to yank
|
||||
* the carpet out from under EPICS threads that are still using Com.dll
|
||||
*/
|
||||
#if _WIN32_WINNT >= 0x0501
|
||||
/*
|
||||
* Only in WXP
|
||||
* That's a shame because this is probably much faster
|
||||
@@ -127,22 +175,7 @@ BOOL WINAPI DllMain (
|
||||
success = GetModuleHandleEx (
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
|
||||
( LPCTSTR ) DllMain, & dllHandle );
|
||||
#else
|
||||
{
|
||||
char name[256];
|
||||
DWORD nChar = GetModuleFileName (
|
||||
hModule, name, sizeof ( name ) );
|
||||
if ( nChar && nChar < sizeof ( name ) ) {
|
||||
dllHandle = LoadLibrary ( name );
|
||||
if ( ! dllHandle ) {
|
||||
success = FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
success = FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( success ) {
|
||||
success = TlsSetValue ( dllHandleIndex, dllHandle );
|
||||
}
|
||||
@@ -207,8 +240,8 @@ static win32ThreadGlobal * fetchWin32ThreadGlobal ( void )
|
||||
|
||||
InitializeCriticalSection ( & pWin32ThreadGlobal->mutex );
|
||||
ellInit ( & pWin32ThreadGlobal->threadList );
|
||||
pWin32ThreadGlobal->tlsIndexThreadLibraryEPICS = TlsAlloc();
|
||||
if ( pWin32ThreadGlobal->tlsIndexThreadLibraryEPICS == 0xFFFFFFFF ) {
|
||||
pWin32ThreadGlobal->flsIndexThreadLibraryEPICS = FlsAlloc(&epicsParmCleanupWIN32);
|
||||
if ( pWin32ThreadGlobal->flsIndexThreadLibraryEPICS == FLS_OUT_OF_INDEXES ) {
|
||||
DeleteCriticalSection ( & pWin32ThreadGlobal->mutex );
|
||||
free ( pWin32ThreadGlobal );
|
||||
pWin32ThreadGlobal = 0;
|
||||
@@ -233,10 +266,14 @@ static void epicsParmCleanupDataWIN32 ( win32ThreadParam * pParm )
|
||||
}
|
||||
}
|
||||
|
||||
static void epicsParmCleanupWIN32 ( win32ThreadParam * pParm )
|
||||
static void WINAPI epicsParmCleanupWIN32 ( void * praw )
|
||||
{
|
||||
win32ThreadGlobal * pGbl = fetchWin32ThreadGlobal ();
|
||||
if ( ! pGbl ) {
|
||||
win32ThreadParam * pParm = praw;
|
||||
win32ThreadGlobal * pGbl;
|
||||
if(!praw)
|
||||
return;
|
||||
|
||||
if ( ! (pGbl = fetchWin32ThreadGlobal ()) ) {
|
||||
fprintf ( stderr, "epicsParmCleanupWIN32: unable to find ctx\n" );
|
||||
return;
|
||||
}
|
||||
@@ -464,7 +501,7 @@ static unsigned WINAPI epicsWin32ThreadEntry ( LPVOID lpParameter )
|
||||
if ( pGbl ) {
|
||||
setThreadName ( pParm->id, pParm->pName );
|
||||
|
||||
success = TlsSetValue ( pGbl->tlsIndexThreadLibraryEPICS, pParm );
|
||||
success = FlsSetValue ( pGbl->flsIndexThreadLibraryEPICS, pParm );
|
||||
if ( success ) {
|
||||
osdThreadHooksRun ( ( epicsThreadId ) pParm );
|
||||
/* printf ( "starting thread %d\n", pParm->id ); */
|
||||
@@ -482,15 +519,15 @@ static unsigned WINAPI epicsWin32ThreadEntry ( LPVOID lpParameter )
|
||||
|
||||
epicsExitCallAtThreadExits ();
|
||||
|
||||
/*
|
||||
* CAUTION: !!!! the thread id might continue to be used after this thread exits !!!!
|
||||
/* On Windows we could omit this and rely on the callback given to FlsAlloc() to free.
|
||||
* However < vista doesn't implement FLS at all, and WINE (circa 5.0.3) doesn't
|
||||
* implement fully (dtor never runs). So for EPICS threads, we explicitly
|
||||
* free() here.
|
||||
*/
|
||||
if ( pGbl ) {
|
||||
TlsSetValue ( pGbl->tlsIndexThreadLibraryEPICS, (void*)0xdeadbeef );
|
||||
if(pGbl && FlsSetValue ( pGbl->flsIndexThreadLibraryEPICS, NULL )) {
|
||||
epicsParmCleanupWIN32 ( pParm );
|
||||
}
|
||||
|
||||
epicsParmCleanupWIN32 ( pParm );
|
||||
|
||||
return retStat; /* this indirectly closes the thread handle */
|
||||
}
|
||||
|
||||
@@ -550,7 +587,7 @@ static win32ThreadParam * epicsThreadImplicitCreate ( void )
|
||||
win32ThreadPriority = GetThreadPriority ( pParm->handle );
|
||||
assert ( win32ThreadPriority != THREAD_PRIORITY_ERROR_RETURN );
|
||||
pParm->epicsPriority = epicsThreadGetOsiPriorityValue ( win32ThreadPriority );
|
||||
success = TlsSetValue ( pGbl->tlsIndexThreadLibraryEPICS, pParm );
|
||||
success = FlsSetValue ( pGbl->flsIndexThreadLibraryEPICS, pParm );
|
||||
if ( ! success ) {
|
||||
epicsParmCleanupWIN32 ( pParm );
|
||||
pParm = 0;
|
||||
@@ -638,6 +675,8 @@ epicsThreadId epicsThreadCreateOpt (
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* after this point, new thread is responsible to free(pParmWIN32) */
|
||||
|
||||
return ( epicsThreadId ) pParmWIN32;
|
||||
}
|
||||
|
||||
@@ -685,7 +724,7 @@ static void* getMyWin32ThreadParam ( win32ThreadGlobal * pGbl )
|
||||
}
|
||||
|
||||
pParm = ( win32ThreadParam * )
|
||||
TlsGetValue ( pGbl->tlsIndexThreadLibraryEPICS );
|
||||
FlsGetValue ( pGbl->flsIndexThreadLibraryEPICS );
|
||||
if ( ! pParm ) {
|
||||
pParm = epicsThreadImplicitCreate ();
|
||||
}
|
||||
@@ -1046,6 +1085,11 @@ LIBCOM_API void epicsStdCall epicsThreadShowAll ( unsigned level )
|
||||
win32ThreadGlobal * pGbl = fetchWin32ThreadGlobal ();
|
||||
win32ThreadParam * pParm;
|
||||
|
||||
#ifdef USE_TLSALLOC_FALLBACK
|
||||
fprintf(epicsGetStdout(), "Warning: For this target, use of epicsThread* from non-EPICS threads\n"
|
||||
" May leak memory. Recommend to upgrade to >= Window Vista\n");
|
||||
#endif
|
||||
|
||||
if ( ! pGbl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -639,7 +639,7 @@ epicsFindAddr(void *addr, epicsSymbol *sym_p)
|
||||
|
||||
if ( nearest.raw && ( (idx = ARR(c,nearest,0,st_name)) < es->strMap->max ) ) {
|
||||
sym_p->s_nam = strtab + idx;
|
||||
sym_p->s_val = (char*)(size_t) ARR(c, nearest, 0, st_value) + es->addr;
|
||||
sym_p->s_val = (char*)(uintptr_t) ARR(c, nearest, 0, st_value) + es->addr;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -523,11 +523,16 @@ LIBCOM_API unsigned int epicsStdCall epicsThreadGetStackSize (epicsThreadStackSi
|
||||
|
||||
LIBCOM_API void epicsStdCall epicsThreadOnce(epicsThreadOnceId *id, void (*func)(void *), void *arg)
|
||||
{
|
||||
static struct epicsThreadOSD threadOnceComplete;
|
||||
#define EPICS_THREAD_ONCE_DONE &threadOnceComplete
|
||||
static const struct epicsThreadOSD threadOnceComplete;
|
||||
#define EPICS_THREAD_ONCE_DONE (void*)&threadOnceComplete
|
||||
int status;
|
||||
void *prev, *self;
|
||||
|
||||
epicsThreadInit();
|
||||
if(epicsAtomicGetPtrT((void**)id) == EPICS_THREAD_ONCE_DONE) {
|
||||
return; /* fast path. global init already done. No need to lock */
|
||||
}
|
||||
|
||||
self = epicsThreadGetIdSelf();
|
||||
status = mutexLock(&onceLock);
|
||||
if(status) {
|
||||
fprintf(stderr,"epicsThreadOnce: pthread_mutex_lock returned %s.\n",
|
||||
@@ -535,21 +540,25 @@ LIBCOM_API void epicsStdCall epicsThreadOnce(epicsThreadOnceId *id, void (*func)
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (*id != EPICS_THREAD_ONCE_DONE) {
|
||||
if (*id == EPICS_THREAD_ONCE_INIT) { /* first call */
|
||||
*id = epicsThreadGetIdSelf(); /* mark active */
|
||||
/* slow path, check again and attempt to begin */
|
||||
prev = epicsAtomicCmpAndSwapPtrT((void**)id, EPICS_THREAD_ONCE_INIT, self);
|
||||
|
||||
if (prev != EPICS_THREAD_ONCE_DONE) {
|
||||
if (prev == EPICS_THREAD_ONCE_INIT) { /* first call, already marked active */
|
||||
status = pthread_mutex_unlock(&onceLock);
|
||||
checkStatusQuit(status,"pthread_mutex_unlock", "epicsThreadOnce");
|
||||
func(arg);
|
||||
status = mutexLock(&onceLock);
|
||||
checkStatusQuit(status,"pthread_mutex_lock", "epicsThreadOnce");
|
||||
*id = EPICS_THREAD_ONCE_DONE; /* mark done */
|
||||
} else if (*id == epicsThreadGetIdSelf()) {
|
||||
epicsAtomicSetPtrT((void**)id, EPICS_THREAD_ONCE_DONE); /* mark done */
|
||||
|
||||
} else if (prev == self) {
|
||||
status = pthread_mutex_unlock(&onceLock);
|
||||
checkStatusQuit(status,"pthread_mutex_unlock", "epicsThreadOnce");
|
||||
cantProceed("Recursive epicsThreadOnce() initialization\n");
|
||||
|
||||
} else
|
||||
while (*id != EPICS_THREAD_ONCE_DONE) {
|
||||
while ((prev = epicsAtomicGetPtrT((void**)id)) != EPICS_THREAD_ONCE_DONE) {
|
||||
/* Another thread is in the above func(arg) call. */
|
||||
status = pthread_mutex_unlock(&onceLock);
|
||||
checkStatusQuit(status,"pthread_mutex_unlock", "epicsThreadOnce");
|
||||
|
||||
@@ -216,6 +216,42 @@ static void timeEpicsThreadPrivateGet ()
|
||||
printf("epicsThreadPrivateGet() takes %f microseconds\n", delay);
|
||||
}
|
||||
|
||||
static void onceAction(void*) {}
|
||||
|
||||
static void timeOnce ()
|
||||
{
|
||||
#define NITER 100
|
||||
epicsThreadOnceId once[NITER];
|
||||
double tSlow = 0.0, tFast = 0.0,
|
||||
tSlow2= 0.0, tFast2= 0.0;
|
||||
unsigned i;
|
||||
|
||||
for(i=0; i<NITER; i++) {
|
||||
epicsUInt64 t0, t1, t2;
|
||||
|
||||
once[i] = EPICS_THREAD_ONCE_INIT;
|
||||
t0 = epicsMonotonicGet();
|
||||
epicsThreadOnce(&once[i], &onceAction, NULL);
|
||||
t1 = epicsMonotonicGet();
|
||||
epicsThreadOnce(&once[i], &onceAction, NULL);
|
||||
t2 = epicsMonotonicGet();
|
||||
|
||||
tSlow += t1 - t0;
|
||||
tSlow2 += (t1 - t0)*(t1 - t0);
|
||||
tFast += t2 - t1;
|
||||
tFast2 += (t2 - t1)*(t2 - t1);
|
||||
}
|
||||
|
||||
tSlow /= NITER;
|
||||
tSlow2 /= NITER;
|
||||
tFast /= NITER;
|
||||
tFast2 /= NITER;
|
||||
|
||||
printf("epicsThreadOnce(), slow path %.0f +- %.0f ns, fast path %.0f +- %.0f ns\n",
|
||||
tSlow, sqrt(tSlow2 - tSlow*tSlow),
|
||||
tFast, sqrt(tFast2 - tFast*tFast));
|
||||
#undef NITER
|
||||
}
|
||||
|
||||
MAIN(epicsThreadPerform)
|
||||
{
|
||||
@@ -224,5 +260,6 @@ MAIN(epicsThreadPerform)
|
||||
threadSleepTest ();
|
||||
epicsThreadGetIdSelfPerfTest ();
|
||||
timeEpicsThreadPrivateGet ();
|
||||
timeOnce ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "epicsEvent.h"
|
||||
#include "epicsAssert.h"
|
||||
#include "epicsGuard.h"
|
||||
#include "epicsAtomic.h"
|
||||
#include "tsFreeList.h"
|
||||
#include "epicsUnitTest.h"
|
||||
#include "testMain.h"
|
||||
@@ -84,7 +85,7 @@ private:
|
||||
delayVerify & operator = ( const delayVerify & );
|
||||
};
|
||||
|
||||
static volatile unsigned expireCount;
|
||||
static int expireCount;
|
||||
static epicsEvent expireEvent;
|
||||
|
||||
delayVerify::delayVerify ( double expectedDelayIn, epicsTimerQueue &queueIn ) :
|
||||
@@ -128,7 +129,7 @@ inline void delayVerify::start ( const epicsTime &expireTime )
|
||||
epicsTimerNotify::expireStatus delayVerify::expire ( const epicsTime ¤tTime )
|
||||
{
|
||||
this->expireStamp = currentTime;
|
||||
if ( --expireCount == 0u ) {
|
||||
if ( epics::atomic::decrement ( expireCount ) == 0u ) {
|
||||
expireEvent.signal ();
|
||||
}
|
||||
return noRestart;
|
||||
@@ -155,7 +156,7 @@ void testAccuracy ()
|
||||
}
|
||||
testOk1 ( timerCount == nTimers );
|
||||
|
||||
expireCount = nTimers;
|
||||
epics::atomic::set ( expireCount, nTimers );
|
||||
for ( i = 0u; i < nTimers; i++ ) {
|
||||
epicsTime cur = epicsTime::getCurrent ();
|
||||
pTimers[i]->setBegin ( cur );
|
||||
|
||||
Reference in New Issue
Block a user