From 9dd2da9238a3e1427e6c1480713e972e03ec5bec 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 569a436abc91c4023c297ce958c28fccfa5c41d6 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 ff314a95778287df86eac3f3f274f5d0a43a5d41 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 44e7af53402900cccf4c5a18de86f66f853b4cb1 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 | 82 ++++--- ioc/pvalink_channel.cpp | 199 ++++++++-------- ioc/pvalink_jlif.cpp | 12 +- ioc/pvalink_link.cpp | 104 ++++----- ioc/pvalink_lset.cpp | 189 +++++++++------ ioc/pvxs/iochooks.h | 15 +- ioc/singlesource.cpp | 29 ++- ioc/subscriptionctx.h | 3 +- setup.py | 5 + test/Makefile | 1 + test/testpvalink.cpp | 342 +++++++++++++++++++++++----- test/testpvalink.db | 132 +++++++++-- test/testqgroup.cpp | 3 + test/testqsingle.cpp | 3 + 17 files changed, 891 insertions(+), 378 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..0aaa5578e 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,9 +251,6 @@ 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(); }; diff --git a/ioc/pvalink_channel.cpp b/ioc/pvalink_channel.cpp index d86bc8d5a..970d9ae9f 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,107 +309,84 @@ 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; @@ -421,45 +401,46 @@ void pvaLinkChannel::run() 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->proc != pvaLink::CP); + bool check_passive = 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..3d04bf653 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,41 @@ 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'); } } // namespace pvalink diff --git a/ioc/pvalink_lset.cpp b/ioc/pvalink_lset.cpp index d471375a0..66e43d605 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,18 @@ void pvaOpenLink(DBLINK *plink) 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 = 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(); } return; @@ -119,6 +134,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 +167,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 +224,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 +240,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; } @@ -355,9 +367,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 +385,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 +398,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 +416,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 +432,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 +456,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(); - // 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); + uint16_t prec = 0; + (void)self->fld_meta["display.precision"].as(prec); + if(precision) + *precision = prec; + + 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 +476,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 +504,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 +519,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 +534,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 +598,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 +628,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 +640,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 +674,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..bbd7d1753 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 @@ -80,16 +103,15 @@ namespace 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); + testqsrvWaitForLinkConnected(&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); + { + QSrvWaitForLinkUpdate C(&i1->inp); + testdbPutFieldOk("target:ai", DBF_FLOAT, 5.0); + } // now it's changed testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 5L); @@ -104,8 +126,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 +138,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 +146,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 +166,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,30 +191,31 @@ 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 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); @@ -202,39 +224,239 @@ namespace testdbGetArrFieldEqual("target:aai_inp", DBF_ULONG, 10, 5, expected_ulong); } + 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); + + auto special(server::SharedPV::buildReadonly()); + special.open(nt::NTScalar{TypeCode::Int32}.create() + .update("value", 43)); + serv.addPV("special:pv", special); + + 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); - 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); + testEq(dbGetLinkDBFtype(&inp->inp), DBF_DOUBLE); -#else - testSkip(5, "Not USE_MULTILOCK"); -#endif + // 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(85); testSetup(); + pvxs::logger_config_env(); try { @@ -252,7 +474,13 @@ MAIN(testpvalink) testPut(); testStrings(); testArrays(); - (void)testPutAsync; + testStringArray(); + testPutAsync(); + testDisconnect(); + testMeta(); + testFwd(); + testAtomic(); + testEnum(); testqsrvShutdownOk(); IOC.shutdown(); testqsrvCleanup(); @@ -261,6 +489,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..bd2990ad8 100644 --- a/test/testpvalink.db +++ b/test/testpvalink.db @@ -48,31 +48,22 @@ 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(calc, "async:seq") { + field(CALC, "VAL+1") } -record(calcout, "async:slow") { - field(ODLY, "1") - field(CALC, "A") - field(FLNK, "async:slow2") - 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:slow2") { - field(INP , "async:seq PP") - field(TPRO, "1") +record(longin, "async:target") { + field(INP , "async:seq PP MS") } -record(longin, "async:after") { - field(INP , "async:seq PP") - field(MDEL, "-1") - field(TPRO, "1") +record(longin, "async:next") { + field(INP , "async:seq PP MS") } record(aao, "source:aao") { @@ -90,4 +81,105 @@ record(aai, "target:aai_inp") { record(aai, "target:aai_out") { field(NELM, "2") field(FTVL, "ULONG") -} \ No newline at end of file +} + +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 1975a36a9ecb8152793d29041fb184a778e974c9 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 --- ioc/groupsourcehooks.cpp | 17 ++++------ ioc/iochooks.cpp | 69 ++++++++++++++++++++++++++++++++++++--- ioc/iocsource.cpp | 40 ----------------------- ioc/iocsource.h | 2 -- ioc/pvalink.cpp | 20 ++++++++---- ioc/pvxs3x.dbd | 2 -- ioc/pvxs7x.dbd | 3 -- ioc/qsrvpvt.h | 37 +++++++++++++++++++++ ioc/singlesourcehooks.cpp | 16 ++++----- qsrv/softMain.cpp | 3 -- 10 files changed, 128 insertions(+), 81 deletions(-) create mode 100644 ioc/qsrvpvt.h diff --git a/ioc/groupsourcehooks.cpp b/ioc/groupsourcehooks.cpp index 9b5050b5a..a5395a547 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" @@ -176,8 +177,6 @@ using namespace pvxs; */ void qsrvGroupSourceInit(initHookState theInitHookState) { try { - if(!IOCSource::enabled()) - return; if (theInitHookState == initHookAfterInitDatabase) { GroupConfigProcessor processor; epicsGuard G(processor.config.groupMapMutex); @@ -209,6 +208,10 @@ void qsrvGroupSourceInit(initHookState theInitHookState) { } } +} // namespace + +namespace pvxs { +namespace ioc { /** * 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. @@ -220,7 +223,7 @@ void qsrvGroupSourceInit(initHookState theInitHookState) { * 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" @@ -236,10 +239,4 @@ void pvxsGroupSourceRegistrar() { 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..f4c820415 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,11 @@ #include "iocshcommand.h" #include "utilpvt.h" +#include "qsrvpvt.h" // 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 { @@ -286,6 +287,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 +347,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 +373,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..2621b8ce0 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 */ @@ -404,17 +405,22 @@ 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/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..3649fc976 --- /dev/null +++ b/ioc/qsrvpvt.h @@ -0,0 +1,37 @@ +/* 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) +void single_enable(); +#else +static inline +void single_enable() {} +#endif + +#if EPICS_VERSION_INT >= VERSION_INT(7, 0, 0 ,0) +void group_enable(); +void pvalink_enable(); +#else +static inline +void group_enable() {} +static inline +void pvalink_enable() {} +#endif + +#if EPICS_VERSION_INT >= VERSION_INT(7, 0, 4, 0) +# define USE_DEINIT_HOOKS +#endif + +}} // namespace pvxs::ioc + +#endif // QSRVPVT_H diff --git a/ioc/singlesourcehooks.cpp b/ioc/singlesourcehooks.cpp index e86a278a8..b88da6c79 100644 --- a/ioc/singlesourcehooks.cpp +++ b/ioc/singlesourcehooks.cpp @@ -21,6 +21,7 @@ #include #include +#include "qsrvpvt.h" #include "iocshcommand.h" #include "singlesource.h" @@ -148,8 +149,6 @@ dbServer qsrv2Server = { * @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 @@ -158,6 +157,10 @@ void qsrvSingleSourceInit(initHookState theInitHookState) { } } +} // namespace + +namespace pvxs { +namespace ioc { /** * 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. @@ -170,7 +173,7 @@ void qsrvSingleSourceInit(initHookState theInitHookState) { * 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 single_enable() { // Register commands to be available in the IOC shell IOCShCommand("pvxsl", "details", "List PV names.\n") @@ -179,11 +182,6 @@ void pvxsSingleSourceRegistrar() { 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 {