Merge branch '7.0' into PSI-7.0

This commit is contained in:
2025-03-21 13:57:06 +01:00
31 changed files with 486 additions and 281 deletions

View File

@ -52,8 +52,6 @@ environment:
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
- CMP: vs2015
- CMP: vs2013
- CMP: vs2012
- CMP: vs2010
- CMP: gcc
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
# TODO: static linking w/ readline isn't working. Bypass auto-detect
@ -67,11 +65,6 @@ platform:
# Matrix configuration: exclude sets of jobs
matrix:
exclude:
# VS2012 and older installs don't have the 64 bit compiler
- platform: x64
CMP: vs2012
- platform: x64
CMP: vs2010
# Exclude more jobs to reduce build time
# Skip 32-bit for "middle-aged" compilers
- platform: x86

View File

@ -59,8 +59,6 @@ environment:
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
- CMP: vs2015
- CMP: vs2013
- CMP: vs2012
- CMP: vs2010
- CMP: gcc
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
# TODO: static linking w/ readline isn't working. Bypass auto-detect
@ -74,11 +72,6 @@ platform:
# Matrix configuration: exclude sets of jobs
matrix:
exclude:
# VS2012 and older installs don't have the 64 bit compiler
- platform: x64
CMP: vs2012
- platform: x64
CMP: vs2010
# Exclude more jobs to reduce build time
# Skip 32-bit for "middle-aged" compilers
- platform: x86

View File

@ -51,9 +51,9 @@ jobs:
matrix:
# Job names also name artifacts, character limitations apply
include:
- os: ubuntu-22.04
cmp: gcc-12
name: "Ub-22 gcc-12 c++20 Werror"
- os: ubuntu-24.04
cmp: gcc
name: "Ub-24 gcc-13 c++20 Werror"
# Turn all warnings into errors,
# except for those we could not fix (yet).
# Remove respective -Wno-error=... flag once it is fixed.
@ -74,79 +74,79 @@ jobs:
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3'
CMD_LDFLAGS=-Wl,-z,relro"
- os: ubuntu-20.04
- os: ubuntu-22.04
cmp: gcc
configuration: default
cross: "windows-x64-mingw"
name: "Ub-20 gcc + MinGW"
name: "Ub-22 gcc + MinGW"
- os: ubuntu-20.04
- os: ubuntu-22.04
cmp: gcc
configuration: static
cross: "windows-x64-mingw"
name: "Ub-20 gcc + MinGW, static"
name: "Ub-22 gcc + MinGW, static"
- os: ubuntu-20.04
- os: ubuntu-22.04
cmp: gcc
configuration: static
extra: "CMD_CXXFLAGS=-std=c++11"
name: "Ub-20 gcc C++11, static"
name: "Ub-22 gcc C++11, static"
- os: ubuntu-20.04
- os: ubuntu-22.04
cmp: gcc
configuration: static
extra: "CMD_CFLAGS=-funsigned-char CMD_CXXFLAGS=-funsigned-char"
name: "Ub-20 gcc unsigned char"
name: "Ub-22 gcc unsigned char"
- os: ubuntu-20.04
- os: ubuntu-22.04
cmp: clang
configuration: default
name: "Ub-20 clang"
name: "Ub-22 clang"
- os: ubuntu-20.04
- os: ubuntu-22.04
cmp: clang
configuration: default
extra: "CMD_CXXFLAGS=-std=c++11"
name: "Ub-20 clang C++11"
name: "Ub-22 clang C++11"
- os: ubuntu-20.04
- os: ubuntu-22.04
cmp: gcc
configuration: default
cross: "RTEMS-pc686-qemu@5"
name: "Ub-20 gcc + RT-5.1 pc686"
name: "Ub-22 gcc + RT-5.1 pc686"
- os: ubuntu-20.04
- os: ubuntu-22.04
cmp: gcc
configuration: default
cross: "RTEMS-beatnik@5"
test: NO
name: "Ub-20 gcc + RT-5.1 beatnik"
name: "Ub-22 gcc + RT-5.1 beatnik"
- os: ubuntu-20.04
- os: ubuntu-22.04
cmp: gcc
configuration: default
cross: "RTEMS-xilinx_zynq_a9_qemu@5"
test: NO
name: "Ub-20 gcc + RT-5.1 xilinx_zynq_a9_qemu"
name: "Ub-22 gcc + RT-5.1 xilinx_zynq_a9_qemu"
- os: ubuntu-20.04
- os: ubuntu-22.04
cmp: gcc
configuration: default
cross: "RTEMS-uC5282@5"
test: NO
name: "Ub-20 gcc + RT-5.1 uC5282"
name: "Ub-22 gcc + RT-5.1 uC5282"
- os: ubuntu-20.04
- os: ubuntu-22.04
cmp: gcc
configuration: default
name: "Ub-20 gcc + RT-4.10"
name: "Ub-22 gcc + RT-4.10"
cross: "RTEMS-pc386-qemu@4.10"
test: NO
- os: ubuntu-20.04
- os: ubuntu-22.04
cmp: gcc
configuration: default
name: "Ub-20 gcc + RT-4.9"
name: "Ub-22 gcc + RT-4.9"
cross: "RTEMS-pc386-qemu@4.9"
- os: macos-latest
@ -246,10 +246,15 @@ jobs:
matrix:
# Job names also name artifacts, character limitations apply
include:
#- name: "CentOS-7"
# image: centos:7
# cmp: gcc
# configuration: default
- name: "CentOS-8"
image: centos:8
cmp: gcc
configuration: default
- name: "Rocky-9"
image: rockylinux:9
cmp: gcc
configuration: default
- name: "Fedora-33"
image: fedora:33
@ -262,47 +267,29 @@ jobs:
configuration: default
steps:
- name: "Build newer Git"
# actions/checkout@v2 wants git >=2.18
# centos:7 has 1.8
if: matrix.image=='centos:7'
- name: "Fix repo URLs on CentOS-8"
# centos:8 is frozen, repos are in the vault
if: matrix.image=='centos:8'
run: |
yum -y install curl make gcc curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-ExtUtils-MakeMaker
curl https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.29.0.tar.gz | tar -xz
cd git-*
make -j2 prefix=/usr/local all
make prefix=/usr/local install
cd ..
rm -rf git-*
type -a git
git --version
sed -i -e "s|mirrorlist=|#mirrorlist=|" \
-e "s|#baseurl=http://mirror|baseurl=http://vault|" \
/etc/yum.repos.d/CentOS-Linux-{BaseOS,AppStream,Extras,Plus}.repo
- name: "Redhat setup"
run: |
dnfyum() {
dnf -y "$@" || yum -y "$@"
return $?
}
dnfyum install python3 gdb make perl gcc-c++ glibc-devel readline-devel ncurses-devel perl-devel perl-Test-Simple
git --version || dnfyum install git
# rather than just bite the bullet and link python3 -> python,
# people would rather just break all existing scripts...
[ -e /usr/bin/python ] || ln -sf python3 /usr/bin/python
python --version
dnf -y install python3 gdb make perl gcc-c++ glibc-devel readline-devel ncurses-devel perl-devel perl-Test-Simple
git --version || dnf -y install git
python3 --version
- uses: actions/checkout@v4
with:
submodules: true
- name: Automatic core dumper analysis
uses: mdavidsaver/ci-core-dumper@master
if: matrix.image!='centos:7'
- name: Automatic core dumper analysis
uses: mdavidsaver/ci-core-dumper@node16
if: matrix.image=='centos:7'
- name: Prepare and compile dependencies
run: python .ci/cue.py prepare
run: python3 .ci/cue.py prepare
- name: Build main module
run: python .ci/cue.py build
run: python3 .ci/cue.py build
- name: Run main module tests
run: python .ci/cue.py -T 20M test
run: python3 .ci/cue.py -T 20M test
- name: Upload tapfiles Artifact
if: ${{ always() }}
uses: actions/upload-artifact@v4
@ -312,4 +299,55 @@ jobs:
if-no-files-found: ignore
- name: Collect and show test results
if: ${{ always() }}
run: python .ci/cue.py -T 5M test-results
run: python3 .ci/cue.py -T 5M test-results
build-docker:
name: Docker CentOS-7
runs-on: ubuntu-latest
env:
CMP: gcc
BCFG: default
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Run...
run: |
env > env.list
cat <<EOF > runit.sh
#!/bin/sh
set -e -x
cd /io
id
sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo
sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo
sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo
yum -y install epel-release
yum -y install \
curl make gcc curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-ExtUtils-MakeMaker \
python3 gdb make perl gcc-c++ glibc-devel readline-devel ncurses-devel perl-devel perl-Test-Simple \
libevent-devel sudo re2c
[ -e /usr/bin/python ] || ln -sf /usr/bin/python3 /usr/bin/python
# fake out cue.py
ln -s /bin/true /usr/bin/apt-get
# quiet warnings spam from perl
export LANG=C
python --version
python .ci/cue.py prepare
python .ci/cue.py build
python .ci/cue.py -T 15M test
python .ci/cue.py test-results
EOF
chmod +x runit.sh
docker run --rm --quiet \
--pull=always \
--env-file env.list \
-v `pwd`:/io \
centos:7 \
/io/runit.sh

View File

@ -18,13 +18,28 @@ __This version of EPICS has not been released yet.__
__Add new items below here__
=======
### Reduce symbol and macro pollution from epicsAtomic.h on WIN32
`epicsAtomic.h` no longer pulls in as many unneeded declarations and macros from
`windows.h`. Prior to this change, including `epicsAtomic.h` at the wrong time
could result in unexpected compiler errors. Due to the nature of `windows.h`,
some unneeded declarations are still pulled in, however the number is greatly reduced.
Code that needs these declarations should explicitly include `windows.h` before `epicsAtomic.h`.
### epicsExport simplifications
`epicsExportAddress()`, `epicsExportRegistrar()` and `epicsRegisterFunction()`
no longer require to be wrapped in `extern "C" { }` in C++ code.
### Build system `$(PYTHON)` default changed
### New `dbServerStats()` API for iocStats
A new routine provides the ability to request channel and client counts from
named server layers that implement the `stats()` method, or to get a summary
of the counts from all registered server layers. A preprocessor macro
`HAS_DBSERVER_STATS` macro is defined in the `dbServer.h` header file to
simplify code that needs to support older versions of Base as well.
-----
## EPICS Release 7.0.9
@ -44,12 +59,6 @@ record type by opening these files in a text editor intead of opening a browser
and loading the HTML versions or finding and opening the files from the EPICS
Documentation site.
### fdManager file descriptor limit removed
In order to support file descriptors above 1023, fdManager now uses
poll() instead of select() on all architectures that support it
(Linux, MacOS, Windows, newer RTEMS).
### Post monitors from compress record when it's reset
Writing into a compress record's `RES` field now posts a monitor event instead

View File

@ -115,10 +115,9 @@ static int dbca_chan_count;
* During link modification or IOC shutdown the pca->plink pointer (guarded by caLink.lock)
* is used as a flag to indicate that a link is no longer active.
*
* References to the struct caLink are owned by the dbCaTask, and any scanOnceCallback()
* which is in progress.
* References to the struct caLink are owned by the dbCaTask.
*
* The libca and scanOnceCallback callbacks take no action if pca->plink==NULL.
* The libca callbacks take no action if pca->plink==NULL.
*
* dbCaPutLinkCallback causes an additional complication because
* when dbCaRemoveLink is called the callback may not have occured.
@ -788,38 +787,6 @@ static long doLocked(struct link *plink, dbLinkUserCallback rtn, void *priv)
return status;
}
static void scanComplete(void *raw, dbCommon *prec)
{
caLink *pca = raw;
epicsMutexMustLock(pca->lock);
if(!pca->plink) {
/* IOC shutdown or link re-targeted. Do nothing. */
} else if(pca->scanningOnce==0) {
errlogPrintf("dbCa.c complete callback w/ scanningOnce==0\n");
} else if(--pca->scanningOnce){
/* another scan is queued */
if(scanOnceCallback(prec, scanComplete, raw)) {
errlogPrintf("dbCa.c failed to re-queue scanOnce\n");
} else
caLinkInc(pca);
}
epicsMutexUnlock(pca->lock);
caLinkDec(pca);
}
/* must be called with pca->lock held */
static void scanLinkOnce(dbCommon *prec, caLink *pca) {
if(pca->scanningOnce==0) {
if(scanOnceCallback(prec, scanComplete, pca)) {
errlogPrintf("dbCa.c failed to queue scanOnce\n");
} else
caLinkInc(pca);
}
if(pca->scanningOnce<5)
pca->scanningOnce++;
/* else too many scans queued */
}
static lset dbCa_lset = {
0, 1, /* not Constant, Volatile */
NULL, dbCaRemoveLink,
@ -856,7 +823,9 @@ static void connectionCallback(struct connection_handler_args arg)
if (precord &&
((ppv_link->pvlMask & pvlOptCP) ||
((ppv_link->pvlMask & pvlOptCPP) && precord->scan == 0)))
scanLinkOnce(precord, pca);
{
link_action |= CA_DBPROCESS;
}
goto done;
}
pca->hasReadAccess = ca_read_access(arg.chid);
@ -988,7 +957,9 @@ static void eventCallback(struct event_handler_args arg)
if ((ppv_link->pvlMask & pvlOptCP) ||
((ppv_link->pvlMask & pvlOptCPP) && precord->scan == 0))
scanLinkOnce(precord, pca);
{
addAction(pca, CA_DBPROCESS);
}
}
done:
epicsMutexUnlock(pca->lock);
@ -1061,7 +1032,9 @@ static void accessRightsCallback(struct access_rights_handler_args arg)
if (precord &&
((ppv_link->pvlMask & pvlOptCP) ||
((ppv_link->pvlMask & pvlOptCPP) && precord->scan == 0)))
scanLinkOnce(precord, pca);
{
addAction(pca, CA_DBPROCESS);
}
done:
epicsMutexUnlock(pca->lock);
}
@ -1273,6 +1246,13 @@ static void dbCaTask(void *arg)
printLinks(pca);
}
}
if (link_action & CA_DBPROCESS) {
dbCommon *prec;
epicsMutexMustLock(pca->lock);
prec = pca->plink->precord;
epicsMutexUnlock(pca->lock);
db_process(prec);
}
}
SEVCHK(ca_flush_io(), "dbCaTask");
}

View File

@ -31,6 +31,7 @@
#define CA_MONITOR_STRING 0x20
#define CA_GET_ATTRIBUTES 0x40
#define CA_SYNC 0x1000
#define CA_DBPROCESS 0x2000
/* write type */
#define CA_PUT 0x1
#define CA_PUT_CALLBACK 0x2

View File

@ -635,9 +635,21 @@ long dbChannelGetField(dbChannel *chan, short dbrType, void *pbuffer,
{
dbCommon *precord = chan->addr.precord;
long status = 0;
unsigned char local_fl = 0;
dbScanLock(precord);
if (!pfl && (ellCount(&chan->pre_chain) || ellCount(&chan->post_chain))) {
pfl = db_create_read_log(chan);
if (pfl) {
local_fl = 1;
pfl = dbChannelRunPreChain(chan, pfl);
pfl = dbChannelRunPostChain(chan, pfl);
}
}
status = dbChannelGet(chan, dbrType, pbuffer, options, nRequest, pfl);
if (local_fl) {
db_delete_field_log(pfl);
}
dbScanUnlock(precord);
return status;
}

View File

@ -511,6 +511,10 @@ DBCORE_API long dbChannelGet(dbChannel *chan, short type,
* \param[in,out] nRequest Pointer to the element count.
* \param[in] pfl Pointer to a db_field_log or NULL.
* \returns 0, or an error status value.
*
* \since UNRELEASED If pfl is NULL and chan has filters, db_create_read_log() will be called
* internally to create a temporary db_field_log which is passed to dbChannelGet()
* then deallocated.
*/
DBCORE_API long dbChannelGetField(dbChannel *chan, short type,
void *pbuffer, long *options, long *nRequest, void *pfl);

View File

@ -127,6 +127,31 @@ int dbServerClient(char *pBuf, size_t bufSize)
return -1;
}
int dbServerStats(const char *name, unsigned *channels, unsigned *clients)
{
dbServer *psrv = (dbServer *)ellFirst(&serverList);
if (state != running || !psrv)
return -1;
unsigned tch = 0, tcl = 0, nmatch = 0;
for (; psrv; psrv = (dbServer *)ellNext(&psrv->node)) {
if (psrv->stats &&
(!name || strcmp(name, psrv->name) == 0)) {
unsigned lch = 0, lcl = 0;
psrv->stats(&lch, &lcl);
tch += lch;
tcl += lcl;
nmatch++;
if (name)
break; /* No duplicate names in serverList */
}
}
if (channels) *channels = tch;
if (clients) *clients = tcl;
return nmatch;
}
#define STARTSTOP(routine, method, newState) \
void routine(void) \
{ \

View File

@ -17,9 +17,6 @@
* the dbServer interface provides allow the IOC to start, pause and stop
* the servers together, and to provide status and debugging information
* to the IOC user/developer through a common set of commands.
*
* @todo No API is provided yet for calling stats() methods.
* Nothing in the IOC calls dbStopServers(), not sure where it should go.
*/
#ifndef INC_dbServer_H
@ -59,8 +56,8 @@ typedef struct dbServer {
/** @brief Get number of channels and clients currently connected.
*
* @param channels NULL or pointer for returning channel count.
* @param clients NULL or pointer for returning client count.
* @param channels @c NULL or pointer for returning channel count.
* @param clients @c NULL or pointer for returning client count.
*/
void (* stats) (unsigned *channels, unsigned *clients);
@ -145,6 +142,30 @@ DBCORE_API void dbsr(unsigned level);
*/
DBCORE_API int dbServerClient(char *pBuf, size_t bufSize);
/** @brief CPP Macro indicating the dbServerStats() routine exists.
* @since UNRELEASED
*/
#define HAS_DBSERVER_STATS
/** @brief Fetch statistics from server layers.
*
* This is an API for iocStats and similar to fetch the number of channels
* and clients connected to the registered server layers.
* If the name given is NULL the statistics returned are the totals from
* all registered server layers, otherwise just from the named server.
* @param name Server name
* @param channels NULL, or where to return the channel count
* @param clients NULL or where to return the client count
* @returns -1 if the IOC isn't running or no servers are registered, without
* writing to the statistics variables. Otherwise it writes to the statistics
* variables and returns the number of dbServer::stats() methods called,
* 0 if a named server wasn't found or doesn't have a stats() method.
*
* @since UNRELEASED
*/
DBCORE_API int dbServerStats(const char *name, unsigned *channels,
unsigned *clients);
/** @brief Initialize all registered servers.
*
* Calls all dbServer::init() methods.

View File

@ -202,7 +202,6 @@ void testdbGetFieldEqual(const char* pv, int dbrType, ...)
void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap)
{
dbChannel *chan = dbChannelCreate(pv);
db_field_log *pfl = NULL;
long nReq = 1;
union anybuf pod;
long status = S_dbLib_recNotFound;
@ -212,18 +211,7 @@ void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap)
goto done;
}
if(ellCount(&chan->filters)) {
pfl = db_create_read_log(chan);
if (!pfl) {
testFail("can't db_create_read_log w/ %s", pv);
goto done;
}
pfl = dbChannelRunPreChain(chan, pfl);
pfl = dbChannelRunPostChain(chan, pfl);
}
status = dbChannelGetField(chan, dbrType, pod.bytes, NULL, &nReq, pfl);
status = dbChannelGetField(chan, dbrType, pod.bytes, NULL, &nReq, NULL);
if (status) {
testFail("dbGetField(\"%s\", %d, ...) -> %#lx (%s)", pv, dbrType, status, errSymMsg(status));
goto done;
@ -261,7 +249,6 @@ void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap)
}
done:
db_delete_field_log(pfl);
if(chan)
dbChannelDelete(chan);
}
@ -288,7 +275,6 @@ done:
void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsigned long cnt, const void *pbufraw)
{
dbChannel *chan = dbChannelCreate(pv);
db_field_log *pfl = NULL;
const long vSize = dbValueSize(dbfType);
const long nStore = vSize * nRequest;
long status = S_dbLib_recNotFound;
@ -300,24 +286,13 @@ void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsign
goto done;
}
if(ellCount(&chan->filters)) {
pfl = db_create_read_log(chan);
if (!pfl) {
testFail("can't db_create_read_log w/ %s", pv);
goto done;
}
pfl = dbChannelRunPreChain(chan, pfl);
pfl = dbChannelRunPostChain(chan, pfl);
}
gbuf = gstore = malloc(nStore);
if(!gbuf && nStore!=0) { /* note that malloc(0) is allowed to return NULL on success */
testFail("Allocation failed esize=%ld total=%ld", vSize, nStore);
return;
}
status = dbChannelGetField(chan, dbfType, gbuf, NULL, &nRequest, pfl);
status = dbChannelGetField(chan, dbfType, gbuf, NULL, &nRequest, NULL);
if (status) {
testFail("dbGetField(\"%s\", %d, ...) -> %#lx", pv, dbfType, status);

View File

@ -148,6 +148,7 @@ int dbChannel_get_count(
long options;
long i;
long zero = 0;
unsigned char local_fl = 0;
/* The order of the DBR* elements in the "newSt" structures below is
* very important and must correspond to the order of processing
@ -156,6 +157,16 @@ int dbChannel_get_count(
dbScanLock(dbChannelRecord(chan));
/* If filters are involved in a read, create field log and run filters */
if (!pfl && (ellCount(&chan->pre_chain) || ellCount(&chan->post_chain))) {
pfl = db_create_read_log(chan);
if (pfl) {
local_fl = 1;
pfl = dbChannelRunPreChain(chan, pfl);
pfl = dbChannelRunPostChain(chan, pfl);
}
}
switch(buffer_type) {
case(oldDBR_STRING):
status = dbChannelGet(chan, DBR_STRING, pbuffer, &zero, nRequest, pfl);
@ -800,6 +811,8 @@ int dbChannel_get_count(
dbScanUnlock(dbChannelRecord(chan));
if (local_fl) db_delete_field_log(pfl);
if (status) return -1;
return 0;
}
@ -1029,3 +1042,17 @@ int db_put_process(processNotify *ppn, notifyPutType type,
ppn->status = notifyError;
return 1;
}
void db_process(struct dbCommon *prec)
{
if (prec->pact) {
if (dbAccessDebugPUTF && prec->tpro)
printf("%s: dbPutField to Active '%s', setting RPRO=1\n",
epicsThreadGetNameSelf(), prec->name);
prec->rpro = TRUE;
} else {
/* indicate that dbPutField called dbProcess */
prec->putf = TRUE;
(void)dbProcess(prec);
}
}

View File

@ -22,6 +22,8 @@ extern "C" {
#include "dbCoreAPI.h"
struct dbCommon;
DBCORE_API extern struct dbBase *pdbbase;
DBCORE_API extern volatile int interruptAccept;
@ -36,7 +38,9 @@ DBCORE_API int dbChannel_put(struct dbChannel *chan, int src_type,
const void *psrc, long no_elements);
DBCORE_API int dbChannel_get_count(struct dbChannel *chan,
int buffer_type, void *pbuffer, long *nRequest, void *pfl);
#ifdef EPICS_DBCA_PRIVATE_API
DBCORE_API void db_process(struct dbCommon *prec);
#endif
#ifdef __cplusplus
}

View File

@ -540,7 +540,6 @@ static void read_reply ( void *pArg, struct dbChannel *dbch,
const int readAccess = asCheckGet ( pciu->asClientPVT );
int status;
int autosize;
int local_fl = 0;
long item_count;
ca_uint32_t payload_size;
dbAddr *paddr=&dbch->addr;
@ -582,21 +581,9 @@ static void read_reply ( void *pArg, struct dbChannel *dbch,
return;
}
/* If filters are involved in a read, create field log and run filters */
if (!pfl && (ellCount(&dbch->pre_chain) || ellCount(&dbch->post_chain))) {
pfl = db_create_read_log(dbch);
if (pfl) {
local_fl = 1;
pfl = dbChannelRunPreChain(dbch, pfl);
pfl = dbChannelRunPostChain(dbch, pfl);
}
}
status = dbChannel_get_count ( dbch, pevext->msg.m_dataType,
pPayload, &item_count, pfl);
if (local_fl) db_delete_field_log(pfl);
if ( status < 0 ) {
/* Clients recv the status of the operation directly to the
* event/put/get callback. (from CA_V41())
@ -663,7 +650,6 @@ static int read_action ( caHdrLargeArray *mp, void *pPayloadIn, struct client *p
ca_uint32_t payloadSize;
void *pPayload;
int status;
int local_fl = 0;
db_field_log *pfl = NULL;
if ( ! pciu ) {
@ -702,21 +688,9 @@ static int read_action ( caHdrLargeArray *mp, void *pPayloadIn, struct client *p
return RSRV_OK;
}
/* If filters are involved in a read, create field log and run filters */
if (ellCount(&pciu->dbch->pre_chain) || ellCount(&pciu->dbch->post_chain)) {
pfl = db_create_read_log(pciu->dbch);
if (pfl) {
local_fl = 1;
pfl = dbChannelRunPreChain(pciu->dbch, pfl);
pfl = dbChannelRunPostChain(pciu->dbch, pfl);
}
}
status = dbChannel_get ( pciu->dbch, mp->m_dataType,
pPayload, mp->m_count, pfl );
if (local_fl) db_delete_field_log(pfl);
if ( status < 0 ) {
send_err ( mp, ECA_GETFAIL, pClient, RECORD_NAME ( pciu->dbch ) );
SEND_UNLOCK ( pClient );

View File

@ -130,9 +130,11 @@ static void check(short dbr_type) {
off = Offset; req = 10; \
memset(buf, 0, sizeof(buf)); \
(void) dbPutField(&offaddr, DBR_LONG, &off, 1); \
dbScanLock(dbChannelRecord(pch)); \
pfl = db_create_read_log(pch); \
testOk(pfl && pfl->type == dbfl_type_ref, "Valid pfl, type = ref"); \
testOk(!dbChannelGetField(pch, DBR_LONG, buf, NULL, &req, pfl), "Got Field value"); \
testOk(!dbChannelGet(pch, DBR_LONG, buf, NULL, &req, pfl), "Got Field value"); \
dbScanUnlock(dbChannelRecord(pch)); \
testOk(req == Size, "Got %ld elements (expected %d)", req, Size); \
if (!testOk(!memcmp(buf, Expected, sizeof(Expected)), "Data correct")) \
for (i=0; i<Size; i++) \
@ -174,6 +176,7 @@ static void check(short dbr_type) {
off = Offset; req = 15; \
memset(buf, 0, sizeof(buf)); \
(void) dbPutField(&offaddr, DBR_LONG, &off, 1); \
dbScanLock(dbChannelRecord(pch)); \
pfl = db_create_read_log(pch); \
pfl->type = dbfl_type_ref; \
pfl->field_type = DBF_CHAR; \
@ -181,7 +184,8 @@ static void check(short dbr_type) {
pfl->no_elements = 26; \
pfl->dtor = freeArray; \
pfl->u.r.field = epicsStrDup("abcdefghijklmnopqrsstuvwxyz"); \
testOk(!dbChannelGetField(pch, DBR_LONG, buf, NULL, &req, pfl), "Got Field value"); \
testOk(!dbChannelGet(pch, DBR_LONG, buf, NULL, &req, pfl), "Got Field value"); \
dbScanUnlock(dbChannelRecord(pch)); \
testOk(req == Size, "Got %ld elements (expected %d)", req, Size); \
if (!testOk(!memcmp(buf, Expected, sizeof(Expected)), "Data correct")) \
for (i=0; i<Size; i++) \

View File

@ -40,6 +40,8 @@ void oneReport(unsigned level)
void oneStats(unsigned *channels, unsigned *clients)
{
oneState = STATS_CALLED;
if (channels) *channels = 2;
if (clients) *clients = 1;
}
int oneClient(char *pbuf, size_t len)
@ -128,8 +130,9 @@ MAIN(dbServerTest)
char name[16];
char *theName = "The One";
int status;
unsigned ch=0, cl=0;
testPlan(25);
testPlan(35);
/* Prove that we handle substring names properly */
epicsEnvSet("EPICS_IOC_IGNORE_SERVERS", "none ones");
@ -151,6 +154,9 @@ MAIN(dbServerTest)
testDiag("Registering dbServer 'disabled'");
testOk(dbRegisterServer(&disabled) == 0, "Registration accepted");
testOk(dbServerStats("one", &ch, &cl) == -1 && oneState == NOTHING_CALLED,
"dbServerStats returns error before IOC running");
testDiag("Changing server state");
dbInitServers();
testOk(oneState == INIT_CALLED, "dbInitServers");
@ -163,8 +169,26 @@ MAIN(dbServerTest)
testDiag("Checking server methods called");
dbsr(0);
testOk(oneState == REPORT_CALLED, "dbsr called report()");
testOk(oneState == REPORT_CALLED, "dbsr called one::report()");
testDiag("Checking stats functionality");
testOk(dbServerStats("none", &ch, &cl) == 0, "Stats: unknown name ignored");
testOk(dbServerStats("one", &ch, &cl) == 1 && oneState == STATS_CALLED,
"dbServerStats('one') called one::stats()");
testOk(ch == 2 && cl == 1, "Stats: ch==%d, cl==%d (expected 2, 1)", ch, cl);
testOk(dbServerStats("no-routines", &ch, &cl) == 0,
"dbServerStats('no-routines') layer not counted");
testOk(ch == 0 && cl == 0, "Stats: ch==%d, cl==%d (expected 0, 0)", ch, cl);
ch = 10; cl = 10; oneState = NOTHING_CALLED;
testOk(dbServerStats(NULL, NULL, &cl) == 1 && oneState == STATS_CALLED,
"dbServerStats(NULL, &cl) called one::stats()");
testOk(dbServerStats(NULL, &ch, NULL) == 1 && oneState == STATS_CALLED,
"dbServerStats(NULL, &ch) called one::stats()");
testOk(ch == 2 && cl == 1, "Stats: ch==%d, cl==%d (expected 2, 1)", ch, cl);
testDiag("Checking client identification");
oneSim = NULL;
name[0] = 0;
status = dbServerClient(name, sizeof(name));
@ -188,6 +212,9 @@ MAIN(dbServerTest)
status = dbServerClient(name, sizeof(name));
testOk(oneState != CLIENT_CALLED_KNOWN, "No call to client() when paused");
testOk(dbServerStats("one", &ch, &cl) == -1 && oneState != STATS_CALLED,
"No call to stats() when paused");
dbStopServers();
testOk(oneState == STOP_CALLED, "dbStopServers");
testOk(dbUnregisterServer(&toolate) != 0, "No unreg' if not reg'ed");

View File

@ -221,29 +221,27 @@ testHarness_SRCS += linkFilterTest.c
TESTFILES += ../linkFilterTest.db
TESTS += linkFilterTest
# These are compile-time tests, no need to link or run
TARGETS += dbHeaderTest$(OBJ)
TARGET_SRCS += dbHeaderTest.cpp
TARGETS += dbHeaderTestxx$(OBJ)
TARGET_SRCS += dbHeaderTestxx.cpp
TARGETS += $(COMMON_DIR)/epicsExportTestIoc.dbd
DBDDEPENDS_FILES += epicsExportTestIoc.dbd$(DEP)
epicsExportTestIoc_DBD += base.dbd
epicsExportTestIoc_DBD += epicsExportTest.dbd
TESTFILES += $(COMMON_DIR)/epicsExportTestIoc.dbd ../epicsExportTest.db
TESTLIBRARY += epicsExportTestLib
epicsExportTestLib_SRCS += epicsExportTest.c
epicsExportTestLib_LIBS += dbCore Com
TESTPROD_HOST += epicsExportTest
TESTS += epicsExportTest
TARGETS += epicsExportTest$(OBJ)
epicsExportTest_SRCS += epicsExportTestMain.c
epicsExportTest_SRCS += epicsExportTestIoc_registerRecordDeviceDriver.cpp
epicsExportTest_LIBS = epicsExportTestLib
TESTLIBRARY += epicsExportTestxxLib
epicsExportTestxxLib_SRCS += epicsExportTestxx.cpp
epicsExportTestxxLib_LIBS += dbCore Com
TESTPROD_HOST += epicsExportTestxx
TESTS += epicsExportTestxx
TARGETS += epicsExportTestxx$(OBJ)
@ -251,6 +249,12 @@ epicsExportTestxx_SRCS += epicsExportTestMain.c
epicsExportTestxx_SRCS += epicsExportTestIoc_registerRecordDeviceDriver.cpp
epicsExportTestxx_LIBS = epicsExportTestxxLib
# These are compile-time tests, no need to link or run
TARGETS += dbHeaderTest$(OBJ)
TARGET_SRCS += dbHeaderTest.cpp
TARGETS += dbHeaderTestxx$(OBJ)
TARGET_SRCS += dbHeaderTestxx.cpp
ifeq ($(T_A),$(EPICS_HOST_ARCH))
# Host-only tests of softIoc/softIocPVA, caget and pvget (if present)
# Unfortunately hangs too often on CI systems:

View File

@ -16,7 +16,10 @@
#include <iocsh.h>
#include <epicsExport.h>
void epicsExportTestIoc_registerRecordDeviceDriver(struct dbBase *);
#ifdef __cplusplus
extern "C"
#endif
int epicsExportTestIoc_registerRecordDeviceDriver(struct dbBase *);
static int* testVarEquals(const char* name, int expected)
{
@ -41,10 +44,10 @@ MAIN(epicsExportTest)
{
int *p1, *p2;
testPlan(30);
testPlan(31);
testdbPrepare();
testdbReadDatabase("epicsExportTestIoc.dbd", 0, 0);
epicsExportTestIoc_registerRecordDeviceDriver(pdbbase);
testOk(epicsExportTestIoc_registerRecordDeviceDriver(pdbbase)==0, "registerRecordDeviceDriver");
testDiag("Testing if dsets and functions are found");
testdbReadDatabase("epicsExportTest.db", 0, 0);

View File

@ -21,6 +21,7 @@
#include "ellLib.h"
#include "epicsMutex.h"
#include "epicsThread.h"
#include "cantProceed.h"
#include "initHooks.h"
@ -52,19 +53,26 @@ static void initHookInit(void)
int initHookRegister(initHookFunction func)
{
initHookLink *newHook;
ELLNODE *cur;
if (!func) return 0;
initHookInit();
newHook = (initHookLink *)malloc(sizeof(initHookLink));
if (!newHook) {
printf("Cannot malloc a new initHookLink\n");
return -1;
epicsMutexMustLock(listLock);
for(cur = ellFirst(&functionList); cur; cur = ellNext(cur)) {
const initHookLink *fn = CONTAINER(cur, initHookLink, node);
if(fn->func==func) {
/* silently ignore duplicate */
epicsMutexUnlock(listLock);
return 0;
}
}
newHook = (initHookLink *)mallocMustSucceed(sizeof(initHookLink), "initHookRegister");
newHook->func = func;
epicsMutexMustLock(listLock);
ellAdd(&functionList, &newHook->node);
epicsMutexUnlock(listLock);
return 0;

View File

@ -163,7 +163,11 @@ typedef void (*initHookFunction)(initHookState state);
*
* Registers \p func for initHook notifications
* \param func Pointer to application's notification function.
* \return 0 if Ok, -1 on error (memory allocation failure).
* \return Always zero. (before UNRELEASED could return -1 on allocation failure)
*
* \since UNRELEASED initHookRegister is idempotent.
* Previously, repeated registrations would result
* in duplicate calls to the hook function.
*/
LIBCOM_API int initHookRegister(initHookFunction func);

View File

@ -21,11 +21,7 @@
# error compiler/gcc/compilerSpecific.h is only for use with the gnu compiler
#endif
#if __GNUC__ > 2
#define EPICS_ALWAYS_INLINE __inline__ __attribute__((always_inline))
#else
# define EPICS_ALWAYS_INLINE __inline__
#endif
/* Expands to a 'const char*' which describes the name of the current function scope */
#define EPICS_FUNCTION __PRETTY_FUNCTION__
@ -42,9 +38,7 @@
/*
* CXX_PLACEMENT_DELETE - defined if compiler supports placement delete
*/
#if __GNUC__ > 2 || ( __GNUC__ == 2 && __GNUC_MINOR__ >= 96 )
#define CXX_PLACEMENT_DELETE
#endif
#endif /* __cplusplus */
@ -56,21 +50,19 @@
/*
* Deprecation marker
*/
#if (__GNUC__ > 2)
#define EPICS_DEPRECATED __attribute__((deprecated))
#endif
/*
* Unused marker
*/
#ifndef vxWorks
// VxWorks does not mark abort() or exit() noreturn!
#define EPICS_UNUSED __attribute__((unused))
#endif
/*
* No return marker
*/
#ifndef vxWorks
// VxWorks does not mark abort() or exit() noreturn!
#define EPICS_NORETURN __attribute__((noreturn))
#endif
#endif /* ifndef compilerSpecific_h */

View File

@ -339,32 +339,6 @@ void epicsThread :: show ( unsigned level ) const throw ()
}
extern "C" {
static epicsThreadOnceId okToBlockOnce = EPICS_THREAD_ONCE_INIT;
epicsThreadPrivateId okToBlockPrivate;
static const int okToBlockNo = 0;
static const int okToBlockYes = 1;
static void epicsThreadOnceIdInit(void *)
{
okToBlockPrivate = epicsThreadPrivateCreate();
}
int epicsStdCall epicsThreadIsOkToBlock(void)
{
const int *pokToBlock;
epicsThreadOnce(&okToBlockOnce, epicsThreadOnceIdInit, NULL);
pokToBlock = (int *) epicsThreadPrivateGet(okToBlockPrivate);
return (pokToBlock ? *pokToBlock : 0);
}
void epicsStdCall epicsThreadSetOkToBlock(int isOkToBlock)
{
const int *pokToBlock;
epicsThreadOnce(&okToBlockOnce, epicsThreadOnceIdInit, NULL);
pokToBlock = (isOkToBlock) ? &okToBlockYes : &okToBlockNo;
epicsThreadPrivateSet(okToBlockPrivate, (void *)pokToBlock);
}
epicsThreadId epicsStdCall epicsThreadMustCreate (
const char *name, unsigned int priority, unsigned int stackSize,
EPICSTHREADFUNC funptr,void *parm)
@ -375,12 +349,3 @@ extern "C" {
return id;
}
} // extern "C"
static epicsThreadId initMainThread(void) {
epicsThreadId main = epicsThreadGetIdSelf();
epicsThreadSetOkToBlock(1);
return main;
}
// Ensure the main thread gets a unique ID and allows blocking I/O
epicsThreadId epicsThreadMainId = initMainThread();

View File

@ -37,6 +37,7 @@ typedef struct epicsThreadOSD {
int isRealTimeScheduled;
int isOnThreadList;
int isRunning;
int isOkToBlock;
unsigned int osiPriority;
int joinable;
char name[1]; /* actually larger */

View File

@ -55,6 +55,7 @@ struct taskVar {
int refcnt;
int joinable;
int isRunning;
int isOkToBlock;
EPICSTHREADFUNC funptr;
void *parm;
unsigned int threadVariableCapacity;
@ -219,7 +220,7 @@ void epicsThreadExitMain (void)
static rtems_status_code
setThreadInfo(rtems_id tid, const char *name, EPICSTHREADFUNC funptr,
void *parm, int joinable)
void *parm, int joinable, int isOkToBlock)
{
struct taskVar *v;
uint32_t note;
@ -235,6 +236,7 @@ setThreadInfo(rtems_id tid, const char *name, EPICSTHREADFUNC funptr,
v->threadVariableCapacity = 0;
v->threadVariables = NULL;
v->isRunning = 1;
v->isOkToBlock = isOkToBlock;
if (joinable) {
char c[3] = {0,0,0};
strncpy(c, v->name, 3);
@ -284,7 +286,7 @@ epicsThreadInit (void)
epicsMutexOsdPrepare(&taskVarMutex);
epicsMutexOsdPrepare(&onceMutex);
rtems_task_ident (RTEMS_SELF, 0, &tid);
if(setThreadInfo (tid, "_main_", NULL, NULL, 0) != RTEMS_SUCCESSFUL)
if(setThreadInfo (tid, "_main_", NULL, NULL, 0, 1) != RTEMS_SUCCESSFUL)
cantProceed("epicsThreadInit() unable to setup _main_");
osdThreadHooksRunMain((epicsThreadId)tid);
initialized = 1;
@ -338,7 +340,7 @@ epicsThreadCreateOpt (
name, rtems_status_text(sc));
return 0;
}
sc = setThreadInfo (tid, name, funptr, parm, opts->joinable);
sc = setThreadInfo (tid, name, funptr, parm, opts->joinable, 0);
if (sc != RTEMS_SUCCESSFUL) {
errlogPrintf ("epicsThreadCreate create failure during setup for %s: %s\n",
name, rtems_status_text(sc));
@ -870,3 +872,27 @@ LIBCOM_API int epicsThreadGetCPUs(void)
return 1;
#endif
}
int epicsStdCall epicsThreadIsOkToBlock(void)
{
uint32_t note = 0;
struct taskVar *v;
rtems_task_get_note (RTEMS_SELF, RTEMS_NOTEPAD_TASKVAR, &note);
v = (void *)note;
return v && v->isOkToBlock;
}
void epicsStdCall epicsThreadSetOkToBlock(int isOkToBlock)
{
uint32_t note = 0;
struct taskVar *v;
rtems_task_get_note (RTEMS_SELF, RTEMS_NOTEPAD_TASKVAR, &note);
v = (void *)note;
if(v)
v->isOkToBlock = !!isOkToBlock;
}

View File

@ -19,37 +19,68 @@
#define EPICS_ATOMIC_OS_NAME "WIN32"
#ifdef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN_DETECTED_epicsAtomicOSD_h
#else
/* Disable extra declarations that we don't need here (i.e. winsock1, rpc, etc.) */
#pragma push_macro("WIN32_LEAN_AND_MEAN")
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifdef VC_EXTRALEAN
# define VC_EXTRALEAN_DETECTED_epicsAtomicOSD_h
#else
# define VC_EXTRALEAN
#endif
#ifdef STRICT
# define STRICT_DETECTED_epicsAtomicOSD_h
#else
#pragma push_macro("STRICT")
#undef STRICT
#define STRICT
#endif
/* Disable min/max macros from windows.h. These macros can cause issues with headers such as <algorithm> that declare or use std::min/max */
#pragma push_macro("NOMINMAX")
#undef NOMINMAX
#define NOMINMAX
/* Disable 'service controller' includes */
#pragma push_macro("NOSERVICE")
#undef NOSERVICE
#define NOSERVICE
/* Disable 'input management engine' includes */
#pragma push_macro("NOIME")
#undef NOIME
#define NOIME
/* Disable 'modem configuration extensions' includes */
#pragma push_macro("NOMCX")
#undef NOMCX
#define NOMCX
/* Disable GDI includes */
#pragma push_macro("NOGDI")
#undef NOGDI
#define NOGDI
/* Disable crypto stuff */
#pragma push_macro("NOCRYPT")
#undef NOCRYPT
#define NOCRYPT
/* Disable sound driver routines */
#pragma push_macro("NOSOUND")
#undef NOSOUND
#define NOSOUND
/* Disable Kanji writing system support */
#pragma push_macro("NOKANJI")
#undef NOKANJI
#define NOKANJI
#include "windows.h"
#ifndef WIN32_LEAN_AND_MEAN_DETECTED_epicsAtomicOSD_h
# undef WIN32_LEAN_AND_MEAN
#endif
#ifndef VC_EXTRALEAN_DETECTED_epicsAtomicOSD_h
# undef VC_EXTRALEAN
#endif
#ifndef STRICT_DETECTED_epicsAtomicOSD_h
# undef STRICT
#endif
/* Restore previous macro values */
#pragma pop_macro("WIN32_LEAN_AND_MEAN")
#pragma pop_macro("STRICT")
#pragma pop_macro("NOMINMAX")
#pragma pop_macro("NOSERVICE")
#pragma pop_macro("NOIME")
#pragma pop_macro("NOMCX")
#pragma pop_macro("NOGDI")
#pragma pop_macro("NOCRYPT")
#pragma pop_macro("NOSOUND")
#pragma pop_macro("NOKANJI")
#if defined ( _WIN64 )
# define MS_ATOMIC_64

View File

@ -103,6 +103,7 @@ typedef struct epicsThreadOSD {
char isSuspended;
int joinable;
int isRunning;
int isOkToBlock;
HANDLE timer; /* waitable timer */
} win32ThreadParam;
@ -586,6 +587,7 @@ static win32ThreadParam * epicsThreadImplicitCreate ( void )
pParm->handle = handle;
pParm->id = id;
pParm->isOkToBlock = 1;
win32ThreadPriority = GetThreadPriority ( pParm->handle );
assert ( win32ThreadPriority != THREAD_PRIORITY_ERROR_RETURN );
pParm->epicsPriority = epicsThreadGetOsiPriorityValue ( win32ThreadPriority );
@ -1224,3 +1226,17 @@ void testPriorityMapping ()
return 0;
}
#endif
int epicsStdCall epicsThreadIsOkToBlock(void)
{
struct epicsThreadOSD *pthreadInfo = epicsThreadGetIdSelf();
return(pthreadInfo->isOkToBlock);
}
void epicsStdCall epicsThreadSetOkToBlock(int isOkToBlock)
{
struct epicsThreadOSD *pthreadInfo = epicsThreadGetIdSelf();
pthreadInfo->isOkToBlock = !!isOkToBlock;
}

View File

@ -665,6 +665,7 @@ static epicsThreadOSD *createImplicit(void)
assert(pthreadInfo);
pthreadInfo->tid = tid;
pthreadInfo->osiPriority = 0;
pthreadInfo->isOkToBlock = 1;
#if defined(_POSIX_THREAD_PRIORITY_SCHEDULING) && _POSIX_THREAD_PRIORITY_SCHEDULING > 0
if(pthread_getschedparam(tid,&pthreadInfo->schedPolicy,&pthreadInfo->schedParam) == 0) {
@ -1095,3 +1096,17 @@ LIBCOM_API int epicsThreadGetCPUs(void)
#endif
return 1;
}
int epicsStdCall epicsThreadIsOkToBlock(void)
{
epicsThreadOSD *pthreadInfo = epicsThreadGetIdSelf();
return(pthreadInfo->isOkToBlock);
}
void epicsStdCall epicsThreadSetOkToBlock(int isOkToBlock)
{
epicsThreadOSD *pthreadInfo = epicsThreadGetIdSelf();
pthreadInfo->isOkToBlock = !!isOkToBlock;
}

View File

@ -35,6 +35,7 @@ typedef struct epicsThreadOSD {
int isRealTimeScheduled;
int isOnThreadList;
int isRunning;
int isOkToBlock;
unsigned int osiPriority;
int joinable;
char name[1]; /* actually larger */

View File

@ -132,7 +132,8 @@ static void epicsThreadInit(void)
taskIdListSize = ID_LIST_CHUNK;
atRebootRegister();
ALLOT_JOIN(0);
done = 1;
done = 1; /* avoids recursive call */
epicsThreadSetOkToBlock(1);
}
lock = 0;
}
@ -582,3 +583,30 @@ LIBCOM_API int epicsThreadGetCPUs(void)
{
return 1;
}
static epicsThreadOnceId okToBlockOnce = EPICS_THREAD_ONCE_INIT;
static epicsThreadPrivateId okToBlockPrivate;
static const int okToBlockNo = 0;
static const int okToBlockYes = 1;
static void epicsThreadOnceIdInit(void *not_used)
{
okToBlockPrivate = epicsThreadPrivateCreate();
}
int epicsStdCall epicsThreadIsOkToBlock(void)
{
const int *pokToBlock;
epicsThreadOnce(&okToBlockOnce, epicsThreadOnceIdInit, NULL);
pokToBlock = (int *) epicsThreadPrivateGet(okToBlockPrivate);
return (pokToBlock ? *pokToBlock : 0);
}
void epicsStdCall epicsThreadSetOkToBlock(int isOkToBlock)
{
const int *pokToBlock;
epicsThreadOnce(&okToBlockOnce, epicsThreadOnceIdInit, NULL);
pokToBlock = (isOkToBlock) ? &okToBlockYes : &okToBlockNo;
epicsThreadPrivateSet(okToBlockPrivate, (void *)pokToBlock);
}

View File

@ -7,8 +7,8 @@ use base 'Pod::Simple::XHTML';
BEGIN {
if ($Pod::Simple::XHTML::VERSION < '3.16') {
# encode_entities() wasn't a method, add it
our *encode_entities = sub {
# Add encode_entities() as a method
sub encode_entities {
my ($self, $str) = @_;
my %entities = (
q{>} => 'gt',
@ -37,4 +37,23 @@ sub resolve_pod_page_link {
return $ret;
}
sub _end_head {
my $h = delete $_[0]{in_head};
my $add = $_[0]->html_h_level;
$add = 1 unless defined $add;
$h += $add - 1;
my $id = $_[0]->idify($_[0]{htext});
my $text = $_[0]{scratch};
my $hid = qq{<h$h id="$id">};
my $link = qq{ <a class='sect' href="#$id">&sect;</a>};
$_[0]{'scratch'} = $_[0]->backlink && ($h - $add == 0)
# backlinks enabled && =head1
? qq{$hid<a href="#_podtop_">$text</a> $link</h$h>}
: qq{$hid$text $link</h$h>};
$_[0]->emit;
push @{ $_[0]{'to_index'} }, [$h, $id, delete $_[0]{'htext'}];
}
1;

View File

@ -69,6 +69,11 @@ A[href="#POD_ERRORS"] {
color: #FF0000;
}
A.sect {
color: #99ccff;
vertical-align: super;
}
TD {
margin: 0;
padding: 0;