diff --git a/.appveyor.yml b/.appveyor.yml index cf6534906..7632b32c4 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -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 diff --git a/.appveyor/epics-base-7.yml b/.appveyor/epics-base-7.yml index 218577c28..8616544e9 100644 --- a/.appveyor/epics-base-7.yml +++ b/.appveyor/epics-base-7.yml @@ -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 diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index 6fcd0cc0b..1bb55d14c 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -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 < 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 diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index f1953168f..82b9a921e 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -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 diff --git a/modules/database/src/ioc/db/dbCa.c b/modules/database/src/ioc/db/dbCa.c index 42fe374a8..98a38d427 100644 --- a/modules/database/src/ioc/db/dbCa.c +++ b/modules/database/src/ioc/db/dbCa.c @@ -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"); } diff --git a/modules/database/src/ioc/db/dbCaPvt.h b/modules/database/src/ioc/db/dbCaPvt.h index 6c1d902ca..1ee0745e0 100644 --- a/modules/database/src/ioc/db/dbCaPvt.h +++ b/modules/database/src/ioc/db/dbCaPvt.h @@ -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 diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c index fd682c93e..ad0531580 100644 --- a/modules/database/src/ioc/db/dbChannel.c +++ b/modules/database/src/ioc/db/dbChannel.c @@ -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; } diff --git a/modules/database/src/ioc/db/dbChannel.h b/modules/database/src/ioc/db/dbChannel.h index d4cb9e9bc..4bfdd6bc3 100644 --- a/modules/database/src/ioc/db/dbChannel.h +++ b/modules/database/src/ioc/db/dbChannel.h @@ -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); diff --git a/modules/database/src/ioc/db/dbServer.c b/modules/database/src/ioc/db/dbServer.c index d39b345e0..527fb97ca 100644 --- a/modules/database/src/ioc/db/dbServer.c +++ b/modules/database/src/ioc/db/dbServer.c @@ -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) \ { \ diff --git a/modules/database/src/ioc/db/dbServer.h b/modules/database/src/ioc/db/dbServer.h index 326166060..2083a705a 100644 --- a/modules/database/src/ioc/db/dbServer.h +++ b/modules/database/src/ioc/db/dbServer.h @@ -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. diff --git a/modules/database/src/ioc/db/dbUnitTest.c b/modules/database/src/ioc/db/dbUnitTest.c index 5ac710f48..671763636 100644 --- a/modules/database/src/ioc/db/dbUnitTest.c +++ b/modules/database/src/ioc/db/dbUnitTest.c @@ -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); diff --git a/modules/database/src/ioc/db/db_access.c b/modules/database/src/ioc/db/db_access.c index 0943315cf..62e8ffe3c 100644 --- a/modules/database/src/ioc/db/db_access.c +++ b/modules/database/src/ioc/db/db_access.c @@ -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); + } +} diff --git a/modules/database/src/ioc/db/db_access_routines.h b/modules/database/src/ioc/db/db_access_routines.h index ee2f07940..1871b9343 100644 --- a/modules/database/src/ioc/db/db_access_routines.h +++ b/modules/database/src/ioc/db/db_access_routines.h @@ -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 } diff --git a/modules/database/src/ioc/rsrv/camessage.c b/modules/database/src/ioc/rsrv/camessage.c index 56fbde088..5ef2b92c3 100644 --- a/modules/database/src/ioc/rsrv/camessage.c +++ b/modules/database/src/ioc/rsrv/camessage.c @@ -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 ); diff --git a/modules/database/test/ioc/db/dbChArrTest.cpp b/modules/database/test/ioc/db/dbChArrTest.cpp index 96870d212..1560f72a6 100644 --- a/modules/database/test/ioc/db/dbChArrTest.cpp +++ b/modules/database/test/ioc/db/dbChArrTest.cpp @@ -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; itype = 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 #include -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); diff --git a/modules/libcom/src/iocsh/initHooks.c b/modules/libcom/src/iocsh/initHooks.c index 90b1de2c5..2def0e2d4 100644 --- a/modules/libcom/src/iocsh/initHooks.c +++ b/modules/libcom/src/iocsh/initHooks.c @@ -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; diff --git a/modules/libcom/src/iocsh/initHooks.h b/modules/libcom/src/iocsh/initHooks.h index 573817f6b..57a1cc1fc 100644 --- a/modules/libcom/src/iocsh/initHooks.h +++ b/modules/libcom/src/iocsh/initHooks.h @@ -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); diff --git a/modules/libcom/src/osi/compiler/gcc/compilerSpecific.h b/modules/libcom/src/osi/compiler/gcc/compilerSpecific.h index f475ed11b..42723b97c 100644 --- a/modules/libcom/src/osi/compiler/gcc/compilerSpecific.h +++ b/modules/libcom/src/osi/compiler/gcc/compilerSpecific.h @@ -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 +#define EPICS_ALWAYS_INLINE __inline__ __attribute__((always_inline)) /* 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 +#define CXX_PLACEMENT_DELETE #endif /* __cplusplus */ @@ -56,21 +50,19 @@ /* * Deprecation marker */ -#if (__GNUC__ > 2) -# define EPICS_DEPRECATED __attribute__((deprecated)) -#endif +#define EPICS_DEPRECATED __attribute__((deprecated)) /* * 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 */ diff --git a/modules/libcom/src/osi/epicsThread.cpp b/modules/libcom/src/osi/epicsThread.cpp index b1273fd55..5de8fd99b 100644 --- a/modules/libcom/src/osi/epicsThread.cpp +++ b/modules/libcom/src/osi/epicsThread.cpp @@ -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(); diff --git a/modules/libcom/src/osi/os/Linux/osdThread.h b/modules/libcom/src/osi/os/Linux/osdThread.h index 4bf7f29a6..a691b7ad6 100644 --- a/modules/libcom/src/osi/os/Linux/osdThread.h +++ b/modules/libcom/src/osi/os/Linux/osdThread.h @@ -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 */ diff --git a/modules/libcom/src/osi/os/RTEMS-score/osdThread.c b/modules/libcom/src/osi/os/RTEMS-score/osdThread.c index e34717a36..abae0d20a 100644 --- a/modules/libcom/src/osi/os/RTEMS-score/osdThread.c +++ b/modules/libcom/src/osi/os/RTEMS-score/osdThread.c @@ -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, ¬e); + 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, ¬e); + v = (void *)note; + + if(v) + v->isOkToBlock = !!isOkToBlock; +} diff --git a/modules/libcom/src/osi/os/WIN32/epicsAtomicOSD.h b/modules/libcom/src/osi/os/WIN32/epicsAtomicOSD.h index 6af1bc81b..a1221deb7 100644 --- a/modules/libcom/src/osi/os/WIN32/epicsAtomicOSD.h +++ b/modules/libcom/src/osi/os/WIN32/epicsAtomicOSD.h @@ -19,37 +19,68 @@ #define EPICS_ATOMIC_OS_NAME "WIN32" -#ifdef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN_DETECTED_epicsAtomicOSD_h -#else -# define WIN32_LEAN_AND_MEAN -#endif +/* 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 -#ifdef VC_EXTRALEAN -# define VC_EXTRALEAN_DETECTED_epicsAtomicOSD_h -#else -# define VC_EXTRALEAN -#endif +#pragma push_macro("STRICT") +#undef STRICT +#define STRICT -#ifdef STRICT -# define STRICT_DETECTED_epicsAtomicOSD_h -#else -# define STRICT -#endif +/* Disable min/max macros from windows.h. These macros can cause issues with headers such as 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 diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.c b/modules/libcom/src/osi/os/WIN32/osdThread.c index d055b9bd9..d95c9971b 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.c +++ b/modules/libcom/src/osi/os/WIN32/osdThread.c @@ -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; +} diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c index 5e1e7feca..1c3910591 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.c +++ b/modules/libcom/src/osi/os/posix/osdThread.c @@ -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; +} diff --git a/modules/libcom/src/osi/os/posix/osdThread.h b/modules/libcom/src/osi/os/posix/osdThread.h index ea49030a3..ceb72fb1f 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.h +++ b/modules/libcom/src/osi/os/posix/osdThread.h @@ -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 */ diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.c b/modules/libcom/src/osi/os/vxWorks/osdThread.c index f64fb64d5..b1430433c 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.c +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.c @@ -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); +} diff --git a/src/tools/EPICS/PodXHtml.pm b/src/tools/EPICS/PodXHtml.pm index d39212988..3a5e64502 100644 --- a/src/tools/EPICS/PodXHtml.pm +++ b/src/tools/EPICS/PodXHtml.pm @@ -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{}; + my $link = qq{ §}; + $_[0]{'scratch'} = $_[0]->backlink && ($h - $add == 0) + # backlinks enabled && =head1 + ? qq{$hid$text $link} + : qq{$hid$text $link}; + $_[0]->emit; + push @{ $_[0]{'to_index'} }, [$h, $id, delete $_[0]{'htext'}]; +} + 1; diff --git a/src/tools/style.css b/src/tools/style.css index 09647403b..06df90945 100644 --- a/src/tools/style.css +++ b/src/tools/style.css @@ -69,6 +69,11 @@ A[href="#POD_ERRORS"] { color: #FF0000; } +A.sect { + color: #99ccff; + vertical-align: super; +} + TD { margin: 0; padding: 0;