From a9938c676fab0d5a9ec1bb42be26a272ddc4f3d7 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 31 Oct 2023 09:49:37 -0700 Subject: [PATCH] ioc: combine registrars and detect QSRV1 also consolidates initHook and epicsAtExit() hooks into a single sequence. --- ioc/groupsourcehooks.cpp | 92 ++++++---------- ioc/iochooks.cpp | 225 +++++++++++++++++++++++++++++++++----- ioc/iocsource.cpp | 40 ------- ioc/iocsource.h | 2 - ioc/pvalink.cpp | 187 +++++++++---------------------- ioc/pvalink.h | 28 +++-- ioc/pvalink_channel.cpp | 36 +++--- ioc/pvalink_jlif.cpp | 7 +- ioc/pvalink_link.cpp | 5 +- ioc/pvalink_lset.cpp | 18 +-- ioc/pvxs/iochooks.h | 74 ++++++++++--- ioc/pvxs3x.dbd | 2 - ioc/pvxs7x.dbd | 3 - ioc/qsrvpvt.h | 67 ++++++++++++ ioc/singlesourcehooks.cpp | 52 +++------ qsrv/softMain.cpp | 3 - test/testioc.h | 26 ----- test/testpvalink.cpp | 18 +-- test/testqgroup.cpp | 2 +- test/testqsingle.cpp | 20 +++- 20 files changed, 501 insertions(+), 406 deletions(-) create mode 100644 ioc/qsrvpvt.h diff --git a/ioc/groupsourcehooks.cpp b/ioc/groupsourcehooks.cpp index 9b5050b5a..bf9e16a2e 100644 --- a/ioc/groupsourcehooks.cpp +++ b/ioc/groupsourcehooks.cpp @@ -20,6 +20,7 @@ #include #include +#include "qsrvpvt.h" #include "groupsource.h" #include "groupconfigprocessor.h" #include "iocshcommand.h" @@ -161,66 +162,49 @@ long dbLoadGroup(const char* jsonFilename, const char* macros) { } } -} -} // namespace pvxs::ioc -using namespace pvxs::ioc; +void processGroups() +{ + GroupConfigProcessor processor; + epicsGuard G(processor.config.groupMapMutex); -namespace { -using namespace pvxs; + // Parse all info(Q:Group... records to configure groups + processor.loadConfigFromDb(); -/** - * Initialise qsrv database group records by adding them as sources in our running pvxs server instance - * - * @param theInitHookState the initHook state - we only want to trigger on the initHookAfterIocBuilt state - ignore all others - */ -void qsrvGroupSourceInit(initHookState theInitHookState) { - try { - if(!IOCSource::enabled()) - return; - if (theInitHookState == initHookAfterInitDatabase) { - GroupConfigProcessor processor; - epicsGuard G(processor.config.groupMapMutex); + // Load group configuration files + processor.loadConfigFiles(); - // Parse all info(Q:Group... records to configure groups - processor.loadConfigFromDb(); + // checks on groupConfigMap + processor.validateGroups(); - // Load group configuration files - processor.loadConfigFiles(); + // Configure groups + processor.defineGroups(); - // checks on groupConfigMap - processor.validateGroups(); + // Resolve triggers + processor.resolveTriggerReferences(); - // Configure groups - processor.defineGroups(); + // Create Server Groups + processor.createGroups(); +} - // Resolve triggers - processor.resolveTriggerReferences(); +void addGroupSrc() +{ + pvxs::ioc::server() + .addSource("qsrvGroup", std::make_shared(), 1); +} - // Create Server Groups - processor.createGroups(); - } else if (theInitHookState == initHookAfterIocBuilt) { - // Load group configuration from parsed groups in iocServer - pvxs::ioc::server().addSource("qsrvGroup", std::make_shared(), 1); - } - } catch(std::exception& e) { - fprintf(stderr, "ERROR: Unhandled exception in %s(%d): %s\n", - __func__, theInitHookState, e.what()); - } +void resetGroups() +{ + auto& config(IOCGroupConfig::instance()); + + // server stopped at this point, but lock anyway + epicsGuard G(config.groupMapMutex); + + config.groupMap.clear(); + config.groupConfigFiles.clear(); } -/** - * IOC pvxs Group Source registrar. This implements the required registrar function that is called by xxxx_registerRecordDeviceDriver, - * the auto-generated stub created for all IOC implementations. - *

- * It is registered by using the `epicsExportRegistrar()` macro. - *

- * 1. Register your hook handler to handle any state hooks that you want to implement. Here we install - * an `initHookState` handler connected to the `initHookAfterIocBuilt` state. It will add all of the - * group record type sources defined so far. Note that you can define sources up until the `iocInit()` call, - * after which point the `initHookAfterIocBuilt` handlers are called and will register all the defined records. - */ -void pvxsGroupSourceRegistrar() { +void group_enable() { // Register commands to be available in the IOC shell IOCShCommand("pvxgl", "[level, [pattern]]", "Group Sources list.\n" @@ -232,14 +216,6 @@ void pvxsGroupSourceRegistrar() { IOCShCommand("dbLoadGroup", "JSON file", "macros", dbLoadGroupMsg) .implementation<&dbLoadGroupCmd>(); - - initHookRegister(&qsrvGroupSourceInit); } -} // namespace - -// in .dbd file -//registrar(pvxsGroupSourceRegistrar) -extern "C" { -epicsExportRegistrar(pvxsGroupSourceRegistrar); -} +}} // namespace pvxs::ioc diff --git a/ioc/iochooks.cpp b/ioc/iochooks.cpp index 020a119ae..3291a5709 100644 --- a/ioc/iochooks.cpp +++ b/ioc/iochooks.cpp @@ -18,8 +18,12 @@ #include #include +#include #include #include +#include +#include +#include #include #include @@ -28,14 +32,18 @@ #include "iocshcommand.h" #include "utilpvt.h" +#include "qsrvpvt.h" + +#ifdef USE_QSRV_SINGLE +# include +#endif +#ifdef USE_PVA_LINKS +# include "pvalink.h" +#endif // include last to avoid clash of #define printf with other headers #include -#if EPICS_VERSION_INT >= VERSION_INT(7, 0, 4, 0) -# define USE_DEINIT_HOOKS -#endif - namespace pvxs { namespace ioc { @@ -90,16 +98,13 @@ void initialisePvxsServer() { pvxServer->srv = std::move(newsrv); } -/** - * The function to call when we exit the IOC process. This is only installed as the callback function - * after the database has been initialized. This function will stop the pvxs server instance and destroy the - * object. - * - * @param pep - The pointer to the exit parameter list - unused - */ static -void pvxsAtExit(void*) noexcept { +void pvxsExitBeforeIocShutdown(void*) noexcept +{ try { +#ifdef USE_PVA_LINKS + linkGlobal_t::deinit(); +#endif Guard (pvxServer->lock); if(auto srv = std::move(pvxServer->srv)) { pvxServer->srv = server::Server(); @@ -112,19 +117,57 @@ void pvxsAtExit(void*) noexcept { } } -void testPrepare() +static +void pvxsExitAfterIocShutdown(void*) noexcept +{ + try { +#ifdef USE_PVA_LINKS + linkGlobal_t::dtor(); +#endif + + } catch(std::exception& e) { + fprintf(stderr, "Error in %s : %s\n", __func__, e.what()); + } +} + +static +void testPrepareImpl() { if(pvxServer) initialisePvxsServer(); // re-create server for next test cycle } +void testPrepare() +{ +#ifndef USE_PREPARE_CLEANUP_HOOKS + testPrepareImpl(); +#endif +} + void testShutdown() { #ifndef USE_DEINIT_HOOKS - pvxsAtExit(nullptr); + pvxsExitBeforeIocShutdown(nullptr); #endif } +void testAfterShutdown() +{ +#ifndef USE_DEINIT_HOOKS + pvxsExitAfterIocShutdown(nullptr); +#endif +} + +void testCleanupPrepare() +{ + server::Server trash; + { + Guard G(pvxServer->lock); + trash = std::move(pvxServer->srv); + } + resetGroups(); +} + //////////////////////////////////// // Two ioc shell commands for pvxs //////////////////////////////////// @@ -165,6 +208,35 @@ void pvxsi() { printf("%s", capture.str().c_str()); } +#ifdef USE_QSRV_SINGLE +TestIOC::TestIOC() { + testdbPrepare(); + testPrepare(); +} + +void TestIOC::init() { + if(!isRunning) { + testIocInitOk(); + isRunning = true; + } +} + +void TestIOC::shutdown() { + if(isRunning) { + isRunning = false; + testShutdown(); + testIocShutdownOk(); + testAfterShutdown(); + } +} + +TestIOC::~TestIOC() { + shutdown(); + testCleanupPrepare(); + testdbCleanup(); +} +#endif // USE_QSRV_SINGLE + namespace { void pvxrefshow() { @@ -236,26 +308,53 @@ void pvxrefdiff() { } // namespace -/** - * Initialise and control state of pvxs ioc server instance in response to iocInitHook events. - * Installed on the initHookState hook this function will respond to the following events: - * - initHookAfterInitDatabase: Set the exit callback only when we have initialized the database - * - initHookAfterCaServerRunning: Start the pvxs server instance after the CA server starts running - * - initHookAfterCaServerPaused: Pause the pvxs server instance if the CA server pauses - * - * @param theInitHookState the initHook state to respond to - */ static -void pvxsInitHook(initHookState theInitHookState) { +void pvxsInitHook(initHookState theInitHookState) noexcept { switch(theInitHookState) { +#ifdef USE_PREPARE_CLEANUP_HOOKS + case initHookAfterPrepareDatabase: // test only + testPrepareImpl(); + break; +#endif + case initHookAtBeginning: + dbRegisterQSRV2(); + break; + case initHookAfterCaLinkInit: +#ifdef USE_PVA_LINKS + linkGlobal_t::alloc(); +#endif +#ifndef USE_DEINIT_HOOKS + // before epicsExit(exitDatabase), + // so hook registered here will be run after iocShutdown() + { + static bool installed = false; + if(!installed) { + epicsAtExit(&pvxsExitAfterIocShutdown, nullptr); + installed = true; + } + } +#endif + break; case initHookAfterInitDatabase: - // when de-init hooks not available, register for later cleanup via atexit() - // function to run before exitDatabase + processGroups(); #ifndef USE_DEINIT_HOOKS - epicsAtExit(&pvxsAtExit, nullptr); + // register for later cleanup before iocShutdown() + { + static bool installed = false; + if(!installed) { + epicsAtExit(&pvxsExitBeforeIocShutdown, nullptr); + installed = true; + } + } +#endif + break; + case initHookAfterIocBuilt: +#ifdef USE_PVA_LINKS + linkGlobal_t::init(); #endif + addSingleSrc(); + addGroupSrc(); break; - case initHookAfterCaServerRunning: case initHookAfterIocRunning: if(auto srv = server()) { srv.start(); @@ -271,7 +370,15 @@ void pvxsInitHook(initHookState theInitHookState) { #ifdef USE_DEINIT_HOOKS // use de-init hook when available case initHookAtShutdown: - pvxsAtExit(nullptr); + pvxsExitBeforeIocShutdown(nullptr); + break; + case initHookAfterShutdown: + pvxsExitAfterIocShutdown(nullptr); + break; +#endif +#ifdef USE_PREPARE_CLEANUP_HOOKS + case initHookBeforeCleanupDatabase: // test only + testCleanupPrepare(); break; #endif default: @@ -286,6 +393,56 @@ using namespace pvxs::ioc; namespace { +bool enable2() { + // detect if also linked with qsrv.dbd + const bool permit = !registryDeviceSupportFind("devWfPDBDemo"); + bool request = permit; + bool quiet = false; + + auto env_dis = getenv("EPICS_IOC_IGNORE_SERVERS"); + auto env_ena = getenv("PVXS_QSRV_ENABLE"); + + if(env_dis && strstr(env_dis, "qsrv2")) { + request = false; + quiet = true; + + } else if(env_ena && epicsStrCaseCmp(env_ena, "YES")==0) { + request = true; + + } else if(env_ena && epicsStrCaseCmp(env_ena, "NO")==0) { + request = false; + quiet = true; + + } else if(env_ena) { + // will be seen during initialization, print synchronously + fprintf(stderr, "ERROR: PVXS_QSRV_ENABLE=%s not YES/NO. Defaulting to %s.\n", + env_ena, + request ? "YES" : "NO"); + } + + const bool enable = permit && request; + + if(quiet) { + // shut up, I know what I'm doing... + } else if(request && !permit) { + fprintf(stderr, + "WARNING: QSRV1 detected, disabling QSRV2.\n" + " If not intended, omit qsrv.dbd when including pvxsIoc.dbd\n"); + + } else { + printf("INFO: PVXS QSRV2 is loaded, %spermitted, and %s.\n", + permit ? "" : "NOT ", + enable ? "ENABLED" : "disabled"); + + if(!permit) { + printf(" Not permitted due to confict with QSRV1.\n" + " Remove qsrv.dbd from IOC.\n"); + } + } + + return enable; +} + /** * IOC pvxs base registrar. This implements the required registrar function that is called by xxxx_registerRecordDeviceDriver, * the auto-generated stub created for all IOC implementations. @@ -296,10 +453,12 @@ namespace { * 2. Also make sure that you initialize your server implementation - PVXS in our case - so that it will be available for the shell. * 3. Lastly register your hook handler to handle any state hooks that you want to implement */ -void pvxsBaseRegistrar() { +void pvxsBaseRegistrar() noexcept { try { pvxs::logger_config_env(); + bool enableQ = enable2(); + pvxServer = new pvxServer_t(); IOCShCommand("pvxsr", "[show_detailed_information?]", "PVXS Server Report. " @@ -320,6 +479,12 @@ void pvxsBaseRegistrar() { // Register our hook handler to intercept certain state changes initHookRegister(&pvxsInitHook); + + if(enableQ) { + single_enable(); + group_enable(); + pvalink_enable(); + } } catch (std::exception& e) { fprintf(stderr, "Error in %s : %s\n", __func__, e.what()); } diff --git a/ioc/iocsource.cpp b/ioc/iocsource.cpp index 8ebabf96b..869d7d830 100644 --- a/ioc/iocsource.cpp +++ b/ioc/iocsource.cpp @@ -36,46 +36,6 @@ DEFINE_LOGGER(_log, "pvxs.ioc.db"); namespace pvxs { namespace ioc { - -bool IOCSource::enabled() -{ - /* -1 - disabled - * 0 - lazy init, check environment - * 1 - enabled - */ - static std::atomic ena{}; - - auto e = ena.load(); - if(e==0) { - e = inUnitTest() ? 1 : -1; // default to disabled normally (not unittest) - - auto env_dis = getenv("EPICS_IOC_IGNORE_SERVERS"); - auto env_ena = getenv("PVXS_QSRV_ENABLE"); - - if(env_dis && strstr(env_dis, "qsrv2")) { - e = -1; - - } else if(env_ena && epicsStrCaseCmp(env_ena, "YES")==0) { - e = 1; - - } else if(env_ena && epicsStrCaseCmp(env_ena, "NO")==0) { - e = -1; - - } else if(env_ena) { - // will be seen during initialization, print synchronously - fprintf(stderr, "ERROR: PVXS_QSRV_ENABLE=%s not YES/NO. Defaulting to %s.\n", - env_ena, - e==1 ? "YES" : "NO"); - } - printf("INFO: PVXS QSRV2 is loaded and %s\n", - e==1 ? "ENABLED." : "disabled.\n" - " To enable set: epicsEnvSet(\"PVXS_QSRV_ENABLE\",\"YES\")\n" - " and ensure that $EPICS_IOC_IGNORE_SERVERS does not contain \"qsrv2\"."); - ena = e; - } - return e==1; -} - void IOCSource::initialize(Value& value, const MappingInfo &info, const Channel& chan) { if(info.type==MappingInfo::Scalar) { diff --git a/ioc/iocsource.h b/ioc/iocsource.h index 9b16bd73a..c4b27795e 100644 --- a/ioc/iocsource.h +++ b/ioc/iocsource.h @@ -43,8 +43,6 @@ enum type { class IOCSource { public: - static bool enabled(); - static void initialize(Value& value, const MappingInfo &info, const Channel &chan); static void get(Value& valuePrototype, diff --git a/ioc/pvalink.cpp b/ioc/pvalink.cpp index 257889660..5a8f398f3 100644 --- a/ioc/pvalink.cpp +++ b/ioc/pvalink.cpp @@ -36,6 +36,7 @@ #include "dbentry.h" #include "iocshcommand.h" #include "utilpvt.h" +#include "qsrvpvt.h" #include /* redirects stdout/stderr; include after util.h from libevent */ #include /* defines epicsExportSharedSymbols */ @@ -44,147 +45,57 @@ # define HAVE_SHUTDOWN_HOOKS #endif -namespace pvxs { namespace ioc { +namespace pvxs { +namespace ioc { -using namespace pvxlink; - -namespace { - -// halt, and clear, scan workers before dbCloseLinks() (cf. iocShutdown()) -static void shutdownStep1() +void linkGlobal_t::alloc() { - // no locking here as we assume that shutdown doesn't race startup - if(!pvaGlobal) return; + if(linkGlobal) { + cantProceed("# Missing call to testqsrvShutdownOk() and/or testqsrvCleanup()"); + } + linkGlobal = new linkGlobal_t; - pvaGlobal->close(); + // TODO "local" provider + if (inUnitTest()) { + linkGlobal->provider_remote = ioc::server().clientConfig().build(); + } else { + linkGlobal->provider_remote = client::Config().build(); + } } -// Cleanup pvaGlobal, including PVA client and QSRV providers ahead of PDB cleanup -// specifically QSRV provider must be free'd prior to db_cleanup_events() -static void shutdownStep2() +void linkGlobal_t::init() { - if(!pvaGlobal) return; + Guard G(linkGlobal->lock); + linkGlobal->running = true; + for(linkGlobal_t::channels_t::iterator it(linkGlobal->channels.begin()), end(linkGlobal->channels.end()); + it != end; ++it) { - Guard G(pvaGlobal->lock); - assert(pvaLink::cnt_pvaLink<=1u); // dbRemoveLink() already called - assert(pvaGlobal->channels.empty()); - } - - delete pvaGlobal; - pvaGlobal = NULL; -} - -#ifndef HAVE_SHUTDOWN_HOOKS -static void stopPVAPool(void*) -{ - try { - shutdownStep1(); - }catch(std::exception& e){ - fprintf(stderr, "Error while stopping PVA link pool : %s\n", e.what()); - } -} + std::shared_ptr chan(it->second.lock()); + if(!chan) continue; -static void finalizePVA(void*) -{ - try { - shutdownStep2(); - }catch(std::exception& e){ - fprintf(stderr, "Error initializing pva link handling : %s\n", e.what()); + chan->open(); } } -#endif -/* The Initialization game... - * - * # Parse links during dbPutString() (calls our jlif*) - * # announce initHookAfterCaLinkInit - * # dbChannelInit() (needed for QSRV to work) - * # Re-parse links (calls to our jlif*) - * # Open links. Calls jlif::get_lset() and then lset::openLink() - * # announce initHookAfterInitDatabase - * # ... scan threads start ... - * # announce initHookAfterIocBuilt - */ -void initPVALink(initHookState state) +void linkGlobal_t::deinit() { - try { - if(state==initHookAfterCaLinkInit) { - // before epicsExit(exitDatabase), - // so hook registered here will be run after iocShutdown() - // which closes links - if(pvaGlobal) { - cantProceed("# Missing call to testqsrvShutdownOk() and/or testqsrvCleanup()"); - } - pvaGlobal = new pvaGlobal_t; - -#ifndef HAVE_SHUTDOWN_HOOKS - static bool atexitInstalled; - if(!atexitInstalled) { - epicsAtExit(finalizePVA, NULL); - atexitInstalled = true; - } -#endif - - } else if(state==initHookAfterInitDatabase) { - // TODO "local" provider - if (inUnitTest()) { - pvaGlobal->provider_remote = ioc::server().clientConfig().build(); - } else { - pvaGlobal->provider_remote = client::Config().build(); - } - - } else if(state==initHookAfterIocBuilt) { - // after epicsExit(exitDatabase) - // so hook registered here will be run before iocShutdown() - -#ifndef HAVE_SHUTDOWN_HOOKS - epicsAtExit(stopPVAPool, NULL); -#endif - - Guard G(pvaGlobal->lock); - pvaGlobal->running = true; - - for(pvaGlobal_t::channels_t::iterator it(pvaGlobal->channels.begin()), end(pvaGlobal->channels.end()); - it != end; ++it) - { - std::shared_ptr chan(it->second.lock()); - if(!chan) continue; - - chan->open(); - } -#ifdef HAVE_SHUTDOWN_HOOKS - } else if(state==initHookAtShutdown) { - shutdownStep1(); + // no locking here as we assume that shutdown doesn't race startup + if(!linkGlobal) return; - } else if(state==initHookAfterShutdown) { - shutdownStep2(); -#endif - } - }catch(std::exception& e){ - cantProceed("Error initializing pva link handling : %s\n", e.what()); - } + linkGlobal->close(); } -} // namespace - -// halt, and clear, scan workers before dbCloseLinks() (cf. iocShutdown()) -void testqsrvShutdownOk(void) +void linkGlobal_t::dtor() { - try { - shutdownStep1(); - }catch(std::exception& e){ - testAbort("Error while stopping PVA link pool : %s\n", e.what()); + { + Guard G(linkGlobal->lock); + assert(pvaLink::cnt_pvaLink<=1u); // dbRemoveLink() already called + assert(linkGlobal->channels.empty()); } -} -void testqsrvCleanup(void) -{ - try { - shutdownStep2(); - }catch(std::exception& e){ - testAbort("Error initializing pva link handling : %s\n", e.what()); - } + delete linkGlobal; + linkGlobal = NULL; } static @@ -219,7 +130,7 @@ DBLINK* testGetLink(const char *pv) void testqsrvWaitForLinkConnected(struct link *plink, bool conn) { if(conn) - pvaGlobal->provider_remote.hurryUp(); + linkGlobal->provider_remote.hurryUp(); std::shared_ptr lchan(testGetPVALink(plink)); Guard G(lchan->lock); while(lchan->connected!=conn) { @@ -273,7 +184,7 @@ extern "C" void dbpvar(const char *precordname, int level) { try { - if(!pvaGlobal) { + if(!linkGlobal) { printf("PVA links not initialized\n"); return; } @@ -287,13 +198,13 @@ void dbpvar(const char *precordname, int level) size_t nchans = 0, nlinks = 0, nconn = 0; - pvaGlobal_t::channels_t channels; + linkGlobal_t::channels_t channels; { - Guard G(pvaGlobal->lock); - channels = pvaGlobal->channels; // copy snapshot + Guard G(linkGlobal->lock); + channels = linkGlobal->channels; // copy snapshot } - for(pvaGlobal_t::channels_t::const_iterator it(channels.begin()), end(channels.end()); + for(linkGlobal_t::channels_t::const_iterator it(channels.begin()), end(channels.end()); it != end; ++it) { std::shared_ptr chan(it->second.lock()); @@ -404,17 +315,21 @@ void dbpvar(const char *precordname, int level) } static -void installPVAAddLinkHook() +const iocshVarDef pvaLinkNWorkersDef[] = { + { + "pvaLinkNWorkers", + iocshArgInt, + &pvaLinkNWorkers + }, + {0, iocshArgInt, 0} +}; + +void pvalink_enable() { - initHookRegister(&initPVALink); IOCShCommand("dbpvar", "dbpvar", "record name", "level") .implementation<&dbpvar>(); + iocshRegisterVariable(pvaLinkNWorkersDef); + } }} // namespace pvxs::ioc - -extern "C" { - using pvxs::ioc::installPVAAddLinkHook; - epicsExportRegistrar(installPVAAddLinkHook); - epicsExportAddress(int, pvaLinkNWorkers); -} diff --git a/ioc/pvalink.h b/ioc/pvalink.h index 0aaa5578e..d94173ebf 100644 --- a/ioc/pvalink.h +++ b/ioc/pvalink.h @@ -50,8 +50,8 @@ extern "C" { extern int pvaLinkNWorkers; } -namespace pvxlink { -using namespace pvxs; +namespace pvxs { +namespace ioc { typedef epicsGuard Guard; typedef epicsGuardRelease UnGuard; @@ -103,7 +103,7 @@ struct pvaLinkConfig : public jlink virtual ~pvaLinkConfig(); }; -struct pvaGlobal_t final : private epicsThreadRunable { +struct linkGlobal_t final : private epicsThreadRunable { client::Context provider_remote; MPMCFIFO> queue; @@ -128,18 +128,24 @@ struct pvaGlobal_t final : private epicsThreadRunable { virtual void run() override final; public: - pvaGlobal_t(); - pvaGlobal_t(const pvaGlobal_t&) = delete; - pvaGlobal_t& operator=(const pvaGlobal_t&) = delete; - virtual ~pvaGlobal_t(); + linkGlobal_t(); + linkGlobal_t(const linkGlobal_t&) = delete; + linkGlobal_t& operator=(const linkGlobal_t&) = delete; + virtual ~linkGlobal_t(); void close(); + + // IOC lifecycle hooks + static void alloc(); + static void init(); + static void deinit(); + static void dtor(); }; -extern pvaGlobal_t *pvaGlobal; +extern linkGlobal_t *linkGlobal; struct pvaLinkChannel final : public epicsThreadRunable ,public std::enable_shared_from_this { - const pvaGlobal_t::channels_key_t key; // tuple of (channelName, pvRequest key) + const linkGlobal_t::channels_key_t key; // tuple of (channelName, pvRequest key) const Value pvRequest; // used with monitor INST_COUNTER(pvaLinkChannel); @@ -175,7 +181,7 @@ struct pvaLinkChannel final : public epicsThreadRunable // set when 'links' is modified to trigger re-compute of record scan list bool links_changed = false; - pvaLinkChannel(const pvaGlobal_t::channels_key_t& key, const Value &pvRequest); + pvaLinkChannel(const linkGlobal_t::channels_key_t& key, const Value &pvRequest); virtual ~pvaLinkChannel(); void open(); @@ -256,6 +262,6 @@ struct pvaLink final : public pvaLinkConfig }; -} // namespace pvalink +}} // namespace pvxs::ioc #endif // PVALINK_H diff --git a/ioc/pvalink_channel.cpp b/ioc/pvalink_channel.cpp index 970d9ae9f..f952f6be2 100644 --- a/ioc/pvalink_channel.cpp +++ b/ioc/pvalink_channel.cpp @@ -19,13 +19,13 @@ DEFINE_LOGGER(_logupdate, "pvxs.ioc.link.channel.update"); int pvaLinkNWorkers = 1; -namespace pvxlink { -using namespace pvxs; +namespace pvxs { +namespace ioc { -pvaGlobal_t *pvaGlobal; +linkGlobal_t *linkGlobal; -pvaGlobal_t::pvaGlobal_t() +linkGlobal_t::linkGlobal_t() :queue() ,running(false) ,putReq(TypeDef(TypeCode::Struct, { @@ -46,11 +46,11 @@ pvaGlobal_t::pvaGlobal_t() worker.start(); } -pvaGlobal_t::~pvaGlobal_t() +linkGlobal_t::~linkGlobal_t() { } -void pvaGlobal_t::run() +void linkGlobal_t::run() { while(1) { auto w = queue.pop(); @@ -66,7 +66,7 @@ void pvaGlobal_t::run() } -void pvaGlobal_t::close() +void linkGlobal_t::close() { { Guard G(lock); @@ -85,8 +85,8 @@ bool pvaLinkChannel::LinkSort::operator()(const pvaLink *L, const pvaLink *R) co return L->monorder < R->monorder; } -// being called with pvaGlobal::lock held -pvaLinkChannel::pvaLinkChannel(const pvaGlobal_t::channels_key_t &key, const Value& pvRequest) +// being called with linkGlobal::lock held +pvaLinkChannel::pvaLinkChannel(const linkGlobal_t::channels_key_t &key, const Value& pvRequest) :key(key) ,pvRequest(pvRequest) ,AP(new AfterPut) @@ -94,8 +94,8 @@ pvaLinkChannel::pvaLinkChannel(const pvaGlobal_t::channels_key_t &key, const Val pvaLinkChannel::~pvaLinkChannel() { { - Guard G(pvaGlobal->lock); - pvaGlobal->channels.erase(key); + Guard G(linkGlobal->lock); + linkGlobal->channels.erase(key); } Guard G(lock); @@ -107,7 +107,7 @@ void pvaLinkChannel::open() { Guard G(lock); - op_mon = pvaGlobal->provider_remote.monitor(key.first) + op_mon = linkGlobal->provider_remote.monitor(key.first) .maskConnected(true) .maskDisconnected(false) .rawRequest(pvRequest) @@ -115,7 +115,7 @@ void pvaLinkChannel::open() { log_debug_printf(_logger, "Monitor %s wakeup\n", key.first.c_str()); try { - pvaGlobal->queue.push(shared_from_this()); + linkGlobal->queue.push(shared_from_this()); }catch(std::bad_weak_ptr&){ log_err_printf(_logger, "channel '%s' open during dtor?", key.first.c_str()); } @@ -212,14 +212,14 @@ void linkPutDone(pvaLinkChannel *self, client::Result&& result) log_debug_printf(_logger, "linkPutDone: %s, needscans = %i\n", self->key.first.c_str(), needscans); if(needscans) { - pvaGlobal->queue.push(self->AP); + linkGlobal->queue.push(self->AP); } } // call with channel lock held void pvaLinkChannel::put(bool force) { - auto pvReq(pvaGlobal->putReq.cloneEmpty() + auto pvReq(linkGlobal->putReq.cloneEmpty() .update("record._options.block", !after_put.empty())); unsigned reqProcess = 0; @@ -265,7 +265,7 @@ void pvaLinkChannel::put(bool force) log_debug_printf(_logger, "%s Start put %s\n", key.first.c_str(), doit ? "true": "false"); if(doit) { // start net Put, cancels in-progress put - op_put = pvaGlobal->provider_remote.put(key.first) + op_put = linkGlobal->provider_remote.put(key.first) .rawRequest(pvReq) .build([this](Value&& prototype) -> Value { @@ -440,7 +440,7 @@ void pvaLinkChannel::run() log_debug_printf(_logger, "Requeueing %s\n", key.first.c_str()); // re-queue until monitor queue is empty - pvaGlobal->queue.push(shared_from_this()); + linkGlobal->queue.push(shared_from_this()); } -} // namespace pvalink +}} // namespace pvxs::ioc diff --git a/ioc/pvalink_jlif.cpp b/ioc/pvalink_jlif.cpp index bd07adb6b..49b17a372 100644 --- a/ioc/pvalink_jlif.cpp +++ b/ioc/pvalink_jlif.cpp @@ -11,7 +11,8 @@ #include // redirects stdout/stderr #include -namespace pvxlink { +namespace pvxs { +namespace ioc { pvaLinkConfig::~pvaLinkConfig() {} namespace { @@ -298,9 +299,9 @@ jlif lsetPVA = { NULL }; -} //namespace pvalink +}} //namespace pvxs::ioc extern "C" { -using pvxlink::lsetPVA; +using pvxs::ioc::lsetPVA; epicsExportAddress(jlif, lsetPVA); } diff --git a/ioc/pvalink_link.cpp b/ioc/pvalink_link.cpp index 3d04bf653..59e5ea252 100644 --- a/ioc/pvalink_link.cpp +++ b/ioc/pvalink_link.cpp @@ -14,7 +14,8 @@ DEFINE_LOGGER(_logger, "pvxs.ioc.link.link"); -namespace pvxlink { +namespace pvxs { +namespace ioc { pvaLink::pvaLink() { @@ -118,4 +119,4 @@ void pvaLink::onTypeChange() fld_meta ? 'Y' : 'N'); } -} // namespace pvalink +}} // namespace pvxs::ioc diff --git a/ioc/pvalink_lset.cpp b/ioc/pvalink_lset.cpp index ba0fd4e2f..df87988fb 100644 --- a/ioc/pvalink_lset.cpp +++ b/ioc/pvalink_lset.cpp @@ -17,9 +17,9 @@ DEFINE_LOGGER(_logger, "pvxs.ioc.link.lset"); -namespace pvxlink { +namespace pvxs { +namespace ioc { namespace { -using namespace pvxs; #define TRY pvaLink *self = static_cast(plink->value.json.jlink); assert(self->alive); try #define CATCH() catch(std::exception& e) { \ @@ -75,16 +75,16 @@ void pvaOpenLink(DBLINK *plink) return; // nothing to do... auto pvRequest(self->makeRequest()); - pvaGlobal_t::channels_key_t key = std::make_pair(self->channelName, std::string(SB()<channelName, std::string(SB()< chan; bool doOpen = false; { - Guard G(pvaGlobal->lock); + Guard G(linkGlobal->lock); - pvaGlobal_t::channels_t::iterator it(pvaGlobal->channels.find(key)); + linkGlobal_t::channels_t::iterator it(linkGlobal->channels.find(key)); - if(it!=pvaGlobal->channels.end()) { + if(it!=linkGlobal->channels.end()) { // re-use existing channel chan = it->second.lock(); } @@ -97,7 +97,7 @@ void pvaOpenLink(DBLINK *plink) chan.reset(new pvaLinkChannel(key, pvRequest)); chan->AP->lc = chan; - pvaGlobal->channels.insert(std::make_pair(key, chan)); + linkGlobal->channels.insert(std::make_pair(key, chan)); doOpen = true; } else { @@ -105,7 +105,7 @@ void pvaOpenLink(DBLINK *plink) plink->precord->name, self->channelName.c_str()); } - doOpen &= pvaGlobal->running; // if not running, then open from initHook + doOpen &= linkGlobal->running; // if not running, then open from initHook } if(doOpen) { @@ -686,4 +686,4 @@ lset pva_lset = { #endif }; -} // namespace pvxlink +}} // namespace pvxs::ioc diff --git a/ioc/pvxs/iochooks.h b/ioc/pvxs/iochooks.h index 65b33cccc..29d5f2642 100644 --- a/ioc/pvxs/iochooks.h +++ b/ioc/pvxs/iochooks.h @@ -100,27 +100,71 @@ void testPrepare(); PVXS_IOC_API void testShutdown(); -#ifdef PVXS_EXPERT_API_ENABLED +/** Call just after testIocShutdownOk() + * @since UNRELEASED + */ PVXS_IOC_API -void testqsrvWaitForLinkConnected(struct link *plink, bool conn=true); +void testAfterShutdown(); + +/** Call just before testdbCleanup() + * @since UNRELEASED + */ PVXS_IOC_API -void testqsrvWaitForLinkConnected(const char* pv, bool conn=true); +void testCleanupPrepare(); + +#if EPICS_VERSION_INT >= VERSION_INT(3, 15, 0 ,0) -class PVXS_IOC_API QSrvWaitForLinkUpdate final { - struct link * const plink; - unsigned seq; +/** Manage Test IOC life-cycle calls. + * + * Makes necessary calls to dbUnitTest.h API + * as well as any added calls needed by PVXS components. + * + @code + * MAIN(mytest) { + * testPlan(0); + * pvxs::testSetup(); + * pvxs::logger_config_env(); // (optional) + * { + * TestIOC ioc; // testdbPrepare() + * + * // mytestioc.dbd must include pvxsIoc.dbd + * testdbReadDatabase("mytestioc.dbd", NULL, NULL); + * mytestioc_registerRecordDeviceDriver(pdbbase); + * testdbReadDatabase("sometest.db", NULL, NULL); + * + * // tests before iocInit() + * + * ioc.init(); + * + * // tests after iocInit() + * + * ioc.shutdown(); // (optional) in ~TestIOC if omitted + * } + * { + * ... repeat ... + * } + * epicsExitCallAtExits(); + * cleanup_for_valgrind(); + * } + @endcode + * + * @since UNRELEASED + */ +class PVXS_IOC_API TestIOC final { + bool isRunning = false; public: - QSrvWaitForLinkUpdate(struct link *plink); - QSrvWaitForLinkUpdate(const char* pv); - ~QSrvWaitForLinkUpdate(); + TestIOC(); + ~TestIOC(); + //! iocInit() + void init(); + //! iocShutdown() + void shutdown(); + //! between iocInit() and iocShutdown() ? + inline + bool running() const { return isRunning; } }; -PVXS_IOC_API -void testqsrvShutdownOk(void); - -PVXS_IOC_API -void testqsrvCleanup(void); -#endif // PVXS_EXPERT_API_ENABLED +#endif // base >= 3.15 }} // namespace pvxs::ioc #endif // PVXS_IOCHOOKS_H diff --git a/ioc/pvxs3x.dbd b/ioc/pvxs3x.dbd index 6330d4a3e..b5273d354 100644 --- a/ioc/pvxs3x.dbd +++ b/ioc/pvxs3x.dbd @@ -1,6 +1,4 @@ registrar(pvxsBaseRegistrar) -registrar(pvxsSingleSourceRegistrar) -registrar(pvxsGroupSourceRegistrar) # from demo.cpp device(waveform, CONSTANT, devWfPDBQ2Demo, "QSRV2 Demo") diff --git a/ioc/pvxs7x.dbd b/ioc/pvxs7x.dbd index e0ec7a6b3..aa4957652 100644 --- a/ioc/pvxs7x.dbd +++ b/ioc/pvxs7x.dbd @@ -1,7 +1,4 @@ registrar(pvxsBaseRegistrar) -registrar(pvxsSingleSourceRegistrar) -registrar(pvxsGroupSourceRegistrar) -registrar(installPVAAddLinkHook) link("pva", "lsetPVA") # from demo.cpp diff --git a/ioc/qsrvpvt.h b/ioc/qsrvpvt.h new file mode 100644 index 000000000..feaab0403 --- /dev/null +++ b/ioc/qsrvpvt.h @@ -0,0 +1,67 @@ +/* Copyright - See the COPYRIGHT that is included with this distribution. + * pvxs is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ +#ifndef QSRVPVT_H +#define QSRVPVT_H + +#include +#include + +namespace pvxs { +namespace ioc { + +#if EPICS_VERSION_INT >= VERSION_INT(3, 15, 0 ,0) +# define USE_QSRV_SINGLE +void single_enable(); +void dbRegisterQSRV2(); +void addSingleSrc(); +#else +static inline void single_enable() {} +static inline void dbRegisterQSRV2() {} +static inline void addSingleSrc() {} +#endif + +#if EPICS_VERSION_INT >= VERSION_INT(7, 0, 0 ,0) +# define USE_PVA_LINKS +void group_enable(); +void pvalink_enable(); +void processGroups(); +void addGroupSrc(); +void resetGroups(); +#else +static inline void group_enable() {} +static inline void pvalink_enable() {} +static inline void processGroups() {} +static inline void addGroupSrc() {} +static inline void resetGroups() {} +#endif + +#if EPICS_VERSION_INT >= VERSION_INT(7, 0, 4, 0) +# define USE_DEINIT_HOOKS +#endif +#if EPICS_VERSION_INT > VERSION_INT(7, 0, 7, 0) +# define USE_PREPARE_CLEANUP_HOOKS +#endif + +#ifdef USE_PVA_LINKS +// test utilities for PVA links + +PVXS_IOC_API +void testqsrvWaitForLinkConnected(struct link *plink, bool conn=true); +PVXS_IOC_API +void testqsrvWaitForLinkConnected(const char* pv, bool conn=true); + +class PVXS_IOC_API QSrvWaitForLinkUpdate final { + struct link * const plink; + unsigned seq; +public: + QSrvWaitForLinkUpdate(struct link *plink); + QSrvWaitForLinkUpdate(const char* pv); + ~QSrvWaitForLinkUpdate(); +}; +#endif + +}} // namespace pvxs::ioc + +#endif // QSRVPVT_H diff --git a/ioc/singlesourcehooks.cpp b/ioc/singlesourcehooks.cpp index e86a278a8..2dd720ddb 100644 --- a/ioc/singlesourcehooks.cpp +++ b/ioc/singlesourcehooks.cpp @@ -21,6 +21,7 @@ #include #include +#include "qsrvpvt.h" #include "iocshcommand.h" #include "singlesource.h" @@ -142,48 +143,29 @@ dbServer qsrv2Server = { qClient, }; -/** - * Initialise qsrv database single records by adding them as sources in our running pvxs server instance - * - * @param theInitHookState the initHook state - we only want to trigger on the initHookAfterIocBuilt state - ignore all others - */ -void qsrvSingleSourceInit(initHookState theInitHookState) { - if(!IOCSource::enabled()) - return; - if (theInitHookState == initHookAtBeginning) { - (void)dbRegisterServer(&qsrv2Server); - } else - if (theInitHookState == initHookAfterIocBuilt) { - pvxs::ioc::server().addSource("qsrvSingle", std::make_shared(), 0); - } +} // namespace + +namespace pvxs { +namespace ioc { + +void dbRegisterQSRV2() +{ + (void)dbRegisterServer(&qsrv2Server); } -/** - * IOC pvxs Single Source registrar. This implements the required registrar function that is called by xxxx_registerRecordDeviceDriver, - * the auto-generated stub created for all IOC implementations. - * - * It is registered by using the `epicsExportRegistrar()` macro. - * - * 1. Specify here all of the commands that you want to be registered and available in the IOC shell. - * 2. Register your hook handler to handle any state hooks that you want to implement. Here we install - * an `initHookState` handler connected to the `initHookAfterIocBuilt` state. It will add all of the - * single record type sources defined so far. Note that you can define sources up until the `iocInit()` call, - * after which point the `initHookAfterIocBuilt` handlers are called and will register all the defined records. - */ -void pvxsSingleSourceRegistrar() { +void addSingleSrc() +{ + pvxs::ioc::server() + .addSource("qsrvSingle", std::make_shared(), 0); +} + +void single_enable() { // Register commands to be available in the IOC shell IOCShCommand("pvxsl", "details", "List PV names.\n") .implementation<&pvxsl>(); - - initHookRegister(&qsrvSingleSourceInit); } -} // namespace +}} // namespace pvxs::ioc -// in .dbd file -//registrar(pvxsSingleSourceRegistrar) -extern "C" { -epicsExportRegistrar(pvxsSingleSourceRegistrar); -} diff --git a/qsrv/softMain.cpp b/qsrv/softMain.cpp index c5e3d179d..eb98d3231 100644 --- a/qsrv/softMain.cpp +++ b/qsrv/softMain.cpp @@ -141,9 +141,6 @@ int main(int argc, char *argv[]) bool loadedDb = false; bool ranScript = false; - if(!getenv("PVXS_QSRV_ENABLE")) - epicsEnvSet("PVXS_QSRV_ENABLE","YES"); - #if EPICS_VERSION_INT >= VERSION_INT(7, 0, 3, 1) // attempt to compute relative paths { diff --git a/test/testioc.h b/test/testioc.h index 05a5b3a3e..3ed6fb4b3 100644 --- a/test/testioc.h +++ b/test/testioc.h @@ -16,32 +16,6 @@ #include #include -class TestIOC { - bool running = false; -public: - TestIOC() { - testdbPrepare(); - pvxs::ioc::testPrepare(); - } - void init() { - if(!running) { - testIocInitOk(); - running = true; - } - } - void shutdown() { - if(running) { - pvxs::ioc::testShutdown(); - testIocShutdownOk(); - running = false; - } - } - ~TestIOC() { - this->shutdown(); - testdbCleanup(); - } -}; - struct TestClient : pvxs::client::Context { TestClient() : pvxs::client::Context(pvxs::ioc::server().clientConfig().build()) {} diff --git a/test/testpvalink.cpp b/test/testpvalink.cpp index 54fba198e..1f3ea9e9b 100644 --- a/test/testpvalink.cpp +++ b/test/testpvalink.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -19,16 +20,17 @@ #define PVXS_ENABLE_EXPERT_API -//#include -//#include "utilities.h" -#include "dblocker.h" +#include #include -#include "pvxs/iochooks.h" +#include +#include +#include #include #include + +#include "dblocker.h" +#include "qsrvpvt.h" #include "pvalink.h" -#include "testioc.h" -//#include "pv/qsrv.h" using namespace pvxs::ioc; using namespace pvxs; @@ -489,6 +491,7 @@ MAIN(testpvalink) testdbReadDatabase("testpvalink.db", NULL, NULL); IOC.init(); + testGet(); testFieldLinks(); testProc(); @@ -504,9 +507,6 @@ MAIN(testpvalink) testFwd(); testAtomic(); testEnum(); - testqsrvShutdownOk(); - IOC.shutdown(); - testqsrvCleanup(); } catch (std::exception &e) { diff --git a/test/testqgroup.cpp b/test/testqgroup.cpp index b5741f923..08d7e7a2c 100644 --- a/test/testqgroup.cpp +++ b/test/testqgroup.cpp @@ -722,7 +722,7 @@ MAIN(testqgroup) testPlan(37); testSetup(); { - TestIOC ioc; + ioc::TestIOC ioc; asSetFilename("../testioc.acf"); generalTimeRegisterCurrentProvider("test", 1, &testTimeCurrent); testdbReadDatabase("testioc.dbd", nullptr, nullptr); diff --git a/test/testqsingle.cpp b/test/testqsingle.cpp index b65f37b11..201b9b07b 100644 --- a/test/testqsingle.cpp +++ b/test/testqsingle.cpp @@ -881,13 +881,27 @@ void testMonitorAIFilt(TestClient& ctxt) MAIN(testqsingle) { - testPlan(87); + testPlan(88); testSetup(); pvxs::logger_config_env(); + generalTimeRegisterCurrentProvider("test", 1, &testTimeCurrent); +#if EPICS_VERSION_INT>=VERSION_INT(7, 0, 0, 0) + // start up once to check shutdown and re-start { - TestIOC ioc; + ioc::TestIOC ioc; + testdbReadDatabase("testioc.dbd", nullptr, nullptr); + testOk1(!testioc_registerRecordDeviceDriver(pdbbase)); + testdbReadDatabase("testqsingle.db", nullptr, nullptr); + ioc.init(); + } +#else + // eg. arrInitialize() had a local "firstTime" flag + testSkip(1, "test ioc reinit did not work yet..."); +#endif + { + ioc::TestIOC ioc; + // https://github.com/epics-base/epics-base/issues/438 asSetFilename("../testioc.acf"); - generalTimeRegisterCurrentProvider("test", 1, &testTimeCurrent); testdbReadDatabase("testioc.dbd", nullptr, nullptr); testOk1(!testioc_registerRecordDeviceDriver(pdbbase)); testdbReadDatabase("testqsingle.db", nullptr, nullptr);