diff --git a/.ci-local/cdt-check.sh b/.ci-local/cdt-check.sh index cee56c5..5afc686 100755 --- a/.ci-local/cdt-check.sh +++ b/.ci-local/cdt-check.sh @@ -5,7 +5,7 @@ set -e ret=0 echo "Checking for GCC style static constructors or destructors" -for ff in src/O.*/*.o +for ff in src/O.*/*.o ioc/O.*/*.o do if nm "$ff" | grep __static_initialization_and_destruction then diff --git a/Makefile b/Makefile index 28165b5..609c46e 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,11 @@ src_DEPEND_DIRS = configure DIRS += tools tools_DEPEND_DIRS = src +DIRS += ioc +ioc_DEPEND_DIRS = src + DIRS += test -test_DEPEND_DIRS = src +test_DEPEND_DIRS = src ioc DIRS += example example_DEPEND_DIRS = src diff --git a/ioc/Makefile b/ioc/Makefile new file mode 100644 index 0000000..f2e64b8 --- /dev/null +++ b/ioc/Makefile @@ -0,0 +1,35 @@ +TOP=.. + +include $(TOP)/configure/CONFIG +# cfg/ sometimes isn't correctly included due to a Base bug +# so we do here (maybe again) as workaround +include $(TOP)/configure/CONFIG_PVXS_MODULE +include $(TOP)/configure/CONFIG_PVXS_VERSION +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE +#============================= + +# access to private headers +USR_CPPFLAGS += -I$(TOP)/src +USR_CPPFLAGS += -DPVXS_IOC_API_BUILDING + +DBD += pvxsIoc.dbd + +INC += pvxs/iochooks.h + +LIBRARY += pvxsIoc + +SHRLIB_VERSION = $(PVXS_MAJOR_VERSION).$(PVXS_MINOR_VERSION) + +pvxsIoc_SRCS += iochooks.cpp + +LIB_LIBS += pvxs +LIB_LIBS += $(EPICS_BASE_IOC_LIBS) + +#=========================== + +include $(TOP)/configure/RULES +include $(TOP)/configure/RULES_PVXS_MODULE +#---------------------------------------- +# ADD RULES AFTER THIS LINE + diff --git a/ioc/iochooks.cpp b/ioc/iochooks.cpp new file mode 100644 index 0000000..ae1a4e6 --- /dev/null +++ b/ioc/iochooks.cpp @@ -0,0 +1,244 @@ +/** + * 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. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace pvxs; + +namespace { +std::atomic instance{}; + +DEFINE_LOGGER(log, "pvxs.ioc"); + +void pvxsl(int detail) +{ + try { + if(auto serv = instance.load()) { + for(auto& pair : serv->listSource()) { + auto src = serv->getSource(pair.first); + if(!src) + continue; // race? + + auto list = src->onList(); + + if(detail>0) + printf("# Source %s@%d%s\n", + pair.first.c_str(), pair.second, + list.dynamic ? " [dynamic]":""); + + if(!list.names) { + if(detail>0) + printf("# no PVs\n"); + } else { + for(auto& name : *list.names) { + printf("%s\n", name.c_str()); + } + } + } + } + } catch(std::exception& e) { + fprintf(stderr, "Error in %s : %s\n", __func__, e.what()); + } +} + +void pvxsr(int detail) +{ + try { + if(auto serv = instance.load()) { + // TODO + (void)serv; + } + } catch(std::exception& e) { + fprintf(stderr, "Error in %s : %s\n", __func__, e.what()); + } +} + +template +struct index_sequence {}; + +template +struct next_index_sequence {}; + +template +struct next_index_sequence> +{ + typedef index_sequence type; +}; + +template +struct build_index_sequence +{ + typedef typename build_index_sequence::type type; +}; + +template +struct build_index_sequence +{ + typedef index_sequence type; +}; + +template +using make_index_sequence = typename build_index_sequence<0, sizeof...(Args)>::type; + +template +struct Arg; + +template<> +struct Arg { + static constexpr iocshArgType code = iocshArgInt; + static int get(const iocshArgBuf& buf) { return buf.ival; } +}; + +template<> +struct Arg { + static constexpr iocshArgType code = iocshArgDouble; + static double get(const iocshArgBuf& buf) { return buf.dval; } +}; + +template<> +struct Arg { + static constexpr iocshArgType code = iocshArgString; + static const char* get(const iocshArgBuf& buf) { return buf.sval; } +}; + +template +struct ToStr { typedef const char* type; }; + +template +struct Reg { + const char* const name; + const char* const argnames[sizeof...(Args)]; + + constexpr explicit Reg(const char* name, typename ToStr::type... descs) + :name(name) + ,argnames{descs...} + {} + + template + static + void call(const iocshArgBuf* args) + { + (*fn)(Arg::get(args[Idxs])...); + } + + template + void doit(index_sequence) + { + static const iocshArg args[sizeof...(Args)] = {{argnames[Idxs], Arg::code}...}; + static const iocshFuncDef def = {name, sizeof...(Args), (const iocshArg* const*)&args}; + + iocshRegister(&def, &call); + } + + template + void ister() + { + doit(make_index_sequence{}); + } +}; + +void pvxsAtExit(void* unused) +{ + try { + if(auto serv = instance.load()) { + if(instance.compare_exchange_strong(serv, nullptr)) { + // take ownership + std::unique_ptr trash(serv); + trash->stop(); + log_debug_printf(log, "Stopped Server?%s", "\n"); + } + } + } catch(std::exception& e) { + fprintf(stderr, "Error in %s : %s\n", __func__, e.what()); + } +} + +void pvxsInitHook(initHookState state) +{ + try { + // iocBuild() + if(state==initHookAfterInitDatabase) { + // we want to run before exitDatabase + epicsAtExit(&pvxsAtExit, nullptr); + } + // iocRun()/iocPause() + if(state==initHookAfterCaServerRunning) { + if(auto serv = instance.load()) { + serv->start(); + log_debug_printf(log, "Started Server %p", serv); + } + } + if(state==initHookAfterCaServerPaused) { + if(auto serv = instance.load()) { + serv->stop(); + log_debug_printf(log, "Stopped Server %p", serv); + } + } + } catch(std::exception& e) { + fprintf(stderr, "Error in %s : %s\n", __func__, e.what()); + } +} + +void pvxsRegistrar() +{ + try { + pvxs::logger_config_env(); + + Reg("pvxsl", "detail").ister<&pvxsl>(); + Reg("pvxsr", "detail").ister<&pvxsr>(); + + auto serv = instance.load(); + if(!serv) { + std::unique_ptr temp(new server::Server(server::Config::from_env())); + + if(instance.compare_exchange_strong(serv, temp.get())) { + log_debug_printf(log, "Installing Server %p\n", temp.get()); + temp.release(); + } else { + log_crit_printf(log, "Race installing Server? %p\n", serv); + } + } else { + log_err_printf(log, "Stale Server? %p\n", serv); + } + + initHookRegister(&pvxsInitHook); + } catch(std::exception& e) { + fprintf(stderr, "Error in %s : %s\n", __func__, e.what()); + } +} + +} // namesapce + +namespace pvxs { +namespace ioc { + +server::Server server() +{ + if(auto serv = instance.load()) { + return *serv; + } else { + throw std::logic_error("No Instance"); + } +} + +}} // namespace pvxs::ioc + +extern "C" { +epicsExportRegistrar(pvxsRegistrar); +} diff --git a/ioc/pvxs/iochooks.h b/ioc/pvxs/iochooks.h new file mode 100644 index 0000000..da3bd5d --- /dev/null +++ b/ioc/pvxs/iochooks.h @@ -0,0 +1,56 @@ +/** + * 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 PVXS_IOCHOOKS_H +#define PVXS_IOCHOOKS_H + +#include + +#if defined(_WIN32) || defined(__CYGWIN__) + +# if defined(PVXS_IOC_API_BUILDING) && defined(EPICS_BUILD_DLL) +/* building library as dll */ +# define PVXS_IOC_API __declspec(dllexport) +# elif !defined(PVXS_IOC_API_BUILDING) && defined(EPICS_CALL_DLL) +/* calling library in dll form */ +# define PVXS_IOC_API __declspec(dllimport) +# endif + +#elif __GNUC__ >= 4 +# define PVXS_IOC_API __attribute__ ((visibility("default"))) +#endif + +#ifndef PVXS_IOC_API +# define PVXS_IOC_API +#endif + + +namespace pvxs { +namespace server { +class Server; +} +namespace ioc { + +/** Return the singleton Server instance which is setup + * for use in an IOC process. + * + * This Server instance is created during in a registrar function, + * started by the initHookAfterCaServerRunning phase of iocInit(), + * and stopped and destroyed via an epicsAtExit() hook. + * + * Any configuration changes via. epicsEnvSet() must be made before registrars are run + * by \*_registerRecordDeviceDriver(pdbbase). + * + * server::SharedPV and server::Source added before iocInit() will be available immediately. + * Others may be added (or removed) later. + * + * @throws std::logic_error if called before instance is created, or after instance is destroyed. + */ +PVXS_IOC_API +server::Server server(); + +}} // namespace pvxs::ioc + +#endif // PVXS_IOCHOOKS_H diff --git a/ioc/pvxsIoc.dbd b/ioc/pvxsIoc.dbd new file mode 100644 index 0000000..fad48df --- /dev/null +++ b/ioc/pvxsIoc.dbd @@ -0,0 +1 @@ +registrar(pvxsRegistrar) diff --git a/test/Makefile b/test/Makefile index 9f08a29..703ae43 100644 --- a/test/Makefile +++ b/test/Makefile @@ -12,7 +12,7 @@ include $(TOP)/configure/CONFIG_PVXS_VERSION # access to private headers USR_CPPFLAGS += -I$(TOP)/src -PROD_LIBS += pvxs Com +PROD_LIBS = pvxs Com TESTPROD_HOST += testsock testsock_SRCS += testsock.cpp @@ -78,6 +78,20 @@ TESTPROD_HOST += testrpc testrpc_SRCS += testrpc.cpp TESTS += testrpc +ifdef BASE_3_15 + +DBDDEPENDS_FILES += testioc.dbd$(DEP) +testioc_DBD = base.dbd pvxsIoc.dbd +TESTFILES += $(COMMON_DIR)/testioc.dbd + +TESTPROD_HOST += testioc +testioc_SRCS += testioc.cpp +testioc_SRCS += testioc_registerRecordDeviceDriver.cpp +testioc_LIBS = pvxsIoc pvxs $(EPICS_BASE_IOC_LIBS) +TESTS += testioc + +endif + TESTPROD_HOST += mcat mcat_SRCS += mcat.cpp # not a unittest diff --git a/test/testioc.cpp b/test/testioc.cpp new file mode 100644 index 0000000..1b82659 --- /dev/null +++ b/test/testioc.cpp @@ -0,0 +1,49 @@ +/** + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include + +extern "C" { +extern int testioc_registerRecordDeviceDriver(struct dbBase*); +} + +using namespace pvxs; + +namespace { + +} // namespace + +MAIN(testioc) +{ + testPlan(3); + testSetup(); + + testdbPrepare(); + + testThrows([]{ + ioc::server(); + }); + + testdbReadDatabase("testioc.dbd", nullptr, nullptr); + testEq(0, testioc_registerRecordDeviceDriver(pdbbase)); + + testTrue(!!ioc::server()); + + testIocInitOk(); + + testIocShutdownOk(); + + testdbCleanup(); + + return testDone(); +}