Files
pva2pva/pdbApp/pdb.cpp
Michael Davidsaver bdbf57350b replace shareLib.h with QSRV_API
and hopefully avoid at least some of the maddening
include order bugs so common with sharedLib.h.
2019-09-05 20:01:56 -07:00

764 lines
27 KiB
C++

#include <vector>
#include <utility>
#include <errlog.h>
#include <epicsString.h>
#include <epicsAtomic.h>
// printfs in this file will be redirected for capture
#include <epicsStdio.h>
#include <dbAccess.h>
#include <dbChannel.h>
#include <dbStaticLib.h>
#include <dbNotify.h>
#include <dbEvent.h>
#include <pv/pvAccess.h>
#include <pv/configuration.h>
#include "helper.h"
#include "pdbsingle.h"
#include "pvif.h"
#ifdef USE_MULTILOCK
# include "pdbgroup.h"
#endif
#include <epicsExport.h>
namespace pvd = epics::pvData;
namespace pva = epics::pvAccess;
int PDBProviderDebug;
namespace {
struct Splitter {
const char sep, *cur, *end;
Splitter(const char *s, char sep)
:sep(sep), cur(s)
{
assert(s);
end = strchr(cur, sep);
}
bool operator!() const { return !cur; }
bool snip(std::string& ret) {
if(!cur) return false;
if(end) ret = std::string(cur, end-cur);
else ret = std::string(cur);
if(end) {
cur = end+1;
end = strchr(cur, sep);
} else {
cur = NULL;
}
return true;
}
};
struct GroupMemberInfo {
// consumes builder
GroupMemberInfo(const std::string& a, const std::string& b, const std::tr1::shared_ptr<PVIFBuilder>& builder)
:pvname(a), pvfldname(b), builder(builder), putorder(0) {}
std::string pvname, // aka. name passed to dbChannelOpen()
pvfldname; // PVStructure sub-field
std::string structID; // ID to assign to sub-field
typedef std::set<std::string> triggers_t;
triggers_t triggers; // names in GroupInfo::members_names which are post()d on events from pvfldname
std::tr1::shared_ptr<PVIFBuilder> builder; // not actually shared, but allows us to be copyable
int putorder;
bool operator<(const GroupMemberInfo& o) const {
return putorder<o.putorder;
}
};
struct GroupInfo {
GroupInfo(const std::string& name) : name(name),atomic(Unset),hastriggers(false) {}
std::string name, structID;
typedef std::vector<GroupMemberInfo> members_t;
members_t members;
typedef std::map<std::string, size_t> members_map_t;
members_map_t members_map;
typedef std::set<std::string> triggers_set_t;
typedef std::map<std::string, triggers_set_t> triggers_t;
triggers_t triggers;
enum tribool {Unset,True,False} atomic;
bool hastriggers;
};
// Iterates all PDB records and gathers info() to construct PDB groups
struct PDBProcessor
{
typedef std::map<std::string, GroupInfo> groups_t;
groups_t groups;
std::string recbase;
GroupInfo *curgroup;
// validate trigger mappings and process into bit map form
void resolveTriggers()
{
FOREACH(groups_t::iterator, it, end, groups) { // for each group
GroupInfo& info = it->second;
if(info.hastriggers) {
FOREACH(GroupInfo::triggers_t::iterator, it2, end2, info.triggers) { // for each trigger source
const std::string& src = it2->first;
GroupInfo::triggers_set_t& targets = it2->second;
GroupInfo::members_map_t::iterator it2x = info.members_map.find(src);
if(it2x==info.members_map.end()) {
fprintf(stderr, "Error: Group \"%s\" defines triggers from non-existant field \"%s\"\n",
info.name.c_str(), src.c_str());
continue;
}
GroupMemberInfo& srcmem = info.members[it2x->second];
if(PDBProviderDebug>2)
fprintf(stderr, " pdb trg '%s.%s' -> ",
info.name.c_str(), src.c_str());
FOREACH(GroupInfo::triggers_set_t::const_iterator, it3, end3, targets) { // for each trigger target
const std::string& target = *it3;
if(target=="*") {
for(size_t i=0; i<info.members.size(); i++) {
if(info.members[i].pvname.empty())
continue;
srcmem.triggers.insert(info.members[i].pvfldname);
if(PDBProviderDebug>2)
fprintf(stderr, "%s, ", info.members[i].pvfldname.c_str());
}
} else {
GroupInfo::members_map_t::iterator it3x = info.members_map.find(target);
if(it3x==info.members_map.end()) {
fprintf(stderr, "Error: Group \"%s\" defines triggers to non-existant field \"%s\"\n",
info.name.c_str(), target.c_str());
continue;
}
const GroupMemberInfo& targetmem = info.members[it3x->second];
if(targetmem.pvname.empty()) {
if(PDBProviderDebug>2)
fprintf(stderr, "<ignore: %s>, ", targetmem.pvfldname.c_str());
} else {
// and finally, update source BitSet
srcmem.triggers.insert(targetmem.pvfldname);
if(PDBProviderDebug>2)
fprintf(stderr, "%s, ", targetmem.pvfldname.c_str());
}
}
}
if(PDBProviderDebug>2) fprintf(stderr, "\n");
}
} else {
if(PDBProviderDebug>1) fprintf(stderr, " pdb default triggers for '%s'\n", info.name.c_str());
FOREACH(GroupInfo::members_t::iterator, it2, end2, info.members) {
GroupMemberInfo& mem = *it2;
if(mem.pvname.empty())
continue;
mem.triggers.insert(mem.pvfldname); // default is self trigger
}
}
}
}
PDBProcessor() : curgroup(NULL)
{
for(pdbRecordIterator rec; !rec.done(); rec.next())
{
const char *json = rec.info("Q:group");
if(!json) continue;
#ifndef USE_MULTILOCK
static bool warned;
if(!warned) {
warned = true;
fprintf(stderr, "%s: ignoring info(Q:Group, ...\n", rec.name());
}
#else
if(PDBProviderDebug>2) {
fprintf(stderr, "%s: info(Q:Group, ...\n", rec.name());
}
try {
GroupConfig conf;
GroupConfig::parse(json, conf);
if(!conf.warning.empty())
fprintf(stderr, "%s: warning(s) from info(Q:group, ...\n%s", rec.name(), conf.warning.c_str());
recbase = rec.name();
recbase += ".";
for(GroupConfig::groups_t::const_iterator git=conf.groups.begin(), gend=conf.groups.end();
git!=gend; ++git)
{
const std::string& grpname = git->first;
const GroupConfig::Group& grp = git->second;
if(dbChannelTest(grpname.c_str())==0) {
fprintf(stderr, "%s : Error: Group name conflicts with record name. Ignoring...\n", grpname.c_str());
continue;
}
groups_t::iterator it = groups.find(grpname);
if(it==groups.end()) {
// lazy creation of group
std::pair<groups_t::iterator, bool> ins(groups.insert(std::make_pair(grpname, GroupInfo(grpname))));
it = ins.first;
}
curgroup = &it->second;
if(!grp.id.empty())
curgroup->structID = grp.id;
for(GroupConfig::Group::fields_t::const_iterator fit=grp.fields.begin(), fend=grp.fields.end();
fit!=fend; ++fit)
{
const std::string& fldname = fit->first;
const GroupConfig::Field& fld = fit->second;
GroupInfo::members_map_t::const_iterator oldgrp(curgroup->members_map.find(fldname));
if(oldgrp!=curgroup->members_map.end()) {
fprintf(stderr, "%s.%s Warning: ignoring duplicate mapping %s%s\n",
grpname.c_str(), fldname.c_str(),
recbase.c_str(), fld.channel.c_str());
continue;
}
std::tr1::shared_ptr<PVIFBuilder> builder(PVIFBuilder::create(fld.type));
curgroup->members.push_back(GroupMemberInfo(fld.channel.empty() ? fld.channel : recbase + fld.channel, fldname, builder));
curgroup->members.back().structID = fld.id;
curgroup->members.back().putorder = fld.putorder;
curgroup->members_map[fldname] = (size_t)-1; // placeholder see below
if(PDBProviderDebug>2) {
fprintf(stderr, " pdb map '%s.%s' <-> '%s'\n",
curgroup->name.c_str(),
curgroup->members.back().pvfldname.c_str(),
curgroup->members.back().pvname.c_str());
}
if(!fld.trigger.empty()) {
GroupInfo::triggers_t::iterator it = curgroup->triggers.find(fldname);
if(it==curgroup->triggers.end()) {
std::pair<GroupInfo::triggers_t::iterator, bool> ins(curgroup->triggers.insert(
std::make_pair(fldname, GroupInfo::triggers_set_t())));
it = ins.first;
}
Splitter sep(fld.trigger.c_str(), ',');
std::string target;
while(sep.snip(target)) {
curgroup->hastriggers = true;
it->second.insert(target);
}
}
}
if(grp.atomic_set) {
GroupInfo::tribool V = grp.atomic ? GroupInfo::True : GroupInfo::False;
if(curgroup->atomic!=GroupInfo::Unset && curgroup->atomic!=V)
fprintf(stderr, "%s Warning: pdb atomic setting inconsistent '%s'\n",
grpname.c_str(), curgroup->name.c_str());
curgroup->atomic=V;
if(PDBProviderDebug>2)
fprintf(stderr, " pdb atomic '%s' %s\n",
curgroup->name.c_str(), curgroup->atomic ? "YES" : "NO");
}
}
}catch(std::exception& e){
fprintf(stderr, "%s: Error parsing info(\"Q:group\", ... : %s\n",
rec.record()->name, e.what());
}
#endif // USE_MULTILOCK
}
// re-sort GroupInfo::members to ensure the shorter names appear first
// allows use of 'existing' PVIFBuilder on leaves.
for(groups_t::iterator it = groups.begin(), end = groups.end(); it!=end; ++it)
{
GroupInfo& info = it->second;
std::sort(info.members.begin(),
info.members.end());
info.members_map.clear();
for(size_t i=0, N=info.members.size(); i<N; i++)
{
info.members_map[info.members[i].pvfldname] = i;
}
}
resolveTriggers();
// must not re-sort members after this point as resolveTriggers()
// has stored array indicies.
}
};
}
size_t PDBProvider::num_instances;
PDBProvider::PDBProvider(const epics::pvAccess::Configuration::const_shared_pointer &)
{
/* Long view
* 1. PDBProcessor collects info() tags and builds config of groups and group fields
* (including those w/o a dbChannel)
* 2. Build pvd::Structure and discard those w/o dbChannel
* 3. Build the lockers for the triggers of each group field
*/
PDBProcessor proc;
pvd::FieldCreatePtr fcreate(pvd::getFieldCreate());
pvd::PVDataCreatePtr pvbuilder(pvd::getPVDataCreate());
pvd::StructureConstPtr _options(fcreate->createFieldBuilder()
->addNestedStructure("_options")
->add("queueSize", pvd::pvUInt)
->add("atomic", pvd::pvBoolean)
->endNested()
->createStructure());
#ifdef USE_MULTILOCK
// assemble group PVD structure definitions and build dbLockers
FOREACH(PDBProcessor::groups_t::const_iterator, it, end, proc.groups)
{
const GroupInfo &info=it->second;
try{
if(persist_pv_map.find(info.name)!=persist_pv_map.end())
throw std::runtime_error("name already in used");
PDBGroupPV::shared_pointer pv(new PDBGroupPV());
pv->weakself = pv;
pv->name = info.name;
pv->pgatomic = info.atomic!=GroupInfo::False; // default true if Unset
pv->monatomic = info.hastriggers;
// some gymnastics because Info isn't copyable
pvd::shared_vector<PDBGroupPV::Info> members;
typedef std::map<std::string, size_t> members_map_t;
members_map_t members_map;
{
size_t nchans = 0;
for(size_t i=0, N=info.members.size(); i<N; i++)
if(!info.members[i].pvname.empty())
nchans++;
pvd::shared_vector<PDBGroupPV::Info> temp(nchans);
members.swap(temp);
}
std::vector<dbCommon*> records(members.size());
pvd::FieldBuilderPtr builder(fcreate->createFieldBuilder());
builder = builder->add("record", _options);
if(!info.structID.empty())
builder = builder->setId(info.structID);
for(size_t i=0, J=0, N=info.members.size(); i<N; i++)
{
const GroupMemberInfo &mem = info.members[i];
// parse down attachment point to build/traverse structure
FieldName parts(mem.pvfldname);
if(!parts.empty()) {
for(size_t j=0; j<parts.size()-1; j++) {
if(parts[j].isArray())
builder = builder->addNestedStructureArray(parts[j].name);
else
builder = builder->addNestedStructure(parts[j].name);
}
}
if(!mem.structID.empty())
builder = builder->setId(mem.structID);
DBCH chan;
if(!mem.pvname.empty()) {
DBCH temp(mem.pvname);
unsigned ftype = dbChannelFieldType(temp);
// can't include in multi-locking
if(ftype>=DBF_INLINK && ftype<=DBF_FWDLINK)
throw std::runtime_error("Can't include link fields in group");
chan.swap(temp);
}
if(!parts.empty())
builder = mem.builder->dtype(builder, parts.back().name, chan);
else
builder = mem.builder->dtype(builder, "", chan);
if(!parts.empty()) {
for(size_t j=0; j<parts.size()-1; j++)
builder = builder->endNested();
}
if(!mem.pvname.empty()) {
members_map[mem.pvfldname] = J;
PDBGroupPV::Info& info = members[J];
info.allowProc = mem.putorder != std::numeric_limits<int>::min();
info.builder = PTRMOVE(mem.builder);
assert(info.builder.get());
info.attachment.swap(parts);
info.chan.swap(chan);
// info.triggers populated below
assert(info.chan);
records[J] = dbChannelRecord(info.chan);
J++;
}
}
pv->members.swap(members);
pv->fielddesc = builder->createStructure();
pv->complete = pvbuilder->createPVStructure(pv->fielddesc);
pv->complete->getSubFieldT<pvd::PVBoolean>("record._options.atomic")->put(pv->monatomic);
DBManyLock L(&records[0], records.size(), 0);
pv->locker.swap(L);
// construct locker for records triggered by each member
for(size_t i=0, J=0, N=info.members.size(); i<N; i++)
{
const GroupMemberInfo &mem = info.members[i];
if(mem.pvname.empty()) continue;
PDBGroupPV::Info& info = pv->members[J++];
if(mem.triggers.empty()) continue;
std::vector<dbCommon*> trig_records;
trig_records.reserve(mem.triggers.size());
FOREACH(GroupMemberInfo::triggers_t::const_iterator, it, end, mem.triggers) {
members_map_t::const_iterator imap(members_map.find(*it));
if(imap==members_map.end())
throw std::logic_error("trigger resolution missed map to non-dbChannel");
info.triggers.push_back(imap->second);
trig_records.push_back(records[imap->second]);
}
DBManyLock L(&trig_records[0], trig_records.size(), 0);
info.locker.swap(L);
}
persist_pv_map[info.name] = pv;
}catch(std::exception& e){
fprintf(stderr, "%s: Error Group not created: %s\n", info.name.c_str(), e.what());
}
}
#else
if(!proc.groups.empty()) {
fprintf(stderr, "Group(s) were defined, but need Base >=3.16.0.2 to function. Ignoring.\n");
}
#endif // USE_MULTILOCK
event_context = db_init_events();
if(!event_context)
throw std::runtime_error("Failed to create dbEvent context");
int ret = db_start_events(event_context, "PDB-event", NULL, NULL, epicsThreadPriorityCAServerLow-1);
if(ret!=DB_EVENT_OK)
throw std::runtime_error("Failed to stsart dbEvent context");
// setup group monitors
#ifdef USE_MULTILOCK
for(persist_pv_map_t::iterator next = persist_pv_map.begin(),
end = persist_pv_map.end(),
it = next!=end ? next++ : end;
it != end; it = next==end ? end : next++)
{
const PDBPV::shared_pointer& ppv = it->second;
PDBGroupPV *pv = dynamic_cast<PDBGroupPV*>(ppv.get());
if(!pv)
continue;
try {
// prepare for monitor
size_t i=0;
FOREACH(PDBGroupPV::members_t::iterator, it2, end2, pv->members)
{
PDBGroupPV::Info& info = *it2;
info.evt_VALUE.index = info.evt_PROPERTY.index = i++;
info.evt_VALUE.self = info.evt_PROPERTY.self = pv;
assert(info.chan);
info.pvif.reset(info.builder->attach(info.chan, pv->complete, info.attachment));
// TODO: don't need evt_PROPERTY for PVIF plain
info.evt_PROPERTY.create(event_context, info.chan, &pdb_group_event, DBE_PROPERTY);
if(!info.triggers.empty()) {
info.evt_VALUE.create(event_context, info.chan, &pdb_group_event, DBE_VALUE|DBE_ALARM);
}
}
}catch(std::exception& e){
fprintf(stderr, "%s: Error during dbEvent setup : %s\n", pv->name.c_str(), e.what());
persist_pv_map.erase(it);
}
}
#endif // USE_MULTILOCK
epics::atomic::increment(num_instances);
}
PDBProvider::~PDBProvider()
{
epics::atomic::decrement(num_instances);
destroy();
}
void PDBProvider::destroy()
{
dbEventCtx ctxt = NULL;
persist_pv_map_t ppv;
{
epicsGuard<epicsMutex> G(transient_pv_map.mutex());
persist_pv_map.swap(ppv);
std::swap(ctxt, event_context);
}
ppv.clear(); // indirectly calls all db_cancel_events()
if(ctxt) db_close_events(ctxt);
}
std::string PDBProvider::getProviderName() { return "QSRV"; }
namespace {
struct ChannelFindRequesterNOOP : public pva::ChannelFind
{
const pva::ChannelProvider::weak_pointer provider;
ChannelFindRequesterNOOP(const pva::ChannelProvider::shared_pointer& prov) : provider(prov) {}
virtual ~ChannelFindRequesterNOOP() {}
virtual void destroy() {}
virtual std::tr1::shared_ptr<pva::ChannelProvider> getChannelProvider() { return provider.lock(); }
virtual void cancel() {}
};
}
pva::ChannelFind::shared_pointer
PDBProvider::channelFind(const std::string &channelName, const pva::ChannelFindRequester::shared_pointer &requester)
{
pva::ChannelFind::shared_pointer ret(new ChannelFindRequesterNOOP(shared_from_this()));
bool found = false;
{
epicsGuard<epicsMutex> G(transient_pv_map.mutex());
if(persist_pv_map.find(channelName)!=persist_pv_map.end()
|| transient_pv_map.find(channelName)
|| dbChannelTest(channelName.c_str())==0)
found = true;
}
requester->channelFindResult(pvd::Status(), ret, found);
return ret;
}
pva::ChannelFind::shared_pointer
PDBProvider::channelList(pva::ChannelListRequester::shared_pointer const & requester)
{
pva::ChannelFind::shared_pointer ret;
pvd::PVStringArray::svector names;
for(pdbRecordIterator rec; !rec.done(); rec.next())
{
names.push_back(rec.name());
}
{
epicsGuard<epicsMutex> G(transient_pv_map.mutex());
for(persist_pv_map_t::const_iterator it=persist_pv_map.begin(), end=persist_pv_map.end();
it != end; ++it)
{
names.push_back(it->first);
}
}
// check for duplicates?
requester->channelListResult(pvd::Status::Ok,
shared_from_this(),
pvd::freeze(names), false);
return ret;
}
pva::Channel::shared_pointer
PDBProvider::createChannel(std::string const & channelName,
pva::ChannelRequester::shared_pointer const & channelRequester,
short priority)
{
return createChannel(channelName, channelRequester, priority, "???");
}
pva::Channel::shared_pointer
PDBProvider::createChannel(std::string const & channelName,
pva::ChannelRequester::shared_pointer const & requester,
short priority, std::string const & address)
{
pva::Channel::shared_pointer ret;
PDBPV::shared_pointer pv;
pvd::Status status;
{
epicsGuard<epicsMutex> G(transient_pv_map.mutex());
pv = transient_pv_map.find(channelName);
if(!pv) {
persist_pv_map_t::const_iterator it=persist_pv_map.find(channelName);
if(it!=persist_pv_map.end()) {
pv = it->second;
}
}
if(!pv) {
dbChannel *pchan = dbChannelCreate(channelName.c_str());
if(pchan) {
DBCH chan(pchan);
pv.reset(new PDBSinglePV(chan, shared_from_this()));
transient_pv_map.insert(channelName, pv);
PDBSinglePV::shared_pointer spv = std::tr1::static_pointer_cast<PDBSinglePV>(pv);
spv->weakself = spv;
spv->activate();
}
}
}
if(pv) {
ret = pv->connect(shared_from_this(), requester);
}
if(!ret) {
status = pvd::Status(pvd::Status::STATUSTYPE_ERROR, "not found");
}
requester->channelCreated(status, ret);
return ret;
}
FieldName::FieldName(const std::string& pv)
{
if(pv.empty())
return;
Splitter S(pv.c_str(), '.');
std::string part;
while(S.snip(part)) {
if(part.empty())
throw std::runtime_error("Empty field component in: "+pv);
if(part[part.size()-1]==']') {
const size_t open = part.find_last_of('['),
N = part.size();
bool ok = open!=part.npos;
epicsUInt32 index = 0;
for(size_t i=open+1; ok && i<(N-1); i++) {
ok &= part[i]>='0' && part[i]<='9';
index = 10*index + part[i] - '0';
}
if(!ok)
throw std::runtime_error("Invalid field array sub-script in : "+pv);
parts.push_back(Component(part.substr(0, open), index));
} else {
parts.push_back(Component(part));
}
}
if(parts.empty())
throw std::runtime_error("Empty field name");
if(parts.back().isArray())
throw std::runtime_error("leaf field may not have sub-script : "+pv);
}
epics::pvData::PVFieldPtr
FieldName::lookup(const epics::pvData::PVStructurePtr& S, epics::pvData::PVField **ppsar) const
{
if(ppsar)
*ppsar = 0;
pvd::PVFieldPtr ret = S;
for(size_t i=0, N=parts.size(); i<N; i++) {
pvd::PVStructure* parent = dynamic_cast<pvd::PVStructure*>(ret.get());
if(!parent)
throw std::runtime_error("mid-field is not structure");
ret = parent->getSubFieldT(parts[i].name);
if(parts[i].isArray()) {
pvd::PVStructureArray* sarr = dynamic_cast<pvd::PVStructureArray*>(ret.get());
if(!sarr)
throw std::runtime_error("indexed field is not structure array");
if(ppsar && !*ppsar)
*ppsar = sarr;
pvd::PVStructureArray::const_svector V(sarr->view());
if(V.size()<=parts[i].index || !V[parts[i].index]) {
// automatic re-size and ensure non-null
V.clear(); // drop our extra ref so that reuse() might avoid a copy
pvd::PVStructureArray::svector E(sarr->reuse());
if(E.size()<=parts[i].index)
E.resize(parts[i].index+1);
if(!E[parts[i].index])
E[parts[i].index] = pvd::getPVDataCreate()->createPVStructure(sarr->getStructureArray()->getStructure());
ret = E[parts[i].index];
sarr->replace(pvd::freeze(E));
} else {
ret = V[parts[i].index];
}
}
}
return ret;
}
void FieldName::show() const
{
if(parts.empty()) {
printf("/");
return;
}
bool first = true;
for(size_t i=0, N=parts.size(); i<N; i++)
{
if(!first) {
printf(".");
} else {
first = false;
}
if(parts[i].isArray())
printf("%s[%u]", parts[i].name.c_str(), (unsigned)parts[i].index);
else
printf("%s", parts[i].name.c_str());
}
}
extern "C" {
epicsExportAddress(int, PDBProviderDebug);
}