ioc: combine registrars and detect QSRV1

also consolidates initHook and epicsAtExit() hooks
into a single sequence.
This commit is contained in:
Michael Davidsaver
2023-10-31 09:49:37 -07:00
parent 6d1216daad
commit eddc687021
20 changed files with 516 additions and 425 deletions
+36 -60
View File
@@ -20,6 +20,7 @@
#include <pvxs/source.h>
#include <pvxs/iochooks.h>
#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<epicsMutex> 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<epicsMutex> 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();
// Resolve triggers
processor.resolveTriggerReferences();
// 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<pvxs::ioc::GroupSource>(), 1);
}
} catch(std::exception& e) {
fprintf(stderr, "ERROR: Unhandled exception in %s(%d): %s\n",
__func__, theInitHookState, e.what());
}
// Create Server Groups
processor.createGroups();
}
/**
* 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.
*<p>
* It is registered by using the `epicsExportRegistrar()` macro.
*<p>
* 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 addGroupSrc()
{
pvxs::ioc::server()
.addSource("qsrvGroup", std::make_shared<pvxs::ioc::GroupSource>(), 1);
}
void resetGroups()
{
auto& config(IOCGroupConfig::instance());
// server stopped at this point, but lock anyway
epicsGuard<epicsMutex> G(config.groupMapMutex);
config.groupMap.clear();
config.groupConfigFiles.clear();
}
void group_enable() {
// Register commands to be available in the IOC shell
IOCShCommand<int, const char*>("pvxgl", "[level, [pattern]]",
"Group Sources list.\n"
@@ -232,14 +216,6 @@ void pvxsGroupSourceRegistrar() {
IOCShCommand<const char*, const char*>("dbLoadGroup",
"JSON file", "macros", dbLoadGroupMsg)
.implementation<&dbLoadGroupCmd>();
initHookRegister(&qsrvGroupSourceInit);
}
} // namespace
// in .dbd file
//registrar(pvxsGroupSourceRegistrar)
extern "C" {
epicsExportRegistrar(pvxsGroupSourceRegistrar);
}
}} // namespace pvxs::ioc
+199 -39
View File
@@ -18,8 +18,12 @@
#include <epicsExport.h>
#include <epicsExit.h>
#include <epicsString.h>
#include <initHooks.h>
#include <iocsh.h>
#include <dbAccess.h>
#include <dbStaticLib.h>
#include <registryDeviceSupport.h>
#include <pvxs/iochooks.h>
#include <pvxs/log.h>
@@ -28,14 +32,18 @@
#include "iocshcommand.h"
#include "utilpvt.h"
#include "qsrvpvt.h"
#ifdef USE_QSRV_SINGLE
# include <dbUnitTest.h>
#endif
#ifdef USE_PVA_LINKS
# include "pvalink.h"
#endif
// include last to avoid clash of #define printf with other headers
#include <epicsStdio.h>
#if EPICS_VERSION_INT >= VERSION_INT(7, 0, 4, 0)
# define USE_DEINIT_HOOKS
#endif
namespace pvxs {
namespace ioc {
@@ -80,29 +88,24 @@ void initialisePvxsServer() {
using namespace pvxs::server;
Config conf = ::pvxs::impl::inUnitTest() ? Config::isolated() : Config::from_env();
Server newsrv(conf);
threadOnce<&pvxServerInit>();
Guard G(pvxServer->lock);
if(pvxServer->srv)
throw std::logic_error(SB()<<__func__<<" found existing server?!?");
pvxServer->srv = std::move(newsrv);
if(!pvxServer->srv) {
pvxServer->srv = Server(conf);
}
}
/**
* 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();
assert(!pvxServer->srv);
srv.stop();
IOCGroupConfigCleanup();
log_debug_printf(_logname, "Stopped Server%s", "\n");
@@ -112,19 +115,56 @@ void pvxsAtExit(void*) noexcept {
}
}
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()
{
initialisePvxsServer(); // re-create server for next test cycle
}
void testPrepare()
{
if(pvxServer)
initialisePvxsServer(); // re-create server for next test cycle
#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 +205,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 +305,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) {
case initHookAfterInitDatabase:
// when de-init hooks not available, register for later cleanup via atexit()
// function to run before exitDatabase
#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
epicsAtExit(&pvxsAtExit, nullptr);
// 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 initHookAfterCaServerRunning:
case initHookAfterInitDatabase:
processGroups();
#ifndef USE_DEINIT_HOOKS
// 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 initHookAfterIocRunning:
if(auto srv = server()) {
srv.start();
@@ -271,7 +367,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 +390,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,11 +450,11 @@ 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();
pvxServer = new pvxServer_t();
bool enableQ = enable2();
IOCShCommand<int>("pvxsr", "[show_detailed_information?]", "PVXS Server Report. "
"Shows information about server config (level==0)\n"
@@ -320,6 +474,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());
}
-40
View File
@@ -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<int> 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) {
-2
View File
@@ -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,
+58 -142
View File
@@ -36,6 +36,7 @@
#include "dbentry.h"
#include "iocshcommand.h"
#include "utilpvt.h"
#include "qsrvpvt.h"
#include <epicsStdio.h> /* redirects stdout/stderr; include after util.h from libevent */
#include <epicsExport.h> /* defines epicsExportSharedSymbols */
@@ -44,147 +45,58 @@
# define HAVE_SHUTDOWN_HOOKS
#endif
namespace pvxs { namespace ioc {
namespace pvxs {
namespace ioc {
using namespace pvxlink;
void linkGlobal_t::alloc()
{
if(linkGlobal) {
cantProceed("# Missing call to testqsrvShutdownOk() and/or testqsrvCleanup()");
}
linkGlobal = new linkGlobal_t;
namespace {
// TODO "local" provider
if (inUnitTest()) {
linkGlobal->provider_remote = ioc::server().clientConfig().build();
} else {
linkGlobal->provider_remote = client::Config().build();
}
}
// halt, and clear, scan workers before dbCloseLinks() (cf. iocShutdown())
static void shutdownStep1()
void linkGlobal_t::init()
{
Guard G(linkGlobal->lock);
linkGlobal->running = true;
for(linkGlobal_t::channels_t::iterator it(linkGlobal->channels.begin()), end(linkGlobal->channels.end());
it != end; ++it)
{
std::shared_ptr<pvaLinkChannel> chan(it->second.lock());
if(!chan) continue;
chan->open();
}
}
void linkGlobal_t::deinit()
{
// no locking here as we assume that shutdown doesn't race startup
if(!pvaGlobal) return;
if(!linkGlobal) return;
pvaGlobal->close();
linkGlobal->close();
}
// 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::dtor()
{
if(!pvaGlobal) return;
if(!linkGlobal) return;
{
Guard G(pvaGlobal->lock);
Guard G(linkGlobal->lock);
assert(pvaLink::cnt_pvaLink<=1u); // dbRemoveLink() already called
assert(pvaGlobal->channels.empty());
assert(linkGlobal->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());
}
}
static void finalizePVA(void*)
{
try {
shutdownStep2();
}catch(std::exception& e){
fprintf(stderr, "Error initializing pva link handling : %s\n", e.what());
}
}
#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)
{
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<pvaLinkChannel> chan(it->second.lock());
if(!chan) continue;
chan->open();
}
#ifdef HAVE_SHUTDOWN_HOOKS
} else if(state==initHookAtShutdown) {
shutdownStep1();
} else if(state==initHookAfterShutdown) {
shutdownStep2();
#endif
}
}catch(std::exception& e){
cantProceed("Error initializing pva link handling : %s\n", e.what());
}
}
} // namespace
// halt, and clear, scan workers before dbCloseLinks() (cf. iocShutdown())
void testqsrvShutdownOk(void)
{
try {
shutdownStep1();
}catch(std::exception& e){
testAbort("Error while stopping PVA link pool : %s\n", e.what());
}
}
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 +131,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<pvaLinkChannel> lchan(testGetPVALink(plink));
Guard G(lchan->lock);
while(lchan->connected!=conn) {
@@ -273,7 +185,7 @@ extern "C"
void dbpvar(const char *precordname, int level)
{
try {
if(!pvaGlobal) {
if(!linkGlobal) {
printf("PVA links not initialized\n");
return;
}
@@ -287,13 +199,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<pvaLinkChannel> chan(it->second.lock());
@@ -404,17 +316,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<const char*, int>("dbpvar", "dbpvar", "record name", "level")
.implementation<&dbpvar>();
iocshRegisterVariable(pvaLinkNWorkersDef);
}
}} // namespace pvxs::ioc
extern "C" {
using pvxs::ioc::installPVAAddLinkHook;
epicsExportRegistrar(installPVAAddLinkHook);
epicsExportAddress(int, pvaLinkNWorkers);
}
+17 -11
View File
@@ -50,8 +50,8 @@ extern "C" {
extern int pvaLinkNWorkers;
}
namespace pvxlink {
using namespace pvxs;
namespace pvxs {
namespace ioc {
typedef epicsGuard<epicsMutex> Guard;
typedef epicsGuardRelease<epicsMutex> 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<std::weak_ptr<epicsThreadRunable>> queue;
@@ -128,18 +128,24 @@ private:
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<pvaLinkChannel>
{
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();
@@ -262,6 +268,6 @@ struct pvaLink final : public pvaLinkConfig
};
} // namespace pvalink
}} // namespace pvxs::ioc
#endif // PVALINK_H
+18 -18
View File
@@ -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
{
@@ -433,7 +433,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
+4 -3
View File
@@ -11,7 +11,8 @@
#include <epicsStdio.h> // redirects stdout/stderr
#include <epicsExport.h>
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);
}
+3 -2
View File
@@ -14,7 +14,8 @@
DEFINE_LOGGER(_logger, "pvxs.ioc.link.link");
namespace pvxlink {
namespace pvxs {
namespace ioc {
pvaLink::pvaLink()
{
@@ -131,4 +132,4 @@ pvaLink::scanOnUpdate_t pvaLink::scanOnUpdate() const
return scanOnUpdateNo;
}
} // namespace pvalink
}} // namespace pvxs::ioc
+9 -9
View File
@@ -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<pvaLink*>(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()<<pvRequest.format()));
linkGlobal_t::channels_key_t key = std::make_pair(self->channelName, std::string(SB()<<pvRequest.format()));
std::shared_ptr<pvaLinkChannel> 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) {
@@ -704,4 +704,4 @@ lset pva_lset = {
#endif
};
} // namespace pvxlink
}} // namespace pvxs::ioc
+60 -16
View File
@@ -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);
PVXS_IOC_API
void testqsrvWaitForLinkConnected(const char* pv, bool conn=true);
void testAfterShutdown();
class PVXS_IOC_API QSrvWaitForLinkUpdate final {
struct link * const plink;
unsigned seq;
/** Call just before testdbCleanup()
* @since UNRELEASED
*/
PVXS_IOC_API
void testCleanupPrepare();
#if EPICS_VERSION_INT >= VERSION_INT(3, 15, 0 ,0)
/** 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
-2
View File
@@ -1,6 +1,4 @@
registrar(pvxsBaseRegistrar)
registrar(pvxsSingleSourceRegistrar)
registrar(pvxsGroupSourceRegistrar)
# from demo.cpp
device(waveform, CONSTANT, devWfPDBQ2Demo, "QSRV2 Demo")
-3
View File
@@ -1,7 +1,4 @@
registrar(pvxsBaseRegistrar)
registrar(pvxsSingleSourceRegistrar)
registrar(pvxsGroupSourceRegistrar)
registrar(installPVAAddLinkHook)
link("pva", "lsetPVA")
# from demo.cpp
+67
View File
@@ -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 <pvxs/version.h>
#include <pvxs/iochooks.h>
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
+17 -35
View File
@@ -21,6 +21,7 @@
#include <pvxs/server.h>
#include <pvxs/iochooks.h>
#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<pvxs::ioc::SingleSource>(), 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<pvxs::ioc::SingleSource>(), 0);
}
void single_enable() {
// Register commands to be available in the IOC shell
IOCShCommand<int>("pvxsl", "details",
"List PV names.\n")
.implementation<&pvxsl>();
initHookRegister(&qsrvSingleSourceInit);
}
} // namespace
}} // namespace pvxs::ioc
// in .dbd file
//registrar(pvxsSingleSourceRegistrar)
extern "C" {
epicsExportRegistrar(pvxsSingleSourceRegistrar);
}
-3
View File
@@ -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
{
-26
View File
@@ -16,32 +16,6 @@
#include <dbUnitTest.h>
#include <dbChannel.h>
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()) {}
+9 -9
View File
@@ -8,6 +8,7 @@
#include <epicsExit.h>
#include <dbLock.h>
#include <dbLink.h>
#include <dbUnitTest.h>
#include <aiRecord.h>
#include <aaoRecord.h>
#include <aaiRecord.h>
@@ -19,16 +20,17 @@
#define PVXS_ENABLE_EXPERT_API
//#include <pv/qsrv.h>
//#include "utilities.h"
#include "dblocker.h"
#include <pvxs/log.h>
#include <pvxs/client.h>
#include "pvxs/iochooks.h"
#include <pvxs/server.h>
#include <pvxs/iochooks.h>
#include <pvxs/unittest.h>
#include <pvxs/nt.h>
#include <pvxs/sharedpv.h>
#include "dblocker.h"
#include "qsrvpvt.h"
#include "pvalink.h"
#include "testioc.h"
//#include "pv/qsrv.h"
using namespace pvxs::ioc;
using namespace pvxs;
@@ -494,6 +496,7 @@ MAIN(testpvalink)
testdbReadDatabase("testpvalink.db", NULL, NULL);
IOC.init();
testGet();
testFieldLinks();
testProc();
@@ -509,9 +512,6 @@ MAIN(testpvalink)
testFwd();
testAtomic();
testEnum();
testqsrvShutdownOk();
IOC.shutdown();
testqsrvCleanup();
}
catch (std::exception &e)
{
+2 -2
View File
@@ -722,9 +722,9 @@ MAIN(testqgroup)
testPlan(37);
testSetup();
{
TestIOC ioc;
asSetFilename("../testioc.acf");
generalTimeRegisterCurrentProvider("test", 1, &testTimeCurrent);
ioc::TestIOC ioc;
asSetFilename("../testioc.acf");
testdbReadDatabase("testioc.dbd", nullptr, nullptr);
testOk1(!testioc_registerRecordDeviceDriver(pdbbase));
testdbReadDatabase("image.db", nullptr, "N=img");
+17 -3
View File
@@ -878,13 +878,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);