Files
pva2pva/pdbApp/pdb.cpp
Michael Davidsaver 3f4e8e33e7 use reftrack
2017-09-05 13:40:23 -05:00

566 lines
19 KiB
C++

#include <vector>
#include <utility>
#include <errlog.h>
#include <epicsString.h>
#include <epicsAtomic.h>
#define epicsExportSharedSymbols
#include "helper.h"
#include "pdbsingle.h"
#include "pdbgroup.h"
#include "pvif.h"
namespace pvd = epics::pvData;
namespace pva = epics::pvAccess;
int PDBProviderDebug = 3;
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 {
GroupMemberInfo(const std::string& a, const std::string& b) :pvname(a), pvfldname(b) {}
std::string pvname, pvfldname;
std::set<size_t> triggers; // indices in GroupInfo::members which are post()d on events from pvfldname
size_t index; // index in GroupInfo::members
};
struct GroupInfo {
GroupInfo(const std::string& name) : name(name),atomic(Unset),hastriggers(false) {}
std::string name;
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;
// split off the group name from an info() value
// find or create this group
void setupGroup(const char **pvalue)
{
const char *start = *pvalue,
*split = strchr(start, '|');
if(!split) {
std::ostringstream strm;
strm<<"Expected '|' in \""<<start<<"\"";
throw std::runtime_error(strm.str());
}else if(start==split) {
std::ostringstream strm;
strm<<"Empty group name in \""<<start<<"\"";
throw std::runtime_error(strm.str());
}
std::string groupname(start, split-start);
groups_t::iterator it = groups.find(groupname);
if(it==groups.end()) {
// lazy creation of group
std::pair<groups_t::iterator, bool> ins(groups.insert(std::make_pair(groupname, GroupInfo(groupname))));
it = ins.first;
}
curgroup = &it->second;
*pvalue = split+1; // write back first char after '|'
}
// process "pdbGroup" to create/extend PDB to PVA mappings
void addMappings(const char *value)
{
Splitter tok(value, '|');
std::string mapent;
while(tok.snip(mapent))
{
size_t eq = mapent.find_first_of('=');
if(eq==mapent.npos) {
std::ostringstream strm;
strm<<"Expected '=' in \""<<value<<"\"";
throw std::runtime_error(strm.str());
}
std::string pvf(mapent.substr(0, eq)),
dbf(mapent.substr(eq+1));
if(pvf.empty() || dbf.empty()) {
std::ostringstream strm;
strm<<"empty PVA or DB field name in \""<<value<<"\"";
throw std::runtime_error(strm.str());
}
curgroup->members.push_back(GroupMemberInfo(recbase + dbf, pvf));
curgroup->members.back().index = curgroup->members.size()-1;
curgroup->members_map[pvf] = curgroup->members.back().index;
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());
}
}
}
// process "pdbTrigger" to create/extend PDB to PVA monitor trigger mappings
void addTriggers(const char *value)
{
Splitter tok(value, '|');
std::string trigent;
while(tok.snip(trigent))
{
size_t eq = trigent.find_first_of('>');
if(eq==trigent.npos) {
std::ostringstream strm;
strm<<"Expected '>' in \""<<value<<"\"";
throw std::runtime_error(strm.str());
}
std::string pvf(trigent.substr(0, eq)),
trigs(trigent.substr(eq+1));
GroupInfo::triggers_t::iterator it = curgroup->triggers.find(pvf);
if(it==curgroup->triggers.end()) {
std::pair<GroupInfo::triggers_t::iterator, bool> ins(curgroup->triggers.insert(
std::make_pair(pvf, GroupInfo::triggers_set_t())));
it = ins.first;
}
Splitter sep(trigs.c_str(), ',');
std::string target;
while(sep.snip(target)) {
curgroup->hastriggers = true;
it->second.insert(target);
}
}
}
// validate trigger mappings and process into bit map form
void resolveTriggers()
{
FOREACH(it, end, groups) { // for each group
GroupInfo& info = it->second;
if(info.hastriggers) {
FOREACH(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, "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(it3, end3, targets) { // for each trigger target
const std::string& target = *it3;
if(target=="*") {
for(size_t i=0; i<info.members.size(); i++) {
srcmem.triggers.insert(i);
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, "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];
// and finally, update source BitSet
srcmem.triggers.insert(targetmem.index);
if(PDBProviderDebug>2)
fprintf(stderr, "%s, ", info.members[targetmem.index].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(it2, end2, info.members) {
GroupMemberInfo& mem = *it2;
mem.triggers.insert(mem.index); // default is self trigger
}
}
}
}
void setAtomic(const char *value)
{
GroupInfo::tribool V = GroupInfo::Unset;
if(epicsStrCaseCmp(value,"yes")==0) V = GroupInfo::True;
else if(epicsStrCaseCmp(value,"no")==0) V = GroupInfo::False;
else {
fprintf(stderr, "Ignoring unknown value for pdbAtomic \"%s\".\n", value);
}
if(V==GroupInfo::Unset) return;
if(curgroup->atomic!=GroupInfo::Unset && curgroup->atomic!=V)
fprintf(stderr, " pdb atomic setting inconsistent '%s'\n",
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");
}
PDBProcessor() : curgroup(NULL)
{
/* syntax:
* info(pdbGroup, "<groupname>|field=FLD|other=FLD2?{}|...")
* eg. connect PVA "<groupname>.field" to "<recordname>.FLD"
* info(pdbTrigger, "<groupname>|field>field,other,...|field2>*")
* eg. subscribe to "<recordname>.FLD" (aka ".field"), on event
* post() a PVA monitor with ".FLD" and ".FLD2" (aka ".other").
* If no triggers specified, then every field subscribed to trigger itself
* info(pdbAtomic, "<groupname>|...")
* Allowed values are "YES" or "NO". Defaults to "YES"
* Whether triggers use multi or single record locking.
* Defines default to get/put operations, but individual
* requests may override.
*/
for(pdbRecordIterator rec; !rec.done(); rec.next())
{
bool firsttag = true;
for(pdbInfoIterator info(rec); !info.done(); info.next()) {
if(firsttag) {
recbase = rec.record()->name;
recbase += ".";
firsttag = false;
}
const char *value = info.value();
const char *tagname = info.name();
try {
if(strncmp(tagname, "pdbGroup", 8)==0) {
setupGroup(&value);
addMappings(value);
} else if(strncmp(tagname, "pdbTrigger", 10)==0) {
setupGroup(&value);
addTriggers(value);
} else if(strncmp(tagname, "pdbAtomic", 9)==0) {
setupGroup(&value);
setAtomic(value);
}
}catch(std::exception& e){
fprintf(stderr, "Error processing PDB info(%s, \"%s\") for record \"%s\": %s\n",
tagname, value, rec.record()->name, e.what());
}
}
}
resolveTriggers();
}
};
}
size_t PDBProvider::num_instances;
PDBProvider::PDBProvider(const epics::pvAccess::Configuration::shared_pointer &)
{
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());
// assemble group PVD structure definitions and build dbLockers
FOREACH(it, end, proc.groups)
{
GroupInfo &info=it->second;
try{
if(persist_pv_map.find(info.name)!=persist_pv_map.end())
throw std::runtime_error("name already in used");
const size_t nchans = info.members.size();
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;
pvd::shared_vector<PDBGroupPV::Info> members(nchans);
std::vector<dbCommon*> records(nchans);
pvd::FieldBuilderPtr builder(fcreate->createFieldBuilder());
builder->add("record", _options);
for(size_t i=0; i<nchans; i++)
{
GroupMemberInfo &mem = info.members[i];
PDBGroupPV::Info& info = members[i];
DBCH chan(mem.pvname);
builder->add(mem.pvfldname, PVIF::dtype(chan));
info.attachment = mem.pvfldname;
info.chan.swap(chan);
info.triggers.reserve(mem.triggers.size());
FOREACH(idx, endidx, mem.triggers) {
info.triggers.push_back(*idx);
}
records[i] = dbChannelRecord(info.chan);
}
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);
for(size_t i=0; i<nchans; i++)
{
PDBGroupPV::Info& info = pv->members[i];
if(info.triggers.empty()) continue;
std::vector<dbCommon*> trig_records(info.triggers.size());
for(size_t idx=0; idx<trig_records.size(); idx++) {
trig_records[idx] = records[info.triggers[idx]];
}
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: pdbGroup not created: %s\n", info.name.c_str(), e.what());
}
}
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
try {
FOREACH(it, end, persist_pv_map)
{
const PDBPV::shared_pointer& ppv = it->second;
PDBGroupPV *pv = dynamic_cast<PDBGroupPV*>(ppv.get());
if(!pv)
continue;
// prepare for monitor
size_t i=0;
FOREACH(it, end, pv->members)
{
PDBGroupPV::Info& info = *it;
info.evt_VALUE.index = info.evt_PROPERTY.index = i++;
info.evt_VALUE.self = info.evt_PROPERTY.self = pv;
info.pvif.reset(PVIF::attach(info.chan,
pv->complete->getSubFieldT<pvd::PVStructure>(info.attachment)));
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(...){
db_close_events(event_context);
// TODO, remove PV and continue?
throw;
}
epics::atomic::increment(num_instances);
}
PDBProvider::~PDBProvider()
{
epics::atomic::decrement(num_instances);
{
epicsGuard<epicsMutex> G(transient_pv_map.mutex());
if(event_context) {
/* Explicit destroy to ensure that the dbEventCtx
* is free'd from the event thread.
*/
errlogPrintf("Warning: PDBProvider free'd without destroy(). Possible race condition\nn");
}
}
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 "PDBProvider"; }
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;
requester->channelListResult(pvd::Status(pvd::Status::STATUSTYPE_ERROR, "not supported"),
ret, pvd::PVStringArray::const_svector(), true);
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;
}