From ddafd37fa41b06896862ebe538a856df01cf7cfb Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 17 Aug 2023 09:18:54 -0700 Subject: [PATCH 01/16] WIP: import existing pvalink 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 710aabd214b440211be4435606525b45146ddfbb Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 17 Aug 2023 09:18:54 -0700 Subject: [PATCH 02/16] WIP: port pvalink --- 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 d0fd5df08776ac23d5482c514155d41311e3ae60 Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Thu, 14 Sep 2023 16:12:50 +0200 Subject: [PATCH 03/16] Fix pvaGetValue for string scalars --- ioc/pvalink_lset.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ioc/pvalink_lset.cpp b/ioc/pvalink_lset.cpp index 42de52346..870343d9d 100644 --- a/ioc/pvalink_lset.cpp +++ b/ioc/pvalink_lset.cpp @@ -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; From 5081a60ea9b44251ee796da0e5ef542dd1fc6f3a Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Wed, 23 Aug 2023 10:50:33 +0200 Subject: [PATCH 04/16] add pvalink json schema --- documentation/pvalink-schema-0.json | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) 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..00abd9823 --- /dev/null +++ b/documentation/pvalink-schema-0.json @@ -0,0 +1,35 @@ +{ + "$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 }, + "local": { "type": "boolean", "default": false } + }, + "additionalProperties": false +} From a6e724617ba2b91669838394580281799d05dba5 Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Tue, 12 Sep 2023 10:06:41 +0200 Subject: [PATCH 05/16] Remove pvaLink* variables --- ioc/pvalink.cpp | 4 ---- ioc/pvalink.h | 2 -- ioc/pvalink_null.cpp | 2 -- 3 files changed, 8 deletions(-) diff --git a/ioc/pvalink.cpp b/ioc/pvalink.cpp index 4ec1cdd00..15d67605a 100644 --- a/ioc/pvalink.cpp +++ b/ioc/pvalink.cpp @@ -38,9 +38,6 @@ # define HAVE_SHUTDOWN_HOOKS #endif -int pvaLinkDebug; -int pvaLinkIsolate; - namespace pvxs { namespace ioc { using namespace pvxlink; @@ -351,6 +348,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..18ba4d377 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; } diff --git a/ioc/pvalink_null.cpp b/ioc/pvalink_null.cpp index 4dce607d6..1dd4bdb26 100644 --- a/ioc/pvalink_null.cpp +++ b/ioc/pvalink_null.cpp @@ -11,11 +11,9 @@ static void installPVAAddLinkHook() {} struct jlif {} lsetPVA; extern "C" { -int pvaLinkDebug; int pvaLinkNWorkers; epicsExportRegistrar(installPVAAddLinkHook); epicsExportAddress(jlif, lsetPVA); - epicsExportAddress(int, pvaLinkDebug); epicsExportAddress(int, pvaLinkNWorkers); } From 4491ba031ce803a39491fd720a9c77e885f6aaec Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Tue, 5 Sep 2023 12:35:16 +0200 Subject: [PATCH 06/16] Move close() call to pvaGlobal_t from worker queue --- ioc/pvalink.cpp | 2 +- ioc/pvalink.h | 1 + ioc/pvalink_channel.cpp | 16 ++++++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ioc/pvalink.cpp b/ioc/pvalink.cpp index 15d67605a..5e487d5a0 100644 --- a/ioc/pvalink.cpp +++ b/ioc/pvalink.cpp @@ -50,7 +50,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 diff --git a/ioc/pvalink.h b/ioc/pvalink.h index 18ba4d377..066017abe 100644 --- a/ioc/pvalink.h +++ b/ioc/pvalink.h @@ -113,6 +113,7 @@ struct pvaGlobal_t : private epicsThreadRunable { pvaGlobal_t(); virtual ~pvaGlobal_t(); + void close(); }; extern pvaGlobal_t *pvaGlobal; diff --git a/ioc/pvalink_channel.cpp b/ioc/pvalink_channel.cpp index 7b7096398..964ea2ca8 100644 --- a/ioc/pvalink_channel.cpp +++ b/ioc/pvalink_channel.cpp @@ -33,12 +33,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 +51,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; From 5a62f8b6989d4050a436a073da7c152429f98798 Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Tue, 5 Sep 2023 12:54:43 +0200 Subject: [PATCH 07/16] Removed latch state --- ioc/pvalink.cpp | 6 +++--- ioc/pvalink.h | 3 +-- ioc/pvalink_channel.cpp | 16 +++++++++------- ioc/pvalink_jlif.cpp | 2 +- ioc/pvalink_link.cpp | 4 ++-- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ioc/pvalink.cpp b/ioc/pvalink.cpp index 5e487d5a0..710b394b1 100644 --- a/ioc/pvalink.cpp +++ b/ioc/pvalink.cpp @@ -246,7 +246,7 @@ void dbpvar(const char *precordname, int level) } nchans++; - if(chan->connected_latched) + if(chan->state == pvaLinkChannel::Connected) nconn++; if(!precordname) @@ -255,7 +255,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 { @@ -263,7 +263,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) { diff --git a/ioc/pvalink.h b/ioc/pvalink.h index 066017abe..2472686fc 100644 --- a/ioc/pvalink.h +++ b/ioc/pvalink.h @@ -139,8 +139,7 @@ struct pvaLinkChannel : public epicsThreadRunable Disconnected, Connecting, Connected, - } state = Disconnected, - state_latched = Disconnected; + } state = Disconnected; bool isatomic = false; bool queued = false; // added to WorkQueue diff --git a/ioc/pvalink_channel.cpp b/ioc/pvalink_channel.cpp index 964ea2ca8..cc9390493 100644 --- a/ioc/pvalink_channel.cpp +++ b/ioc/pvalink_channel.cpp @@ -301,7 +301,7 @@ 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])) { + } else if(state_latched == Connected && !op_mon.changed.logical_and(scan_changed[idx])) { return; } else if (precord->pact) { @@ -323,6 +323,8 @@ void pvaLinkChannel::run() queued = false; + state_latched = state; + Value top; try { top = op_mon->pop(); @@ -393,17 +395,17 @@ void pvaLinkChannel::run() // handle next update from monitor queue. // still under lock to safeguard concurrent calls to lset functions - if(connected && root) { + if(state == Connected && root) { // DEBUG(this, <queue.add(shared_from_this()); + pvaGlobal->queue.push(shared_from_this()); } else { run_done.signal(); } diff --git a/ioc/pvalink_jlif.cpp b/ioc/pvalink_jlif.cpp index 85cd6ac60..3738697e7 100644 --- a/ioc/pvalink_jlif.cpp +++ b/ioc/pvalink_jlif.cpp @@ -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"); } diff --git a/ioc/pvalink_link.cpp b/ioc/pvalink_link.cpp index af4e4e30c..b2c1c2075 100644 --- a/ioc/pvalink_link.cpp +++ b/ioc/pvalink_link.cpp @@ -66,7 +66,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 @@ -110,7 +110,7 @@ void pvaLink::onTypeChange() { // DEBUG(this,<precord->name<<" type change"); - 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"); From ebe9d698062ab2f5a50597831fa626abe4d1e133 Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Thu, 7 Sep 2023 10:36:42 +0200 Subject: [PATCH 08/16] Update .gitignore to ignore VS code configuration --- .gitignore | 1 + 1 file changed, 1 insertion(+) 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 From cc23da693afb7ecaa36e3c05f9654f5fa06f9191 Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Thu, 7 Sep 2023 10:43:04 +0200 Subject: [PATCH 09/16] Add lset(pva) support for base 7.x --- ioc/Makefile | 7 +++++++ ioc/pvalink_lset.cpp | 9 +++------ ioc/{pvxsIoc.dbd => pvxs3x.dbd} | 1 + ioc/pvxs7x.dbd | 11 +++++++++++ 4 files changed, 22 insertions(+), 6 deletions(-) rename ioc/{pvxsIoc.dbd => pvxs3x.dbd} (89%) create mode 100644 ioc/pvxs7x.dbd diff --git a/ioc/Makefile b/ioc/Makefile index 225566e61..9d9ecc823 100644 --- a/ioc/Makefile +++ b/ioc/Makefile @@ -90,3 +90,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_lset.cpp b/ioc/pvalink_lset.cpp index 870343d9d..d01f2a63d 100644 --- a/ioc/pvalink_lset.cpp +++ b/ioc/pvalink_lset.cpp @@ -7,13 +7,14 @@ #include #include #include -#include // redirect stdout/stderr #include #include "dbentry.h" #include "pvalink.h" #include "utilpvt.h" +#include // redirect stdout/stderr; include after libevent/util.h + DEFINE_LOGGER(loglset, "pvxs.pvalink.lset"); namespace pvxlink { @@ -29,7 +30,6 @@ using namespace pvxs; dbfType getLinkType(DBLINK *plink) { - dbCommon *prec = plink->precord; ioc::DBEntry ent(plink->precord); for(long status = dbFirstField(ent, 0); !status; status = dbNextField(ent, 0)) { @@ -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; @@ -602,8 +602,6 @@ void pvaScanForward(DBLINK *plink) } //namespace -namespace pvalink { - lset pva_lset = { 0, 1, // non-const, volatile &pvaOpenLink, @@ -626,5 +624,4 @@ lset pva_lset = { //&pvaReportLink, }; -} //namespace pvalink } // namespace pvxlink diff --git a/ioc/pvxsIoc.dbd b/ioc/pvxs3x.dbd similarity index 89% rename from ioc/pvxsIoc.dbd rename to ioc/pvxs3x.dbd index 6330d4a3e..e296ec6e1 100644 --- a/ioc/pvxsIoc.dbd +++ b/ioc/pvxs3x.dbd @@ -1,6 +1,7 @@ registrar(pvxsBaseRegistrar) registrar(pvxsSingleSourceRegistrar) registrar(pvxsGroupSourceRegistrar) +registrar(installPVAAddLinkHook) # from demo.cpp device(waveform, CONSTANT, devWfPDBQ2Demo, "QSRV2 Demo") 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) From b254dc91a664f5e0e2c522c3904b0f60c405ee6b Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Thu, 14 Sep 2023 09:13:12 +0200 Subject: [PATCH 10/16] Remove pvalink support for base 3.x --- ioc/Makefile | 5 ----- ioc/pvalink_null.cpp | 19 ------------------- ioc/pvxs3x.dbd | 1 - 3 files changed, 25 deletions(-) delete mode 100644 ioc/pvalink_null.cpp diff --git a/ioc/Makefile b/ioc/Makefile index 9d9ecc823..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) diff --git a/ioc/pvalink_null.cpp b/ioc/pvalink_null.cpp deleted file mode 100644 index 1dd4bdb26..000000000 --- a/ioc/pvalink_null.cpp +++ /dev/null @@ -1,19 +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 pvaLinkNWorkers; - - epicsExportRegistrar(installPVAAddLinkHook); - epicsExportAddress(jlif, lsetPVA); - epicsExportAddress(int, pvaLinkNWorkers); -} diff --git a/ioc/pvxs3x.dbd b/ioc/pvxs3x.dbd index e296ec6e1..6330d4a3e 100644 --- a/ioc/pvxs3x.dbd +++ b/ioc/pvxs3x.dbd @@ -1,7 +1,6 @@ registrar(pvxsBaseRegistrar) registrar(pvxsSingleSourceRegistrar) registrar(pvxsGroupSourceRegistrar) -registrar(installPVAAddLinkHook) # from demo.cpp device(waveform, CONSTANT, devWfPDBQ2Demo, "QSRV2 Demo") From 417d401ba8eef268fde7f1da76df462a6446306e Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Tue, 12 Sep 2023 11:12:24 +0200 Subject: [PATCH 11/16] Update cached value object in pvaLinkChannel::run --- ioc/pvalink_channel.cpp | 81 ++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 45 deletions(-) diff --git a/ioc/pvalink_channel.cpp b/ioc/pvalink_channel.cpp index cc9390493..0ccb833df 100644 --- a/ioc/pvalink_channel.cpp +++ b/ioc/pvalink_channel.cpp @@ -6,10 +6,14 @@ #include +#include + #include "pvalink.h" #include "dblocker.h" #include "dbmanylocker.h" +DEFINE_LOGGER(_logger, "ioc.pvalink.channel"); + int pvaLinkNWorkers = 1; namespace pvxlink { @@ -94,7 +98,7 @@ 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&) @@ -301,8 +305,11 @@ void pvaLinkChannel::run_dbProcess(size_t idx) if(scan_check_passive[idx] && precord->scan!=0) { return; - } else if(state_latched == Connected && !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) @@ -323,8 +330,6 @@ void pvaLinkChannel::run() queued = false; - state_latched = state; - Value top; try { top = op_mon->pop(); @@ -332,11 +337,10 @@ void pvaLinkChannel::run() 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++; @@ -355,9 +359,31 @@ 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)) + { + 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. @@ -389,41 +415,6 @@ void pvaLinkChannel::run() 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(state == 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 = state_latched == Connected; - - if(links_changed) { } } From 2f715a5ce8db435b9d7da833884eac09a4fecd36 Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Thu, 7 Sep 2023 13:14:41 +0200 Subject: [PATCH 12/16] Removing queued state from pvaLikeChannel --- ioc/pvalink.h | 1 - ioc/pvalink_channel.cpp | 8 +------- ioc/pvalink_jlif.cpp | 4 ---- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/ioc/pvalink.h b/ioc/pvalink.h index 2472686fc..1121b54d9 100644 --- a/ioc/pvalink.h +++ b/ioc/pvalink.h @@ -142,7 +142,6 @@ struct pvaLinkChannel : public epicsThreadRunable } 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 0ccb833df..4ff17c758 100644 --- a/ioc/pvalink_channel.cpp +++ b/ioc/pvalink_channel.cpp @@ -103,11 +103,7 @@ void pvaLinkChannel::open() .rawRequest(pvRequest) .event([this](const client::Subscription&) { - Guard G(lock); - if(!queued) { - pvaGlobal->queue.push(shared_from_this()); - queued = true; - } + pvaGlobal->queue.push(shared_from_this()); }) .exec(); providerName = "remote"; @@ -328,8 +324,6 @@ void pvaLinkChannel::run() { Guard G(lock); - queued = false; - Value top; try { top = op_mon->pop(); diff --git a/ioc/pvalink_jlif.cpp b/ioc/pvalink_jlif.cpp index 3738697e7..cf24c28c8 100644 --- a/ioc/pvalink_jlif.cpp +++ b/ioc/pvalink_jlif.cpp @@ -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); From 09ec0b74691f61e1848302659a00f11d5a1fa473 Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Thu, 7 Sep 2023 15:28:20 +0200 Subject: [PATCH 13/16] Add debug functionality --- ioc/pvalink.cpp | 2 ++ ioc/pvalink_channel.cpp | 18 ++++++++++++++--- ioc/pvalink_link.cpp | 8 ++++++-- ioc/pvalink_lset.cpp | 43 +++++++++++++++++++++++------------------ 4 files changed, 47 insertions(+), 24 deletions(-) diff --git a/ioc/pvalink.cpp b/ioc/pvalink.cpp index 710b394b1..2430c1bea 100644 --- a/ioc/pvalink.cpp +++ b/ioc/pvalink.cpp @@ -27,6 +27,8 @@ #include /* redirects stdout/stderr */ +#include + #include "pvalink.h" #include "dblocker.h" #include "dbentry.h" diff --git a/ioc/pvalink_channel.cpp b/ioc/pvalink_channel.cpp index 4ff17c758..caf19c5ca 100644 --- a/ioc/pvalink_channel.cpp +++ b/ioc/pvalink_channel.cpp @@ -103,6 +103,7 @@ 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()); }) .exec(); @@ -153,7 +154,7 @@ Value linkBuildPut(pvaLinkChannel *self, Value&& prototype) } else { // assume scalar } } -// DEBUG(this, <key.first.c_str()); return top; } @@ -172,7 +173,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(); @@ -247,7 +248,7 @@ void pvaLinkChannel::put(bool force) } pvReq["record._options.process"] = proc; -// DEBUG(this, <provider_remote.put(key.first) @@ -324,10 +325,13 @@ void pvaLinkChannel::run() { Guard G(lock); + 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; } @@ -360,6 +364,8 @@ void pvaLinkChannel::run() // 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 @@ -406,6 +412,8 @@ void pvaLinkChannel::run() scan_check_passive.push_back(link->pp != 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; @@ -424,15 +432,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.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_link.cpp b/ioc/pvalink_link.cpp index b2c1c2075..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() @@ -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,7 +112,7 @@ 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->state == pvaLinkChannel::Connected && lchan->root); // we should only be called when connected diff --git a/ioc/pvalink_lset.cpp b/ioc/pvalink_lset.cpp index d01f2a63d..277fadbf1 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(loglset, "pvxs.pvalink.lset"); +DEFINE_LOGGER(_logger, "pvxs.pvalink.lset"); namespace pvxlink { namespace { @@ -26,7 +26,7 @@ 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) { @@ -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 @@ -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() @@ -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; } @@ -365,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; @@ -390,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; @@ -415,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; @@ -428,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; @@ -442,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; @@ -464,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; @@ -483,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; @@ -499,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; @@ -516,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; } @@ -563,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; @@ -593,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() } From 414cec984760e11a3d7f49461855329d69f67748 Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Mon, 11 Sep 2023 16:55:53 +0200 Subject: [PATCH 14/16] Rename internal fields to match json spec --- ioc/pvalink.cpp | 4 ++-- ioc/pvalink.h | 4 ++-- ioc/pvalink_channel.cpp | 6 +++--- ioc/pvalink_jlif.cpp | 30 +++++++++++++++--------------- ioc/pvalink_lset.cpp | 6 +++--- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/ioc/pvalink.cpp b/ioc/pvalink.cpp index 2430c1bea..88b7193e7 100644 --- a/ioc/pvalink.cpp +++ b/ioc/pvalink.cpp @@ -301,14 +301,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; diff --git a/ioc/pvalink.h b/ioc/pvalink.h index 1121b54d9..d79749e64 100644 --- a/ioc/pvalink.h +++ b/ioc/pvalink.h @@ -68,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; diff --git a/ioc/pvalink_channel.cpp b/ioc/pvalink_channel.cpp index caf19c5ca..86ad7737c 100644 --- a/ioc/pvalink_channel.cpp +++ b/ioc/pvalink_channel.cpp @@ -220,7 +220,7 @@ void pvaLinkChannel::put(bool force) doit = true; - switch(link->pp) { + switch(link->proc) { case pvaLink::NPP: reqProcess |= 1; break; @@ -405,11 +405,11 @@ 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()); diff --git a/ioc/pvalink_jlif.cpp b/ioc/pvalink_jlif.cpp index cf24c28c8..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; diff --git a/ioc/pvalink_lset.cpp b/ioc/pvalink_lset.cpp index 277fadbf1..0e667ea2a 100644 --- a/ioc/pvalink_lset.cpp +++ b/ioc/pvalink_lset.cpp @@ -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 @@ -355,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); } From 87e8bdb9dc2e0f5cabb5309f6df6b897053665c9 Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Wed, 13 Sep 2023 15:16:56 +0200 Subject: [PATCH 15/16] WIP: allow for puts --- ioc/pvalink_channel.cpp | 44 +++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/ioc/pvalink_channel.cpp b/ioc/pvalink_channel.cpp index 86ad7737c..308128fb0 100644 --- a/ioc/pvalink_channel.cpp +++ b/ioc/pvalink_channel.cpp @@ -5,6 +5,7 @@ */ #include +#include #include @@ -135,23 +136,34 @@ 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()); + } } } log_debug_printf(_logger, "%s put built\n", self->key.first.c_str()); @@ -212,10 +224,8 @@ 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; From 7d804720729fb691e71cdbcac1d07b3b03cffe9f Mon Sep 17 00:00:00 2001 From: Simon Rose Date: Fri, 8 Sep 2023 13:48:44 +0200 Subject: [PATCH 16/16] Add tests for pvalink properties --- ioc/pvalink.cpp | 10 +- ioc/pvxs/iochooks.h | 9 ++ test/Makefile | 7 ++ test/testioc.h | 1 + test/testpvalink.cpp | 218 +++++++++++++++++++++++++++++++++++++++++++ test/testpvalink.db | 63 +++++++++++++ 6 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 test/testpvalink.cpp create mode 100644 test/testpvalink.db diff --git a/ioc/pvalink.cpp b/ioc/pvalink.cpp index 88b7193e7..24eb59f85 100644 --- a/ioc/pvalink.cpp +++ b/ioc/pvalink.cpp @@ -25,15 +25,15 @@ #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) @@ -126,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) 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/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..1a7b4ea2b --- /dev/null +++ b/test/testpvalink.cpp @@ -0,0 +1,218 @@ + +#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); + + // TODO: This test will only be implemented after the pva link puts work. + //testdbGetFieldEqual("target:i2.VAL", DBF_LONG, 14L); + testdbGetFieldEqual("src:o2.VAL", DBF_LONG, 14L); + } + + 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(37); + testSetup(); + + try + { + TestIOC IOC; + + testdbReadDatabase("testioc.dbd", NULL, NULL); + testioc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("testpvalink.db", NULL, NULL); + + IOC.init(); + testGet(); + testFieldLinks(); + testProc(); + testSevr(); + testPut(); + (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..97dfae93c --- /dev/null +++ b/test/testpvalink.db @@ -0,0 +1,63 @@ + +# 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(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") +}