From 688c6f8da30b298bdacaa87fff1ab849a660ece4 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 17 Aug 2023 09:18:54 -0700 Subject: [PATCH 1/5] pvalink: import existing from pva2pva f1a3db44158a239a44d14b99b7823f340e95d7e0 --- ioc/Makefile | 6 + ioc/pvalink.cpp | 352 +++++++++++++++++++++++++++ ioc/pvalink.h | 253 +++++++++++++++++++ ioc/pvalink_channel.cpp | 435 +++++++++++++++++++++++++++++++++ ioc/pvalink_jlif.cpp | 321 ++++++++++++++++++++++++ ioc/pvalink_link.cpp | 156 ++++++++++++ ioc/pvalink_lset.cpp | 524 ++++++++++++++++++++++++++++++++++++++++ ioc/pvalink_null.cpp | 16 ++ 8 files changed, 2063 insertions(+) create mode 100644 ioc/pvalink.cpp create mode 100644 ioc/pvalink.h create mode 100644 ioc/pvalink_channel.cpp create mode 100644 ioc/pvalink_jlif.cpp create mode 100644 ioc/pvalink_link.cpp create mode 100644 ioc/pvalink_lset.cpp create mode 100644 ioc/pvalink_null.cpp diff --git a/ioc/Makefile b/ioc/Makefile index 4c0cea824..225566e61 100644 --- a/ioc/Makefile +++ b/ioc/Makefile @@ -57,10 +57,16 @@ pvxsIoc_SRCS += groupconfigprocessor.cpp pvxsIoc_SRCS += groupprocessorcontext.cpp pvxsIoc_SRCS += groupsource.cpp pvxsIoc_SRCS += groupsourcehooks.cpp +pvxsIoc_SRCS += pvalink.cpp +pvxsIoc_SRCS += pvalink_channel.cpp +pvxsIoc_SRCS += pvalink_jlif.cpp +pvxsIoc_SRCS += pvalink_link.cpp +pvxsIoc_SRCS += pvalink_lset.cpp else # BASE_7_0 pvxsIoc_SRCS += dummygroup.cpp +pvxsIoc_SRCS += pvalink_null.cpp endif # BASE_7_0 diff --git a/ioc/pvalink.cpp b/ioc/pvalink.cpp new file mode 100644 index 000000000..48f9fa782 --- /dev/null +++ b/ioc/pvalink.cpp @@ -0,0 +1,352 @@ + +#include +#include + +#define EPICS_DBCA_PRIVATE_API +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* redirects stdout/stderr */ + +#include +#include +#include +#include +#include + +#include "pv/qsrv.h" +#include "helper.h" +#include "pvif.h" +#include "pvalink.h" + +#include /* defines epicsExportSharedSymbols */ + +#if EPICS_VERSION_INT>=VERSION_INT(7,0,6,0) +# define HAVE_SHUTDOWN_HOOKS +#endif + +int pvaLinkDebug; +int pvaLinkIsolate; + +using namespace pvalink; + +namespace { + +// halt, and clear, scan workers before dbCloseLinks() (cf. iocShutdown()) +static void shutdownStep1() +{ + // no locking here as we assume that shutdown doesn't race startup + if(!pvaGlobal) return; + + pvaGlobal->queue.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() +{ + if(!pvaGlobal) return; + + { + Guard G(pvaGlobal->lock); + if(pvaGlobal->channels.size()) { + fprintf(stderr, "pvaLink leaves %zu channels open\n", + pvaGlobal->channels.size()); + } + } + + 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) { + pvac::ClientProvider local("server:QSRV"), + remote("pva"); + pvaGlobal->provider_local = local; + pvaGlobal->provider_remote = remote; + + } 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::tr1::shared_ptr 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()); + } +} + +void testqsrvWaitForLinkEvent(struct link *plink) +{ + std::tr1::shared_ptr lchan; + { + DBScanLocker lock(plink->precord); + + if(plink->type!=JSON_LINK || !plink->value.json.jlink || plink->value.json.jlink->pif!=&lsetPVA) { + testAbort("Not a PVA link"); + } + pvaLink *pval = static_cast(plink->value.json.jlink); + lchan = pval->lchan; + } + if(lchan) { + lchan->run_done.wait(); + } +} + +extern "C" +void dbpvar(const char *precordname, int level) +{ + try { + if(!pvaGlobal) { + printf("PVA links not initialized\n"); + return; + } + + if (!precordname || precordname[0] == '\0' || !strcmp(precordname, "*")) { + precordname = NULL; + printf("PVA links in all records\n\n"); + } else { + printf("PVA links in record named '%s'\n\n", precordname); + } + + size_t nchans = 0, nlinks = 0, nconn = 0; + + pvaGlobal_t::channels_t channels; + { + Guard G(pvaGlobal->lock); + channels = pvaGlobal->channels; // copy snapshot + } + + for(pvaGlobal_t::channels_t::const_iterator it(channels.begin()), end(channels.end()); + it != end; ++it) + { + std::tr1::shared_ptr chan(it->second.lock()); + if(!chan) continue; + + Guard G(chan->lock); + + if(precordname) { + // only show links fields of these records + bool match = false; + for(pvaLinkChannel::links_t::const_iterator it2(chan->links.begin()), end2(chan->links.end()); + it2 != end2; ++it2) + { + const pvaLink *pval = *it2; + // plink==NULL shouldn't happen, but we are called for debugging, so be paranoid. + if(pval->plink && epicsStrGlobMatch(pval->plink->precord->name, precordname)) { + match = true; + nlinks++; + } + } + if(!match) + continue; + } + + nchans++; + if(chan->connected_latched) + nconn++; + + if(!precordname) + nlinks += chan->links.size(); + + if(level<=0) + continue; + + if(level>=2 || (!chan->connected_latched && level==1)) { + if(chan->key.first.size()<=28) { + printf("%28s ", chan->key.first.c_str()); + } else { + printf("%s\t", chan->key.first.c_str()); + } + + printf("conn=%c %zu disconnects, %zu type changes", + chan->connected_latched?'T':'F', + chan->num_disconnect, + chan->num_type_change); + if(chan->op_put.valid()) { + printf(" Put"); + } + + if(level>=3) { + printf(", provider '%s'", chan->providerName.c_str()); + } + printf("\n"); + // level 4 reserved for channel/provider details + + if(level>=5) { + for(pvaLinkChannel::links_t::const_iterator it2(chan->links.begin()), end2(chan->links.end()); + it2 != end2; ++it2) + { + const pvaLink *pval = *it2; + + if(!pval->plink) + continue; + else if(precordname && !epicsStrGlobMatch(pval->plink->precord->name, precordname)) + continue; + + const char *fldname = "???"; + pdbRecordIterator rec(pval->plink->precord); + for(bool done = !!dbFirstField(&rec.ent, 0); !done; done = !!dbNextField(&rec.ent, 0)) + { + if(rec.ent.pfield == (void*)pval->plink) { + fldname = rec.ent.pflddes->name; + break; + } + } + + printf("%*s%s.%s", 30, "", pval->plink ? pval->plink->precord->name : "", fldname); + + switch(pval->pp) { + case pvaLinkConfig::NPP: printf(" NPP"); break; + case pvaLinkConfig::Default: printf(" Def"); break; + case pvaLinkConfig::PP: printf(" PP"); break; + case pvaLinkConfig::CP: printf(" CP"); break; + case pvaLinkConfig::CPP: printf(" CPP"); break; + } + switch(pval->ms) { + case pvaLinkConfig::NMS: printf(" NMS"); break; + case pvaLinkConfig::MS: printf(" MS"); break; + case pvaLinkConfig::MSI: printf(" MSI"); break; + } + + printf(" Q=%u pipe=%c defer=%c time=%c retry=%c morder=%d\n", + unsigned(pval->queueSize), + pval->pipeline ? 'T' : 'F', + pval->defer ? 'T' : 'F', + pval->time ? 'T' : 'F', + pval->retry ? 'T' : 'F', + pval->monorder); + } + printf("\n"); + } + } + } + + printf(" %zu/%zu channels connected used by %zu links\n", + nconn, nchans, nlinks); + + } catch(std::exception& e) { + fprintf(stderr, "Error: %s\n", e.what()); + } +} + +static +void installPVAAddLinkHook() +{ + initHookRegister(&initPVALink); + epics::iocshRegister("dbpvar", "record name", "level"); + epics::registerRefCounter("pvaLinkChannel", &pvaLinkChannel::num_instances); + epics::registerRefCounter("pvaLink", &pvaLink::num_instances); +} + +extern "C" { + epicsExportRegistrar(installPVAAddLinkHook); + epicsExportAddress(jlif, lsetPVA); + epicsExportAddress(int, pvaLinkDebug); + epicsExportAddress(int, pvaLinkNWorkers); +} diff --git a/ioc/pvalink.h b/ioc/pvalink.h new file mode 100644 index 000000000..77141fd34 --- /dev/null +++ b/ioc/pvalink.h @@ -0,0 +1,253 @@ +#ifndef PVALINK_H +#define PVALINK_H + +#include +#include + +#define EPICS_DBCA_PRIVATE_API +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "helper.h" +#include "pvif.h" +#include "tpool.h" + +extern "C" { + QSRV_API extern int pvaLinkDebug; + QSRV_API extern int pvaLinkIsolate; + QSRV_API extern int pvaLinkNWorkers; +} + +#if 0 +# define TRACE(X) std::cerr<<"PVAL "<<__func__<<" " X <<"\n" +#else +# define TRACE(X) do {} while(0) +#endif + +// pvaLink and pvaLinkChannel have ->debug +#define DEBUG(OBJ, X) do{ if((OBJ)->debug) std::cout X<<"\n"; }while(0) + +namespace pvalink { + +namespace pvd = epics::pvData; +namespace pva = epics::pvAccess; + +typedef epicsGuard Guard; +typedef epicsGuardRelease UnGuard; + +struct pvaLink; +struct pvaLinkChannel; + +extern lset pva_lset; +extern jlif lsetPVA; + +struct pvaLinkConfig : public jlink +{ + // configuration, output of jlif parsing + //! Channel (aka PV) name string + std::string channelName; + //! sub-field within addressed PVStructure + std::string fieldName; + + size_t queueSize; + + enum pp_t { + NPP, + Default, // for put() only. For monitor, treated as NPP + PP, // for put() only, For monitor, treated as NPP + CP, // for monitor only, put treats as pp + CPP, // for monitor only, put treats as pp + } pp; + enum ms_t { + NMS, + MS, + MSI, + } ms; + + bool defer, pipeline, time, retry, local, always; + int monorder; + + // internals used by jlif parsing + std::string jkey; + + pvaLinkConfig(); + virtual ~pvaLinkConfig(); +}; + +struct pvaGlobal_t { + pvac::ClientProvider provider_local, + provider_remote; + + const pvd::PVDataCreatePtr create; + + WorkQueue queue; + + pvd::Mutex lock; + + bool running; // set after dbEvent is initialized and safe to use + + // a tuple of channel name and printed pvRequest (or Monitor) + typedef std::pair channels_key_t; + // pvaLinkChannel dtor prunes dead entires + typedef std::map > channels_t; + // Cache of active Channels (really about caching Monitor) + channels_t channels; + + pvaGlobal_t(); + ~pvaGlobal_t(); +}; +extern pvaGlobal_t *pvaGlobal; + +struct pvaLinkChannel : public pvac::ClientChannel::MonitorCallback, + public pvac::ClientChannel::PutCallback, + public epicsThreadRunable, + public std::tr1::enable_shared_from_this +{ + const pvaGlobal_t::channels_key_t key; // tuple of (channelName, pvRequest key) + const pvd::PVStructure::const_shared_pointer pvRequest; // used with monitor + + static size_t num_instances; + + pvd::Mutex lock; + epicsEvent run_done; // used by testing code + + pvac::ClientChannel chan; + pvac::Monitor op_mon; + pvac::Operation op_put; + + std::string providerName; + size_t num_disconnect, num_type_change; + bool connected; + bool connected_latched; // connection status at the run() + bool isatomic; + bool queued; // added to WorkQueue + bool debug; // set if any jlink::debug is set + std::tr1::shared_ptr previous_root; + typedef std::set after_put_t; + after_put_t after_put; + + struct LinkSort { + bool operator()(const pvaLink *L, const pvaLink *R) const; + }; + + typedef std::set links_t; + + // list of currently attached links. maintained by pvaLink ctor/dtor + // TODO: sort by PHAS + links_t links; + + // set when 'links' is modified to trigger re-compute of record scan list + bool links_changed; + + pvaLinkChannel(const pvaGlobal_t::channels_key_t& key, const epics::pvData::PVStructure::const_shared_pointer &pvRequest); + virtual ~pvaLinkChannel(); + + void open(); + void put(bool force=false); // begin Put op. + + // pvac::ClientChanel::MonitorCallback + virtual void monitorEvent(const pvac::MonitorEvent& evt) OVERRIDE FINAL; + + // pvac::ClientChanel::PutCallback + virtual void putBuild(const epics::pvData::StructureConstPtr& build, pvac::ClientChannel::PutCallback::Args& args) OVERRIDE FINAL; + virtual void putDone(const pvac::PutEvent& evt) OVERRIDE FINAL; + struct AfterPut : public epicsThreadRunable { + std::tr1::weak_ptr lc; + virtual ~AfterPut() {} + virtual void run() OVERRIDE FINAL; + }; + std::tr1::shared_ptr AP; +private: + virtual void run() OVERRIDE FINAL; + void run_dbProcess(size_t idx); // idx is index in scan_records + + // ==== Treat remaining as local to run() + + std::vector scan_records; + std::vector scan_check_passive; + std::vector scan_changed; + + DBManyLock atomic_lock; +}; + +struct pvaLink : public pvaLinkConfig +{ + static size_t num_instances; + + bool alive; // attempt to catch some use after free + dbfType type; + + DBLINK * plink; // may be NULL + + std::tr1::shared_ptr lchan; + + bool used_scratch, used_queue; + pvd::shared_vector put_scratch, put_queue; + + // cached fields from channel op_mon + // updated in onTypeChange() + epics::pvData::PVField::const_shared_pointer fld_value; + epics::pvData::PVScalar::const_shared_pointer fld_severity, + fld_seconds, + fld_nanoseconds; + epics::pvData::PVStructure::const_shared_pointer fld_display, + fld_control, + fld_valueAlarm; + epics::pvData::BitSet proc_changed; + + // cached snapshot of alarm and timestamp + // captured in pvaGetValue(). + // we choose not to ensure consistency with display/control meta-data + epicsTimeStamp snap_time; + short snap_severity; + + pvaLink(); + virtual ~pvaLink(); + + // returns pvRequest to be used with monitor + pvd::PVStructurePtr makeRequest(); + + bool valid() const; + + // fetch a sub-sub-field of the top monitored field. + pvd::PVField::const_shared_pointer getSubField(const char *name); + + void onDisconnect(); + void onTypeChange(); +}; + + +} // namespace pvalink + +#endif // PVALINK_H diff --git a/ioc/pvalink_channel.cpp b/ioc/pvalink_channel.cpp new file mode 100644 index 000000000..6effcccc0 --- /dev/null +++ b/ioc/pvalink_channel.cpp @@ -0,0 +1,435 @@ + +#include + +#include + +#include "pvalink.h" + +int pvaLinkNWorkers = 1; + +namespace pvalink { + +pvaGlobal_t *pvaGlobal; + + +pvaGlobal_t::pvaGlobal_t() + :create(pvd::getPVDataCreate()) + ,queue("PVAL") + ,running(false) +{ + // worker should be above PVA worker priority? + queue.start(std::max(1, pvaLinkNWorkers), epicsThreadPriorityMedium); +} + +pvaGlobal_t::~pvaGlobal_t() +{ +} + +size_t pvaLinkChannel::num_instances; +size_t pvaLink::num_instances; + + +bool pvaLinkChannel::LinkSort::operator()(const pvaLink *L, const pvaLink *R) const { + if(L->monorder==R->monorder) + return L < R; + return L->monorder < R->monorder; +} + +// being called with pvaGlobal::lock held +pvaLinkChannel::pvaLinkChannel(const pvaGlobal_t::channels_key_t &key, const pvd::PVStructure::const_shared_pointer& pvRequest) + :key(key) + ,pvRequest(pvRequest) + ,num_disconnect(0u) + ,num_type_change(0u) + ,connected(false) + ,connected_latched(false) + ,isatomic(false) + ,queued(false) + ,debug(false) + ,links_changed(false) + ,AP(new AfterPut) +{} + +pvaLinkChannel::~pvaLinkChannel() { + { + Guard G(pvaGlobal->lock); + pvaGlobal->channels.erase(key); + } + + Guard G(lock); + + assert(links.empty()); + REFTRACE_DECREMENT(num_instances); +} + +void pvaLinkChannel::open() +{ + Guard G(lock); + + try { + chan = pvaGlobal->provider_local.connect(key.first); + DEBUG(this, <provider_local.name(); + } catch(std::exception& e){ + // The PDBProvider doesn't have a way to communicate to us + // whether this is an invalid record or group name, + // or if this is some sort of internal error. + // So we are forced to assume it is an invalid name. + DEBUG(this, <provider_remote.connect(key.first); + DEBUG(this, <provider_remote.name(); + } + + op_mon = chan.monitor(this, pvRequest); + + REFTRACE_INCREMENT(num_instances); +} + +static +pvd::StructureConstPtr putRequestType = pvd::getFieldCreate()->createFieldBuilder() + ->addNestedStructure("field") + ->endNested() + ->addNestedStructure("record") + ->addNestedStructure("_options") + ->add("block", pvd::pvBoolean) + ->add("process", pvd::pvString) // "true", "false", or "passive" + ->endNested() + ->endNested() + ->createStructure(); + +// call with channel lock held +void pvaLinkChannel::put(bool force) +{ + pvd::PVStructurePtr pvReq(pvd::getPVDataCreate()->createPVStructure(putRequestType)); + pvReq->getSubFieldT("record._options.block")->put(!after_put.empty()); + + unsigned reqProcess = 0; + bool doit = force; + for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it) + { + pvaLink *link = *it; + + if(!link->used_scratch) continue; + + pvd::shared_vector temp; + temp.swap(link->put_scratch); + link->used_scratch = false; + temp.swap(link->put_queue); + link->used_queue = true; + + doit = true; + + switch(link->pp) { + case pvaLink::NPP: + reqProcess |= 1; + break; + case pvaLink::Default: + break; + case pvaLink::PP: + case pvaLink::CP: + case pvaLink::CPP: + reqProcess |= 2; + break; + } + } + + /* By default, use remote default (passive). + * Request processing, or not, if any link asks. + * Prefer PP over NPP if both are specified. + * + * TODO: per field granularity? + */ + const char *proc = "passive"; + if((reqProcess&2) || force) { + proc = "true"; + } else if(reqProcess&1) { + proc = "false"; + } + pvReq->getSubFieldT("record._options.process")->put(proc); + + DEBUG(this, <create->createPVStructure(build)); + + for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it) + { + pvaLink *link = *it; + + if(!link->used_queue) continue; + link->used_queue = false; // clear early so unexpected exception won't get us in a retry loop + + pvd::PVFieldPtr value(link->fieldName.empty() ? pvd::PVFieldPtr(top) : top->getSubField(link->fieldName)); + if(value && value->getField()->getType()==pvd::structure) { + // maybe drill into NTScalar et al. + pvd::PVFieldPtr sub(static_cast(value.get())->getSubField("value")); + if(sub) + value.swap(sub); + } + + if(!value) continue; // TODO: how to signal error? + + pvd::PVStringArray::const_svector choices; // TODO populate from op_mon + + DEBUG(this, <getFullName()); + copyDBF2PVD(link->put_queue, value, args.tosend, choices); + + link->put_queue.clear(); + } + DEBUG(this, < chan; + AFLinker(const std::tr1::shared_ptr& chan) :chan(chan) {} + void operator()(pvaLinkChannel::AfterPut *) { + chan.reset(); + } +}; +} // namespace + +void pvaLinkChannel::putDone(const pvac::PutEvent& evt) +{ + if(evt.event==pvac::PutEvent::Fail) { + errlogPrintf("%s PVA link put ERROR: %s\n", key.first.c_str(), evt.message.c_str()); + } + + bool needscans; + { + Guard G(lock); + + DEBUG(this, <queue.add(AP); + } +} + +void pvaLinkChannel::AfterPut::run() +{ + std::set toscan; + std::tr1::shared_ptr link(lc.lock()); + if(!link) + return; + + { + Guard G(link->lock); + toscan.swap(link->after_put); + } + + for(after_put_t::iterator it=toscan.begin(), end=toscan.end(); + it!=end; ++it) + { + dbCommon *prec = *it; + dbScanLock(prec); + if(prec->pact) { // complete async. processing + (prec)->rset->process(prec); + + } else { + // maybe the result of "cancellation" or some record support logic error? + errlogPrintf("%s : not PACT when async PVA link completed. Logic error?\n", prec->name); + } + dbScanUnlock(prec); + } + +} + +void pvaLinkChannel::monitorEvent(const pvac::MonitorEvent& evt) +{ + bool queue = false; + + { + DEBUG(this, <queue.add(shared_from_this()); + } +} + +// the work in calling dbProcess() which is common to +// both dbScanLock() and dbScanLockMany() +void pvaLinkChannel::run_dbProcess(size_t idx) +{ + dbCommon *precord = scan_records[idx]; + + if(scan_check_passive[idx] && precord->scan!=0) { + return; + + } else if(connected_latched && !op_mon.changed.logical_and(scan_changed[idx])) { + return; + + } else if (precord->pact) { + if (precord->tpro) + printf("%s: Active %s\n", + epicsThreadGetNameSelf(), precord->name); + precord->rpro = TRUE; + + } + dbProcess(precord); +} + +// Running from global WorkQueue thread +void pvaLinkChannel::run() +{ + bool requeue = false; + { + Guard G(lock); + + queued = false; + + connected_latched = connected; + + // pop next update from monitor queue. + // still under lock to safeguard concurrent calls to lset functions + if(connected && !op_mon.poll()) { + DEBUG(this, <onDisconnect(); + } + + // Don't clear previous_root on disconnect. + // We will usually re-connect with the same type, + // and may get back the same PVStructure. + + } else if(previous_root.get() != (const void*)op_mon.root.get()) { + num_type_change++; + + for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it) + { + pvaLink *link = *it; + link->onTypeChange(); + } + + previous_root = std::tr1::static_pointer_cast(op_mon.root); + } + + // at this point we know we will re-queue, but not immediately + // so an expected error won't get us stuck in a tight loop. + requeue = queued = connected_latched; + + if(links_changed) { + // a link has been added or removed since the last update. + // rebuild our cached list of records to (maybe) process. + + scan_records.clear(); + scan_check_passive.clear(); + scan_changed.clear(); + + for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it) + { + pvaLink *link = *it; + assert(link && link->alive); + + if(!link->plink) continue; + + // only scan on monitor update for input links + if(link->type!=DBF_INLINK) + continue; + + // NPP and none/Default don't scan + // PP, CP, and CPP do scan + // PP and CPP only if SCAN=Passive + if(link->pp != pvaLink::PP && link->pp != pvaLink::CPP && link->pp != pvaLink::CP) + continue; + + scan_records.push_back(link->plink->precord); + scan_check_passive.push_back(link->pp != pvaLink::CP); + scan_changed.push_back(link->proc_changed); + } + + DBManyLock ML(scan_records); + + atomic_lock.swap(ML); + + links_changed = false; + } + } + + if(scan_records.empty()) { + // Nothing to do, so don't bother locking + + } else if(isatomic && scan_records.size() > 1u) { + DBManyLocker L(atomic_lock); + + for(size_t i=0, N=scan_records.size(); iqueue.add(shared_from_this()); + } else { + run_done.signal(); + } +} + +} // namespace pvalink diff --git a/ioc/pvalink_jlif.cpp b/ioc/pvalink_jlif.cpp new file mode 100644 index 000000000..d52d30a15 --- /dev/null +++ b/ioc/pvalink_jlif.cpp @@ -0,0 +1,321 @@ +#include + +#include // redirects stdout/stderr + +#include "pvalink.h" + +namespace pvalink { +pvaLinkConfig::pvaLinkConfig() + :queueSize(4) + ,pp(Default) + ,ms(NMS) + ,defer(false) + ,pipeline(false) + ,time(false) + ,retry(false) + ,local(false) + ,always(false) + ,monorder(0) +{} +pvaLinkConfig::~pvaLinkConfig() {} +} + +namespace { + +using namespace pvalink; + +/* link options. + * + * "pvname" # short-hand, sets PV name only + * + * { + * "pv":"name", + * "field":"blah.foo", + * "Q":5, + * "pipeline":false, + * "proc":true, // false, true, none, "", "NPP", "PP", "CP", "CPP" + * "sevr":true, // false, true, "NMS", "MS", "MSI", "MSS" + * "time":true, // false, true + * "monorder":#,// order of processing during CP scan + * "defer":true,// whether to immediately start Put, or only queue value to be sent + * "retry":true,// queue Put while disconnected, and retry on connect + * "always":true,// CP/CPP updates always process a like, even if its input field hasn't changed + * "local":false,// Require local channel + * } + */ + +jlink* pva_alloc_jlink(short dbr) +{ + try { + TRACE(); + return new pvaLink; + + }catch(std::exception& e){ + errlogPrintf("Error allocating pva link: %s\n", e.what()); + return NULL; + } +} + +#define TRY pvaLinkConfig *pvt = static_cast(pjlink); (void)pvt; try +#define CATCH(RET) catch(std::exception& e){ \ + errlogPrintf("Error in %s link: %s\n", __FUNCTION__, e.what()); \ + return RET; } + +void pva_free_jlink(jlink *pjlink) +{ + TRY { + TRACE(); + delete pvt; + }catch(std::exception& e){ + errlogPrintf("Error freeing pva link: %s\n", e.what()); + } +} + +jlif_result pva_parse_null(jlink *pjlink) +{ + TRY { + TRACE(<jkey<<" "); + if(pvt->parseDepth!=1) { + // ignore + } else if(pvt->jkey == "proc") { + pvt->pp = pvaLinkConfig::Default; + } else if(pvt->jkey == "sevr") { + pvt->ms = pvaLinkConfig::NMS; + } else if(pvt->jkey == "local") { + pvt->local = false; // alias for local:false + } else if(pvt->debug) { + printf("pva link parsing unknown none depth=%u key=\"%s\"\n", + pvt->parseDepth, pvt->jkey.c_str()); + } + + pvt->jkey.clear(); + return jlif_continue; + }CATCH(jlif_stop) +} + +jlif_result pva_parse_bool(jlink *pjlink, int val) +{ + TRY { + TRACE(<jkey<<" "<<(val?"true":"false")); + if(pvt->parseDepth!=1) { + // ignore + } else if(pvt->jkey == "proc") { + pvt->pp = val ? pvaLinkConfig::PP : pvaLinkConfig::NPP; + } else if(pvt->jkey == "sevr") { + pvt->ms = val ? pvaLinkConfig::MS : pvaLinkConfig::NMS; + } else if(pvt->jkey == "defer") { + pvt->defer = !!val; + } else if(pvt->jkey == "pipeline") { + pvt->pipeline = !!val; + } else if(pvt->jkey == "time") { + pvt->time = !!val; + } else if(pvt->jkey == "retry") { + pvt->retry = !!val; + } else if(pvt->jkey == "local") { + pvt->local = !!val; + } else if(pvt->jkey == "always") { + pvt->always = !!val; + } else if(pvt->debug) { + printf("pva link parsing unknown integer depth=%u key=\"%s\" value=%s\n", + pvt->parseDepth, pvt->jkey.c_str(), val ? "true" : "false"); + } + + pvt->jkey.clear(); + return jlif_continue; + }CATCH(jlif_stop) +} + +jlif_result pva_parse_integer(jlink *pjlink, long long val) +{ + TRY { + TRACE(<jkey<<" "<parseDepth!=1) { + // ignore + } else if(pvt->jkey == "Q") { + pvt->queueSize = val < 1 ? 1 : size_t(val); + } else if(pvt->jkey == "monorder") { + pvt->monorder = std::max(-1024, std::min(int(val), 1024)); + } else if(pvt->debug) { + printf("pva link parsing unknown integer depth=%u key=\"%s\" value=%lld\n", + pvt->parseDepth, pvt->jkey.c_str(), val); + } + + pvt->jkey.clear(); + return jlif_continue; + }CATCH(jlif_stop) +} + +jlif_result pva_parse_string(jlink *pjlink, const char *val, size_t len) +{ + TRY{ + std::string sval(val, len); + TRACE(<jkey<<" "<parseDepth==0 || (pvt->parseDepth==1 && pvt->jkey=="pv")) { + pvt->channelName = sval; + + } else if(pvt->parseDepth > 1) { + // ignore + + } else if(pvt->jkey=="field") { + pvt->fieldName = sval; + + } else if(pvt->jkey=="proc") { + if(sval.empty()) { + pvt->pp = pvaLinkConfig::Default; + } else if(sval=="CP") { + pvt->pp = pvaLinkConfig::CP; + } else if(sval=="CPP") { + pvt->pp = pvaLinkConfig::CPP; + } else if(sval=="PP") { + pvt->pp = pvaLinkConfig::PP; + } else if(sval=="NPP") { + pvt->pp = pvaLinkConfig::NPP; + } else if(pvt->debug) { + printf("pva link parsing unknown proc depth=%u key=\"%s\" value=\"%s\"\n", + pvt->parseDepth, pvt->jkey.c_str(), sval.c_str()); + } + + } else if(pvt->jkey=="sevr") { + if(sval=="NMS") { + pvt->ms = pvaLinkConfig::NMS; + } else if(sval=="MS") { + pvt->ms = pvaLinkConfig::MS; + } else if(sval=="MSI") { + pvt->ms = pvaLinkConfig::MSI; + } else if(sval=="MSS") { + // not sure how to handle mapping severity for MSS. + // leave room for this to happen compatibly later by + // handling as alias for MS until then. + pvt->ms = pvaLinkConfig::MS; + } else if(pvt->debug) { + printf("pva link parsing unknown sevr depth=%u key=\"%s\" value=\"%s\"\n", + pvt->parseDepth, pvt->jkey.c_str(), sval.c_str()); + } + + } else if(pvt->debug) { + printf("pva link parsing unknown string depth=%u key=\"%s\" value=\"%s\"\n", + pvt->parseDepth, pvt->jkey.c_str(), sval.c_str()); + } + + pvt->jkey.clear(); + return jlif_continue; + }CATCH(jlif_stop) +} + +jlif_key_result pva_parse_start_map(jlink *pjlink) +{ + TRY { + TRACE(); + return jlif_key_continue; + }CATCH(jlif_key_stop) +} + +jlif_result pva_parse_key_map(jlink *pjlink, const char *key, size_t len) +{ + TRY { + std::string sval(key, len); + TRACE(<jkey = sval; + + return jlif_continue; + }CATCH(jlif_stop) +} + +jlif_result pva_parse_end_map(jlink *pjlink) +{ + TRY { + TRACE(); + return jlif_continue; + }CATCH(jlif_stop) +} + +struct lset* pva_get_lset(const jlink *pjlink) +{ + TRACE(); + return &pva_lset; +} + +void pva_report(const jlink *rpjlink, int lvl, int indent) +{ + const pvaLink *pval = static_cast(rpjlink); + try { + (void)pval; + printf("%*s'pva': %s", indent, "", pval->channelName.c_str()); + if(!pval->fieldName.empty()) + printf("|.%s", pval->fieldName.c_str()); + + switch(pval->pp) { + case pvaLinkConfig::NPP: printf(" NPP"); break; + case pvaLinkConfig::Default: printf(" Def"); break; + case pvaLinkConfig::PP: printf(" PP"); break; + case pvaLinkConfig::CP: printf(" CP"); break; + case pvaLinkConfig::CPP: printf(" CPP"); break; + } + switch(pval->ms) { + case pvaLinkConfig::NMS: printf(" NMS"); break; + case pvaLinkConfig::MS: printf(" MS"); break; + case pvaLinkConfig::MSI: printf(" MSI"); break; + } + if(lvl>0) { + printf(" Q=%u pipe=%c defer=%c time=%c retry=%c morder=%d", + unsigned(pval->queueSize), + pval->pipeline ? 'T' : 'F', + pval->defer ? 'T' : 'F', + pval->time ? 'T' : 'F', + pval->retry ? 'T' : 'F', + pval->monorder); + } + + if(pval->lchan) { + // after open() + Guard G(pval->lchan->lock); + + printf(" conn=%c", pval->lchan->connected ? 'T' : 'F'); + if(pval->lchan->op_put.valid()) { + printf(" Put"); + } + + if(lvl>0) { + printf(" #disconn=%zu prov=%s", pval->lchan->num_disconnect, pval->lchan->providerName.c_str()); + } + if(lvl>1) { + printf(" inprog=%c", + pval->lchan->queued?'T':'F'); + } + if(lvl>5) { + std::ostringstream strm; + pval->lchan->chan.show(strm); + printf("\n%*s CH: %s", indent, "", strm.str().c_str()); + } + } else { + printf(" No Channel"); + } + printf("\n"); + }CATCH() +} + +} //namespace + +namespace pvalink { + +jlif lsetPVA = { + "pva", + &pva_alloc_jlink, + &pva_free_jlink, + &pva_parse_null, + &pva_parse_bool, + &pva_parse_integer, + NULL, + &pva_parse_string, + &pva_parse_start_map, + &pva_parse_key_map, + &pva_parse_end_map, + NULL, + NULL, + NULL, + &pva_get_lset, + &pva_report, + NULL +}; + +} //namespace pvalink diff --git a/ioc/pvalink_link.cpp b/ioc/pvalink_link.cpp new file mode 100644 index 000000000..3bb14f74d --- /dev/null +++ b/ioc/pvalink_link.cpp @@ -0,0 +1,156 @@ +#include +#include + +#include "pvalink.h" + +namespace pvalink { + +pvaLink::pvaLink() + :alive(true) + ,type((dbfType)-1) + ,plink(0) + ,used_scratch(false) + ,used_queue(false) +{ + REFTRACE_INCREMENT(num_instances); + + snap_severity = INVALID_ALARM; + snap_time.secPastEpoch = 0; + snap_time.nsec = 0; + + //TODO: valgrind tells me these aren't initialized by Base, but probably should be. + parseDepth = 0; + parent = 0; +} + +pvaLink::~pvaLink() +{ + alive = false; + + if(lchan) { // may be NULL if parsing fails + Guard G(lchan->lock); + + lchan->links.erase(this); + lchan->links_changed = true; + + bool new_debug = false; + for(pvaLinkChannel::links_t::const_iterator it(lchan->links.begin()), end(lchan->links.end()) + ; it!=end; ++it) + { + const pvaLink *pval = *it; + if(pval->debug) { + new_debug = true; + break; + } + } + + lchan->debug = new_debug; + } + + REFTRACE_DECREMENT(num_instances); +} + +static +pvd::StructureConstPtr monitorRequestType = pvd::getFieldCreate()->createFieldBuilder() + ->addNestedStructure("field") + ->endNested() + ->addNestedStructure("record") + ->addNestedStructure("_options") + ->add("pipeline", pvd::pvBoolean) + ->add("atomic", pvd::pvBoolean) + ->add("queueSize", pvd::pvUInt) + ->endNested() + ->endNested() + ->createStructure(); + +pvd::PVStructurePtr pvaLink::makeRequest() +{ + pvd::PVStructurePtr ret(pvd::getPVDataCreate()->createPVStructure(monitorRequestType)); + ret->getSubFieldT("record._options.pipeline")->put(pipeline); + ret->getSubFieldT("record._options.atomic")->put(true); + ret->getSubFieldT("record._options.queueSize")->put(queueSize); + return ret; +} + +// caller must lock lchan->lock +bool pvaLink::valid() const +{ + return lchan->connected_latched && lchan->op_mon.root; +} + +// caller must lock lchan->lock +pvd::PVField::const_shared_pointer pvaLink::getSubField(const char *name) +{ + pvd::PVField::const_shared_pointer ret; + if(valid()) { + if(fieldName.empty()) { + // we access the top level struct + ret = lchan->op_mon.root->getSubField(name); + + } else { + // we access a sub-struct + ret = lchan->op_mon.root->getSubField(fieldName); + if(!ret) { + // noop + } else if(ret->getField()->getType()!=pvd::structure) { + // addressed sub-field isn't a sub-structure + if(strcmp(name, "value")!=0) { + // unless we are trying to fetch the "value", we fail here + ret.reset(); + } + } else { + ret = static_cast(ret.get())->getSubField(name); + } + } + } + return ret; +} + +// call with channel lock held +void pvaLink::onDisconnect() +{ + DEBUG(this,<precord->name<<" disconnect"); + // TODO: option to remain queue'd while disconnected + + used_queue = used_scratch = false; +} + +void pvaLink::onTypeChange() +{ + DEBUG(this,<precord->name<<" type change"); + + assert(lchan->connected_latched && !!lchan->op_mon.root); // we should only be called when connected + + fld_value = getSubField("value"); + fld_seconds = std::tr1::dynamic_pointer_cast(getSubField("timeStamp.secondsPastEpoch")); + fld_nanoseconds = std::tr1::dynamic_pointer_cast(getSubField("timeStamp.nanoseconds")); + fld_severity = std::tr1::dynamic_pointer_cast(getSubField("alarm.severity")); + fld_display = std::tr1::dynamic_pointer_cast(getSubField("display")); + fld_control = std::tr1::dynamic_pointer_cast(getSubField("control")); + fld_valueAlarm = std::tr1::dynamic_pointer_cast(getSubField("valueAlarm")); + + proc_changed.clear(); + + // build mask of all "changed" bits associated with our .value + // CP/CPP input links will process this link only for updates where + // the changed mask and proc_changed share at least one set bit. + if(fld_value) { + // bit for this field + proc_changed.set(fld_value->getFieldOffset()); + + // bits of all parent fields + for(const pvd::PVStructure* parent = fld_value->getParent(); parent; parent = parent->getParent()) { + proc_changed.set(parent->getFieldOffset()); + } + + if(fld_value->getField()->getType()==pvd::structure) + { + // bits of all child fields + const pvd::PVStructure *val = static_cast(fld_value.get()); + for(size_t i=val->getFieldOffset(), N=val->getNextFieldOffset(); i +#include +#include +#include // redirect stdout/stderr + +#include + +#include "pvalink.h" + + +namespace { + +using namespace pvalink; + +#define TRY pvaLink *self = static_cast(plink->value.json.jlink); assert(self->alive); try +#define CATCH() catch(std::exception& e) { \ + errlogPrintf("pvaLink %s fails %s: %s\n", CURRENT_FUNCTION, plink->precord->name, e.what()); \ +} + +#define CHECK_VALID() if(!self->valid()) { DEBUG(self, <channelName<<" !valid"); return -1;} + +dbfType getLinkType(DBLINK *plink) +{ + dbCommon *prec = plink->precord; + pdbRecordIterator iter(prec); + + for(long status = dbFirstField(&iter.ent, 0); !status; status = dbNextField(&iter.ent, 0)) { + if(iter.ent.pfield==plink) + return iter.ent.pflddes->field_type; + } + throw std::logic_error("DBLINK* corrupt"); +} + +void pvaOpenLink(DBLINK *plink) +{ + try { + pvaLink* self((pvaLink*)plink->value.json.jlink); + self->type = getLinkType(plink); + + // workaround for Base not propagating info(base:lsetDebug to us + { + pdbRecordIterator rec(plink->precord); + + if(epicsStrCaseCmp(rec.info("base:lsetDebug", "NO"), "YES")==0) { + self->debug = 1; + } + } + + DEBUG(self, <precord->name<<" OPEN "<channelName); + + // still single threaded at this point. + // also, no pvaLinkChannel::lock yet + + self->plink = plink; + + if(self->channelName.empty()) + return; // nothing to do... + + pvd::PVStructure::const_shared_pointer pvRequest(self->makeRequest()); + pvaGlobal_t::channels_key_t key; + + { + std::ostringstream strm; + strm<<*pvRequest; // print the request as a convient key for our channel cache + + key = std::make_pair(self->channelName, strm.str()); + } + + std::tr1::shared_ptr chan; + bool doOpen = false; + { + Guard G(pvaGlobal->lock); + + pvaGlobal_t::channels_t::iterator it(pvaGlobal->channels.find(key)); + + if(it!=pvaGlobal->channels.end()) { + // re-use existing channel + chan = it->second.lock(); + } + + if(!chan) { + // open new channel + + chan.reset(new pvaLinkChannel(key, pvRequest)); + chan->AP->lc = chan; + pvaGlobal->channels.insert(std::make_pair(key, chan)); + doOpen = true; + } + + doOpen &= pvaGlobal->running; // if not running, then open from initHook + } + + if(doOpen) { + chan->open(); // start subscription + } + + if(!self->local || chan->providerName=="QSRV"){ + Guard G(chan->lock); + + chan->links.insert(self); + chan->links_changed = true; + + self->lchan.swap(chan); // we are now attached + + self->lchan->debug |= !!self->debug; + } else { + // TODO: only print duing iocInit()? + fprintf(stderr, "%s Error: local:true link to '%s' can't be fulfilled\n", + plink->precord->name, self->channelName.c_str()); + plink->lset = NULL; + } + + return; + }CATCH() + // on error, prevent any further calls to our lset functions + plink->lset = NULL; +} + +void pvaRemoveLink(struct dbLocker *locker, DBLINK *plink) +{ + try { + p2p::auto_ptr self((pvaLink*)plink->value.json.jlink); + DEBUG(self, <precord->name<<" "<channelName); + assert(self->alive); + + }CATCH() +} + +int pvaIsConnected(const DBLINK *plink) +{ + TRY { + Guard G(self->lchan->lock); + + bool ret = self->valid(); + DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->lock); + CHECK_VALID(); + + // if fieldName is empty, use top struct value + // if fieldName not empty + // if sub-field is struct, use sub-struct .value + // if sub-field not struct, treat as value + + pvd::PVField::const_shared_pointer value(self->getSubField("value")); + + pvd::ScalarType ftype = pvd::pvInt; // default for un-mapable types. + if(!value) { + // no-op + } else if(value->getField()->getType()==pvd::scalar) + ftype = static_cast(value->getField().get())->getScalarType(); + else if(value->getField()->getType()==pvd::scalarArray) + ftype = static_cast(value->getField().get())->getElementType(); + + int ret; + switch(ftype) { +#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case pvd::pv##PVACODE: ret = DBF_##DBFTYPE; +#define CASE_REAL_INT64 +#include "pv/typemap.h" +#undef CASE_REAL_INT64 +#undef CASE + case pvd::pvString: ret = DBF_STRING; // TODO: long string? + } + + DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->lock); + CHECK_VALID(); + + long ret = 0; + if(self->fld_value && self->fld_value->getField()->getType()==pvd::scalarArray) + ret = static_cast(self->fld_value.get())->getLength(); + + DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->lock); + + if(!self->valid()) { + // disconnected + if(self->ms != pvaLink::NMS) { + recGblSetSevr(plink->precord, LINK_ALARM, self->snap_severity); + } + // TODO: better capture of disconnect time + epicsTimeGetCurrent(&self->snap_time); + if(self->time) { + plink->precord->time = self->snap_time; + } + DEBUG(self, <channelName<<" !valid"); + return -1; + } + + if(self->fld_value) { + long status = copyPVD2DBF(self->fld_value, pbuffer, dbrType, pnRequest); + if(status) { + DEBUG(self, <precord->name<<" "<channelName<<" "<fld_seconds) { + self->snap_time.secPastEpoch = self->fld_seconds->getAs() - POSIX_TIME_AT_EPICS_EPOCH; + if(self->fld_nanoseconds) { + self->snap_time.nsec = self->fld_nanoseconds->getAs(); + } else { + self->snap_time.nsec = 0u; + } + } else { + self->snap_time.secPastEpoch = 0u; + self->snap_time.nsec = 0u; + } + + if(self->fld_severity) { + self->snap_severity = self->fld_severity->getAs(); + } else { + self->snap_severity = NO_ALARM; + } + + if((self->snap_severity!=NO_ALARM && self->ms == pvaLink::MS) || + (self->snap_severity==INVALID_ALARM && self->ms == pvaLink::MSI)) + { + recGblSetSevr(plink->precord, LINK_ALARM, self->snap_severity); + } + + if(self->time) { + plink->precord->time = self->snap_time; + } + + DEBUG(self, <precord->name<<" "<channelName<<" OK"); + return 0; + }CATCH() + return -1; +} + +long pvaGetControlLimits(const DBLINK *plink, double *lo, double *hi) +{ + TRY { + Guard G(self->lchan->lock); + CHECK_VALID(); + + if(self->fld_control) { + pvd::PVScalar::const_shared_pointer value; + if(lo) { + value = std::tr1::static_pointer_cast(self->fld_control->getSubField("limitLow")); + *lo = value ? value->getAs() : 0.0; + } + if(hi) { + value = std::tr1::static_pointer_cast(self->fld_control->getSubField("limitHigh")); + *hi = value ? value->getAs() : 0.0; + } + } else { + *lo = *hi = 0.0; + } + DEBUG(self, <precord->name<<" "<channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)); + return 0; + }CATCH() + return -1; +} + +long pvaGetGraphicLimits(const DBLINK *plink, double *lo, double *hi) +{ + TRY { + Guard G(self->lchan->lock); + CHECK_VALID(); + + if(self->fld_display) { + pvd::PVScalar::const_shared_pointer value; + if(lo) { + value = std::tr1::static_pointer_cast(self->fld_display->getSubField("limitLow")); + *lo = value ? value->getAs() : 0.0; + } + if(hi) { + value = std::tr1::static_pointer_cast(self->fld_display->getSubField("limitHigh")); + *hi = value ? value->getAs() : 0.0; + } + } else { + *lo = *hi = 0.0; + } + DEBUG(self, <precord->name<<" "<channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)); + return 0; + }CATCH() + return -1; +} + +long pvaGetAlarmLimits(const DBLINK *plink, double *lolo, double *lo, + double *hi, double *hihi) +{ + TRY { + //Guard G(self->lchan->lock); + //CHECK_VALID(); + *lolo = *lo = *hi = *hihi = 0.0; + DEBUG(self, <precord->name<<" "<channelName<<" "<<(lolo ? *lolo : 0)<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)<<" "<<(hihi ? *hihi : 0)); + return 0; + }CATCH() + return -1; +} + +long pvaGetPrecision(const DBLINK *plink, short *precision) +{ + TRY { + //Guard G(self->lchan->lock); + //CHECK_VALID(); + + // No sane way to recover precision from display.format string. + *precision = 0; + DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->lock); + CHECK_VALID(); + + if(unitsSize==0) return 0; + + if(units && self->fld_display) { + pvd::PVString::const_shared_pointer value(std::tr1::static_pointer_cast(self->fld_display->getSubField("units"))); + if(value) { + const std::string& egu = value->get(); + strncpy(units, egu.c_str(), unitsSize); + } + } else if(units) { + units[0] = '\0'; + } + units[unitsSize-1] = '\0'; + DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->lock); + CHECK_VALID(); + + if(severity) { + *severity = self->snap_severity; + } + if(status) { + *status = self->snap_severity ? LINK_ALARM : NO_ALARM; + } + + DEBUG(self, <precord->name<<" "<channelName<<" "<<(severity ? *severity : 0)<<" "<<(status ? *status : 0)); + return 0; + }CATCH() + return -1; +} + +long pvaGetTimeStamp(const DBLINK *plink, epicsTimeStamp *pstamp) +{ + TRY { + Guard G(self->lchan->lock); + CHECK_VALID(); + + if(pstamp) { + *pstamp = self->snap_time; + } + + DEBUG(self, <precord->name<<" "<channelName<<" "<<(pstamp ? pstamp->secPastEpoch : 0)<<":"<<(pstamp ? pstamp->nsec: 0)); + return 0; + }CATCH() + return -1; +} + +// note that we handle DBF_ENUM differently than in pvif.cpp +pvd::ScalarType DBR2PVD(short dbr) +{ + switch(dbr) { +#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBR_##DBFTYPE: return pvd::pv##PVACODE; +#define CASE_SKIP_BOOL +#define CASE_REAL_INT64 +#include "pv/typemap.h" +#undef CASE_SKIP_BOOL +#undef CASE_REAL_INT64 +#undef CASE + case DBF_ENUM: return pvd::pvUShort; + case DBF_STRING: return pvd::pvString; + } + throw std::invalid_argument("Unsupported DBR code"); +} + +long pvaPutValueX(DBLINK *plink, short dbrType, + const void *pbuffer, long nRequest, bool wait) +{ + TRY { + (void)self; + Guard G(self->lchan->lock); + + if(nRequest < 0) return -1; + + if(!self->retry && !self->valid()) { + DEBUG(self, <channelName<<" !valid"); + return -1; + } + + pvd::ScalarType stype = DBR2PVD(dbrType); + + pvd::shared_vector buf; + + if(dbrType == DBF_STRING) { + const char *sbuffer = (const char*)pbuffer; + pvd::shared_vector sval(nRequest); + + for(long n=0; nput_scratch = pvd::static_shared_vector_cast(pvd::freeze(sval)); + + } else { + pvd::shared_vector val(pvd::ScalarTypeFunc::allocArray(stype, size_t(nRequest))); + + assert(size_t(dbValueSize(dbrType)*nRequest) == val.size()); + + memcpy(val.data(), pbuffer, val.size()); + + self->put_scratch = pvd::freeze(val); + } + + self->used_scratch = true; + +#ifdef USE_MULTILOCK + if(wait) + self->lchan->after_put.insert(plink->precord); +#endif + + if(!self->defer) self->lchan->put(); + + DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->op_put.valid()); + return 0; + }CATCH() + return -1; +} + +long pvaPutValue(DBLINK *plink, short dbrType, + const void *pbuffer, long nRequest) +{ + return pvaPutValueX(plink, dbrType, pbuffer, nRequest, false); +} + +long pvaPutValueAsync(DBLINK *plink, short dbrType, + const void *pbuffer, long nRequest) +{ + return pvaPutValueX(plink, dbrType, pbuffer, nRequest, true); +} + +void pvaScanForward(DBLINK *plink) +{ + TRY { + Guard G(self->lchan->lock); + + if(!self->retry && !self->valid()) { + return; + } + + // FWD_LINK is never deferred, and always results in a Put + self->lchan->put(true); + + DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->op_put.valid()); + }CATCH() +} + +#undef TRY +#undef CATCH + +} //namespace + +namespace pvalink { + +lset pva_lset = { + 0, 1, // non-const, volatile + &pvaOpenLink, + &pvaRemoveLink, + NULL, NULL, NULL, + &pvaIsConnected, + &pvaGetDBFtype, + &pvaGetElements, + &pvaGetValue, + &pvaGetControlLimits, + &pvaGetGraphicLimits, + &pvaGetAlarmLimits, + &pvaGetPrecision, + &pvaGetUnits, + &pvaGetAlarm, + &pvaGetTimeStamp, + &pvaPutValue, + &pvaPutValueAsync, + &pvaScanForward + //&pvaReportLink, +}; + +} //namespace pvalink diff --git a/ioc/pvalink_null.cpp b/ioc/pvalink_null.cpp new file mode 100644 index 000000000..1706c973d --- /dev/null +++ b/ioc/pvalink_null.cpp @@ -0,0 +1,16 @@ + +#include + +static void installPVAAddLinkHook() {} + +struct jlif {} lsetPVA; + +extern "C" { +int pvaLinkDebug; +int pvaLinkNWorkers; + + epicsExportRegistrar(installPVAAddLinkHook); + epicsExportAddress(jlif, lsetPVA); + epicsExportAddress(int, pvaLinkDebug); + epicsExportAddress(int, pvaLinkNWorkers); +} From 1f599b593b3e4936f3cf3c51492e04eb76386435 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 17 Aug 2023 09:18:54 -0700 Subject: [PATCH 2/5] pvalink: porting part 1 --- ioc/pvalink.cpp | 58 +++--- ioc/pvalink.h | 179 +++++++++--------- ioc/pvalink_channel.cpp | 398 +++++++++++++++++++++------------------- ioc/pvalink_jlif.cpp | 57 +++--- ioc/pvalink_link.cpp | 129 ++++++------- ioc/pvalink_lset.cpp | 339 ++++++++++++++++++++++------------ ioc/pvalink_null.cpp | 5 + 7 files changed, 627 insertions(+), 538 deletions(-) diff --git a/ioc/pvalink.cpp b/ioc/pvalink.cpp index 48f9fa782..4ec1cdd00 100644 --- a/ioc/pvalink.cpp +++ b/ioc/pvalink.cpp @@ -1,7 +1,14 @@ +/* + * 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 + #define EPICS_DBCA_PRIVATE_API #include #include @@ -20,16 +27,10 @@ #include /* redirects stdout/stderr */ -#include -#include -#include -#include -#include - -#include "pv/qsrv.h" -#include "helper.h" -#include "pvif.h" #include "pvalink.h" +#include "dblocker.h" +#include "dbentry.h" +#include "iocshcommand.h" #include /* defines epicsExportSharedSymbols */ @@ -40,7 +41,9 @@ int pvaLinkDebug; int pvaLinkIsolate; -using namespace pvalink; +namespace pvxs { namespace ioc { + +using namespace pvxlink; namespace { @@ -123,10 +126,8 @@ void initPVALink(initHookState state) #endif } else if(state==initHookAfterInitDatabase) { - pvac::ClientProvider local("server:QSRV"), - remote("pva"); - pvaGlobal->provider_local = local; - pvaGlobal->provider_remote = remote; + // TODO "local" provider + pvaGlobal->provider_remote = client::Config().build(); } else if(state==initHookAfterIocBuilt) { // after epicsExit(exitDatabase) @@ -142,7 +143,7 @@ void initPVALink(initHookState state) for(pvaGlobal_t::channels_t::iterator it(pvaGlobal->channels.begin()), end(pvaGlobal->channels.end()); it != end; ++it) { - std::tr1::shared_ptr chan(it->second.lock()); + std::shared_ptr chan(it->second.lock()); if(!chan) continue; chan->open(); @@ -183,9 +184,9 @@ void testqsrvCleanup(void) void testqsrvWaitForLinkEvent(struct link *plink) { - std::tr1::shared_ptr lchan; + std::shared_ptr lchan; { - DBScanLocker lock(plink->precord); + DBLocker lock(plink->precord); if(plink->type!=JSON_LINK || !plink->value.json.jlink || plink->value.json.jlink->pif!=&lsetPVA) { testAbort("Not a PVA link"); @@ -225,7 +226,7 @@ void dbpvar(const char *precordname, int level) for(pvaGlobal_t::channels_t::const_iterator it(channels.begin()), end(channels.end()); it != end; ++it) { - std::tr1::shared_ptr chan(it->second.lock()); + std::shared_ptr chan(it->second.lock()); if(!chan) continue; Guard G(chan->lock); @@ -268,7 +269,7 @@ void dbpvar(const char *precordname, int level) chan->connected_latched?'T':'F', chan->num_disconnect, chan->num_type_change); - if(chan->op_put.valid()) { + if(chan->op_put) { printf(" Put"); } @@ -290,11 +291,11 @@ void dbpvar(const char *precordname, int level) continue; const char *fldname = "???"; - pdbRecordIterator rec(pval->plink->precord); - for(bool done = !!dbFirstField(&rec.ent, 0); !done; done = !!dbNextField(&rec.ent, 0)) + ioc::DBEntry rec(pval->plink->precord); + for(bool done = !!dbFirstField(rec, 0); !done; done = !!dbNextField(rec, 0)) { - if(rec.ent.pfield == (void*)pval->plink) { - fldname = rec.ent.pflddes->name; + if(rec->pfield == (void*)pval->plink) { + fldname = rec->pflddes->name; break; } } @@ -339,14 +340,17 @@ static void installPVAAddLinkHook() { initHookRegister(&initPVALink); - epics::iocshRegister("dbpvar", "record name", "level"); - epics::registerRefCounter("pvaLinkChannel", &pvaLinkChannel::num_instances); - epics::registerRefCounter("pvaLink", &pvaLink::num_instances); + IOCShCommand("dbpvar", "dbpvar", "record name", "level") + .implementation<&dbpvar>(); +// epics::registerRefCounter("pvaLinkChannel", &pvaLinkChannel::num_instances); +// epics::registerRefCounter("pvaLink", &pvaLink::num_instances); } +}} // namespace pvxs::ioc + extern "C" { + using pvxs::ioc::installPVAAddLinkHook; epicsExportRegistrar(installPVAAddLinkHook); - epicsExportAddress(jlif, lsetPVA); epicsExportAddress(int, pvaLinkDebug); epicsExportAddress(int, pvaLinkNWorkers); } diff --git a/ioc/pvalink.h b/ioc/pvalink.h index 77141fd34..d144a8acd 100644 --- a/ioc/pvalink.h +++ b/ioc/pvalink.h @@ -1,3 +1,9 @@ +/* + * 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 PVALINK_H #define PVALINK_H @@ -27,44 +33,20 @@ #include #include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include "helper.h" -#include "pvif.h" -#include "tpool.h" +#include +#include "dbmanylocker.h" extern "C" { - QSRV_API extern int pvaLinkDebug; - QSRV_API extern int pvaLinkIsolate; - QSRV_API extern int pvaLinkNWorkers; + extern int pvaLinkDebug; + extern int pvaLinkIsolate; + extern int pvaLinkNWorkers; } -#if 0 -# define TRACE(X) std::cerr<<"PVAL "<<__func__<<" " X <<"\n" -#else -# define TRACE(X) do {} while(0) -#endif - -// pvaLink and pvaLinkChannel have ->debug -#define DEBUG(OBJ, X) do{ if((OBJ)->debug) std::cout X<<"\n"; }while(0) - -namespace pvalink { +namespace pvxlink { +using namespace pvxs; -namespace pvd = epics::pvData; -namespace pva = epics::pvAccess; - -typedef epicsGuard Guard; -typedef epicsGuardRelease UnGuard; +typedef epicsGuard Guard; +typedef epicsGuardRelease UnGuard; struct pvaLink; struct pvaLinkChannel; @@ -80,7 +62,7 @@ struct pvaLinkConfig : public jlink //! sub-field within addressed PVStructure std::string fieldName; - size_t queueSize; + size_t queueSize = 4; enum pp_t { NPP, @@ -88,72 +70,82 @@ struct pvaLinkConfig : public jlink PP, // for put() only, For monitor, treated as NPP CP, // for monitor only, put treats as pp CPP, // for monitor only, put treats as pp - } pp; + } pp = Default; enum ms_t { NMS, MS, MSI, - } ms; + } ms = NMS; - bool defer, pipeline, time, retry, local, always; - int monorder; + bool defer = false; + bool pipeline = false; + bool time = false; + bool retry = false; + bool local = false; + bool always = false; + int monorder = 0; // internals used by jlif parsing std::string jkey; - pvaLinkConfig(); virtual ~pvaLinkConfig(); }; -struct pvaGlobal_t { - pvac::ClientProvider provider_local, - provider_remote; - - const pvd::PVDataCreatePtr create; +struct pvaGlobal_t : private epicsThreadRunable { + client::Context provider_remote; - WorkQueue queue; + MPMCFIFO> queue; - pvd::Mutex lock; + epicsMutex lock; bool running; // set after dbEvent is initialized and safe to use // a tuple of channel name and printed pvRequest (or Monitor) typedef std::pair channels_key_t; // pvaLinkChannel dtor prunes dead entires - typedef std::map > channels_t; + typedef std::map > channels_t; // Cache of active Channels (really about caching Monitor) channels_t channels; +private: + epicsThread worker; + bool workerStop = false; + virtual void run() override final; +public: + pvaGlobal_t(); - ~pvaGlobal_t(); + virtual ~pvaGlobal_t(); }; extern pvaGlobal_t *pvaGlobal; -struct pvaLinkChannel : public pvac::ClientChannel::MonitorCallback, - public pvac::ClientChannel::PutCallback, - public epicsThreadRunable, - public std::tr1::enable_shared_from_this +struct pvaLinkChannel : public epicsThreadRunable + ,public std::enable_shared_from_this { const pvaGlobal_t::channels_key_t key; // tuple of (channelName, pvRequest key) - const pvd::PVStructure::const_shared_pointer pvRequest; // used with monitor + const Value pvRequest; // used with monitor static size_t num_instances; - pvd::Mutex lock; + epicsMutex lock; epicsEvent run_done; // used by testing code - pvac::ClientChannel chan; - pvac::Monitor op_mon; - pvac::Operation op_put; +// std::shared_ptr chan; + std::shared_ptr op_mon; + std::shared_ptr op_put; + Value root; std::string providerName; - size_t num_disconnect, num_type_change; - bool connected; - bool connected_latched; // connection status at the run() - bool isatomic; - bool queued; // added to WorkQueue - bool debug; // set if any jlink::debug is set - std::tr1::shared_ptr previous_root; + size_t num_disconnect = 0u, num_type_change = 0u; + enum state_t { + Disconnected, + Connecting, + Connected, + } state = Disconnected, + state_latched = Disconnected; + + bool isatomic = false; + bool queued = false; // added to WorkQueue + bool debug = false; // set if any jlink::debug is set typedef std::set after_put_t; after_put_t after_put; @@ -168,80 +160,73 @@ struct pvaLinkChannel : public pvac::ClientChannel::MonitorCallback, links_t links; // set when 'links' is modified to trigger re-compute of record scan list - bool links_changed; + bool links_changed = false; - pvaLinkChannel(const pvaGlobal_t::channels_key_t& key, const epics::pvData::PVStructure::const_shared_pointer &pvRequest); + pvaLinkChannel(const pvaGlobal_t::channels_key_t& key, const Value &pvRequest); virtual ~pvaLinkChannel(); void open(); void put(bool force=false); // begin Put op. - // pvac::ClientChanel::MonitorCallback - virtual void monitorEvent(const pvac::MonitorEvent& evt) OVERRIDE FINAL; - - // pvac::ClientChanel::PutCallback - virtual void putBuild(const epics::pvData::StructureConstPtr& build, pvac::ClientChannel::PutCallback::Args& args) OVERRIDE FINAL; - virtual void putDone(const pvac::PutEvent& evt) OVERRIDE FINAL; struct AfterPut : public epicsThreadRunable { - std::tr1::weak_ptr lc; + std::weak_ptr lc; virtual ~AfterPut() {} - virtual void run() OVERRIDE FINAL; + virtual void run() override final; }; - std::tr1::shared_ptr AP; + std::shared_ptr AP; private: - virtual void run() OVERRIDE FINAL; + virtual void run() override final; void run_dbProcess(size_t idx); // idx is index in scan_records // ==== Treat remaining as local to run() std::vector scan_records; std::vector scan_check_passive; - std::vector scan_changed; - DBManyLock atomic_lock; + ioc::DBManyLock atomic_lock; }; -struct pvaLink : public pvaLinkConfig +struct pvaLink final : public pvaLinkConfig { static size_t num_instances; - bool alive; // attempt to catch some use after free - dbfType type; + bool alive = true; // attempt to catch some use after free + dbfType type = (dbfType)-1; - DBLINK * plink; // may be NULL + DBLINK * plink = nullptr; - std::tr1::shared_ptr lchan; + std::shared_ptr lchan; - bool used_scratch, used_queue; - pvd::shared_vector put_scratch, put_queue; + bool used_scratch = false; + bool used_queue = false; + shared_array put_scratch, put_queue; // cached fields from channel op_mon // updated in onTypeChange() - epics::pvData::PVField::const_shared_pointer fld_value; - epics::pvData::PVScalar::const_shared_pointer fld_severity, - fld_seconds, - fld_nanoseconds; - epics::pvData::PVStructure::const_shared_pointer fld_display, - fld_control, - fld_valueAlarm; - epics::pvData::BitSet proc_changed; + Value fld_value; + Value fld_severity, + fld_seconds, + fld_nanoseconds; + Value fld_display, + fld_control, + fld_valueAlarm; // cached snapshot of alarm and timestamp // captured in pvaGetValue(). // we choose not to ensure consistency with display/control meta-data - epicsTimeStamp snap_time; - short snap_severity; + epicsTimeStamp snap_time = {}; + short snap_severity = INVALID_ALARM; pvaLink(); virtual ~pvaLink(); // returns pvRequest to be used with monitor - pvd::PVStructurePtr makeRequest(); + Value makeRequest(); bool valid() const; // fetch a sub-sub-field of the top monitored field. - pvd::PVField::const_shared_pointer getSubField(const char *name); + Value getSubField(const char *name); void onDisconnect(); void onTypeChange(); diff --git a/ioc/pvalink_channel.cpp b/ioc/pvalink_channel.cpp index 6effcccc0..7b7096398 100644 --- a/ioc/pvalink_channel.cpp +++ b/ioc/pvalink_channel.cpp @@ -1,28 +1,60 @@ +/* + * 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 "pvalink.h" +#include "dblocker.h" +#include "dbmanylocker.h" int pvaLinkNWorkers = 1; -namespace pvalink { +namespace pvxlink { +using namespace pvxs; pvaGlobal_t *pvaGlobal; pvaGlobal_t::pvaGlobal_t() - :create(pvd::getPVDataCreate()) - ,queue("PVAL") + :queue() ,running(false) + ,worker(*this, + "pvxlink", + epicsThreadGetStackSize(epicsThreadStackBig), + // worker should be above PVA worker priority? + epicsThreadPriorityMedium) { - // worker should be above PVA worker priority? - queue.start(std::max(1, pvaLinkNWorkers), epicsThreadPriorityMedium); + // TODO respect pvaLinkNWorkers? + worker.start(); } pvaGlobal_t::~pvaGlobal_t() { + { + Guard G(lock); + workerStop = true; + } + queue.push(std::weak_ptr()); + worker.exitWait(); +} + +void pvaGlobal_t::run() +{ + while(1) { + auto w = queue.pop(); + if(auto chan = w.lock()) { + chan->run(); + } + { + Guard G(lock); + if(workerStop) + break; + } + } + } size_t pvaLinkChannel::num_instances; @@ -36,17 +68,9 @@ bool pvaLinkChannel::LinkSort::operator()(const pvaLink *L, const pvaLink *R) co } // being called with pvaGlobal::lock held -pvaLinkChannel::pvaLinkChannel(const pvaGlobal_t::channels_key_t &key, const pvd::PVStructure::const_shared_pointer& pvRequest) +pvaLinkChannel::pvaLinkChannel(const pvaGlobal_t::channels_key_t &key, const Value& pvRequest) :key(key) ,pvRequest(pvRequest) - ,num_disconnect(0u) - ,num_type_change(0u) - ,connected(false) - ,connected_latched(false) - ,isatomic(false) - ,queued(false) - ,debug(false) - ,links_changed(false) ,AP(new AfterPut) {} @@ -59,52 +83,121 @@ pvaLinkChannel::~pvaLinkChannel() { Guard G(lock); assert(links.empty()); - REFTRACE_DECREMENT(num_instances); } void pvaLinkChannel::open() { Guard G(lock); - try { - chan = pvaGlobal->provider_local.connect(key.first); - DEBUG(this, <provider_local.name(); - } catch(std::exception& e){ - // The PDBProvider doesn't have a way to communicate to us - // whether this is an invalid record or group name, - // or if this is some sort of internal error. - // So we are forced to assume it is an invalid name. - DEBUG(this, <provider_remote.monitor(key.first) + .maskConnected(false) + .maskDisconnected(false) + .rawRequest(pvRequest) + .event([this](const client::Subscription&) + { + Guard G(lock); + if(!queued) { + pvaGlobal->queue.push(shared_from_this()); + queued = true; + } + }) + .exec(); + providerName = "remote"; +} + +static +Value linkBuildPut(pvaLinkChannel *self, Value&& prototype) +{ + Guard G(self->lock); + + auto top(std::move(prototype)); + + for(auto link : self->links) + { + if(!link->used_queue) continue; + link->used_queue = false; // clear early so unexpected exception won't get us in a retry loop + + auto value(link->fieldName.empty() ? top : top[link->fieldName]); + if(value.type()==TypeCode::Struct) { + // maybe drill into NTScalar et al. + if(auto sub = value["value"]) + value = std::move(sub); + } + + if(!value) continue; // TODO: how to signal error? + + auto tosend(std::move(link->put_queue)); + + if(value.type().isarray()) { + value = tosend; + + } else if(value.type()==TypeCode::Struct && value.id()=="enum_t") { + + if(tosend.empty()) + continue; // TODO: signal error + + if(tosend.original_type()==ArrayType::String) { + auto sarr(tosend.castTo()); + // TODO: choices... + value["index"] = sarr[0]; + + } else { + auto iarr(tosend.castTo()); + value["index"] = iarr[0]; + } + + } else { // assume scalar + } } - if(!pvaLinkIsolate && !chan) { - chan = pvaGlobal->provider_remote.connect(key.first); - DEBUG(this, <provider_remote.name(); +// DEBUG(this, <key.first.c_str(), e.what()); } - op_mon = chan.monitor(this, pvRequest); + bool needscans; + { + Guard G(self->lock); - REFTRACE_INCREMENT(num_instances); -} +// DEBUG(this, <createFieldBuilder() - ->addNestedStructure("field") - ->endNested() - ->addNestedStructure("record") - ->addNestedStructure("_options") - ->add("block", pvd::pvBoolean) - ->add("process", pvd::pvString) // "true", "false", or "passive" - ->endNested() - ->endNested() - ->createStructure(); + needscans = !self->after_put.empty(); + self->op_put.reset(); + + if(ok) { + // see if we need start a queue'd put + self->put(); + } + } + + if(needscans) { + pvaGlobal->queue.push(self->AP); + } +} // call with channel lock held void pvaLinkChannel::put(bool force) { - pvd::PVStructurePtr pvReq(pvd::getPVDataCreate()->createPVStructure(putRequestType)); - pvReq->getSubFieldT("record._options.block")->put(!after_put.empty()); + // TODO cache TypeDef in global + using namespace pvxs::members; + auto pvReq(TypeDef(TypeCode::Struct, { + Struct("field", {}), + Struct("record", { + Struct("_options", { + Bool("block"), + String("process"), + }), + }), }).create() + .update("record._options.block", !after_put.empty())); unsigned reqProcess = 0; bool doit = force; @@ -114,7 +207,7 @@ void pvaLinkChannel::put(bool force) if(!link->used_scratch) continue; - pvd::shared_vector temp; + shared_array temp; temp.swap(link->put_scratch); link->used_scratch = false; temp.swap(link->put_queue); @@ -148,91 +241,28 @@ void pvaLinkChannel::put(bool force) } else if(reqProcess&1) { proc = "false"; } - pvReq->getSubFieldT("record._options.process")->put(proc); + pvReq["record._options.process"] = proc; - DEBUG(this, <create->createPVStructure(build)); - - for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it) - { - pvaLink *link = *it; - - if(!link->used_queue) continue; - link->used_queue = false; // clear early so unexpected exception won't get us in a retry loop - - pvd::PVFieldPtr value(link->fieldName.empty() ? pvd::PVFieldPtr(top) : top->getSubField(link->fieldName)); - if(value && value->getField()->getType()==pvd::structure) { - // maybe drill into NTScalar et al. - pvd::PVFieldPtr sub(static_cast(value.get())->getSubField("value")); - if(sub) - value.swap(sub); - } - - if(!value) continue; // TODO: how to signal error? - - pvd::PVStringArray::const_svector choices; // TODO populate from op_mon - - DEBUG(this, <getFullName()); - copyDBF2PVD(link->put_queue, value, args.tosend, choices); - - link->put_queue.clear(); - } - DEBUG(this, < chan; - AFLinker(const std::tr1::shared_ptr& chan) :chan(chan) {} - void operator()(pvaLinkChannel::AfterPut *) { - chan.reset(); - } -}; -} // namespace - -void pvaLinkChannel::putDone(const pvac::PutEvent& evt) -{ - if(evt.event==pvac::PutEvent::Fail) { - errlogPrintf("%s PVA link put ERROR: %s\n", key.first.c_str(), evt.message.c_str()); - } - - bool needscans; - { - Guard G(lock); - - DEBUG(this, <queue.add(AP); + op_put = pvaGlobal->provider_remote.put(key.first) + .build([this](Value&& prototype) -> Value + { + return linkBuildPut(this, std::move(prototype)); // TODO + }) + .result([this](client::Result&& result) + { + linkPutDone(this, std::move(result)); + }) + .exec(); } } void pvaLinkChannel::AfterPut::run() { std::set toscan; - std::tr1::shared_ptr link(lc.lock()); + std::shared_ptr link(lc.lock()); if(!link) return; @@ -258,40 +288,6 @@ void pvaLinkChannel::AfterPut::run() } -void pvaLinkChannel::monitorEvent(const pvac::MonitorEvent& evt) -{ - bool queue = false; - - { - DEBUG(this, <queue.add(shared_from_this()); - } -} - // the work in calling dbProcess() which is common to // both dbScanLock() and dbScanLockMany() void pvaLinkChannel::run_dbProcess(size_t idx) @@ -323,25 +319,24 @@ void pvaLinkChannel::run() queued = false; - connected_latched = connected; - - // pop next update from monitor queue. - // still under lock to safeguard concurrent calls to lset functions - if(connected && !op_mon.poll()) { - DEBUG(this, <pop(); + if(!top) { + run_done.signal(); + return; + } - DEBUG(this, <onTypeChange(); - } - - previous_root = std::tr1::static_pointer_cast(op_mon.root); + } catch(std::exception& e) { + // TODO: log } - // at this point we know we will re-queue, but not immediately - // so an expected error won't get us stuck in a tight loop. - requeue = queued = connected_latched; - if(links_changed) { // a link has been added or removed since the last update. // rebuild our cached list of records to (maybe) process. scan_records.clear(); scan_check_passive.clear(); - scan_changed.clear(); for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it) { @@ -396,14 +378,46 @@ void pvaLinkChannel::run() scan_records.push_back(link->plink->precord); scan_check_passive.push_back(link->pp != pvaLink::CP); - scan_changed.push_back(link->proc_changed); } - DBManyLock ML(scan_records); - - atomic_lock.swap(ML); + atomic_lock = ioc::DBManyLock(scan_records); links_changed = false; + + state = Connected; + } + + // handle next update from monitor queue. + // still under lock to safeguard concurrent calls to lset functions + if(connected && root) { +// DEBUG(this, <onTypeChange(); + } + + previous_root = root; + } + + // at this point we know we will re-queue, but not immediately + // so an expected error won't get us stuck in a tight loop. + requeue = queued = connected_latched; + + if(links_changed) { } } @@ -411,7 +425,7 @@ void pvaLinkChannel::run() // Nothing to do, so don't bother locking } else if(isatomic && scan_records.size() > 1u) { - DBManyLocker L(atomic_lock); + ioc::DBManyLocker L(atomic_lock); for(size_t i=0, N=scan_records.size(); i #include // redirects stdout/stderr #include "pvalink.h" -namespace pvalink { -pvaLinkConfig::pvaLinkConfig() - :queueSize(4) - ,pp(Default) - ,ms(NMS) - ,defer(false) - ,pipeline(false) - ,time(false) - ,retry(false) - ,local(false) - ,always(false) - ,monorder(0) -{} +#include + +namespace pvxlink { pvaLinkConfig::~pvaLinkConfig() {} -} namespace { -using namespace pvalink; - /* link options. * * "pvname" # short-hand, sets PV name only @@ -44,10 +37,9 @@ using namespace pvalink; * } */ -jlink* pva_alloc_jlink(short dbr) +jlink* pva_alloc_jlink(short) { try { - TRACE(); return new pvaLink; }catch(std::exception& e){ @@ -64,7 +56,6 @@ jlink* pva_alloc_jlink(short dbr) void pva_free_jlink(jlink *pjlink) { TRY { - TRACE(); delete pvt; }catch(std::exception& e){ errlogPrintf("Error freeing pva link: %s\n", e.what()); @@ -74,7 +65,6 @@ void pva_free_jlink(jlink *pjlink) jlif_result pva_parse_null(jlink *pjlink) { TRY { - TRACE(<jkey<<" "); if(pvt->parseDepth!=1) { // ignore } else if(pvt->jkey == "proc") { @@ -96,7 +86,7 @@ jlif_result pva_parse_null(jlink *pjlink) jlif_result pva_parse_bool(jlink *pjlink, int val) { TRY { - TRACE(<jkey<<" "<<(val?"true":"false")); +// TRACE(<jkey<<" "<<(val?"true":"false")); if(pvt->parseDepth!=1) { // ignore } else if(pvt->jkey == "proc") { @@ -128,7 +118,6 @@ jlif_result pva_parse_bool(jlink *pjlink, int val) jlif_result pva_parse_integer(jlink *pjlink, long long val) { TRY { - TRACE(<jkey<<" "<parseDepth!=1) { // ignore } else if(pvt->jkey == "Q") { @@ -149,7 +138,6 @@ jlif_result pva_parse_string(jlink *pjlink, const char *val, size_t len) { TRY{ std::string sval(val, len); - TRACE(<jkey<<" "<parseDepth==0 || (pvt->parseDepth==1 && pvt->jkey=="pv")) { pvt->channelName = sval; @@ -205,7 +193,6 @@ jlif_result pva_parse_string(jlink *pjlink, const char *val, size_t len) jlif_key_result pva_parse_start_map(jlink *pjlink) { TRY { - TRACE(); return jlif_key_continue; }CATCH(jlif_key_stop) } @@ -214,7 +201,6 @@ jlif_result pva_parse_key_map(jlink *pjlink, const char *key, size_t len) { TRY { std::string sval(key, len); - TRACE(<jkey = sval; return jlif_continue; @@ -224,14 +210,12 @@ jlif_result pva_parse_key_map(jlink *pjlink, const char *key, size_t len) jlif_result pva_parse_end_map(jlink *pjlink) { TRY { - TRACE(); return jlif_continue; }CATCH(jlif_stop) } struct lset* pva_get_lset(const jlink *pjlink) { - TRACE(); return &pva_lset; } @@ -271,7 +255,7 @@ void pva_report(const jlink *rpjlink, int lvl, int indent) Guard G(pval->lchan->lock); printf(" conn=%c", pval->lchan->connected ? 'T' : 'F'); - if(pval->lchan->op_put.valid()) { + if(pval->lchan->op_put) { printf(" Put"); } @@ -282,11 +266,11 @@ void pva_report(const jlink *rpjlink, int lvl, int indent) printf(" inprog=%c", pval->lchan->queued?'T':'F'); } - if(lvl>5) { - std::ostringstream strm; - pval->lchan->chan.show(strm); - printf("\n%*s CH: %s", indent, "", strm.str().c_str()); - } +// if(lvl>5) { +// std::ostringstream strm; +// pval->lchan->chan.show(strm); +// printf("\n%*s CH: %s", indent, "", strm.str().c_str()); +// } } else { printf(" No Channel"); } @@ -296,8 +280,6 @@ void pva_report(const jlink *rpjlink, int lvl, int indent) } //namespace -namespace pvalink { - jlif lsetPVA = { "pva", &pva_alloc_jlink, @@ -319,3 +301,8 @@ jlif lsetPVA = { }; } //namespace pvalink + +extern "C" { +using pvxlink::lsetPVA; +epicsExportAddress(jlif, lsetPVA); +} diff --git a/ioc/pvalink_link.cpp b/ioc/pvalink_link.cpp index 3bb14f74d..af4e4e30c 100644 --- a/ioc/pvalink_link.cpp +++ b/ioc/pvalink_link.cpp @@ -1,23 +1,19 @@ -#include +/* + * 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 "pvalink.h" -namespace pvalink { +namespace pvxlink { pvaLink::pvaLink() - :alive(true) - ,type((dbfType)-1) - ,plink(0) - ,used_scratch(false) - ,used_queue(false) { - REFTRACE_INCREMENT(num_instances); - - snap_severity = INVALID_ALARM; - snap_time.secPastEpoch = 0; - snap_time.nsec = 0; - //TODO: valgrind tells me these aren't initialized by Base, but probably should be. parseDepth = 0; parent = 0; @@ -46,60 +42,55 @@ pvaLink::~pvaLink() lchan->debug = new_debug; } - - REFTRACE_DECREMENT(num_instances); } -static -pvd::StructureConstPtr monitorRequestType = pvd::getFieldCreate()->createFieldBuilder() - ->addNestedStructure("field") - ->endNested() - ->addNestedStructure("record") - ->addNestedStructure("_options") - ->add("pipeline", pvd::pvBoolean) - ->add("atomic", pvd::pvBoolean) - ->add("queueSize", pvd::pvUInt) - ->endNested() - ->endNested() - ->createStructure(); - -pvd::PVStructurePtr pvaLink::makeRequest() +Value pvaLink::makeRequest() { - pvd::PVStructurePtr ret(pvd::getPVDataCreate()->createPVStructure(monitorRequestType)); - ret->getSubFieldT("record._options.pipeline")->put(pipeline); - ret->getSubFieldT("record._options.atomic")->put(true); - ret->getSubFieldT("record._options.queueSize")->put(queueSize); - return ret; + // TODO: cache TypeDef in global + using namespace pvxs::members; + return TypeDef(TypeCode::Struct, { + Struct("field", {}), + Struct("record", { + Struct("_options", { + Bool("pipeline"), + Bool("atomic"), + UInt32("queueSize"), + }), + }), + }).create() + .update("record._options.pipeline", pipeline) + .update("record._options.atomic", true) + .update("record._options.queueSize", uint32_t(queueSize)); } // caller must lock lchan->lock bool pvaLink::valid() const { - return lchan->connected_latched && lchan->op_mon.root; + return lchan->connected_latched && lchan->root; } // caller must lock lchan->lock -pvd::PVField::const_shared_pointer pvaLink::getSubField(const char *name) +Value pvaLink::getSubField(const char *name) { - pvd::PVField::const_shared_pointer ret; + Value ret; if(valid()) { if(fieldName.empty()) { // we access the top level struct - ret = lchan->op_mon.root->getSubField(name); + ret = lchan->root[name]; } else { // we access a sub-struct - ret = lchan->op_mon.root->getSubField(fieldName); + ret = lchan->root[fieldName]; if(!ret) { // noop - } else if(ret->getField()->getType()!=pvd::structure) { + } else if(ret.type()!=TypeCode::Struct) { // addressed sub-field isn't a sub-structure if(strcmp(name, "value")!=0) { // unless we are trying to fetch the "value", we fail here - ret.reset(); + ret = Value(); } } else { - ret = static_cast(ret.get())->getSubField(name); + ret = ret[name]; } } } @@ -109,7 +100,7 @@ pvd::PVField::const_shared_pointer pvaLink::getSubField(const char *name) // call with channel lock held void pvaLink::onDisconnect() { - DEBUG(this,<precord->name<<" disconnect"); +// DEBUG(this,<precord->name<<" disconnect"); // TODO: option to remain queue'd while disconnected used_queue = used_scratch = false; @@ -117,40 +108,38 @@ void pvaLink::onDisconnect() void pvaLink::onTypeChange() { - DEBUG(this,<precord->name<<" type change"); +// DEBUG(this,<precord->name<<" type change"); - assert(lchan->connected_latched && !!lchan->op_mon.root); // we should only be called when connected + assert(lchan->connected_latched && !!lchan->root); // we should only be called when connected fld_value = getSubField("value"); - fld_seconds = std::tr1::dynamic_pointer_cast(getSubField("timeStamp.secondsPastEpoch")); - fld_nanoseconds = std::tr1::dynamic_pointer_cast(getSubField("timeStamp.nanoseconds")); - fld_severity = std::tr1::dynamic_pointer_cast(getSubField("alarm.severity")); - fld_display = std::tr1::dynamic_pointer_cast(getSubField("display")); - fld_control = std::tr1::dynamic_pointer_cast(getSubField("control")); - fld_valueAlarm = std::tr1::dynamic_pointer_cast(getSubField("valueAlarm")); - - proc_changed.clear(); + fld_seconds = getSubField("timeStamp.secondsPastEpoch"); + fld_nanoseconds = getSubField("timeStamp.nanoseconds"); + fld_severity = getSubField("alarm.severity"); + fld_display = getSubField("display"); + fld_control = getSubField("control"); + fld_valueAlarm = getSubField("valueAlarm"); // build mask of all "changed" bits associated with our .value // CP/CPP input links will process this link only for updates where // the changed mask and proc_changed share at least one set bit. - if(fld_value) { - // bit for this field - proc_changed.set(fld_value->getFieldOffset()); - - // bits of all parent fields - for(const pvd::PVStructure* parent = fld_value->getParent(); parent; parent = parent->getParent()) { - proc_changed.set(parent->getFieldOffset()); - } - - if(fld_value->getField()->getType()==pvd::structure) - { - // bits of all child fields - const pvd::PVStructure *val = static_cast(fld_value.get()); - for(size_t i=val->getFieldOffset(), N=val->getNextFieldOffset(); igetFieldOffset()); + +// // bits of all parent fields +// for(const pvd::PVStructure* parent = fld_value->getParent(); parent; parent = parent->getParent()) { +// proc_changed.set(parent->getFieldOffset()); +// } + +// if(fld_value->getField()->getType()==pvd::structure) +// { +// // bits of all child fields +// const pvd::PVStructure *val = static_cast(fld_value.get()); +// for(size_t i=val->getFieldOffset(), N=val->getNextFieldOffset(); i #include #include #include // redirect stdout/stderr -#include - +#include +#include "dbentry.h" #include "pvalink.h" +#include "utilpvt.h" +DEFINE_LOGGER(loglset, "pvxs.pvalink.lset"); +namespace pvxlink { namespace { - -using namespace pvalink; +using namespace pvxs; #define TRY pvaLink *self = static_cast(plink->value.json.jlink); assert(self->alive); try #define CATCH() catch(std::exception& e) { \ - errlogPrintf("pvaLink %s fails %s: %s\n", CURRENT_FUNCTION, plink->precord->name, e.what()); \ + errlogPrintf("pvaLink %s fails %s: %s\n", __func__, plink->precord->name, e.what()); \ } -#define CHECK_VALID() if(!self->valid()) { DEBUG(self, <channelName<<" !valid"); return -1;} +#define CHECK_VALID() if(!self->valid()) { /*DEBUG(self, <<__func__<<" "<channelName<<" !valid");*/ return -1;} dbfType getLinkType(DBLINK *plink) { dbCommon *prec = plink->precord; - pdbRecordIterator iter(prec); + ioc::DBEntry ent(plink->precord); - for(long status = dbFirstField(&iter.ent, 0); !status; status = dbNextField(&iter.ent, 0)) { - if(iter.ent.pfield==plink) - return iter.ent.pflddes->field_type; + for(long status = dbFirstField(ent, 0); !status; status = dbNextField(ent, 0)) { + if(ent->pfield==plink) + return ent->pflddes->field_type; } throw std::logic_error("DBLINK* corrupt"); } @@ -40,14 +47,14 @@ void pvaOpenLink(DBLINK *plink) // workaround for Base not propagating info(base:lsetDebug to us { - pdbRecordIterator rec(plink->precord); + ioc::DBEntry rec(plink->precord); if(epicsStrCaseCmp(rec.info("base:lsetDebug", "NO"), "YES")==0) { self->debug = 1; } } - DEBUG(self, <precord->name<<" OPEN "<channelName); +// DEBUG(self, <precord->name<<" OPEN "<channelName); // still single threaded at this point. // also, no pvaLinkChannel::lock yet @@ -57,17 +64,10 @@ void pvaOpenLink(DBLINK *plink) if(self->channelName.empty()) return; // nothing to do... - pvd::PVStructure::const_shared_pointer pvRequest(self->makeRequest()); - pvaGlobal_t::channels_key_t key; + auto pvRequest(self->makeRequest()); + pvaGlobal_t::channels_key_t key = std::make_pair(self->channelName, SB()<channelName, strm.str()); - } - - std::tr1::shared_ptr chan; + std::shared_ptr chan; bool doOpen = false; { Guard G(pvaGlobal->lock); @@ -120,8 +120,8 @@ void pvaOpenLink(DBLINK *plink) void pvaRemoveLink(struct dbLocker *locker, DBLINK *plink) { try { - p2p::auto_ptr self((pvaLink*)plink->value.json.jlink); - DEBUG(self, <precord->name<<" "<channelName); + std::unique_ptr self((pvaLink*)plink->value.json.jlink); +// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName); assert(self->alive); }CATCH() @@ -133,7 +133,7 @@ int pvaIsConnected(const DBLINK *plink) Guard G(self->lchan->lock); bool ret = self->valid(); - DEBUG(self, <precord->name<<" "<channelName<<" "<precord->name<<" "<<__func__<<" "<channelName<<" "<getSubField("value")); - - pvd::ScalarType ftype = pvd::pvInt; // default for un-mapable types. - if(!value) { - // no-op - } else if(value->getField()->getType()==pvd::scalar) - ftype = static_cast(value->getField().get())->getScalarType(); - else if(value->getField()->getType()==pvd::scalarArray) - ftype = static_cast(value->getField().get())->getElementType(); - - int ret; - switch(ftype) { -#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case pvd::pv##PVACODE: ret = DBF_##DBFTYPE; -#define CASE_REAL_INT64 -#include "pv/typemap.h" -#undef CASE_REAL_INT64 -#undef CASE - case pvd::pvString: ret = DBF_STRING; // TODO: long string? + auto value(self->getSubField("value")); + auto vtype(value.type()); + if(vtype.isarray()) + vtype = vtype.scalarOf(); + + switch(value.type().code) { + case TypeCode::Int8: return DBF_CHAR; + case TypeCode::Int16: return DBF_SHORT; + case TypeCode::Int32: return DBF_LONG; + case TypeCode::Int64: return DBF_INT64; + case TypeCode::UInt8: return DBF_UCHAR; + case TypeCode::UInt16: return DBF_USHORT; + case TypeCode::UInt32: return DBF_ULONG; + case TypeCode::UInt64: return DBF_UINT64; + case TypeCode::Float32: return DBF_FLOAT; + case TypeCode::Float64: return DBF_DOUBLE; + case TypeCode::String: return DBF_STRING; + case TypeCode::Struct: { + if(value.id()=="enum_t" + && value["index"].type().kind()==Kind::Integer + && value["choices"].type()==TypeCode::StringA) + return DBF_ENUM; + } + // fall through + default: + return DBF_LONG; // default for un-mapable types. } - - DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->lock); CHECK_VALID(); - long ret = 0; - if(self->fld_value && self->fld_value->getField()->getType()==pvd::scalarArray) - ret = static_cast(self->fld_value.get())->getLength(); - - DEBUG(self, <precord->name<<" "<channelName<<" "< arr; + if(!self->fld_value.type().isarray()) { + *nelements = 1; + } else if(self->fld_value.as(arr)) { + *nelements = arr.size(); + } + return 0; }CATCH() return -1; } @@ -211,22 +216,130 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, if(self->time) { plink->precord->time = self->snap_time; } - DEBUG(self, <channelName<<" !valid"); +// DEBUG(self, <<__func__<<" "<channelName<<" !valid"); return -1; } - if(self->fld_value) { - long status = copyPVD2DBF(self->fld_value, pbuffer, dbrType, pnRequest); - if(status) { - DEBUG(self, <precord->name<<" "<channelName<<" "<fld_value); + + if(value.type()==TypeCode::Any) + value = value.lookup("->"); + + if(nReq <= 0 || !value) { + if(!pnRequest) { + // TODO: fill in dummy scalar + nReq = 1; + } + + } else if(value.type().isarray()) { + auto arr(value.as>()); + + if(size_t(nReq) < arr.size()) + nReq = arr.size(); + + if(arr.original_type()==ArrayType::String) { + auto sarr(arr.castTo()); + + if(dbrType==DBR_STRING) { + auto cbuf(reinterpret_cast(pbuffer)); + for(size_t i : range(size_t(nReq))) { + strncpy(cbuf + i*MAX_STRING_SIZE, + sarr[i].c_str(), + MAX_STRING_SIZE-1u); + cbuf[i*MAX_STRING_SIZE + MAX_STRING_SIZE-1] = '\0'; + } + } else { + return S_db_badDbrtype; // TODO: allow implicit parse? + } + + } else { + ArrayType dtype; + switch(dbrType) { + case DBR_CHAR: dtype = ArrayType::Int8; break; + case DBR_SHORT: dtype = ArrayType::Int16; break; + case DBR_LONG: dtype = ArrayType::Int32; break; + case DBR_INT64: dtype = ArrayType::Int64; break; + case DBR_UCHAR: dtype = ArrayType::UInt8; break; + case DBR_USHORT: dtype = ArrayType::UInt16; break; + case DBR_ULONG: dtype = ArrayType::UInt32; break; + case DBR_UINT64: dtype = ArrayType::UInt64; break; + case DBR_FLOAT: dtype = ArrayType::Float32; break; + case DBR_DOUBLE: dtype = ArrayType::Float64; break; + default: + return S_db_badDbrtype; + } + + detail::convertArr(dtype, pbuffer, + arr.original_type(), arr.data(), + size_t(nReq)); + } + + } else { // scalar + // TODO: special case for "long string" + + if(value.type()==TypeCode::Struct && self->fld_value.id()=="enum_t") { // NTEnum + auto index(value["index"].as()); + switch(dbrType) { + case DBR_CHAR: *reinterpret_cast(pbuffer) = index; break; + case DBR_SHORT: *reinterpret_cast(pbuffer) = index; break; + case DBR_LONG: *reinterpret_cast(pbuffer) = index; break; + case DBR_INT64: *reinterpret_cast(pbuffer) = index; break; + case DBR_UCHAR: *reinterpret_cast(pbuffer) = index; break; + case DBR_USHORT: *reinterpret_cast(pbuffer) = index; break; + case DBR_ULONG: *reinterpret_cast(pbuffer) = index; break; + case DBR_UINT64: *reinterpret_cast(pbuffer) = index; break; + case DBR_FLOAT: *reinterpret_cast(pbuffer) = index; break; + case DBR_DOUBLE: *reinterpret_cast(pbuffer) = index; break; + case DBR_STRING: { + auto cbuf(reinterpret_cast(pbuffer)); + auto choices(value["choices"].as>()); + if(index>=0 && size_t(index) < choices.size()) { + auto& choice(choices[index]); + strncpy(cbuf, choice.c_str(), MAX_STRING_SIZE-1u); + + } else { + epicsSnprintf(cbuf, MAX_STRING_SIZE-1u, "%u", unsigned(index)); + } + cbuf[MAX_STRING_SIZE-1u] = '\0'; + break; + } + default: + return S_db_badDbrtype; + } + + } else { // plain scalar + switch(dbrType) { + case DBR_CHAR: *reinterpret_cast(pbuffer) = value.as(); break; + case DBR_SHORT: *reinterpret_cast(pbuffer) = value.as(); break; + case DBR_LONG: *reinterpret_cast(pbuffer) = value.as(); break; + case DBR_INT64: *reinterpret_cast(pbuffer) = value.as(); break; + case DBR_UCHAR: *reinterpret_cast(pbuffer) = value.as(); break; + case DBR_USHORT: *reinterpret_cast(pbuffer) = value.as(); break; + case DBR_ULONG: *reinterpret_cast(pbuffer) = value.as(); break; + case DBR_UINT64: *reinterpret_cast(pbuffer) = value.as(); break; + case DBR_FLOAT: *reinterpret_cast(pbuffer) = value.as(); break; + case DBR_DOUBLE: *reinterpret_cast(pbuffer) = value.as(); break; + case DBR_STRING: { + auto cbuf(reinterpret_cast(pbuffer)); + auto sval(value.as()); + strncpy(cbuf, sval.c_str(), MAX_STRING_SIZE-1u); + cbuf[MAX_STRING_SIZE-1u] = '\0'; + } + default: + return S_db_badDbrtype; + } } + nReq = 1; } + if(pnRequest) + *pnRequest = nReq; + if(self->fld_seconds) { - self->snap_time.secPastEpoch = self->fld_seconds->getAs() - POSIX_TIME_AT_EPICS_EPOCH; + self->snap_time.secPastEpoch = self->fld_seconds.as() - POSIX_TIME_AT_EPICS_EPOCH; if(self->fld_nanoseconds) { - self->snap_time.nsec = self->fld_nanoseconds->getAs(); + self->snap_time.nsec = self->fld_nanoseconds.as(); } else { self->snap_time.nsec = 0u; } @@ -236,7 +349,7 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, } if(self->fld_severity) { - self->snap_severity = self->fld_severity->getAs(); + self->snap_severity = self->fld_severity.as(); } else { self->snap_severity = NO_ALARM; } @@ -251,7 +364,7 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, plink->precord->time = self->snap_time; } - DEBUG(self, <precord->name<<" "<channelName<<" OK"); +// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" OK"); return 0; }CATCH() return -1; @@ -264,19 +377,19 @@ long pvaGetControlLimits(const DBLINK *plink, double *lo, double *hi) CHECK_VALID(); if(self->fld_control) { - pvd::PVScalar::const_shared_pointer value; + Value value; if(lo) { - value = std::tr1::static_pointer_cast(self->fld_control->getSubField("limitLow")); - *lo = value ? value->getAs() : 0.0; + if(!self->fld_control["limitLow"].as(*lo)) + *lo = 0.0; } if(hi) { - value = std::tr1::static_pointer_cast(self->fld_control->getSubField("limitHigh")); - *hi = value ? value->getAs() : 0.0; + if(!self->fld_control["limitHigh"].as(*hi)) + *hi = 0.0; } } else { *lo = *hi = 0.0; } - DEBUG(self, <precord->name<<" "<channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)); +// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)); return 0; }CATCH() return -1; @@ -289,19 +402,19 @@ long pvaGetGraphicLimits(const DBLINK *plink, double *lo, double *hi) CHECK_VALID(); if(self->fld_display) { - pvd::PVScalar::const_shared_pointer value; + Value value; if(lo) { - value = std::tr1::static_pointer_cast(self->fld_display->getSubField("limitLow")); - *lo = value ? value->getAs() : 0.0; + if(!self->fld_display["limitLow"].as(*lo)) + *lo = 0.0; } if(hi) { - value = std::tr1::static_pointer_cast(self->fld_display->getSubField("limitHigh")); - *hi = value ? value->getAs() : 0.0; + if(!self->fld_display["limitHigh"].as(*hi)) + *hi = 0.0; } } else { *lo = *hi = 0.0; } - DEBUG(self, <precord->name<<" "<channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)); +// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)); return 0; }CATCH() return -1; @@ -314,7 +427,7 @@ long pvaGetAlarmLimits(const DBLINK *plink, double *lolo, double *lo, //Guard G(self->lchan->lock); //CHECK_VALID(); *lolo = *lo = *hi = *hihi = 0.0; - DEBUG(self, <precord->name<<" "<channelName<<" "<<(lolo ? *lolo : 0)<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)<<" "<<(hihi ? *hihi : 0)); +// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<<(lolo ? *lolo : 0)<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)<<" "<<(hihi ? *hihi : 0)); return 0; }CATCH() return -1; @@ -328,7 +441,7 @@ long pvaGetPrecision(const DBLINK *plink, short *precision) // No sane way to recover precision from display.format string. *precision = 0; - DEBUG(self, <precord->name<<" "<channelName<<" "<precord->name<<" "<<__func__<<" "<channelName<<" "<fld_display) { - pvd::PVString::const_shared_pointer value(std::tr1::static_pointer_cast(self->fld_display->getSubField("units"))); - if(value) { - const std::string& egu = value->get(); - strncpy(units, egu.c_str(), unitsSize); - } + std::string egu; + if(units && self->fld_display.as(egu)) { + strncpy(units, egu.c_str(), unitsSize-1u); + units[unitsSize-1u] = '\0'; } else if(units) { units[0] = '\0'; } units[unitsSize-1] = '\0'; - DEBUG(self, <precord->name<<" "<channelName<<" "<precord->name<<" "<<__func__<<" "<channelName<<" "<snap_severity ? LINK_ALARM : NO_ALARM; } - DEBUG(self, <precord->name<<" "<channelName<<" "<<(severity ? *severity : 0)<<" "<<(status ? *status : 0)); +// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<<(severity ? *severity : 0)<<" "<<(status ? *status : 0)); return 0; }CATCH() return -1; @@ -388,31 +499,14 @@ long pvaGetTimeStamp(const DBLINK *plink, epicsTimeStamp *pstamp) *pstamp = self->snap_time; } - DEBUG(self, <precord->name<<" "<channelName<<" "<<(pstamp ? pstamp->secPastEpoch : 0)<<":"<<(pstamp ? pstamp->nsec: 0)); +// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<<(pstamp ? pstamp->secPastEpoch : 0)<<":"<<(pstamp ? pstamp->nsec: 0)); return 0; }CATCH() return -1; } -// note that we handle DBF_ENUM differently than in pvif.cpp -pvd::ScalarType DBR2PVD(short dbr) -{ - switch(dbr) { -#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBR_##DBFTYPE: return pvd::pv##PVACODE; -#define CASE_SKIP_BOOL -#define CASE_REAL_INT64 -#include "pv/typemap.h" -#undef CASE_SKIP_BOOL -#undef CASE_REAL_INT64 -#undef CASE - case DBF_ENUM: return pvd::pvUShort; - case DBF_STRING: return pvd::pvString; - } - throw std::invalid_argument("Unsupported DBR code"); -} - long pvaPutValueX(DBLINK *plink, short dbrType, - const void *pbuffer, long nRequest, bool wait) + const void *pbuffer, long nRequest, bool wait) { TRY { (void)self; @@ -421,32 +515,42 @@ long pvaPutValueX(DBLINK *plink, short dbrType, if(nRequest < 0) return -1; if(!self->retry && !self->valid()) { - DEBUG(self, <channelName<<" !valid"); +// DEBUG(self, <<__func__<<" "<channelName<<" !valid"); return -1; } - pvd::ScalarType stype = DBR2PVD(dbrType); - - pvd::shared_vector buf; + shared_array buf; if(dbrType == DBF_STRING) { const char *sbuffer = (const char*)pbuffer; - pvd::shared_vector sval(nRequest); + shared_array sval(nRequest); for(long n=0; nput_scratch = pvd::static_shared_vector_cast(pvd::freeze(sval)); + self->put_scratch = sval.freeze().castTo(); } else { - pvd::shared_vector val(pvd::ScalarTypeFunc::allocArray(stype, size_t(nRequest))); - - assert(size_t(dbValueSize(dbrType)*nRequest) == val.size()); + ArrayType dtype; + switch(dbrType) { + case DBR_CHAR: dtype = ArrayType::Int8; break; + case DBR_SHORT: dtype = ArrayType::Int16; break; + case DBR_LONG: dtype = ArrayType::Int32; break; + case DBR_INT64: dtype = ArrayType::Int64; break; + case DBR_UCHAR: dtype = ArrayType::UInt8; break; + case DBR_USHORT: dtype = ArrayType::UInt16; break; + case DBR_ULONG: dtype = ArrayType::UInt32; break; + case DBR_UINT64: dtype = ArrayType::UInt64; break; + case DBR_FLOAT: dtype = ArrayType::Float32; break; + case DBR_DOUBLE: dtype = ArrayType::Float64; break; + default: + return S_db_badDbrtype; + } - memcpy(val.data(), pbuffer, val.size()); + auto val(detail::copyAs(dtype, dtype, pbuffer, size_t(nRequest))); - self->put_scratch = pvd::freeze(val); + self->put_scratch = val.freeze().castTo(); } self->used_scratch = true; @@ -458,7 +562,7 @@ long pvaPutValueX(DBLINK *plink, short dbrType, if(!self->defer) self->lchan->put(); - DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->op_put.valid()); +// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<lchan->op_put.valid()); return 0; }CATCH() return -1; @@ -488,7 +592,7 @@ void pvaScanForward(DBLINK *plink) // FWD_LINK is never deferred, and always results in a Put self->lchan->put(true); - DEBUG(self, <precord->name<<" "<channelName<<" "<lchan->op_put.valid()); +// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<lchan->op_put.valid()); }CATCH() } @@ -522,3 +626,4 @@ lset pva_lset = { }; } //namespace pvalink +} // namespace pvxlink diff --git a/ioc/pvalink_null.cpp b/ioc/pvalink_null.cpp index 1706c973d..4dce607d6 100644 --- a/ioc/pvalink_null.cpp +++ b/ioc/pvalink_null.cpp @@ -1,3 +1,8 @@ +/* + * 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 From c2d3b0b3031cd6a8c9063df34cb74aa04f1aa620 Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Thu, 14 Sep 2023 16:12:50 +0200 Subject: [PATCH 3/5] pvalink: porting part 2 Fix pvaGetValue for string scalars Remove pvaLink* variables Move close() call to pvaGlobal_t from worker queue Removed latch state Update .gitignore to ignore VS code configuration Add lset(pva) support for base 7.x Remove pvalink support for base 3.x Update cached value object in pvaLinkChannel::run Removing queued state from pvaLikeChannel Add debug functionality Rename internal fields to match json spec prepare for puts Fix array response size Add tests for pvalink properties --- .gitignore | 1 + ioc/Makefile | 12 +- ioc/pvalink.cpp | 26 ++-- ioc/pvalink.h | 11 +- ioc/pvalink_channel.cpp | 174 +++++++++++---------- ioc/pvalink_jlif.cpp | 36 ++--- ioc/pvalink_link.cpp | 12 +- ioc/pvalink_lset.cpp | 61 ++++---- ioc/pvalink_null.cpp | 21 --- ioc/pvxs/iochooks.h | 9 ++ ioc/{pvxsIoc.dbd => pvxs3x.dbd} | 0 ioc/pvxs7x.dbd | 11 ++ test/Makefile | 7 + test/testioc.h | 1 + test/testpvalink.cpp | 266 ++++++++++++++++++++++++++++++++ test/testpvalink.db | 93 +++++++++++ 16 files changed, 564 insertions(+), 177 deletions(-) delete mode 100644 ioc/pvalink_null.cpp rename ioc/{pvxsIoc.dbd => pvxs3x.dbd} (100%) create mode 100644 ioc/pvxs7x.dbd create mode 100644 test/testpvalink.cpp create mode 100644 test/testpvalink.db diff --git a/.gitignore b/.gitignore index 293846184..3b2a40b95 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ __pycache__/ *.orig *.log .*.swp +.vscode \ No newline at end of file diff --git a/ioc/Makefile b/ioc/Makefile index 225566e61..5f9f7c884 100644 --- a/ioc/Makefile +++ b/ioc/Makefile @@ -63,11 +63,6 @@ pvxsIoc_SRCS += pvalink_jlif.cpp pvxsIoc_SRCS += pvalink_link.cpp pvxsIoc_SRCS += pvalink_lset.cpp -else # BASE_7_0 - -pvxsIoc_SRCS += dummygroup.cpp -pvxsIoc_SRCS += pvalink_null.cpp - endif # BASE_7_0 pvxsIoc_LIBS += $(EPICS_BASE_IOC_LIBS) @@ -90,3 +85,10 @@ include $(TOP)/configure/RULES_PVXS_MODULE #---------------------------------------- # ADD RULES AFTER THIS LINE +ifdef BASE_7_0 +../O.Common/pvxsIoc.dbd: ../pvxs7x.dbd + $(CP) $< $@ +else +../O.Common/pvxsIoc.dbd: ../pvxs3x.dbd + $(CP) $< $@ +endif \ No newline at end of file diff --git a/ioc/pvalink.cpp b/ioc/pvalink.cpp index 4ec1cdd00..24eb59f85 100644 --- a/ioc/pvalink.cpp +++ b/ioc/pvalink.cpp @@ -25,22 +25,21 @@ #include #include -#include /* redirects stdout/stderr */ +#include #include "pvalink.h" #include "dblocker.h" #include "dbentry.h" #include "iocshcommand.h" +#include "utilpvt.h" +#include /* redirects stdout/stderr; include after util.h from libevent */ #include /* defines epicsExportSharedSymbols */ #if EPICS_VERSION_INT>=VERSION_INT(7,0,6,0) # define HAVE_SHUTDOWN_HOOKS #endif -int pvaLinkDebug; -int pvaLinkIsolate; - namespace pvxs { namespace ioc { using namespace pvxlink; @@ -53,7 +52,7 @@ static void shutdownStep1() // no locking here as we assume that shutdown doesn't race startup if(!pvaGlobal) return; - pvaGlobal->queue.close(); + pvaGlobal->close(); } // Cleanup pvaGlobal, including PVA client and QSRV providers ahead of PDB cleanup @@ -127,7 +126,11 @@ void initPVALink(initHookState state) } else if(state==initHookAfterInitDatabase) { // TODO "local" provider - pvaGlobal->provider_remote = client::Config().build(); + if (inUnitTest()) { + pvaGlobal->provider_remote = ioc::server().clientConfig().build(); + } else { + pvaGlobal->provider_remote = client::Config().build(); + } } else if(state==initHookAfterIocBuilt) { // after epicsExit(exitDatabase) @@ -249,7 +252,7 @@ void dbpvar(const char *precordname, int level) } nchans++; - if(chan->connected_latched) + if(chan->state == pvaLinkChannel::Connected) nconn++; if(!precordname) @@ -258,7 +261,7 @@ void dbpvar(const char *precordname, int level) if(level<=0) continue; - if(level>=2 || (!chan->connected_latched && level==1)) { + if(level>=2 || (chan->state != pvaLinkChannel::Connected && level==1)) { if(chan->key.first.size()<=28) { printf("%28s ", chan->key.first.c_str()); } else { @@ -266,7 +269,7 @@ void dbpvar(const char *precordname, int level) } printf("conn=%c %zu disconnects, %zu type changes", - chan->connected_latched?'T':'F', + chan->state == pvaLinkChannel::Connected?'T':'F', chan->num_disconnect, chan->num_type_change); if(chan->op_put) { @@ -302,14 +305,14 @@ void dbpvar(const char *precordname, int level) printf("%*s%s.%s", 30, "", pval->plink ? pval->plink->precord->name : "", fldname); - switch(pval->pp) { + switch(pval->proc) { case pvaLinkConfig::NPP: printf(" NPP"); break; case pvaLinkConfig::Default: printf(" Def"); break; case pvaLinkConfig::PP: printf(" PP"); break; case pvaLinkConfig::CP: printf(" CP"); break; case pvaLinkConfig::CPP: printf(" CPP"); break; } - switch(pval->ms) { + switch(pval->sevr) { case pvaLinkConfig::NMS: printf(" NMS"); break; case pvaLinkConfig::MS: printf(" MS"); break; case pvaLinkConfig::MSI: printf(" MSI"); break; @@ -351,6 +354,5 @@ void installPVAAddLinkHook() extern "C" { using pvxs::ioc::installPVAAddLinkHook; epicsExportRegistrar(installPVAAddLinkHook); - epicsExportAddress(int, pvaLinkDebug); epicsExportAddress(int, pvaLinkNWorkers); } diff --git a/ioc/pvalink.h b/ioc/pvalink.h index d144a8acd..d79749e64 100644 --- a/ioc/pvalink.h +++ b/ioc/pvalink.h @@ -37,8 +37,6 @@ #include "dbmanylocker.h" extern "C" { - extern int pvaLinkDebug; - extern int pvaLinkIsolate; extern int pvaLinkNWorkers; } @@ -70,12 +68,12 @@ struct pvaLinkConfig : public jlink PP, // for put() only, For monitor, treated as NPP CP, // for monitor only, put treats as pp CPP, // for monitor only, put treats as pp - } pp = Default; + } proc = Default; enum ms_t { NMS, MS, MSI, - } ms = NMS; + } sevr = NMS; bool defer = false; bool pipeline = false; @@ -115,6 +113,7 @@ struct pvaGlobal_t : private epicsThreadRunable { pvaGlobal_t(); virtual ~pvaGlobal_t(); + void close(); }; extern pvaGlobal_t *pvaGlobal; @@ -140,11 +139,9 @@ struct pvaLinkChannel : public epicsThreadRunable Disconnected, Connecting, Connected, - } state = Disconnected, - state_latched = Disconnected; + } state = Disconnected; bool isatomic = false; - bool queued = false; // added to WorkQueue bool debug = false; // set if any jlink::debug is set typedef std::set after_put_t; after_put_t after_put; diff --git a/ioc/pvalink_channel.cpp b/ioc/pvalink_channel.cpp index 7b7096398..d86bc8d5a 100644 --- a/ioc/pvalink_channel.cpp +++ b/ioc/pvalink_channel.cpp @@ -5,11 +5,16 @@ */ #include +#include + +#include #include "pvalink.h" #include "dblocker.h" #include "dbmanylocker.h" +DEFINE_LOGGER(_logger, "ioc.pvalink.channel"); + int pvaLinkNWorkers = 1; namespace pvxlink { @@ -33,12 +38,6 @@ pvaGlobal_t::pvaGlobal_t() pvaGlobal_t::~pvaGlobal_t() { - { - Guard G(lock); - workerStop = true; - } - queue.push(std::weak_ptr()); - worker.exitWait(); } void pvaGlobal_t::run() @@ -57,6 +56,16 @@ void pvaGlobal_t::run() } +void pvaGlobal_t::close() +{ + { + Guard G(lock); + workerStop = true; + } + queue.push(std::weak_ptr()); + worker.exitWait(); +} + size_t pvaLinkChannel::num_instances; size_t pvaLink::num_instances; @@ -90,16 +99,13 @@ void pvaLinkChannel::open() Guard G(lock); op_mon = pvaGlobal->provider_remote.monitor(key.first) - .maskConnected(false) + .maskConnected(true) .maskDisconnected(false) .rawRequest(pvRequest) .event([this](const client::Subscription&) { - Guard G(lock); - if(!queued) { - pvaGlobal->queue.push(shared_from_this()); - queued = true; - } + log_debug_printf(_logger, "Received message: %s %s\n", key.first.c_str(), key.second.c_str()); + pvaGlobal->queue.push(shared_from_this()); }) .exec(); providerName = "remote"; @@ -130,26 +136,37 @@ Value linkBuildPut(pvaLinkChannel *self, Value&& prototype) if(value.type().isarray()) { value = tosend; + } else { + if (tosend.empty()) + continue; // TODO: Signal error - } else if(value.type()==TypeCode::Struct && value.id()=="enum_t") { - - if(tosend.empty()) - continue; // TODO: signal error - - if(tosend.original_type()==ArrayType::String) { - auto sarr(tosend.castTo()); - // TODO: choices... - value["index"] = sarr[0]; - - } else { - auto iarr(tosend.castTo()); - value["index"] = iarr[0]; + if (value.type() == TypeCode::Struct && value.id() == "enum_t") { + value = value["index"]; // We want to assign to the index for enum types } - } else { // assume scalar + switch (tosend.original_type()) + { + case ArrayType::Int8: value = tosend.castTo()[0]; break; + case ArrayType::Int16: value = tosend.castTo()[0]; break; + case ArrayType::Int32: value = tosend.castTo()[0]; break; + case ArrayType::Int64: value = tosend.castTo()[0]; break; + case ArrayType::UInt8: value = tosend.castTo()[0]; break; + case ArrayType::UInt16: value = tosend.castTo()[0]; break; + case ArrayType::UInt32: value = tosend.castTo()[0]; break; + case ArrayType::UInt64: value = tosend.castTo()[0]; break; + case ArrayType::Float32: value = tosend.castTo()[0]; break; + case ArrayType::Float64: value = tosend.castTo()[0]; break; + case ArrayType::String: value = tosend.castTo()[0]; break; + case ArrayType::Bool: + case ArrayType::Null: + case ArrayType::Value: + std::ostringstream buffer; + buffer << tosend.original_type(); + log_exc_printf(_logger, "Unsupported type %s\n", buffer.str().c_str()); + } } } -// DEBUG(this, <key.first.c_str()); return top; } @@ -168,7 +185,7 @@ void linkPutDone(pvaLinkChannel *self, client::Result&& result) { Guard G(self->lock); -// DEBUG(this, <key.first.c_str(), ok ? "OK" : "Not OK"); needscans = !self->after_put.empty(); self->op_put.reset(); @@ -179,6 +196,8 @@ 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); } @@ -207,15 +226,13 @@ void pvaLinkChannel::put(bool force) if(!link->used_scratch) continue; - shared_array temp; - temp.swap(link->put_scratch); + link->put_queue = std::move(link->put_scratch); link->used_scratch = false; - temp.swap(link->put_queue); link->used_queue = true; doit = true; - switch(link->pp) { + switch(link->proc) { case pvaLink::NPP: reqProcess |= 1; break; @@ -243,7 +260,7 @@ void pvaLinkChannel::put(bool force) } pvReq["record._options.process"] = proc; -// DEBUG(this, <provider_remote.put(key.first) @@ -276,6 +293,7 @@ void pvaLinkChannel::AfterPut::run() { dbCommon *prec = *it; dbScanLock(prec); + log_debug_printf(_logger, "AfterPut start processing %s\n", prec->name); if(prec->pact) { // complete async. processing (prec)->rset->process(prec); @@ -297,8 +315,11 @@ void pvaLinkChannel::run_dbProcess(size_t idx) if(scan_check_passive[idx] && precord->scan!=0) { return; - } else if(connected_latched && !op_mon.changed.logical_and(scan_changed[idx])) { - return; + // TODO: This relates to caching of the individual links and comparing it to + // the posted monitor. This is, as I understand it, an optimisation and + // we can sort of ignore it for now. + //} else if(state_latched == Connected && !op_mon.changed.logical_and(scan_changed[idx])) { + // return; } else if (precord->pact) { if (precord->tpro) @@ -317,20 +338,20 @@ void pvaLinkChannel::run() { Guard G(lock); - queued = false; + log_debug_printf(_logger,"Running task %s\n", this->key.first.c_str()); Value top; try { top = op_mon->pop(); if(!top) { + log_debug_printf(_logger, "Queue empty %s\n", this->key.first.c_str()); run_done.signal(); return; } - - } catch(client::Connected&) { - state = Connecting; - + state = Connected; } catch(client::Disconnect&) { + log_debug_printf(_logger, "PVA link %s received disonnection event\n", this->key.first.c_str()); + state = Disconnected; num_disconnect++; @@ -349,9 +370,33 @@ void pvaLinkChannel::run() // and may get back the same PVStructure. } catch(std::exception& e) { - // TODO: log + errlogPrintf("pvalinkChannel::run: Unexpected exception while reading from monitor queue: %s\n", e.what()); } + if (state == Connected) { + // Fetch the data from the incoming monitor + if (root.equalType(top)) + { + log_debug_printf(_logger, "pvalinkChannel update value %s\n", this->key.first.c_str()); + + root.assign(top); + } + else + { + log_debug_printf(_logger, "pvalinkChannel %s update type\n", this->key.first.c_str()); + root = top; + num_type_change++; + + for (links_t::iterator it(links.begin()), end(links.end()); it != end; ++it) + { + pvaLink *link = *it; + link->onTypeChange(); + } + } + + requeue = true; + } + if(links_changed) { // a link has been added or removed since the last update. // rebuild our cached list of records to (maybe) process. @@ -373,51 +418,18 @@ void pvaLinkChannel::run() // NPP and none/Default don't scan // PP, CP, and CPP do scan // PP and CPP only if SCAN=Passive - if(link->pp != pvaLink::PP && link->pp != pvaLink::CPP && link->pp != pvaLink::CP) + if(link->proc != pvaLink::PP && link->proc != pvaLink::CPP && link->proc != pvaLink::CP) continue; scan_records.push_back(link->plink->precord); - scan_check_passive.push_back(link->pp != pvaLink::CP); + scan_check_passive.push_back(link->proc != pvaLink::CP); } + log_debug_printf(_logger, "Links changed, scan_records size = %lu\n", scan_records.size()); + atomic_lock = ioc::DBManyLock(scan_records); links_changed = false; - - state = Connected; - } - - // handle next update from monitor queue. - // still under lock to safeguard concurrent calls to lset functions - if(connected && root) { -// DEBUG(this, <onTypeChange(); - } - - previous_root = root; - } - - // at this point we know we will re-queue, but not immediately - // so an expected error won't get us stuck in a tight loop. - requeue = queued = connected_latched; - - if(links_changed) { } } @@ -433,15 +445,19 @@ void pvaLinkChannel::run() } else { for(size_t i=0, N=scan_records.size(); iname); + ioc::DBLocker L(scan_records[i]); run_dbProcess(i); } } if(requeue) { + log_debug_printf(_logger, "Requeueing %s\n", key.first.c_str()); // re-queue until monitor queue is empty - pvaGlobal->queue.add(shared_from_this()); + pvaGlobal->queue.push(shared_from_this()); } else { + log_debug_printf(_logger, "Run done instead of requeue %s\n", key.first.c_str()); run_done.signal(); } } diff --git a/ioc/pvalink_jlif.cpp b/ioc/pvalink_jlif.cpp index 85cd6ac60..909034946 100644 --- a/ioc/pvalink_jlif.cpp +++ b/ioc/pvalink_jlif.cpp @@ -68,9 +68,9 @@ jlif_result pva_parse_null(jlink *pjlink) if(pvt->parseDepth!=1) { // ignore } else if(pvt->jkey == "proc") { - pvt->pp = pvaLinkConfig::Default; + pvt->proc = pvaLinkConfig::Default; } else if(pvt->jkey == "sevr") { - pvt->ms = pvaLinkConfig::NMS; + pvt->sevr = pvaLinkConfig::NMS; } else if(pvt->jkey == "local") { pvt->local = false; // alias for local:false } else if(pvt->debug) { @@ -90,9 +90,9 @@ jlif_result pva_parse_bool(jlink *pjlink, int val) if(pvt->parseDepth!=1) { // ignore } else if(pvt->jkey == "proc") { - pvt->pp = val ? pvaLinkConfig::PP : pvaLinkConfig::NPP; + pvt->proc = val ? pvaLinkConfig::PP : pvaLinkConfig::NPP; } else if(pvt->jkey == "sevr") { - pvt->ms = val ? pvaLinkConfig::MS : pvaLinkConfig::NMS; + pvt->sevr = val ? pvaLinkConfig::MS : pvaLinkConfig::NMS; } else if(pvt->jkey == "defer") { pvt->defer = !!val; } else if(pvt->jkey == "pipeline") { @@ -149,15 +149,15 @@ jlif_result pva_parse_string(jlink *pjlink, const char *val, size_t len) } else if(pvt->jkey=="proc") { if(sval.empty()) { - pvt->pp = pvaLinkConfig::Default; + pvt->proc = pvaLinkConfig::Default; } else if(sval=="CP") { - pvt->pp = pvaLinkConfig::CP; + pvt->proc = pvaLinkConfig::CP; } else if(sval=="CPP") { - pvt->pp = pvaLinkConfig::CPP; + pvt->proc = pvaLinkConfig::CPP; } else if(sval=="PP") { - pvt->pp = pvaLinkConfig::PP; + pvt->proc = pvaLinkConfig::PP; } else if(sval=="NPP") { - pvt->pp = pvaLinkConfig::NPP; + pvt->proc = pvaLinkConfig::NPP; } else if(pvt->debug) { printf("pva link parsing unknown proc depth=%u key=\"%s\" value=\"%s\"\n", pvt->parseDepth, pvt->jkey.c_str(), sval.c_str()); @@ -165,16 +165,16 @@ jlif_result pva_parse_string(jlink *pjlink, const char *val, size_t len) } else if(pvt->jkey=="sevr") { if(sval=="NMS") { - pvt->ms = pvaLinkConfig::NMS; + pvt->sevr = pvaLinkConfig::NMS; } else if(sval=="MS") { - pvt->ms = pvaLinkConfig::MS; + pvt->sevr = pvaLinkConfig::MS; } else if(sval=="MSI") { - pvt->ms = pvaLinkConfig::MSI; + pvt->sevr = pvaLinkConfig::MSI; } else if(sval=="MSS") { // not sure how to handle mapping severity for MSS. // leave room for this to happen compatibly later by // handling as alias for MS until then. - pvt->ms = pvaLinkConfig::MS; + pvt->sevr = pvaLinkConfig::MS; } else if(pvt->debug) { printf("pva link parsing unknown sevr depth=%u key=\"%s\" value=\"%s\"\n", pvt->parseDepth, pvt->jkey.c_str(), sval.c_str()); @@ -228,14 +228,14 @@ void pva_report(const jlink *rpjlink, int lvl, int indent) if(!pval->fieldName.empty()) printf("|.%s", pval->fieldName.c_str()); - switch(pval->pp) { + switch(pval->proc) { case pvaLinkConfig::NPP: printf(" NPP"); break; case pvaLinkConfig::Default: printf(" Def"); break; case pvaLinkConfig::PP: printf(" PP"); break; case pvaLinkConfig::CP: printf(" CP"); break; case pvaLinkConfig::CPP: printf(" CPP"); break; } - switch(pval->ms) { + switch(pval->sevr) { case pvaLinkConfig::NMS: printf(" NMS"); break; case pvaLinkConfig::MS: printf(" MS"); break; case pvaLinkConfig::MSI: printf(" MSI"); break; @@ -254,7 +254,7 @@ void pva_report(const jlink *rpjlink, int lvl, int indent) // after open() Guard G(pval->lchan->lock); - printf(" conn=%c", pval->lchan->connected ? 'T' : 'F'); + printf(" conn=%c", pval->lchan->state == pvaLinkChannel::Connected ? 'T' : 'F'); if(pval->lchan->op_put) { printf(" Put"); } @@ -262,10 +262,6 @@ void pva_report(const jlink *rpjlink, int lvl, int indent) if(lvl>0) { printf(" #disconn=%zu prov=%s", pval->lchan->num_disconnect, pval->lchan->providerName.c_str()); } - if(lvl>1) { - printf(" inprog=%c", - pval->lchan->queued?'T':'F'); - } // if(lvl>5) { // std::ostringstream strm; // pval->lchan->chan.show(strm); diff --git a/ioc/pvalink_link.cpp b/ioc/pvalink_link.cpp index af4e4e30c..817197642 100644 --- a/ioc/pvalink_link.cpp +++ b/ioc/pvalink_link.cpp @@ -8,8 +8,12 @@ #include +#include + #include "pvalink.h" +DEFINE_LOGGER(_logger, "ioc.pvalink.link"); + namespace pvxlink { pvaLink::pvaLink() @@ -66,7 +70,7 @@ Value pvaLink::makeRequest() // caller must lock lchan->lock bool pvaLink::valid() const { - return lchan->connected_latched && lchan->root; + return lchan->state == pvaLinkChannel::Connected && lchan->root; } // caller must lock lchan->lock @@ -100,7 +104,7 @@ Value pvaLink::getSubField(const char *name) // call with channel lock held void pvaLink::onDisconnect() { -// DEBUG(this,<precord->name<<" disconnect"); + log_debug_printf(_logger, "%s disconnect\n", plink->precord->name); // TODO: option to remain queue'd while disconnected used_queue = used_scratch = false; @@ -108,9 +112,9 @@ void pvaLink::onDisconnect() void pvaLink::onTypeChange() { -// DEBUG(this,<precord->name<<" type change"); + log_debug_printf(_logger, "%s type change\n", plink->precord->name); - assert(lchan->connected_latched && !!lchan->root); // we should only be called when connected + assert(lchan->state == pvaLinkChannel::Connected && lchan->root); // we should only be called when connected fld_value = getSubField("value"); fld_seconds = getSubField("timeStamp.secondsPastEpoch"); diff --git a/ioc/pvalink_lset.cpp b/ioc/pvalink_lset.cpp index 42de52346..d471375a0 100644 --- a/ioc/pvalink_lset.cpp +++ b/ioc/pvalink_lset.cpp @@ -7,14 +7,15 @@ #include #include #include -#include // redirect stdout/stderr #include #include "dbentry.h" #include "pvalink.h" #include "utilpvt.h" -DEFINE_LOGGER(loglset, "pvxs.pvalink.lset"); +#include // redirect stdout/stderr; include after libevent/util.h + +DEFINE_LOGGER(_logger, "pvxs.pvalink.lset"); namespace pvxlink { namespace { @@ -25,11 +26,10 @@ using namespace pvxs; errlogPrintf("pvaLink %s fails %s: %s\n", __func__, plink->precord->name, e.what()); \ } -#define CHECK_VALID() if(!self->valid()) { /*DEBUG(self, <<__func__<<" "<channelName<<" !valid");*/ return -1;} +#define CHECK_VALID() if(!self->valid()) { log_debug_printf(_logger, "%s: %s not valid\n", __func__, self->channelName.c_str()); return -1;} dbfType getLinkType(DBLINK *plink) { - dbCommon *prec = plink->precord; ioc::DBEntry ent(plink->precord); for(long status = dbFirstField(ent, 0); !status; status = dbNextField(ent, 0)) { @@ -54,7 +54,7 @@ void pvaOpenLink(DBLINK *plink) } } -// DEBUG(self, <precord->name<<" OPEN "<channelName); + log_debug_printf(_logger, "%s OPEN %s\n", plink->precord->name, self->channelName.c_str()); // still single threaded at this point. // also, no pvaLinkChannel::lock yet @@ -65,7 +65,7 @@ void pvaOpenLink(DBLINK *plink) return; // nothing to do... auto pvRequest(self->makeRequest()); - pvaGlobal_t::channels_key_t key = std::make_pair(self->channelName, SB()<channelName, std::string(SB()< chan; bool doOpen = false; @@ -121,7 +121,7 @@ void pvaRemoveLink(struct dbLocker *locker, DBLINK *plink) { try { std::unique_ptr self((pvaLink*)plink->value.json.jlink); -// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName); + log_debug_printf(_logger, "%s: %s %s\n", __func__, plink->precord->name, self->channelName.c_str()); assert(self->alive); }CATCH() @@ -133,7 +133,7 @@ int pvaIsConnected(const DBLINK *plink) Guard G(self->lchan->lock); bool ret = self->valid(); -// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<precord->name, self->channelName.c_str()); return ret; }CATCH() @@ -208,7 +208,7 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, if(!self->valid()) { // disconnected - if(self->ms != pvaLink::NMS) { + if(self->sevr != pvaLink::NMS) { recGblSetSevr(plink->precord, LINK_ALARM, self->snap_severity); } // TODO: better capture of disconnect time @@ -216,7 +216,7 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, if(self->time) { plink->precord->time = self->snap_time; } -// DEBUG(self, <<__func__<<" "<channelName<<" !valid"); + log_debug_printf(_logger, "%s: %s not valid", __func__, self->channelName.c_str()); return -1; } @@ -235,7 +235,7 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, } else if(value.type().isarray()) { auto arr(value.as>()); - if(size_t(nReq) < arr.size()) + if(size_t(nReq) > arr.size()) nReq = arr.size(); if(arr.original_type()==ArrayType::String) { @@ -325,6 +325,7 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, auto sval(value.as()); strncpy(cbuf, sval.c_str(), MAX_STRING_SIZE-1u); cbuf[MAX_STRING_SIZE-1u] = '\0'; + break; } default: return S_db_badDbrtype; @@ -354,8 +355,8 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, self->snap_severity = NO_ALARM; } - if((self->snap_severity!=NO_ALARM && self->ms == pvaLink::MS) || - (self->snap_severity==INVALID_ALARM && self->ms == pvaLink::MSI)) + if((self->snap_severity!=NO_ALARM && self->sevr == pvaLink::MS) || + (self->snap_severity==INVALID_ALARM && self->sevr == pvaLink::MSI)) { recGblSetSevr(plink->precord, LINK_ALARM, self->snap_severity); } @@ -364,7 +365,7 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, plink->precord->time = self->snap_time; } -// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" OK"); + log_debug_printf(_logger, "%s: %s %s OK\n", __func__, plink->precord->name, self->channelName.c_str()); return 0; }CATCH() return -1; @@ -389,7 +390,8 @@ long pvaGetControlLimits(const DBLINK *plink, double *lo, double *hi) } else { *lo = *hi = 0.0; } -// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)); + log_debug_printf(_logger, "%s: %s %s %f %f\n", + __func__, plink->precord->name, self->channelName.c_str(), lo ? *lo : 0, hi ? *hi : 0); return 0; }CATCH() return -1; @@ -414,7 +416,8 @@ long pvaGetGraphicLimits(const DBLINK *plink, double *lo, double *hi) } else { *lo = *hi = 0.0; } -// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)); + log_debug_printf(_logger, "%s: %s %s %f %f\n", + __func__, plink->precord->name, self->channelName.c_str(), lo ? *lo : 0, hi ? *hi : 0); return 0; }CATCH() return -1; @@ -427,7 +430,9 @@ long pvaGetAlarmLimits(const DBLINK *plink, double *lolo, double *lo, //Guard G(self->lchan->lock); //CHECK_VALID(); *lolo = *lo = *hi = *hihi = 0.0; -// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<<(lolo ? *lolo : 0)<<" "<<(lo ? *lo : 0)<<" "<<(hi ? *hi : 0)<<" "<<(hihi ? *hihi : 0)); + log_debug_printf(_logger, "%s: %s %s %f %f %f %f\n", + __func__, plink->precord->name, self->channelName.c_str(), + lo ? *lo : 0, lolo ? *lolo : 0, hi ? *hi : 0, hihi ? *hihi : 0); return 0; }CATCH() return -1; @@ -441,7 +446,7 @@ long pvaGetPrecision(const DBLINK *plink, short *precision) // No sane way to recover precision from display.format string. *precision = 0; -// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<precord->name, self->channelName.c_str(), *precision); return 0; }CATCH() return -1; @@ -463,7 +468,7 @@ long pvaGetUnits(const DBLINK *plink, char *units, int unitsSize) units[0] = '\0'; } units[unitsSize-1] = '\0'; -// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<precord->name, self->channelName.c_str(), units); return 0; }CATCH() return -1; @@ -482,8 +487,8 @@ long pvaGetAlarm(const DBLINK *plink, epicsEnum16 *status, if(status) { *status = self->snap_severity ? LINK_ALARM : NO_ALARM; } - -// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<<(severity ? *severity : 0)<<" "<<(status ? *status : 0)); + log_debug_printf(_logger, "%s: %s %s %i %i\n", + __func__, plink->precord->name, self->channelName.c_str(), severity ? *severity : 0, status ? *status : 0); return 0; }CATCH() return -1; @@ -498,8 +503,7 @@ long pvaGetTimeStamp(const DBLINK *plink, epicsTimeStamp *pstamp) if(pstamp) { *pstamp = self->snap_time; } - -// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<<(pstamp ? pstamp->secPastEpoch : 0)<<":"<<(pstamp ? pstamp->nsec: 0)); + log_debug_printf(_logger, "%s: %s %s %i %i\n", __func__, plink->precord->name, self->channelName.c_str(), pstamp ? pstamp->secPastEpoch : 0, pstamp ? pstamp->nsec : 0); return 0; }CATCH() return -1; @@ -515,7 +519,7 @@ long pvaPutValueX(DBLINK *plink, short dbrType, if(nRequest < 0) return -1; if(!self->retry && !self->valid()) { -// DEBUG(self, <<__func__<<" "<channelName<<" !valid"); + log_debug_printf(_logger, "%s: %s not valid\n", __func__, self->channelName.c_str()); return -1; } @@ -562,7 +566,8 @@ long pvaPutValueX(DBLINK *plink, short dbrType, if(!self->defer) self->lchan->put(); -// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<lchan->op_put.valid()); + log_debug_printf(_logger, "%s: %s %s %s\n", __func__, plink->precord->name, self->channelName.c_str(), self->lchan->root.valid() ? "valid": "not valid"); + return 0; }CATCH() return -1; @@ -592,7 +597,8 @@ void pvaScanForward(DBLINK *plink) // FWD_LINK is never deferred, and always results in a Put self->lchan->put(true); -// DEBUG(self, <precord->name<<" "<<__func__<<" "<channelName<<" "<lchan->op_put.valid()); + log_debug_printf(_logger, "%s: %s %s %s\n", + __func__, plink->precord->name, self->channelName.c_str(), self->lchan->root.valid() ? "valid": "not valid"); }CATCH() } @@ -601,8 +607,6 @@ void pvaScanForward(DBLINK *plink) } //namespace -namespace pvalink { - lset pva_lset = { 0, 1, // non-const, volatile &pvaOpenLink, @@ -625,5 +629,4 @@ lset pva_lset = { //&pvaReportLink, }; -} //namespace pvalink } // namespace pvxlink diff --git a/ioc/pvalink_null.cpp b/ioc/pvalink_null.cpp deleted file mode 100644 index 4dce607d6..000000000 --- a/ioc/pvalink_null.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 - -static void installPVAAddLinkHook() {} - -struct jlif {} lsetPVA; - -extern "C" { -int pvaLinkDebug; -int pvaLinkNWorkers; - - epicsExportRegistrar(installPVAAddLinkHook); - epicsExportAddress(jlif, lsetPVA); - epicsExportAddress(int, pvaLinkDebug); - epicsExportAddress(int, pvaLinkNWorkers); -} diff --git a/ioc/pvxs/iochooks.h b/ioc/pvxs/iochooks.h index ca090dbd3..6d8e8c3b7 100644 --- a/ioc/pvxs/iochooks.h +++ b/ioc/pvxs/iochooks.h @@ -100,5 +100,14 @@ void testPrepare(); PVXS_IOC_API void testShutdown(); +PVXS_IOC_API +void testqsrvWaitForLinkEvent(struct link *plink); + +PVXS_IOC_API +void testqsrvShutdownOk(void); + +PVXS_IOC_API +void testqsrvCleanup(void); + }} // namespace pvxs::ioc #endif // PVXS_IOCHOOKS_H diff --git a/ioc/pvxsIoc.dbd b/ioc/pvxs3x.dbd similarity index 100% rename from ioc/pvxsIoc.dbd rename to ioc/pvxs3x.dbd diff --git a/ioc/pvxs7x.dbd b/ioc/pvxs7x.dbd new file mode 100644 index 000000000..e0ec7a6b3 --- /dev/null +++ b/ioc/pvxs7x.dbd @@ -0,0 +1,11 @@ +registrar(pvxsBaseRegistrar) +registrar(pvxsSingleSourceRegistrar) +registrar(pvxsGroupSourceRegistrar) +registrar(installPVAAddLinkHook) +link("pva", "lsetPVA") + +# from demo.cpp +device(waveform, CONSTANT, devWfPDBQ2Demo, "QSRV2 Demo") +device(longin, CONSTANT, devLoPDBQ2UTag, "QSRV2 Set UTag") +# from imagedemo.c +function(QSRV2_image_demo) diff --git a/test/Makefile b/test/Makefile index 2abca671e..47714460a 100644 --- a/test/Makefile +++ b/test/Makefile @@ -11,6 +11,7 @@ include $(TOP)/configure/CONFIG_PVXS_VERSION # access to private headers USR_CPPFLAGS += -I$(TOP)/src +USR_CPPFLAGS += -I$(TOP)/ioc PROD_LIBS = pvxs Com @@ -120,6 +121,12 @@ ifdef BASE_7_0 TESTPROD_HOST += benchdata benchdata_SRCS += benchdata.cpp +TESTPROD_HOST += testpvalink +testpvalink_SRCS += testpvalink.cpp +testpvalink_SRCS += testioc_registerRecordDeviceDriver.cpp +testpvalink_LIBS += pvxsIoc pvxs $(EPICS_BASE_IOC_LIBS) +TESTS += testpvalink + endif ifdef BASE_3_15 diff --git a/test/testioc.h b/test/testioc.h index 39f199402..05a5b3a3e 100644 --- a/test/testioc.h +++ b/test/testioc.h @@ -33,6 +33,7 @@ class TestIOC { if(running) { pvxs::ioc::testShutdown(); testIocShutdownOk(); + running = false; } } ~TestIOC() { diff --git a/test/testpvalink.cpp b/test/testpvalink.cpp new file mode 100644 index 000000000..d66bccc42 --- /dev/null +++ b/test/testpvalink.cpp @@ -0,0 +1,266 @@ + +#include +#include +#include +#include +#include + +//#include +//#include "utilities.h" +#include "dblocker.h" +#include "pvxs/iochooks.h" +#include "pvalink.h" +#include "testioc.h" +//#include "pv/qsrv.h" + +using namespace pvxs::ioc; +using namespace pvxs; + +namespace +{ + void testGet() + { + testDiag("==== testGet ===="); + + longinRecord *i1 = (longinRecord *)testdbRecordPtr("src:i1"); + + while (!dbIsLinkConnected(&i1->inp)) + testqsrvWaitForLinkEvent(&i1->inp); + + testdbGetFieldEqual("target:i.VAL", DBF_LONG, 42L); + + testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 0L); // value before first process + + testdbGetFieldEqual("src:i1.INP", DBF_STRING, "{\"pva\":\"target:i\"}"); + + testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L); + + testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 42L); + + testdbPutFieldOk("src:i1.INP", DBF_STRING, "{\"pva\":\"target:ai\"}"); + + while (!dbIsLinkConnected(&i1->inp)) + testqsrvWaitForLinkEvent(&i1->inp); + + testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 42L); // changing link doesn't automatically process + + testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L); + + testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 4L); // now it's changed + } + + void testFieldLinks() { + + longinRecord *i1 = (longinRecord *)testdbRecordPtr("src:i1"); + + testDiag("==== Test field links ===="); + + std::string pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"field\":\"display.precision\"}}"; + testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length()+1, pv_name.c_str()); + + while (!dbIsLinkConnected(&i1->inp)) + testqsrvWaitForLinkEvent(&i1->inp); + + testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 4L); // changing link doesn't automatically process + + testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L); + + testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 2L); // changing link doesn't automatically process + + } + + void testProc() + { + + longinRecord *i1 = (longinRecord *)testdbRecordPtr("src:i1"); + + testDiag("==== Test proc settings ===="); + + // Set it to CPP + std::string pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"proc\":\"CPP\"}}"; + testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length()+1, pv_name.c_str()); + + while (!dbIsLinkConnected(&i1->inp)) + testqsrvWaitForLinkEvent(&i1->inp); + + // Link should read old value again + testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 4L); + + testdbPutFieldOk("target:ai", DBF_FLOAT, 5.0); + + // We are already connected at this point, wait for the update. + testqsrvWaitForLinkEvent(&i1->inp); + + // now it's changed + testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 5L); + } + + void testSevr() + { + longinRecord *i1 = (longinRecord *)testdbRecordPtr("src:i1"); + + testDiag("==== Test severity forwarding (NMS, MS, MSI) ===="); + + std::string pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"sevr\":\"NMS\"}}"; + testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length() + 1, pv_name.c_str()); + + while (!dbIsLinkConnected(&i1->inp)) + testqsrvWaitForLinkEvent(&i1->inp); + + testdbPutFieldOk("target:ai.LOLO", DBF_FLOAT, 5.0); + testdbPutFieldOk("target:ai.LLSV", DBF_STRING, "MAJOR"); + testdbPutFieldOk("target:ai", DBF_FLOAT, 0.0); + + testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L); + testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevNone); + + pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"sevr\":\"MS\"}}"; + testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length() + 1, pv_name.c_str()); + + while (!dbIsLinkConnected(&i1->inp)) + testqsrvWaitForLinkEvent(&i1->inp); + + testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L); + testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevMajor); + + pv_name = "{\"pva\":{\"pv\":\"target:mbbi\",\"sevr\":\"MSI\"}}"; + testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length() + 1, pv_name.c_str()); + + while (!dbIsLinkConnected(&i1->inp)) + testqsrvWaitForLinkEvent(&i1->inp); + + testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L); + testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevNone); + + testdbPutFieldOk("target:ai", DBF_FLOAT, 1.0); + testqsrvWaitForLinkEvent(&i1->inp); + testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L); + testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevInvalid); + } + + void testPut() + { + testDiag("==== testPut ===="); + + longoutRecord *o2 = (longoutRecord *)testdbRecordPtr("src:o2"); + + while (!dbIsLinkConnected(&o2->out)) + testqsrvWaitForLinkEvent(&o2->out); + + testdbGetFieldEqual("target:i2.VAL", DBF_LONG, 43L); + testdbGetFieldEqual("src:o2.VAL", DBF_LONG, 0L); + testdbGetFieldEqual("src:o2.OUT", DBF_STRING, "{\"pva\":\"target:i2\"}"); + + testdbPutFieldOk("src:o2.VAL", DBF_LONG, 14L); + + testqsrvWaitForLinkEvent(&o2->out); + testqsrvWaitForLinkEvent(&o2->out); + + testdbGetFieldEqual("target:i2.VAL", DBF_LONG, 14L); + testdbGetFieldEqual("src:o2.VAL", DBF_LONG, 14L); + } + + void testStrings() + { + testDiag("==== testStrings ===="); + + stringoutRecord *so = (stringoutRecord *)testdbRecordPtr("src:str"); + + testdbGetFieldEqual("target:str1", DBF_STRING, "foo"); + testdbPutFieldOk("target:str1.PROC", DBF_LONG, 1L); + testdbGetFieldEqual("target:str1", DBF_STRING, "bar"); + + testdbPutFieldOk("src:str.OUT", DBF_STRING, "{pva : \"target:str2\"}"); + + while (!dbIsLinkConnected(&so->out)) + testqsrvWaitForLinkEvent(&so->out); + + testdbPutFieldOk("src:str.PROC", DBF_LONG, 1L); + + testqsrvWaitForLinkEvent(&so->out); + + testdbGetFieldEqual("target:str2", DBF_STRING, "bar"); + } + + void testArrays() + { + aaoRecord *aao = (aaoRecord *)testdbRecordPtr("source:aao"); + testDiag("==== testArrays ===="); + static const epicsFloat32 input_arr[] = {1, 2, -1, 1.2, 0}; + testdbPutArrFieldOk("source:aao", DBR_FLOAT, 5, input_arr); + + testdbGetArrFieldEqual("target:aai_inp", DBF_CHAR, 10, 0, NULL); + + testqsrvWaitForLinkEvent(&aao->out); + testqsrvWaitForLinkEvent(&aao->out); + + static const epicsInt8 expected_char[] = {1, 2, -1, 1, 0}; + testdbPutFieldOk("target:aai_inp.PROC", DBF_LONG, 1L); + testdbGetArrFieldEqual("target:aai_inp", DBF_CHAR, 10, 5, expected_char); + + static const epicsUInt32 expected_ulong[] = {1L, 2L, 4294967295L, 1L, 0}; + testdbGetArrFieldEqual("target:aai_inp", DBF_ULONG, 10, 5, expected_ulong); + } + + void testPutAsync() + { +#ifdef USE_MULTILOCK + testDiag("==== testPutAsync ===="); + + longoutRecord *trig = (longoutRecord *)testdbRecordPtr("async:trig"); + + while (!dbIsLinkConnected(&trig->out)) + testqsrvWaitForLinkEvent(&trig->out); + + testMonitor *done = testMonitorCreate("async:after", DBE_VALUE, 0); + + testdbPutFieldOk("async:trig.PROC", DBF_LONG, 1); + testMonitorWait(done); + + testdbGetFieldEqual("async:trig", DBF_LONG, 1); + testdbGetFieldEqual("async:slow", DBF_LONG, 1); // pushed from async:trig + testdbGetFieldEqual("async:slow2", DBF_LONG, 2); + testdbGetFieldEqual("async:after", DBF_LONG, 3); + +#else + testSkip(5, "Not USE_MULTILOCK"); +#endif + } + +} // namespace + +extern "C" void testioc_registerRecordDeviceDriver(struct dbBase *); + +MAIN(testpvalink) +{ + testPlan(49); + testSetup(); + + try + { + TestIOC IOC; + + testdbReadDatabase("testioc.dbd", NULL, NULL); + testioc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("testpvalink.db", NULL, NULL); + + IOC.init(); + testGet(); + testFieldLinks(); + testProc(); + testSevr(); + testPut(); + testStrings(); + testArrays(); + (void)testPutAsync; + testqsrvShutdownOk(); + IOC.shutdown(); + testqsrvCleanup(); + } + catch (std::exception &e) + { + testFail("Unexpected exception: %s", e.what()); + } + // call epics atexits explicitly as workaround for c++ static dtor issues... + epicsExit(testDone()); +} diff --git a/test/testpvalink.db b/test/testpvalink.db new file mode 100644 index 000000000..793b22f9d --- /dev/null +++ b/test/testpvalink.db @@ -0,0 +1,93 @@ + +# used by testGet(), testFieldLinks, testProc, testSevr +record(longin, "target:i") { + field(VAL, "42") +} +record(ai, "target:ai") { + field(VAL, "4.0") + field(FLNK, "target:mbbi") + field(PREC, "2") +} + +record(longin, "src:i1") { + field(INP, {"pva":"target:i"}) +} + +record(mbbi, "target:mbbi") { + field(INP, "target:ai") + field(ZRSV, "NO_ALARM") + field(ONSV, "INVALID") +} + +# used by testPut() +record(longin, "target:i2") { + field(VAL, "43") +} + +record(longout, "src:o2") { + field(OUT, {"pva":"target:i2"}) +} + +# used by testPutAsync() +record(calc, "async:seq") { + field(CALC, "VAL+1") + field(VAL , "0") + field(TPRO, "1") +} + +record(stringin, "target:str1") { + field(INP, {pva: "src:str"}) + field(VAL, "foo") +} + +record(stringin, "target:str2") { + field(VAL, "baz") +} + +record(stringout, "src:str") { + field(VAL, "bar") +} + +record(longout, "async:trig") { + field(OMSL, "closed_loop") + field(DOL , "async:seq PP") + field(DTYP, "Async Soft Channel") + field(OUT , { "pva":{"pv":"async:slow.A", "proc":true} }) + field(FLNK, "async:after") + field(TPRO, "1") +} + +record(calcout, "async:slow") { + field(ODLY, "1") + field(CALC, "A") + field(FLNK, "async:slow2") + field(TPRO, "1") +} + +record(longin, "async:slow2") { + field(INP , "async:seq PP") + field(TPRO, "1") +} + +record(longin, "async:after") { + field(INP , "async:seq PP") + field(MDEL, "-1") + field(TPRO, "1") +} + +record(aao, "source:aao") { + field(OUT, {pva: "target:aai_out"}) + field(NELM, "5") + field(FTVL, "FLOAT") +} + +record(aai, "target:aai_inp") { + field(INP, {pva: "source:aao"}) + field(NELM, "10") + field(FTVL, "SHORT") +} + +record(aai, "target:aai_out") { + field(NELM, "2") + field(FTVL, "ULONG") +} \ No newline at end of file From 5a9ebb1e8518925c374396079a62057e4943c884 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Wed, 23 Aug 2023 10:50:33 +0200 Subject: [PATCH 4/5] pvalink: porting part 3 add pvalink json schema avoid JSON5 in testpvalink for portability. fixup build with pvalink trap bad_weak_ptr open during dtor Not sure why this is happening, but need not be CRIT. c++11, cleanup, and notes fix pvalink test sync fix test cleanup on exit pvalink disconnected link is always INVALID pvalink logging pvalink capture Disconnect time pvalink eliminate providerName restrict local to dbChannelTest() aka. no qsrv groups pvalink onTypeChange when attaching link to existing channel pvalink eliminate unused Connecting state pvalink add InstCounter pvalink AfterPut can be const pvalink add atomic jlif flag include epicsStdio.h later avoid #define printf troubles assert cleanup state on exit pvalink add newer lset functions test link disconnect testpvalink redo testPutAsync() pvalink fill out meta-data fetch pvalink fix FLNK pvalink cache putReq pvalink test atomic monitor pvalink test enum handling pvalink handle scalar read of empty array make it well defined anyway... pvalink test array of strings handle db_add_event() failure handle record._options.DBE --- documentation/pvalink-schema-0.json | 36 +++ ioc/Makefile | 6 +- ioc/pvalink.cpp | 108 ++++++-- ioc/pvalink.h | 88 ++++--- ioc/pvalink_channel.cpp | 210 +++++++--------- ioc/pvalink_jlif.cpp | 12 +- ioc/pvalink_link.cpp | 117 ++++----- ioc/pvalink_lset.cpp | 233 +++++++++++------ ioc/pvxs/iochooks.h | 15 +- ioc/singlesource.cpp | 29 ++- ioc/subscriptionctx.h | 3 +- setup.py | 5 + test/Makefile | 1 + test/testpvalink.cpp | 378 +++++++++++++++++++++++----- test/testpvalink.db | 153 +++++++++-- test/testqgroup.cpp | 3 + test/testqsingle.cpp | 3 + 17 files changed, 997 insertions(+), 403 deletions(-) create mode 100644 documentation/pvalink-schema-0.json diff --git a/documentation/pvalink-schema-0.json b/documentation/pvalink-schema-0.json new file mode 100644 index 000000000..0f0410235 --- /dev/null +++ b/documentation/pvalink-schema-0.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://mdavidsaver.github.io/pvxs/pvalink-schema-0.json", + "title": "PVA Link schema", + "type": ["string", "object"], + "properties": { + "pv": { "type": "string" }, + "field": { + "type": "string", + "default": "value" + }, + "Q": { + "type": "integer", + "default": 4 + }, + "proc": { + "type": ["boolean", "string", "null"], + "enum": [true, false, null, "", "NPP", "PP", "CP", "CPP"], + "default": null + }, + "sevr": { + "type": ["boolean", "string"], + "enum": [true, false, "NMS", "MS", "MSI", "MSS"], + "default": "NMS" + }, + "time": { "type": "boolean", "default": false }, + "monorder": { "type": "integer", "default": 0 }, + "defer": { "type": "boolean", "default": false }, + "retry": { "type": "boolean", "default": false }, + "pipeline": { "type": "boolean", "default": false }, + "always": { "type": "boolean", "default": false }, + "atomic": { "type": "boolean", "default": false }, + "local": { "type": "boolean", "default": false } + }, + "additionalProperties": false +} diff --git a/ioc/Makefile b/ioc/Makefile index 5f9f7c884..8366e9c1e 100644 --- a/ioc/Makefile +++ b/ioc/Makefile @@ -63,6 +63,10 @@ pvxsIoc_SRCS += pvalink_jlif.cpp pvxsIoc_SRCS += pvalink_link.cpp pvxsIoc_SRCS += pvalink_lset.cpp +else + +pvxsIoc_SRCS += dummygroup.cpp + endif # BASE_7_0 pvxsIoc_LIBS += $(EPICS_BASE_IOC_LIBS) @@ -91,4 +95,4 @@ ifdef BASE_7_0 else ../O.Common/pvxsIoc.dbd: ../pvxs3x.dbd $(CP) $< $@ -endif \ No newline at end of file +endif diff --git a/ioc/pvalink.cpp b/ioc/pvalink.cpp index 24eb59f85..257889660 100644 --- a/ioc/pvalink.cpp +++ b/ioc/pvalink.cpp @@ -20,13 +20,17 @@ #include #include #include +#include #include #include #include #include +#define PVXS_ENABLE_EXPERT_API + #include +#include "channel.h" #include "pvalink.h" #include "dblocker.h" #include "dbentry.h" @@ -63,10 +67,8 @@ static void shutdownStep2() { Guard G(pvaGlobal->lock); - if(pvaGlobal->channels.size()) { - fprintf(stderr, "pvaLink leaves %zu channels open\n", - pvaGlobal->channels.size()); - } + assert(pvaLink::cnt_pvaLink<=1u); // dbRemoveLink() already called + assert(pvaGlobal->channels.empty()); } delete pvaGlobal; @@ -185,20 +187,85 @@ void testqsrvCleanup(void) } } -void testqsrvWaitForLinkEvent(struct link *plink) +static +std::shared_ptr testGetPVALink(struct link *plink) { - std::shared_ptr lchan; - { - DBLocker lock(plink->precord); + DBLocker lock(plink->precord); - if(plink->type!=JSON_LINK || !plink->value.json.jlink || plink->value.json.jlink->pif!=&lsetPVA) { - testAbort("Not a PVA link"); - } - pvaLink *pval = static_cast(plink->value.json.jlink); - lchan = pval->lchan; + if(plink->type!=JSON_LINK || !plink->value.json.jlink || plink->value.json.jlink->pif!=&lsetPVA) { + testAbort("Not a PVA link"); + } + pvaLink *pval = static_cast(plink->value.json.jlink); + if(!pval->lchan) + testAbort("PVA link w/o channel?"); + return pval->lchan; +} + +static +DBLINK* testGetLink(const char *pv) +{ + Channel chan(pv); + switch(dbChannelFieldType(chan)) { + case DBF_INLINK: + case DBF_OUTLINK: + case DBF_FWDLINK: + break; + default: + testAbort("%s : not a link field", pv); + } + return static_cast(dbChannelField(chan)); +} + +void testqsrvWaitForLinkConnected(struct link *plink, bool conn) +{ + if(conn) + pvaGlobal->provider_remote.hurryUp(); + std::shared_ptr lchan(testGetPVALink(plink)); + Guard G(lchan->lock); + while(lchan->connected!=conn) { + testDiag("%s(\"%s\", %c) sleep", __func__, plink->precord->name, conn?'C':'D'); + UnGuard U(G); + if(!lchan->update_evt.wait(10.0)) + testAbort("%s(\"%s\") timeout", __func__, plink->precord->name); + errlogFlush(); + testDiag("%s(\"%s\") wakeup", __func__, plink->precord->name); } - if(lchan) { - lchan->run_done.wait(); + errlogFlush(); +} + +void testqsrvWaitForLinkConnected(const char* pv, bool conn) +{ + testqsrvWaitForLinkConnected(testGetLink(pv), conn); +} + +QSrvWaitForLinkUpdate::QSrvWaitForLinkUpdate(struct link *plink) + :plink(plink) +{ + std::shared_ptr lchan(testGetPVALink(plink)); + Guard G(lchan->lock); + seq = lchan->update_seq; + testDiag("%s(\"%s\") arm at %u", __func__, plink->precord->name, seq); +} + +QSrvWaitForLinkUpdate::QSrvWaitForLinkUpdate(const char *pv) + :QSrvWaitForLinkUpdate(testGetLink(pv)) +{} + +QSrvWaitForLinkUpdate::~QSrvWaitForLinkUpdate() +{ + std::shared_ptr lchan(testGetPVALink(plink)); + Guard G(lchan->lock); + while(seq == lchan->update_seq) { + testDiag("%s(\"%s\") wait for end of %u", __func__, plink->precord->name, seq); + bool ok; + { + UnGuard U(G); + ok = lchan->update_evt.wait(5.0); + } + if(!ok) + testAbort("%s(\"%s\") timeout at %u", __func__, plink->precord->name, seq); + errlogFlush(); + testDiag("%s(\"%s\") wake at %u", __func__, plink->precord->name, seq); } } @@ -252,7 +319,7 @@ void dbpvar(const char *precordname, int level) } nchans++; - if(chan->state == pvaLinkChannel::Connected) + if(chan->connected) nconn++; if(!precordname) @@ -261,7 +328,7 @@ void dbpvar(const char *precordname, int level) if(level<=0) continue; - if(level>=2 || (chan->state != pvaLinkChannel::Connected && level==1)) { + if(level>=2 || (!chan->connected && level==1)) { if(chan->key.first.size()<=28) { printf("%28s ", chan->key.first.c_str()); } else { @@ -269,16 +336,13 @@ void dbpvar(const char *precordname, int level) } printf("conn=%c %zu disconnects, %zu type changes", - chan->state == pvaLinkChannel::Connected?'T':'F', + chan->connected?'T':'F', chan->num_disconnect, chan->num_type_change); if(chan->op_put) { printf(" Put"); } - if(level>=3) { - printf(", provider '%s'", chan->providerName.c_str()); - } printf("\n"); // level 4 reserved for channel/provider details @@ -345,8 +409,6 @@ void installPVAAddLinkHook() initHookRegister(&initPVALink); IOCShCommand("dbpvar", "dbpvar", "record name", "level") .implementation<&dbpvar>(); -// epics::registerRefCounter("pvaLinkChannel", &pvaLinkChannel::num_instances); -// epics::registerRefCounter("pvaLink", &pvaLink::num_instances); } }} // namespace pvxs::ioc diff --git a/ioc/pvalink.h b/ioc/pvalink.h index d79749e64..290fa9de6 100644 --- a/ioc/pvalink.h +++ b/ioc/pvalink.h @@ -34,8 +34,18 @@ #include #include +#include "utilpvt.h" #include "dbmanylocker.h" + +#if EPICS_VERSION_INT> queue; @@ -105,6 +119,9 @@ struct pvaGlobal_t : private epicsThreadRunable { // Cache of active Channels (really about caching Monitor) channels_t channels; + // pvRequest used with PUT + const Value putReq; + private: epicsThread worker; bool workerStop = false; @@ -112,37 +129,36 @@ struct pvaGlobal_t : private epicsThreadRunable { public: pvaGlobal_t(); + pvaGlobal_t(const pvaGlobal_t&) = delete; + pvaGlobal_t& operator=(const pvaGlobal_t&) = delete; virtual ~pvaGlobal_t(); void close(); }; extern pvaGlobal_t *pvaGlobal; -struct pvaLinkChannel : public epicsThreadRunable +struct pvaLinkChannel final : public epicsThreadRunable ,public std::enable_shared_from_this { const pvaGlobal_t::channels_key_t key; // tuple of (channelName, pvRequest key) const Value pvRequest; // used with monitor - static size_t num_instances; + INST_COUNTER(pvaLinkChannel); + // locker order: record lock(s) -> channel lock epicsMutex lock; - epicsEvent run_done; // used by testing code + epicsEvent update_evt; // used by testing code -// std::shared_ptr chan; std::shared_ptr op_mon; std::shared_ptr op_put; Value root; - std::string providerName; size_t num_disconnect = 0u, num_type_change = 0u; - enum state_t { - Disconnected, - Connecting, - Connected, - } state = Disconnected; - bool isatomic = false; + bool connected = false; bool debug = false; // set if any jlink::debug is set + + unsigned update_seq = 0u; // used by testing code + typedef std::set after_put_t; after_put_t after_put; @@ -165,27 +181,38 @@ struct pvaLinkChannel : public epicsThreadRunable void open(); void put(bool force=false); // begin Put op. - struct AfterPut : public epicsThreadRunable { + struct AfterPut final : public epicsThreadRunable { std::weak_ptr lc; - virtual ~AfterPut() {} + AfterPut() = default; + AfterPut(const AfterPut&) = delete; + AfterPut& operator=(const AfterPut&) = delete; + virtual ~AfterPut() = default; virtual void run() override final; }; - std::shared_ptr AP; + const std::shared_ptr AP; private: virtual void run() override final; - void run_dbProcess(size_t idx); // idx is index in scan_records // ==== Treat remaining as local to run() - std::vector scan_records; - std::vector scan_check_passive; + struct ScanTrack { + dbCommon *prec = nullptr; + // if true, only scan if prec->scan==0 + bool check_passive = false; + + ScanTrack() = default; + ScanTrack(dbCommon *prec, bool check_passive) :prec(prec), check_passive(check_passive) {} + void scan(); + }; + std::vector nonatomic_records, + atomic_records; ioc::DBManyLock atomic_lock; }; struct pvaLink final : public pvaLinkConfig { - static size_t num_instances; + INST_COUNTER(pvaLink); bool alive = true; // attempt to catch some use after free dbfType type = (dbfType)-1; @@ -200,19 +227,21 @@ struct pvaLink final : public pvaLinkConfig // cached fields from channel op_mon // updated in onTypeChange() - Value fld_value; - Value fld_severity, + Value fld_value, + fld_severity, + fld_message, fld_seconds, - fld_nanoseconds; - Value fld_display, - fld_control, - fld_valueAlarm; + fld_nanoseconds, + fld_usertag, + fld_meta; // cached snapshot of alarm and timestamp // captured in pvaGetValue(). // we choose not to ensure consistency with display/control meta-data epicsTimeStamp snap_time = {}; + epicsUTag snap_tag = 0; short snap_severity = INVALID_ALARM; + std::string snap_message; pvaLink(); virtual ~pvaLink(); @@ -222,11 +251,14 @@ struct pvaLink final : public pvaLinkConfig bool valid() const; - // fetch a sub-sub-field of the top monitored field. - Value getSubField(const char *name); - void onDisconnect(); void onTypeChange(); + enum scanOnUpdate_t { + scanOnUpdateNo = -1, + scanOnUpdatePassive = 0, + scanOnUpdateYes = 1, + }; + scanOnUpdate_t scanOnUpdate() const; }; diff --git a/ioc/pvalink_channel.cpp b/ioc/pvalink_channel.cpp index d86bc8d5a..00e6941c6 100644 --- a/ioc/pvalink_channel.cpp +++ b/ioc/pvalink_channel.cpp @@ -9,11 +9,13 @@ #include +#include "utilpvt.h" #include "pvalink.h" #include "dblocker.h" #include "dbmanylocker.h" -DEFINE_LOGGER(_logger, "ioc.pvalink.channel"); +DEFINE_LOGGER(_logger, "pvxs.ioc.link.channel"); +DEFINE_LOGGER(_logupdate, "pvxs.ioc.link.channel.update"); int pvaLinkNWorkers = 1; @@ -26,6 +28,14 @@ pvaGlobal_t *pvaGlobal; pvaGlobal_t::pvaGlobal_t() :queue() ,running(false) + ,putReq(TypeDef(TypeCode::Struct, { + members::Struct("field", {}), + members::Struct("record", { + members::Struct("_options", { + members::Bool("block"), + members::String("process"), + }), + }), }).create()) ,worker(*this, "pvxlink", epicsThreadGetStackSize(epicsThreadStackBig), @@ -66,9 +76,8 @@ void pvaGlobal_t::close() worker.exitWait(); } -size_t pvaLinkChannel::num_instances; -size_t pvaLink::num_instances; - +DEFINE_INST_COUNTER(pvaLinkChannel); +DEFINE_INST_COUNTER(pvaLink); bool pvaLinkChannel::LinkSort::operator()(const pvaLink *L, const pvaLink *R) const { if(L->monorder==R->monorder) @@ -104,11 +113,14 @@ void pvaLinkChannel::open() .rawRequest(pvRequest) .event([this](const client::Subscription&) { - log_debug_printf(_logger, "Received message: %s %s\n", key.first.c_str(), key.second.c_str()); - pvaGlobal->queue.push(shared_from_this()); + log_debug_printf(_logger, "Monitor %s wakeup\n", key.first.c_str()); + try { + pvaGlobal->queue.push(shared_from_this()); + }catch(std::bad_weak_ptr&){ + log_err_printf(_logger, "channel '%s' open during dtor?", key.first.c_str()); + } }) .exec(); - providerName = "remote"; } static @@ -138,7 +150,7 @@ Value linkBuildPut(pvaLinkChannel *self, Value&& prototype) value = tosend; } else { if (tosend.empty()) - continue; // TODO: Signal error + continue; // TODO: can't write empty array to scalar field Signal error if (value.type() == TypeCode::Struct && value.id() == "enum_t") { value = value["index"]; // We want to assign to the index for enum types @@ -179,6 +191,7 @@ void linkPutDone(pvaLinkChannel *self, client::Result&& result) ok = true; }catch(std::exception& e){ errlogPrintf("%s PVA link put ERROR: %s\n", self->key.first.c_str(), e.what()); + // TODO: signal INVALID_ALARM ? } bool needscans; @@ -206,24 +219,13 @@ void linkPutDone(pvaLinkChannel *self, client::Result&& result) // call with channel lock held void pvaLinkChannel::put(bool force) { - // TODO cache TypeDef in global - using namespace pvxs::members; - auto pvReq(TypeDef(TypeCode::Struct, { - Struct("field", {}), - Struct("record", { - Struct("_options", { - Bool("block"), - String("process"), - }), - }), }).create() + auto pvReq(pvaGlobal->putReq.cloneEmpty() .update("record._options.block", !after_put.empty())); unsigned reqProcess = 0; bool doit = force; - for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it) + for(auto& link : links) { - pvaLink *link = *it; - if(!link->used_scratch) continue; link->put_queue = std::move(link->put_scratch); @@ -264,6 +266,7 @@ void pvaLinkChannel::put(bool force) if(doit) { // start net Put, cancels in-progress put op_put = pvaGlobal->provider_remote.put(key.first) + .rawRequest(pvReq) .build([this](Value&& prototype) -> Value { return linkBuildPut(this, std::move(prototype)); // TODO @@ -306,160 +309,131 @@ void pvaLinkChannel::AfterPut::run() } -// the work in calling dbProcess() which is common to -// both dbScanLock() and dbScanLockMany() -void pvaLinkChannel::run_dbProcess(size_t idx) +// caller has locked record +void pvaLinkChannel::ScanTrack::scan() { - dbCommon *precord = scan_records[idx]; - - if(scan_check_passive[idx] && precord->scan!=0) { - return; + if(check_passive && prec->scan!=0) { - // TODO: This relates to caching of the individual links and comparing it to - // the posted monitor. This is, as I understand it, an optimisation and - // we can sort of ignore it for now. - //} else if(state_latched == Connected && !op_mon.changed.logical_and(scan_changed[idx])) { - // return; - - } else if (precord->pact) { - if (precord->tpro) - printf("%s: Active %s\n", - epicsThreadGetNameSelf(), precord->name); - precord->rpro = TRUE; + } else if (prec->pact) { + if (prec->tpro) + printf("%s: Active %s\n", epicsThreadGetNameSelf(), prec->name); + prec->rpro = TRUE; + } else { + (void)dbProcess(prec); } - dbProcess(precord); } // Running from global WorkQueue thread void pvaLinkChannel::run() { - bool requeue = false; { Guard G(lock); - log_debug_printf(_logger,"Running task %s\n", this->key.first.c_str()); + log_debug_printf(_logger,"Monitor %s work\n", this->key.first.c_str()); Value top; try { top = op_mon->pop(); if(!top) { - log_debug_printf(_logger, "Queue empty %s\n", this->key.first.c_str()); - run_done.signal(); + log_debug_printf(_logger, "Monitor %s empty\n", this->key.first.c_str()); return; } - state = Connected; - } catch(client::Disconnect&) { - log_debug_printf(_logger, "PVA link %s received disonnection event\n", this->key.first.c_str()); + if(!connected) { + // (re)connect implies type change + log_debug_printf(_logger, "Monitor %s reconnect\n", this->key.first.c_str()); + + root = top; // re-create cache + connected = true; + num_type_change++; + + for(auto link : links) { + link->onTypeChange(); + } + + } else { // update cache + root.assign(top); + } + log_debug_printf(_logupdate, "Monitor %s value %s\n", this->key.first.c_str(), + std::string(SB()<key.first.c_str()); - state = Disconnected; + connected = false; num_disconnect++; // cancel pending put operations op_put.reset(); - for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it) - { - pvaLink *link = *it; + for(auto link : links) { link->onDisconnect(); + link->snap_time = e.time; } // Don't clear previous_root on disconnect. - // We will usually re-connect with the same type, - // and may get back the same PVStructure. + // while disconnected, we will provide the most recent value w/ LINK_ALARM } catch(std::exception& e) { - errlogPrintf("pvalinkChannel::run: Unexpected exception while reading from monitor queue: %s\n", e.what()); + log_exc_printf(_logger, "pvalinkChannel::run: Unexpected exception: %s\n", e.what()); } - if (state == Connected) { - // Fetch the data from the incoming monitor - if (root.equalType(top)) - { - log_debug_printf(_logger, "pvalinkChannel update value %s\n", this->key.first.c_str()); - - root.assign(top); - } - else - { - log_debug_printf(_logger, "pvalinkChannel %s update type\n", this->key.first.c_str()); - root = top; - num_type_change++; - - for (links_t::iterator it(links.begin()), end(links.end()); it != end; ++it) - { - pvaLink *link = *it; - link->onTypeChange(); - } - } - - requeue = true; - } - if(links_changed) { // a link has been added or removed since the last update. // rebuild our cached list of records to (maybe) process. - scan_records.clear(); - scan_check_passive.clear(); + decltype(atomic_records) atomic, nonatomic; + std::vector atomicrecs; - for(links_t::iterator it(links.begin()), end(links.end()); it!=end; ++it) - { - pvaLink *link = *it; + for(auto link : links) { assert(link && link->alive); - if(!link->plink) continue; - - // only scan on monitor update for input links - if(link->type!=DBF_INLINK) + auto sou(link->scanOnUpdate()); + if(sou==pvaLink::scanOnUpdateNo) continue; - // NPP and none/Default don't scan - // PP, CP, and CPP do scan - // PP and CPP only if SCAN=Passive - if(link->proc != pvaLink::PP && link->proc != pvaLink::CPP && link->proc != pvaLink::CP) - continue; + bool check_passive = sou==pvaLink::scanOnUpdatePassive; - scan_records.push_back(link->plink->precord); - scan_check_passive.push_back(link->proc != pvaLink::CP); + if(link->atomic) { + atomicrecs.push_back(link->plink->precord); + atomic.emplace_back(link->plink->precord, check_passive); + } else { + nonatomic.emplace_back(link->plink->precord, check_passive); + } } - log_debug_printf(_logger, "Links changed, scan_records size = %lu\n", scan_records.size()); + log_debug_printf(_logger, "Links changed, %zu with %zu atomic, %zu nonatomic\n", + links.size(), atomic.size(), nonatomic.size()); - atomic_lock = ioc::DBManyLock(scan_records); + atomic_lock = ioc::DBManyLock(atomicrecs); + atomic_records = std::move(atomic); + nonatomic_records = std::move(nonatomic); links_changed = false; } - } - if(scan_records.empty()) { - // Nothing to do, so don't bother locking + update_seq++; + update_evt.signal(); + log_debug_printf(_logger, "%s Sequence point %u\n", key.first.c_str(), update_seq); + } + // unlock link - } else if(isatomic && scan_records.size() > 1u) { + if(!atomic_records.empty()) { ioc::DBManyLocker L(atomic_lock); - - for(size_t i=0, N=scan_records.size(); iname); - - ioc::DBLocker L(scan_records[i]); - run_dbProcess(i); + for(auto& trac : atomic_records) { + trac.scan(); } } - if(requeue) { - log_debug_printf(_logger, "Requeueing %s\n", key.first.c_str()); - // re-queue until monitor queue is empty - pvaGlobal->queue.push(shared_from_this()); - } else { - log_debug_printf(_logger, "Run done instead of requeue %s\n", key.first.c_str()); - run_done.signal(); + for(auto& trac : nonatomic_records) { + ioc::DBLocker L(trac.prec); + trac.scan(); } + + log_debug_printf(_logger, "Requeueing %s\n", key.first.c_str()); + // re-queue until monitor queue is empty + pvaGlobal->queue.push(shared_from_this()); } } // namespace pvalink diff --git a/ioc/pvalink_jlif.cpp b/ioc/pvalink_jlif.cpp index 909034946..bd07adb6b 100644 --- a/ioc/pvalink_jlif.cpp +++ b/ioc/pvalink_jlif.cpp @@ -6,10 +6,9 @@ #include -#include // redirects stdout/stderr - #include "pvalink.h" +#include // redirects stdout/stderr #include namespace pvxlink { @@ -105,6 +104,8 @@ jlif_result pva_parse_bool(jlink *pjlink, int val) pvt->local = !!val; } else if(pvt->jkey == "always") { pvt->always = !!val; + } else if(pvt->jkey == "atomic") { + pvt->atomic = !!val; } else if(pvt->debug) { printf("pva link parsing unknown integer depth=%u key=\"%s\" value=%s\n", pvt->parseDepth, pvt->jkey.c_str(), val ? "true" : "false"); @@ -241,12 +242,13 @@ void pva_report(const jlink *rpjlink, int lvl, int indent) case pvaLinkConfig::MSI: printf(" MSI"); break; } if(lvl>0) { - printf(" Q=%u pipe=%c defer=%c time=%c retry=%c morder=%d", + printf(" Q=%u pipe=%c defer=%c time=%c retry=%c atomic=%c morder=%d", unsigned(pval->queueSize), pval->pipeline ? 'T' : 'F', pval->defer ? 'T' : 'F', pval->time ? 'T' : 'F', pval->retry ? 'T' : 'F', + pval->atomic ? 'T' : 'F', pval->monorder); } @@ -254,13 +256,13 @@ void pva_report(const jlink *rpjlink, int lvl, int indent) // after open() Guard G(pval->lchan->lock); - printf(" conn=%c", pval->lchan->state == pvaLinkChannel::Connected ? 'T' : 'F'); + printf(" conn=%c", pval->lchan->connected ? 'T' : 'F'); if(pval->lchan->op_put) { printf(" Put"); } if(lvl>0) { - printf(" #disconn=%zu prov=%s", pval->lchan->num_disconnect, pval->lchan->providerName.c_str()); + printf(" #disconn=%zu", pval->lchan->num_disconnect); } // if(lvl>5) { // std::ostringstream strm; diff --git a/ioc/pvalink_link.cpp b/ioc/pvalink_link.cpp index 817197642..c1d234601 100644 --- a/ioc/pvalink_link.cpp +++ b/ioc/pvalink_link.cpp @@ -12,7 +12,7 @@ #include "pvalink.h" -DEFINE_LOGGER(_logger, "ioc.pvalink.link"); +DEFINE_LOGGER(_logger, "pvxs.ioc.link.link"); namespace pvxlink { @@ -34,10 +34,7 @@ pvaLink::~pvaLink() lchan->links_changed = true; bool new_debug = false; - for(pvaLinkChannel::links_t::const_iterator it(lchan->links.begin()), end(lchan->links.end()) - ; it!=end; ++it) - { - const pvaLink *pval = *it; + for(auto pval : lchan->links) { if(pval->debug) { new_debug = true; break; @@ -70,35 +67,7 @@ Value pvaLink::makeRequest() // caller must lock lchan->lock bool pvaLink::valid() const { - return lchan->state == pvaLinkChannel::Connected && lchan->root; -} - -// caller must lock lchan->lock -Value pvaLink::getSubField(const char *name) -{ - Value ret; - if(valid()) { - if(fieldName.empty()) { - // we access the top level struct - ret = lchan->root[name]; - - } else { - // we access a sub-struct - ret = lchan->root[fieldName]; - if(!ret) { - // noop - } else if(ret.type()!=TypeCode::Struct) { - // addressed sub-field isn't a sub-structure - if(strcmp(name, "value")!=0) { - // unless we are trying to fetch the "value", we fail here - ret = Value(); - } - } else { - ret = ret[name]; - } - } - } - return ret; + return lchan->connected && lchan->root; } // call with channel lock held @@ -112,38 +81,54 @@ void pvaLink::onDisconnect() void pvaLink::onTypeChange() { - log_debug_printf(_logger, "%s type change\n", plink->precord->name); - - assert(lchan->state == pvaLinkChannel::Connected && lchan->root); // we should only be called when connected - - fld_value = getSubField("value"); - fld_seconds = getSubField("timeStamp.secondsPastEpoch"); - fld_nanoseconds = getSubField("timeStamp.nanoseconds"); - fld_severity = getSubField("alarm.severity"); - fld_display = getSubField("display"); - fld_control = getSubField("control"); - fld_valueAlarm = getSubField("valueAlarm"); - - // build mask of all "changed" bits associated with our .value - // CP/CPP input links will process this link only for updates where - // the changed mask and proc_changed share at least one set bit. -// if(fld_value) { -// // bit for this field -// proc_changed.set(fld_value->getFieldOffset()); - -// // bits of all parent fields -// for(const pvd::PVStructure* parent = fld_value->getParent(); parent; parent = parent->getParent()) { -// proc_changed.set(parent->getFieldOffset()); -// } - -// if(fld_value->getField()->getType()==pvd::structure) -// { -// // bits of all child fields -// const pvd::PVStructure *val = static_cast(fld_value.get()); -// for(size_t i=val->getFieldOffset(), N=val->getNextFieldOffset(); iconnected && lchan->root); // we should only be called when connected + + fld_value = fld_severity = fld_nanoseconds = fld_usertag + = fld_message = fld_severity = fld_meta = Value(); + + Value root; + if(fieldName.empty()) { + root = lchan->root; + } else { + root = lchan->root[fieldName]; + } + if(!root) { + log_warn_printf(_logger, "%s has no %s\n", lchan->key.first.c_str(), fieldName.c_str()); + + } else if(root.type()!=TypeCode::Struct) { + log_debug_printf(_logger, "%s has no meta\n", lchan->key.first.c_str()); + fld_value = root; + + } else { + fld_value = root["value"]; + fld_seconds = root["timeStamp.secondsPastEpoch"]; + fld_nanoseconds = root["timeStamp.nanoseconds"]; + fld_usertag = root["timeStamp.userTag"]; + fld_severity = root["alarm.severity"]; + fld_message = root["alarm.message"]; + fld_meta = std::move(root); + } + + log_debug_printf(_logger, "%s type change V=%c S=%c N=%c S=%c M=%c\n", + plink->precord->name, + fld_value ? 'Y' : 'N', + fld_seconds ? 'Y' : 'N', + fld_nanoseconds ? 'Y' : 'N', + fld_severity ? 'Y' : 'N', + fld_meta ? 'Y' : 'N'); +} + +pvaLink::scanOnUpdate_t pvaLink::scanOnUpdate() const +{ + if(!plink) + return scanOnUpdateNo; + if(type!=DBF_INLINK) + return scanOnUpdateNo; + if(proc == pvaLink::CP) + return scanOnUpdateYes; + if(proc == pvaLink::CPP) + return scanOnUpdatePassive; + return scanOnUpdateNo; } } // namespace pvalink diff --git a/ioc/pvalink_lset.cpp b/ioc/pvalink_lset.cpp index d471375a0..b8f54a24b 100644 --- a/ioc/pvalink_lset.cpp +++ b/ioc/pvalink_lset.cpp @@ -15,7 +15,7 @@ #include // redirect stdout/stderr; include after libevent/util.h -DEFINE_LOGGER(_logger, "pvxs.pvalink.lset"); +DEFINE_LOGGER(_logger, "pvxs.ioc.link.lset"); namespace pvxlink { namespace { @@ -45,6 +45,14 @@ void pvaOpenLink(DBLINK *plink) pvaLink* self((pvaLink*)plink->value.json.jlink); self->type = getLinkType(plink); + if(self->local && dbChannelTest(self->channelName.c_str())!=0) { + // TODO: only print duing iocInit()? + fprintf(stderr, "%s Error: local:true link to '%s' can't be fulfilled\n", + plink->precord->name, self->channelName.c_str()); + plink->lset = NULL; + return; + } + // workaround for Base not propagating info(base:lsetDebug to us { ioc::DBEntry rec(plink->precord); @@ -54,7 +62,9 @@ void pvaOpenLink(DBLINK *plink) } } - log_debug_printf(_logger, "%s OPEN %s\n", plink->precord->name, self->channelName.c_str()); + log_debug_printf(_logger, "%s OPEN %s sevr=%d\n", + plink->precord->name, self->channelName.c_str(), + self->sevr); // still single threaded at this point. // also, no pvaLinkChannel::lock yet @@ -82,10 +92,17 @@ void pvaOpenLink(DBLINK *plink) if(!chan) { // open new channel + log_debug_printf(_logger, "%s CREATE %s\n", + plink->precord->name, self->channelName.c_str()); + chan.reset(new pvaLinkChannel(key, pvRequest)); chan->AP->lc = chan; pvaGlobal->channels.insert(std::make_pair(key, chan)); doOpen = true; + + } else { + log_debug_printf(_logger, "%s REUSE %s\n", + plink->precord->name, self->channelName.c_str()); } doOpen &= pvaGlobal->running; // if not running, then open from initHook @@ -95,20 +112,36 @@ void pvaOpenLink(DBLINK *plink) chan->open(); // start subscription } - if(!self->local || chan->providerName=="QSRV"){ + bool scanInit = false; + { Guard G(chan->lock); chan->links.insert(self); chan->links_changed = true; - self->lchan.swap(chan); // we are now attached + self->lchan = std::move(chan); // we are now attached self->lchan->debug |= !!self->debug; - } else { - // TODO: only print duing iocInit()? - fprintf(stderr, "%s Error: local:true link to '%s' can't be fulfilled\n", - plink->precord->name, self->channelName.c_str()); - plink->lset = NULL; + + if(self->lchan->connected) { + self->onTypeChange(); + auto sou(self->scanOnUpdate()); + switch(sou) { + case pvaLink::scanOnUpdateNo: + break; + case pvaLink::scanOnUpdatePassive: + // record is locked + scanInit = plink->precord->scan==menuScanPassive; + break; + case pvaLink::scanOnUpdateYes: + scanInit = true; + break; + } + } + } + if(scanInit) { + // TODO: initial scan on linkGlobal worker? + scanOnce(plink->precord); } return; @@ -119,6 +152,7 @@ void pvaOpenLink(DBLINK *plink) void pvaRemoveLink(struct dbLocker *locker, DBLINK *plink) { + (void)locker; try { std::unique_ptr self((pvaLink*)plink->value.json.jlink); log_debug_printf(_logger, "%s: %s %s\n", __func__, plink->precord->name, self->channelName.c_str()); @@ -151,8 +185,8 @@ int pvaGetDBFtype(const DBLINK *plink) // if sub-field is struct, use sub-struct .value // if sub-field not struct, treat as value - auto value(self->getSubField("value")); - auto vtype(value.type()); + auto& value(self->fld_value); + auto vtype(self->fld_value.type()); if(vtype.isarray()) vtype = vtype.scalarOf(); @@ -208,15 +242,11 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, if(!self->valid()) { // disconnected - if(self->sevr != pvaLink::NMS) { - recGblSetSevr(plink->precord, LINK_ALARM, self->snap_severity); - } - // TODO: better capture of disconnect time - epicsTimeGetCurrent(&self->snap_time); + (void)recGblSetSevr(plink->precord, LINK_ALARM, INVALID_ALARM); if(self->time) { plink->precord->time = self->snap_time; } - log_debug_printf(_logger, "%s: %s not valid", __func__, self->channelName.c_str()); + log_debug_printf(_logger, "%s: %s not valid\n", __func__, self->channelName.c_str()); return -1; } @@ -228,7 +258,7 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, if(nReq <= 0 || !value) { if(!pnRequest) { - // TODO: fill in dummy scalar + memset(pbuffer, 0, dbValueSize(dbrType)); nReq = 1; } @@ -238,19 +268,15 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, if(size_t(nReq) > arr.size()) nReq = arr.size(); - if(arr.original_type()==ArrayType::String) { - auto sarr(arr.castTo()); + if(dbrType==DBR_STRING) { + auto sarr(arr.castTo()); // may copy+convert - if(dbrType==DBR_STRING) { - auto cbuf(reinterpret_cast(pbuffer)); - for(size_t i : range(size_t(nReq))) { - strncpy(cbuf + i*MAX_STRING_SIZE, - sarr[i].c_str(), - MAX_STRING_SIZE-1u); - cbuf[i*MAX_STRING_SIZE + MAX_STRING_SIZE-1] = '\0'; - } - } else { - return S_db_badDbrtype; // TODO: allow implicit parse? + auto cbuf(reinterpret_cast(pbuffer)); + for(size_t i : range(size_t(nReq))) { + strncpy(cbuf + i*MAX_STRING_SIZE, + sarr[i].c_str(), + MAX_STRING_SIZE-1u); + cbuf[i*MAX_STRING_SIZE + MAX_STRING_SIZE-1] = '\0'; } } else { @@ -267,6 +293,8 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, case DBR_FLOAT: dtype = ArrayType::Float32; break; case DBR_DOUBLE: dtype = ArrayType::Float64; break; default: + log_debug_printf(_logger, "%s: %s unsupported array conversion\n", + __func__, plink->precord->name); return S_db_badDbrtype; } @@ -305,6 +333,8 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, break; } default: + log_debug_printf(_logger, "%s: %s unsupported enum conversion\n", + __func__, plink->precord->name); return S_db_badDbrtype; } @@ -328,6 +358,8 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, break; } default: + log_debug_printf(_logger, "%s: %s unsupported scalar conversion\n", + __func__, plink->precord->name); return S_db_badDbrtype; } } @@ -355,9 +387,17 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, self->snap_severity = NO_ALARM; } + if(self->fld_message && self->snap_severity!=0) { + self->snap_message = self->fld_message.as(); + } else { + self->snap_message.clear(); + } + if((self->snap_severity!=NO_ALARM && self->sevr == pvaLink::MS) || (self->snap_severity==INVALID_ALARM && self->sevr == pvaLink::MSI)) { + log_debug_printf(_logger, "%s: %s recGblSetSevr %d\n", __func__, plink->precord->name, + self->snap_severity); recGblSetSevr(plink->precord, LINK_ALARM, self->snap_severity); } @@ -365,7 +405,8 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, plink->precord->time = self->snap_time; } - log_debug_printf(_logger, "%s: %s %s OK\n", __func__, plink->precord->name, self->channelName.c_str()); + log_debug_printf(_logger, "%s: %s %s snapsevr=%d OK\n", __func__, plink->precord->name, + self->channelName.c_str(), self->snap_severity); return 0; }CATCH() return -1; @@ -377,19 +418,11 @@ long pvaGetControlLimits(const DBLINK *plink, double *lo, double *hi) Guard G(self->lchan->lock); CHECK_VALID(); - if(self->fld_control) { - Value value; - if(lo) { - if(!self->fld_control["limitLow"].as(*lo)) - *lo = 0.0; - } - if(hi) { - if(!self->fld_control["limitHigh"].as(*hi)) - *hi = 0.0; - } - } else { - *lo = *hi = 0.0; - } + if(lo) + (void)self->fld_meta["control.limitLow"].as(*lo); + if(hi) + (void)self->fld_meta["control.limitHigh"].as(*hi); + log_debug_printf(_logger, "%s: %s %s %f %f\n", __func__, plink->precord->name, self->channelName.c_str(), lo ? *lo : 0, hi ? *hi : 0); return 0; @@ -403,19 +436,11 @@ long pvaGetGraphicLimits(const DBLINK *plink, double *lo, double *hi) Guard G(self->lchan->lock); CHECK_VALID(); - if(self->fld_display) { - Value value; - if(lo) { - if(!self->fld_display["limitLow"].as(*lo)) - *lo = 0.0; - } - if(hi) { - if(!self->fld_display["limitHigh"].as(*hi)) - *hi = 0.0; - } - } else { - *lo = *hi = 0.0; - } + if(lo) + (void)self->fld_meta["display.limitLow"].as(*lo); + if(hi) + (void)self->fld_meta["display.limitHigh"].as(*hi); + log_debug_printf(_logger, "%s: %s %s %f %f\n", __func__, plink->precord->name, self->channelName.c_str(), lo ? *lo : 0, hi ? *hi : 0); return 0; @@ -427,9 +452,19 @@ long pvaGetAlarmLimits(const DBLINK *plink, double *lolo, double *lo, double *hi, double *hihi) { TRY { - //Guard G(self->lchan->lock); - //CHECK_VALID(); - *lolo = *lo = *hi = *hihi = 0.0; + Guard G(self->lchan->lock); + CHECK_VALID(); + + if(lolo) + (void)self->fld_meta["valueAlarm.lowAlarmLimit"].as(*lolo); + if(lo) + (void)self->fld_meta["valueAlarm.lowWarningLimit"].as(*lo); + if(hi) + (void)self->fld_meta["valueAlarm.highWarningLimit"].as(*hi); + if(hihi) + (void)self->fld_meta["valueAlarm.highAlarmLimit"].as(*hihi); + + log_debug_printf(_logger, "%s: %s %s %f %f %f %f\n", __func__, plink->precord->name, self->channelName.c_str(), lo ? *lo : 0, lolo ? *lolo : 0, hi ? *hi : 0, hihi ? *hihi : 0); @@ -441,12 +476,15 @@ long pvaGetAlarmLimits(const DBLINK *plink, double *lolo, double *lo, long pvaGetPrecision(const DBLINK *plink, short *precision) { TRY { - //Guard G(self->lchan->lock); - //CHECK_VALID(); + Guard G(self->lchan->lock); + CHECK_VALID(); + + uint16_t prec = 0; + (void)self->fld_meta["display.precision"].as(prec); + if(precision) + *precision = prec; - // No sane way to recover precision from display.format string. - *precision = 0; - log_debug_printf(_logger, "%s: %s %s %i\n", __func__, plink->precord->name, self->channelName.c_str(), *precision); + log_debug_printf(_logger, "%s: %s %s %i\n", __func__, plink->precord->name, self->channelName.c_str(), prec); return 0; }CATCH() return -1; @@ -458,24 +496,23 @@ long pvaGetUnits(const DBLINK *plink, char *units, int unitsSize) Guard G(self->lchan->lock); CHECK_VALID(); - if(unitsSize==0) return 0; + if(!units || unitsSize==0) return 0; + std::string egu; - if(units && self->fld_display.as(egu)) { - strncpy(units, egu.c_str(), unitsSize-1u); - units[unitsSize-1u] = '\0'; - } else if(units) { - units[0] = '\0'; - } + (void)self->fld_meta["display.units"].as(egu); + strncpy(units, egu.c_str(), unitsSize-1); units[unitsSize-1] = '\0'; + log_debug_printf(_logger, "%s: %s %s %s\n", __func__, plink->precord->name, self->channelName.c_str(), units); return 0; }CATCH() return -1; } -long pvaGetAlarm(const DBLINK *plink, epicsEnum16 *status, - epicsEnum16 *severity) +long pvaGetAlarmMsg(const DBLINK *plink, + epicsEnum16 *status, epicsEnum16 *severity, + char *msgbuf, size_t msgbuflen) { TRY { Guard G(self->lchan->lock); @@ -487,6 +524,14 @@ long pvaGetAlarm(const DBLINK *plink, epicsEnum16 *status, if(status) { *status = self->snap_severity ? LINK_ALARM : NO_ALARM; } + if(msgbuf && msgbuflen) { + if(self->snap_message.empty()) { + msgbuf[0] = '\0'; + } else { + epicsSnprintf(msgbuf, msgbuflen-1u, "%s", self->snap_message.c_str()); + msgbuf[msgbuflen-1u] = '\0'; + } + } log_debug_printf(_logger, "%s: %s %s %i %i\n", __func__, plink->precord->name, self->channelName.c_str(), severity ? *severity : 0, status ? *status : 0); return 0; @@ -494,7 +539,13 @@ long pvaGetAlarm(const DBLINK *plink, epicsEnum16 *status, return -1; } -long pvaGetTimeStamp(const DBLINK *plink, epicsTimeStamp *pstamp) +long pvaGetAlarm(const DBLINK *plink, epicsEnum16 *status, + epicsEnum16 *severity) +{ + return pvaGetAlarmMsg(plink, status, severity, nullptr, 0); +} + +long pvaGetTimeStampTag(const DBLINK *plink, epicsTimeStamp *pstamp, epicsUTag *ptag) { TRY { Guard G(self->lchan->lock); @@ -503,12 +554,20 @@ long pvaGetTimeStamp(const DBLINK *plink, epicsTimeStamp *pstamp) if(pstamp) { *pstamp = self->snap_time; } + if(ptag) { + *ptag = self->snap_tag; + } log_debug_printf(_logger, "%s: %s %s %i %i\n", __func__, plink->precord->name, self->channelName.c_str(), pstamp ? pstamp->secPastEpoch : 0, pstamp ? pstamp->nsec : 0); return 0; }CATCH() return -1; } +long pvaGetTimeStamp(const DBLINK *plink, epicsTimeStamp *pstamp) +{ + return pvaGetTimeStampTag(plink, pstamp, nullptr); +} + long pvaPutValueX(DBLINK *plink, short dbrType, const void *pbuffer, long nRequest, bool wait) { @@ -559,10 +618,8 @@ long pvaPutValueX(DBLINK *plink, short dbrType, self->used_scratch = true; -#ifdef USE_MULTILOCK if(wait) self->lchan->after_put.insert(plink->precord); -#endif if(!self->defer) self->lchan->put(); @@ -591,6 +648,7 @@ void pvaScanForward(DBLINK *plink) Guard G(self->lchan->lock); if(!self->retry && !self->valid()) { + (void)recGblSetSevrMsg(plink->precord, LINK_ALARM, INVALID_ALARM, "Disconn"); return; } @@ -602,6 +660,17 @@ void pvaScanForward(DBLINK *plink) }CATCH() } +#if EPICS_VERSION_INT>=VERSION_INT(3,16,1,0) +long pvaDoLocked(struct link *plink, dbLinkUserCallback rtn, void *priv) +{ + TRY { + Guard G(self->lchan->lock); + return (*rtn)(plink, priv); + }CATCH() + return 1; +} +#endif // >= 3.16.1 + #undef TRY #undef CATCH @@ -625,8 +694,14 @@ lset pva_lset = { &pvaGetTimeStamp, &pvaPutValue, &pvaPutValueAsync, - &pvaScanForward - //&pvaReportLink, + &pvaScanForward, +#if EPICS_VERSION_INT>=VERSION_INT(3,16,1,0) + &pvaDoLocked, +#endif +#if EPICS_VERSION_INT>=VERSION_INT(7,0,6,0) + &pvaGetAlarmMsg, + &pvaGetTimeStampTag, +#endif }; } // namespace pvxlink diff --git a/ioc/pvxs/iochooks.h b/ioc/pvxs/iochooks.h index 6d8e8c3b7..65b33cccc 100644 --- a/ioc/pvxs/iochooks.h +++ b/ioc/pvxs/iochooks.h @@ -100,14 +100,27 @@ void testPrepare(); PVXS_IOC_API void testShutdown(); +#ifdef PVXS_EXPERT_API_ENABLED PVXS_IOC_API -void testqsrvWaitForLinkEvent(struct link *plink); +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(); +}; PVXS_IOC_API void testqsrvShutdownOk(void); PVXS_IOC_API void testqsrvCleanup(void); +#endif // PVXS_EXPERT_API_ENABLED }} // namespace pvxs::ioc #endif // PVXS_IOCHOOKS_H diff --git a/ioc/singlesource.cpp b/ioc/singlesource.cpp index 66bc0e14f..1fd292cdd 100644 --- a/ioc/singlesource.cpp +++ b/ioc/singlesource.cpp @@ -102,6 +102,33 @@ void onSubscribe(const std::shared_ptr& subscriptio const DBEventContext& eventContext, std::unique_ptr&& subscriptionOperation) { + auto pvReq(subscriptionOperation->pvRequest()); + unsigned dbe = 0; + if(auto fld = pvReq["record._options.DBE"].ifMarked()) { + switch(fld.type().kind()) { + case Kind::String: { + auto mask(fld.as()); + // name and sloppy parsing a la. caProvider... +#define CASE(EVENT) if(mask.find(#EVENT)!=mask.npos) dbe |= DBE_ ## EVENT + CASE(VALUE); + CASE(ARCHIVE); + CASE(ALARM); +// CASE(PROPERTY); // handled as special case +#undef CASE + break; + } + case Kind::Integer: + case Kind::Real: + dbe = fld.as(); + break; + default: + break; + } + } + dbe &= (DBE_VALUE | DBE_ARCHIVE | DBE_ALARM); + if(!dbe) + dbe = DBE_VALUE | DBE_ALARM; + // inform peer of data type and acquire control of the subscription queue subscriptionContext->subscriptionControl = subscriptionOperation->connect(subscriptionContext->currentValue); @@ -115,7 +142,7 @@ void onSubscribe(const std::shared_ptr& subscriptio subscriptionContext->info->chan, subscriptionValueCallback, subscriptionContext.get(), - DBE_VALUE | DBE_ALARM | DBE_ARCHIVE + dbe ); // second subscription is for Property changes subscriptionContext->pPropertiesEventSubscription.subscribe(eventContext.get(), diff --git a/ioc/subscriptionctx.h b/ioc/subscriptionctx.h index db99c5ed6..2b3f3246a 100644 --- a/ioc/subscriptionctx.h +++ b/ioc/subscriptionctx.h @@ -39,7 +39,8 @@ class Subscription { user_sub, user_arg, select), [chan](dbEventSubscription sub) mutable { - db_cancel_event(sub); + if(sub) + db_cancel_event(sub); chan = Channel(); // dbChannel* must outlive subscription }); if(!sub) diff --git a/setup.py b/setup.py index ec3b9d1bf..d3a413561 100755 --- a/setup.py +++ b/setup.py @@ -593,6 +593,11 @@ def define_DSOS(self): "ioc/singlesourcehooks.cpp", "ioc/singlesrcsubscriptionctx.cpp", "ioc/typeutils.cpp", + "ioc/pvalink_channel.cpp", + "ioc/pvalink.cpp", + "ioc/pvalink_jlif.cpp", + "ioc/pvalink_link.cpp", + "ioc/pvalink_lset.cpp", ] probe = ProbeToolchain() diff --git a/test/Makefile b/test/Makefile index 47714460a..9cd3cb321 100644 --- a/test/Makefile +++ b/test/Makefile @@ -126,6 +126,7 @@ testpvalink_SRCS += testpvalink.cpp testpvalink_SRCS += testioc_registerRecordDeviceDriver.cpp testpvalink_LIBS += pvxsIoc pvxs $(EPICS_BASE_IOC_LIBS) TESTS += testpvalink +TESTFILES += ../testpvalink.db endif diff --git a/test/testpvalink.cpp b/test/testpvalink.cpp index d66bccc42..bffeb95e0 100644 --- a/test/testpvalink.cpp +++ b/test/testpvalink.cpp @@ -1,14 +1,31 @@ +/* + * 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 +#define PVXS_ENABLE_EXPERT_API + //#include //#include "utilities.h" #include "dblocker.h" +#include #include "pvxs/iochooks.h" +#include +#include #include "pvalink.h" #include "testioc.h" //#include "pv/qsrv.h" @@ -16,16 +33,24 @@ using namespace pvxs::ioc; using namespace pvxs; -namespace -{ +namespace { + struct TestMonitor { + testMonitor * const mon; + TestMonitor(const char* pvname, unsigned dbe_mask, unsigned opt=0) + :mon(testMonitorCreate(pvname, dbe_mask, opt)) + {} + ~TestMonitor() { testMonitorDestroy(mon); } + void wait() { testMonitorWait(mon); } + unsigned count(bool reset=true) { return testMonitorCount(mon, reset); } + }; + void testGet() { testDiag("==== testGet ===="); longinRecord *i1 = (longinRecord *)testdbRecordPtr("src:i1"); - while (!dbIsLinkConnected(&i1->inp)) - testqsrvWaitForLinkEvent(&i1->inp); + testqsrvWaitForLinkConnected(&i1->inp); testdbGetFieldEqual("target:i.VAL", DBF_LONG, 42L); @@ -39,8 +64,7 @@ namespace testdbPutFieldOk("src:i1.INP", DBF_STRING, "{\"pva\":\"target:ai\"}"); - while (!dbIsLinkConnected(&i1->inp)) - testqsrvWaitForLinkEvent(&i1->inp); + testqsrvWaitForLinkConnected(&i1->inp); testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 42L); // changing link doesn't automatically process @@ -58,8 +82,7 @@ namespace std::string pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"field\":\"display.precision\"}}"; testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length()+1, pv_name.c_str()); - while (!dbIsLinkConnected(&i1->inp)) - testqsrvWaitForLinkEvent(&i1->inp); + testqsrvWaitForLinkConnected(&i1->inp); testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 4L); // changing link doesn't automatically process @@ -76,20 +99,24 @@ namespace testDiag("==== Test proc settings ===="); + testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 2L); + // Set it to CPP std::string pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"proc\":\"CPP\"}}"; - testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length()+1, pv_name.c_str()); - - while (!dbIsLinkConnected(&i1->inp)) - testqsrvWaitForLinkEvent(&i1->inp); - - // Link should read old value again + { + TestMonitor m("src:i1", DBE_VALUE); + testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length()+1, pv_name.c_str()); + // wait for initial scan + m.wait(); + } + + // Link should read current value of target. testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 4L); - testdbPutFieldOk("target:ai", DBF_FLOAT, 5.0); - - // We are already connected at this point, wait for the update. - testqsrvWaitForLinkEvent(&i1->inp); + { + QSrvWaitForLinkUpdate C(&i1->inp); + testdbPutFieldOk("target:ai", DBF_FLOAT, 5.0); + } // now it's changed testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 5L); @@ -104,8 +131,7 @@ namespace std::string pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"sevr\":\"NMS\"}}"; testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length() + 1, pv_name.c_str()); - while (!dbIsLinkConnected(&i1->inp)) - testqsrvWaitForLinkEvent(&i1->inp); + testqsrvWaitForLinkConnected(&i1->inp); testdbPutFieldOk("target:ai.LOLO", DBF_FLOAT, 5.0); testdbPutFieldOk("target:ai.LLSV", DBF_STRING, "MAJOR"); @@ -117,8 +143,7 @@ namespace pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"sevr\":\"MS\"}}"; testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length() + 1, pv_name.c_str()); - while (!dbIsLinkConnected(&i1->inp)) - testqsrvWaitForLinkEvent(&i1->inp); + testqsrvWaitForLinkConnected(&i1->inp); testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L); testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevMajor); @@ -126,14 +151,16 @@ namespace pv_name = "{\"pva\":{\"pv\":\"target:mbbi\",\"sevr\":\"MSI\"}}"; testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length() + 1, pv_name.c_str()); - while (!dbIsLinkConnected(&i1->inp)) - testqsrvWaitForLinkEvent(&i1->inp); + testqsrvWaitForLinkConnected(&i1->inp); testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L); testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevNone); - testdbPutFieldOk("target:ai", DBF_FLOAT, 1.0); - testqsrvWaitForLinkEvent(&i1->inp); + { + QSrvWaitForLinkUpdate C(&i1->inp); + testdbPutFieldOk("target:ai", DBF_FLOAT, 1.0); + } + testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L); testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevInvalid); } @@ -144,17 +171,16 @@ namespace longoutRecord *o2 = (longoutRecord *)testdbRecordPtr("src:o2"); - while (!dbIsLinkConnected(&o2->out)) - testqsrvWaitForLinkEvent(&o2->out); + testqsrvWaitForLinkConnected(&o2->out); testdbGetFieldEqual("target:i2.VAL", DBF_LONG, 43L); testdbGetFieldEqual("src:o2.VAL", DBF_LONG, 0L); testdbGetFieldEqual("src:o2.OUT", DBF_STRING, "{\"pva\":\"target:i2\"}"); - testdbPutFieldOk("src:o2.VAL", DBF_LONG, 14L); - - testqsrvWaitForLinkEvent(&o2->out); - testqsrvWaitForLinkEvent(&o2->out); + { + QSrvWaitForLinkUpdate C(&o2->out); + testdbPutFieldOk("src:o2.VAL", DBF_LONG, 14L); + } testdbGetFieldEqual("target:i2.VAL", DBF_LONG, 14L); testdbGetFieldEqual("src:o2.VAL", DBF_LONG, 14L); @@ -170,71 +196,294 @@ namespace testdbPutFieldOk("target:str1.PROC", DBF_LONG, 1L); testdbGetFieldEqual("target:str1", DBF_STRING, "bar"); - testdbPutFieldOk("src:str.OUT", DBF_STRING, "{pva : \"target:str2\"}"); - - while (!dbIsLinkConnected(&so->out)) - testqsrvWaitForLinkEvent(&so->out); + testdbPutFieldOk("src:str.OUT", DBF_STRING, R"({"pva" : "target:str2"})"); - testdbPutFieldOk("src:str.PROC", DBF_LONG, 1L); + testqsrvWaitForLinkConnected(&so->out); - testqsrvWaitForLinkEvent(&so->out); + { + QSrvWaitForLinkUpdate C(&so->out); + testdbPutFieldOk("src:str.PROC", DBF_LONG, 1L); + } testdbGetFieldEqual("target:str2", DBF_STRING, "bar"); } + void testToFromString() + { + testDiag("==== %s ====", __func__); + + testqsrvWaitForLinkConnected("testToFromString:src.OUT"); + testqsrvWaitForLinkConnected("testToFromString:str2.INP"); + testqsrvWaitForLinkConnected("testToFromString:out.INP"); + + { + QSrvWaitForLinkUpdate C("testToFromString:out.INP"); + testdbPutFieldOk("testToFromString:src", DBR_LONG, 43); + } + + testdbGetFieldEqual("testToFromString:str1", DBR_STRING, "43"); + testdbGetFieldEqual("testToFromString:str2", DBR_STRING, "43"); + testdbGetFieldEqual("testToFromString:out", DBR_LONG, 43); + } + void testArrays() { - aaoRecord *aao = (aaoRecord *)testdbRecordPtr("source:aao"); + auto aai_inp = (aaiRecord *)testdbRecordPtr("target:aai_inp"); testDiag("==== testArrays ===="); static const epicsFloat32 input_arr[] = {1, 2, -1, 1.2, 0}; - testdbPutArrFieldOk("source:aao", DBR_FLOAT, 5, input_arr); + { + QSrvWaitForLinkUpdate C(&aai_inp->inp); + testdbPutArrFieldOk("source:aao", DBR_FLOAT, 5, input_arr); + } + // underlying channel cache updated, but record has not be re-processed testdbGetArrFieldEqual("target:aai_inp", DBF_CHAR, 10, 0, NULL); - testqsrvWaitForLinkEvent(&aao->out); - testqsrvWaitForLinkEvent(&aao->out); - static const epicsInt8 expected_char[] = {1, 2, -1, 1, 0}; testdbPutFieldOk("target:aai_inp.PROC", DBF_LONG, 1L); testdbGetArrFieldEqual("target:aai_inp", DBF_CHAR, 10, 5, expected_char); static const epicsUInt32 expected_ulong[] = {1L, 2L, 4294967295L, 1L, 0}; testdbGetArrFieldEqual("target:aai_inp", DBF_ULONG, 10, 5, expected_ulong); + + testqsrvWaitForLinkConnected("target:aai_inp_first.INP"); + testdbPutFieldOk("target:aai_inp_first.PROC", DBF_LONG, 1L); + testdbGetFieldEqual("target:aai_inp_first", DBR_DOUBLE, 1.0); + } + + void testStringArray() + { + testDiag("==== %s ====", __func__); + + testqsrvWaitForLinkConnected("sarr:inp.INP"); + + const char expect[3][MAX_STRING_SIZE] = {"one", "two", "three"}; + { + QSrvWaitForLinkUpdate U("sarr:inp.INP"); + + testdbPutArrFieldOk("sarr:src", DBR_STRING, 3, expect); + } + + testdbPutFieldOk("sarr:inp.PROC", DBR_LONG, 0); + + testdbGetArrFieldEqual("sarr:inp", DBR_STRING, 4, 3, expect); } void testPutAsync() { -#ifdef USE_MULTILOCK testDiag("==== testPutAsync ===="); - longoutRecord *trig = (longoutRecord *)testdbRecordPtr("async:trig"); + auto trig = (longoutRecord *)testdbRecordPtr("async:trig"); + auto seq = (calcRecord *)testdbRecordPtr("async:seq"); - while (!dbIsLinkConnected(&trig->out)) - testqsrvWaitForLinkEvent(&trig->out); + testqsrvWaitForLinkConnected(&trig->out); - testMonitor *done = testMonitorCreate("async:after", DBE_VALUE, 0); + TestMonitor done("async:seq", DBE_VALUE, 0); testdbPutFieldOk("async:trig.PROC", DBF_LONG, 1); - testMonitorWait(done); + dbScanLock((dbCommon*)seq); + while(seq->val < 2) { + dbScanUnlock((dbCommon*)seq); + done.wait(); + dbScanLock((dbCommon*)seq); + } + dbScanUnlock((dbCommon*)seq); + + testdbGetFieldEqual("async:target", DBF_LONG, 1); + testdbGetFieldEqual("async:next", DBF_LONG, 2); + testdbGetFieldEqual("async:seq", DBF_LONG, 2); + } + + void testDisconnect() + { + testDiag("==== %s ====", __func__); + auto serv(ioc::server()); + + testdbPutFieldFail(-1, "disconnected.PROC", DBF_LONG, 1); + testdbGetFieldEqual("disconnected.SEVR", DBF_SHORT, epicsSevInvalid); - testdbGetFieldEqual("async:trig", DBF_LONG, 1); - testdbGetFieldEqual("async:slow", DBF_LONG, 1); // pushed from async:trig - testdbGetFieldEqual("async:slow2", DBF_LONG, 2); - testdbGetFieldEqual("async:after", DBF_LONG, 3); + auto special(server::SharedPV::buildReadonly()); + special.open(nt::NTScalar{TypeCode::Int32}.create() + .update("value", 43)); + serv.addPV("special:pv", special); -#else - testSkip(5, "Not USE_MULTILOCK"); -#endif + testqsrvWaitForLinkConnected("disconnected.INP"); + + testdbPutFieldOk("disconnected.PROC", DBF_LONG, 1); + testdbGetFieldEqual("disconnected.SEVR", DBF_SHORT, epicsSevNone); + + serv.removePV("special:pv"); + special.close(); + + testqsrvWaitForLinkConnected("disconnected.INP", false); + + testdbPutFieldFail(-1, "disconnected.PROC", DBF_LONG, 1); + testdbGetFieldEqual("disconnected.SEVR", DBF_SHORT, epicsSevInvalid); + + testdbPutFieldOk("disconnected.INP", DBR_STRING, ""); // avoid further log messages } + void testMeta() + { + testDiag("==== %s ====", __func__); + + testqsrvWaitForLinkConnected("meta:inp.INP"); + + { + auto src = (aiRecord*)testdbRecordPtr("meta:src"); + QSrvWaitForLinkUpdate U("meta:inp.INP"); + dbScanLock((dbCommon*)src); + src->tse = epicsTimeEventDeviceTime; + src->time.secPastEpoch = 0x12345678; + src->time.nsec = 0x10203040; + src->val = 7; + dbProcess((dbCommon*)src); + dbScanUnlock((dbCommon*)src); + } + auto inp = (aiRecord*)testdbRecordPtr("meta:inp"); + + long ret, nelem; + epicsEnum16 stat, sevr; + epicsTimeStamp time; + char egu[10] = ""; + short prec; + double val, lolo, low, high, hihi; + + dbScanLock((dbCommon*)inp); + + testTrue(dbIsLinkConnected(&inp->inp)!=0); + + testEq(dbGetLinkDBFtype(&inp->inp), DBF_DOUBLE); + + // alarm and time meta-data will be "latched" by a call to dbGetLink. + // until then, the initial values are used + + testTrue((ret=dbGetAlarm(&inp->inp, &stat, &sevr))==0 + && stat==LINK_ALARM && sevr==INVALID_ALARM) + <<" ret="<inp, &nelem))==0 && nelem==1) + <<" ret="<val==0) { + dbScanUnlock(prec); + mon.wait(); + dbScanLock(prec); + } + dbScanUnlock(prec); + } + + testdbGetFieldEqual("flnk:tgt", DBF_LONG, 1); + } + + void testAtomic() + { + testDiag("==== %s ====", __func__); + + testqsrvWaitForLinkConnected("atomic:lnk:1.INP"); + testqsrvWaitForLinkConnected("atomic:lnk:2.INP"); + + { + QSrvWaitForLinkUpdate A("atomic:lnk:1.INP"); + QSrvWaitForLinkUpdate B("atomic:lnk:2.INP"); + + testdbPutFieldOk("atomic:src:1.PROC", DBR_LONG, 0); + } + + epicsUInt32 expect; + { + auto src1(testdbRecordPtr("atomic:src:1")); + dbScanLock(src1); + expect = ((calcoutRecord*)src1)->val; + testEq(expect & ~0xff, 0u); + expect |= expect<<8u; + dbScanUnlock(src1); + } + + testdbGetFieldEqual("atomic:lnk:out", DBF_ULONG, expect); + } + + void testEnum() + { + testDiag("==== %s ====", __func__); + + testqsrvWaitForLinkConnected("enum:src:b.OUT"); + testqsrvWaitForLinkConnected("enum:src:s.OUT"); + testqsrvWaitForLinkConnected("enum:tgt:s.INP"); + testqsrvWaitForLinkConnected("enum:tgt:b.INP"); + + { + QSrvWaitForLinkUpdate A("enum:tgt:b.INP"); // last in chain... + + testdbPutFieldOk("enum:src:b", DBR_STRING, "one"); + } + + testdbGetFieldEqual("enum:tgt:s", DBR_STRING, "one"); + // not clear how to handle this case, where a string is + // read as DBR_USHORT, which is actually as DBF_ENUM + testTodoBegin("Not yet implimented"); + testdbGetFieldEqual("enum:tgt:b", DBR_STRING, "one"); + testTodoEnd(); + } } // namespace extern "C" void testioc_registerRecordDeviceDriver(struct dbBase *); MAIN(testpvalink) { - testPlan(49); + testPlan(92); testSetup(); + pvxs::logger_config_env(); try { @@ -251,8 +500,15 @@ MAIN(testpvalink) testSevr(); testPut(); testStrings(); + testToFromString(); testArrays(); - (void)testPutAsync; + testStringArray(); + testPutAsync(); + testDisconnect(); + testMeta(); + testFwd(); + testAtomic(); + testEnum(); testqsrvShutdownOk(); IOC.shutdown(); testqsrvCleanup(); @@ -261,6 +517,8 @@ MAIN(testpvalink) { testFail("Unexpected exception: %s", e.what()); } - // call epics atexits explicitly as workaround for c++ static dtor issues... - epicsExit(testDone()); + // call epics atexits explicitly to handle older base w/o de-init hooks + epicsExitCallAtExits(); + cleanup_for_valgrind(); + return testDone(); } diff --git a/test/testpvalink.db b/test/testpvalink.db index 793b22f9d..082a885fc 100644 --- a/test/testpvalink.db +++ b/test/testpvalink.db @@ -11,6 +11,8 @@ record(ai, "target:ai") { record(longin, "src:i1") { field(INP, {"pva":"target:i"}) + field(MDEL, "-1") + field(TPRO, "1") } record(mbbi, "target:mbbi") { @@ -48,31 +50,37 @@ record(stringout, "src:str") { field(VAL, "bar") } -record(longout, "async:trig") { - field(OMSL, "closed_loop") - field(DOL , "async:seq PP") - field(DTYP, "Async Soft Channel") - field(OUT , { "pva":{"pv":"async:slow.A", "proc":true} }) - field(FLNK, "async:after") - field(TPRO, "1") +record(longout, "testToFromString:src") { + field(VAL , "42") + field(OUT , {pva:"testToFromString:str1"}) +} +record(stringin, "testToFromString:str1") { +} +record(aai, "testToFromString:str2") { + field(FTVL, "STRING") + field(NELM, "5") + field(INP , {pva:{pv:"testToFromString:str1", "proc":"CPP"}}) +} +record(longin, "testToFromString:out") { + field(INP , {pva:{pv:"testToFromString:str2", "proc":"CPP"}}) } -record(calcout, "async:slow") { - field(ODLY, "1") - field(CALC, "A") - field(FLNK, "async:slow2") - field(TPRO, "1") +record(calc, "async:seq") { + field(CALC, "VAL+1") } -record(longin, "async:slow2") { - field(INP , "async:seq PP") - field(TPRO, "1") +record(longout, "async:trig") { + field(DTYP, "Async Soft Channel") + field(OUT , {"pva":{"pv":"async:target", "proc":true}}) + field(FLNK, "async:next") } -record(longin, "async:after") { - field(INP , "async:seq PP") - field(MDEL, "-1") - field(TPRO, "1") +record(longin, "async:target") { + field(INP , "async:seq PP MS") +} + +record(longin, "async:next") { + field(INP , "async:seq PP MS") } record(aao, "source:aao") { @@ -90,4 +98,109 @@ record(aai, "target:aai_inp") { record(aai, "target:aai_out") { field(NELM, "2") field(FTVL, "ULONG") -} \ No newline at end of file +} + +record(ai, "target:aai_inp_first") { + field(INP, {pva: "source:aao"}) +} + +record(longin, "disconnected") { + field(INP, {pva: "special:pv"}) + field(VAL, "42") +} + +record(ao, "meta:src") { + field(DRVH, "10") + field(HOPR, "9") + field(HIHI, "8") + field(HIGH, "7") + field(LOW , "-7") + field(LOLO, "-8") + field(LOPR, "-9") + field(DRVL, "-10") + field(HHSV, "MAJOR") + field(HSV , "MINOR") + field(LSV , "MINOR") + field(LLSV, "MAJOR") + field(PREC, "2") + field(EGU , "arb") +} + +record(ai, "meta:inp") { + field(INP, {pva:{pv:"meta:src", sevr:"MS"}}) +} + +record(longout, "flnk:src") { + field(FLNK, {pva:"flnk:tgt"}) +} + +record(calc, "flnk:tgt") { + field(CALC, "VAL+1") +} + + +record(calcout, "atomic:src:1") { + field(CALC, "RNDM*255") + field(OUT , "atomic:src:2.A PP") + info(Q:group, { + "atomic:src":{ + "a": {+channel:"VAL"} + } + }) +} +record(calc, "atomic:src:2") { + field(CALC, "A<<8") + info(Q:group, { + "atomic:src":{ + "b": {+channel:"VAL", +trigger:"*"} + } + }) +} + +record(longin, "atomic:lnk:1") { + field(INP , { + pva:{pv:"atomic:src", field:"a", atomic:true, monorder:0, proc:"CP"} + }) +} +record(longin, "atomic:lnk:2") { + field(INP , { + pva:{pv:"atomic:src", field:"b", atomic:true, monorder:1, proc:"CP"} + }) + field(FLNK, "atomic:lnk:out") +} +record(calc, "atomic:lnk:out") { + field(INPA, "atomic:lnk:1 NPP MS") + field(INPB, "atomic:lnk:2 NPP MS") + field(CALC, "A|B") +} + +record(bo, "enum:src:b") { + field(OUT , {pva:{pv:"enum:tgt", proc:"PP"}}) + field(ZNAM, "zero") + field(ONAM, "one") +} +record(stringout, "enum:src:s") { + field(OUT , {pva:{pv:"enum:tgt", proc:"PP"}}) +} +record(bi, "enum:tgt") { + field(ZNAM, "zero") + field(ONAM, "one") +} +record(stringin, "enum:tgt:s") { + field(INP , {pva:{pv:"enum:tgt", proc:"CP"}}) +} +record(bi, "enum:tgt:b") { + field(INP , {pva:{pv:"enum:tgt:s", proc:"CP"}}) + field(ZNAM, "zero") + field(ONAM, "one") +} + +record(waveform, "sarr:src") { + field(FTVL, "STRING") + field(NELM, "16") +} +record(waveform, "sarr:inp") { + field(INP , {pva:"sarr:src"}) + field(FTVL, "STRING") + field(NELM, "16") +} diff --git a/test/testqgroup.cpp b/test/testqgroup.cpp index a04ea6a05..b5741f923 100644 --- a/test/testqgroup.cpp +++ b/test/testqgroup.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "testioc.h" @@ -740,6 +741,8 @@ MAIN(testqgroup) testIQ(); testConst(); } + // call epics atexits explicitly to handle older base w/o de-init hooks + epicsExitCallAtExits(); cleanup_for_valgrind(); return testDone(); } diff --git a/test/testqsingle.cpp b/test/testqsingle.cpp index 03e4a7213..b65f37b11 100644 --- a/test/testqsingle.cpp +++ b/test/testqsingle.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -913,6 +914,8 @@ MAIN(testqsingle) timeSim = false; testPutBlock(); } + // call epics atexits explicitly to handle older base w/o de-init hooks + epicsExitCallAtExits(); cleanup_for_valgrind(); return testDone(); } From 8cc9eaafa871721a61e25c954c82ae6a69b650aa Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 31 Oct 2023 09:49:37 -0700 Subject: [PATCH 5/5] ioc: combine registrars and detect QSRV1 also consolidates initHook and epicsAtExit() hooks into a single sequence. --- ioc/groupsourcehooks.cpp | 92 ++++++---------- ioc/iochooks.cpp | 225 +++++++++++++++++++++++++++++++++----- ioc/iocsource.cpp | 40 ------- ioc/iocsource.h | 2 - ioc/pvalink.cpp | 187 +++++++++---------------------- ioc/pvalink.h | 28 +++-- ioc/pvalink_channel.cpp | 36 +++--- ioc/pvalink_jlif.cpp | 7 +- ioc/pvalink_link.cpp | 5 +- ioc/pvalink_lset.cpp | 18 +-- ioc/pvxs/iochooks.h | 74 ++++++++++--- ioc/pvxs3x.dbd | 2 - ioc/pvxs7x.dbd | 3 - ioc/qsrvpvt.h | 67 ++++++++++++ ioc/singlesourcehooks.cpp | 52 +++------ qsrv/softMain.cpp | 3 - test/testioc.h | 26 ----- test/testpvalink.cpp | 18 +-- test/testqgroup.cpp | 2 +- test/testqsingle.cpp | 20 +++- 20 files changed, 501 insertions(+), 406 deletions(-) create mode 100644 ioc/qsrvpvt.h diff --git a/ioc/groupsourcehooks.cpp b/ioc/groupsourcehooks.cpp index 9b5050b5a..bf9e16a2e 100644 --- a/ioc/groupsourcehooks.cpp +++ b/ioc/groupsourcehooks.cpp @@ -20,6 +20,7 @@ #include #include +#include "qsrvpvt.h" #include "groupsource.h" #include "groupconfigprocessor.h" #include "iocshcommand.h" @@ -161,66 +162,49 @@ long dbLoadGroup(const char* jsonFilename, const char* macros) { } } -} -} // namespace pvxs::ioc -using namespace pvxs::ioc; +void processGroups() +{ + GroupConfigProcessor processor; + epicsGuard G(processor.config.groupMapMutex); -namespace { -using namespace pvxs; + // Parse all info(Q:Group... records to configure groups + processor.loadConfigFromDb(); -/** - * Initialise qsrv database group records by adding them as sources in our running pvxs server instance - * - * @param theInitHookState the initHook state - we only want to trigger on the initHookAfterIocBuilt state - ignore all others - */ -void qsrvGroupSourceInit(initHookState theInitHookState) { - try { - if(!IOCSource::enabled()) - return; - if (theInitHookState == initHookAfterInitDatabase) { - GroupConfigProcessor processor; - epicsGuard G(processor.config.groupMapMutex); + // Load group configuration files + processor.loadConfigFiles(); - // Parse all info(Q:Group... records to configure groups - processor.loadConfigFromDb(); + // checks on groupConfigMap + processor.validateGroups(); - // Load group configuration files - processor.loadConfigFiles(); + // Configure groups + processor.defineGroups(); - // checks on groupConfigMap - processor.validateGroups(); + // Resolve triggers + processor.resolveTriggerReferences(); - // Configure groups - processor.defineGroups(); + // Create Server Groups + processor.createGroups(); +} - // Resolve triggers - processor.resolveTriggerReferences(); +void addGroupSrc() +{ + pvxs::ioc::server() + .addSource("qsrvGroup", std::make_shared(), 1); +} - // Create Server Groups - processor.createGroups(); - } else if (theInitHookState == initHookAfterIocBuilt) { - // Load group configuration from parsed groups in iocServer - pvxs::ioc::server().addSource("qsrvGroup", std::make_shared(), 1); - } - } catch(std::exception& e) { - fprintf(stderr, "ERROR: Unhandled exception in %s(%d): %s\n", - __func__, theInitHookState, e.what()); - } +void resetGroups() +{ + auto& config(IOCGroupConfig::instance()); + + // server stopped at this point, but lock anyway + epicsGuard G(config.groupMapMutex); + + config.groupMap.clear(); + config.groupConfigFiles.clear(); } -/** - * IOC pvxs Group Source registrar. This implements the required registrar function that is called by xxxx_registerRecordDeviceDriver, - * the auto-generated stub created for all IOC implementations. - *

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

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