ioc: combine registrars and detect QSRV1
also consolidates initHook and epicsAtExit() hooks into a single sequence.
This commit is contained in:
+36
-60
@@ -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
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
registrar(pvxsBaseRegistrar)
|
||||
registrar(pvxsSingleSourceRegistrar)
|
||||
registrar(pvxsGroupSourceRegistrar)
|
||||
|
||||
# from demo.cpp
|
||||
device(waveform, CONSTANT, devWfPDBQ2Demo, "QSRV2 Demo")
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
registrar(pvxsBaseRegistrar)
|
||||
registrar(pvxsSingleSourceRegistrar)
|
||||
registrar(pvxsGroupSourceRegistrar)
|
||||
registrar(installPVAAddLinkHook)
|
||||
link("pva", "lsetPVA")
|
||||
|
||||
# from demo.cpp
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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()) {}
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user