Files
pvAccess/src/client/clientMonitor.cpp
2018-04-10 16:19:03 -07:00

275 lines
7.2 KiB
C++

/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <epicsMutex.h>
#include <epicsGuard.h>
#include <pv/current_function.h>
#include <pv/pvData.h>
#include <pv/bitSet.h>
#include <pv/reftrack.h>
#define epicsExportSharedSymbols
#include "pv/logger.h"
#include "clientpvt.h"
#include "pv/pvAccess.h"
namespace pvd = epics::pvData;
namespace pva = epics::pvAccess;
typedef epicsGuard<epicsMutex> Guard;
typedef epicsGuardRelease<epicsMutex> UnGuard;
namespace pvac {
struct Monitor::Impl : public pva::MonitorRequester,
public pvac::detail::wrapped_shared_from_this<Monitor::Impl>
{
mutable epicsMutex mutex;
pva::Channel::shared_pointer chan;
operation_type::shared_pointer op;
bool started, done, seenEmpty;
ClientChannel::MonitorCallback *cb;
MonitorEvent event;
pva::MonitorElement::Ref last;
static size_t num_instances;
Impl(ClientChannel::MonitorCallback* cb)
:started(false)
,done(false)
,seenEmpty(false)
,cb(cb)
{REFTRACE_INCREMENT(num_instances);}
virtual ~Impl() {REFTRACE_DECREMENT(num_instances);}
void callEvent(Guard& G, MonitorEvent::event_t evt = MonitorEvent::Fail)
{
ClientChannel::MonitorCallback *cb=this->cb;
if(!cb) return;
event.event = evt;
if(evt==MonitorEvent::Fail || evt==MonitorEvent::Cancel)
this->cb = 0; // last event
try {
UnGuard U(G);
cb->monitorEvent(event);
return;
}catch(std::exception& e){
if(!this->cb || evt==MonitorEvent::Fail) {
LOG(pva::logLevelError, "Unhandled exception in ClientChannel::MonitorCallback::monitorEvent(): %s", e.what());
return;
} else {
event.event = MonitorEvent::Fail;
event.message = e.what();
}
}
// continues error handling
try {
UnGuard U(G);
cb->monitorEvent(event);
return;
}catch(std::exception& e){
LOG(pva::logLevelError, "Unhandled exception following exception in ClientChannel::MonitorCallback::monitorEvent(): %s", e.what());
}
}
// called automatically via wrapped_shared_from_this
void cancel()
{
operation_type::shared_pointer temp;
{
Guard G(mutex);
last.reset();
if(started && op) {
op->stop();
started = false;
}
temp.swap(op);
callEvent(G, MonitorEvent::Cancel);
}
if(temp)
temp->destroy();
}
virtual std::string getRequesterName() OVERRIDE FINAL
{
Guard G(mutex);
return chan ? chan->getRequesterName() : "<dead>";
}
virtual void monitorConnect(pvd::Status const & status,
pva::MonitorPtr const & operation,
pvd::StructureConstPtr const & structure) OVERRIDE FINAL
{
Guard G(mutex);
if(!cb || started || done) return;
if(!status.isOK()) {
event.message = status.getMessage();
} else {
event.message.clear();
}
if(!status.isSuccess()) {
callEvent(G);
} else {
pvd::Status sts(operation->start());
if(sts.isSuccess()) {
started = true;
/* storing raw pointer to operation, which is expected
* to outlive our 'op'.
*/
last.attach(operation);
} else {
event.message = sts.getMessage();
callEvent(G);
}
}
}
virtual void channelDisconnect(bool destroy) OVERRIDE FINAL
{
Guard G(mutex);
if(!cb || done) return;
event.message = "Disconnect";
started = false;
callEvent(G, MonitorEvent::Disconnect);
}
virtual void monitorEvent(pva::MonitorPtr const & monitor) OVERRIDE FINAL
{
Guard G(mutex);
if(!cb || done) return;
event.message.clear();
callEvent(G, MonitorEvent::Data);
}
virtual void unlisten(pva::MonitorPtr const & monitor) OVERRIDE FINAL
{
Guard G(mutex);
if(!cb || done) return;
done = true;
if(seenEmpty)
callEvent(G, MonitorEvent::Data);
// else // wait until final poll()
}
};
size_t Monitor::Impl::num_instances;
Monitor::Monitor(const std::tr1::shared_ptr<Impl>& impl)
:impl(impl)
{}
Monitor::~Monitor() {}
std::string Monitor::name() const
{
return impl ? impl->chan->getChannelName() : "<NULL>";
}
void Monitor::cancel()
{
changed.clear();
overrun.clear();
root.reset();
if(impl) impl->cancel();
}
bool Monitor::poll()
{
if(!impl) return false;
Guard G(impl->mutex);
if(!impl->done && impl->last.next()) {
const epics::pvData::PVStructurePtr& ptr = impl->last->pvStructurePtr;
changed = *impl->last->changedBitSet;
overrun = *impl->last->overrunBitSet;
/* copy the exposed PVStructure for two reasons.
* 1. Prevent accidental use of shared container after release()
* 2. Allows caller to cache results of getSubField() until root.get() changes.
*/
if(!root || (void*)root->getField().get()!=(void*)ptr->getField().get()) {
// initial connection, or new type
root = pvd::getPVDataCreate()->createPVStructure(ptr); // also calls copyUnchecked()
} else {
// same type
const_cast<pvd::PVStructure&>(*root).copyUnchecked(*ptr, changed);
}
impl->seenEmpty = false;
} else {
changed.clear();
overrun.clear();
impl->seenEmpty = true;
}
return !impl->seenEmpty;
}
bool Monitor::complete() const
{
if(!impl) return true;
Guard G(impl->mutex);
return impl->done && impl->seenEmpty;
}
Monitor
ClientChannel::monitor(MonitorCallback *cb,
epics::pvData::PVStructure::const_shared_pointer pvRequest)
{
if(!impl) throw std::logic_error("Dead Channel");
if(!pvRequest)
pvRequest = pvd::createRequest("field()");
std::tr1::shared_ptr<Monitor::Impl> ret(Monitor::Impl::build(cb));
ret->chan = getChannel();
{
Guard G(ret->mutex);
ret->op = ret->chan->createMonitor(ret->internal_shared_from_this(),
std::tr1::const_pointer_cast<pvd::PVStructure>(pvRequest));
}
return Monitor(ret);
}
::std::ostream& operator<<(::std::ostream& strm, const Monitor& op)
{
if(op.impl) {
strm << "Monitor("
"\"" << op.impl->chan->getChannelName() <<"\", "
"\"" << op.impl->chan->getProvider()->getProviderName() <<"\", "
"connected="<<(op.impl->chan->isConnected()?"true":"false")
<<"\")";
} else {
strm << "Monitor()";
}
return strm;
}
namespace detail {
void registerRefTrackMonitor()
{
epics::registerRefCounter("pvac::Monitor::Impl", &Monitor::Impl::num_instances);
}
}
}//namespace pvac