add MonitorFIFO

This commit is contained in:
Michael Davidsaver
2018-05-22 13:04:36 -07:00
parent 1e04c91d3c
commit 5e887a6d02
6 changed files with 1455 additions and 2 deletions

View File

@ -7,6 +7,7 @@ INC += pv/pvAccess.h
INC += pva/client.h
pvAccess_SRCS += pvAccess.cpp
pvAccess_SRCS += monitor.cpp
pvAccess_SRCS += client.cpp
pvAccess_SRCS += clientSync.cpp
pvAccess_SRCS += clientGet.cpp

473
src/client/monitor.cpp Normal file
View File

@ -0,0 +1,473 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <sstream>
#include <stdexcept>
#include <epicsGuard.h>
#include <epicsMath.h>
#define epicsExportSharedSymbols
#include <pv/monitor.h>
#include <pv/pvAccess.h>
#include <pv/reftrack.h>
namespace pvd = epics::pvData;
typedef epicsGuard<epicsMutex> Guard;
typedef epicsGuardRelease<epicsMutex> UnGuard;
namespace epics {namespace pvAccess {
static const MonitorFIFO::Config default_conf = {4, 4, 0};
size_t MonitorFIFO::num_instances;
MonitorFIFO::Source::~Source() {}
MonitorFIFO::MonitorFIFO(const std::tr1::shared_ptr<MonitorRequester> &requester,
const pvData::PVStructure::const_shared_pointer &pvRequest,
const Source::shared_pointer &source, Config *inconf)
:conf(inconf ? *inconf : default_conf)
,requester(requester)
,upstream(source)
,pipeline(false)
,opened(false)
,running(false)
,finished(false)
,needConnected(false)
,needEvent(false)
,needUnlisten(false)
,needClosed(false)
,freeHighLevel(0u)
,flowCount(0)
{
REFTRACE_INCREMENT(num_instances);
if(conf.maxCount==0)
conf.maxCount = 1;
if(conf.defCount==0)
conf.defCount = 1;
pvd::PVScalar::const_shared_pointer O(pvRequest->getSubField<pvd::PVScalar>("record._options.queueSize"));
if(O && conf.actualCount==0) {
try {
conf.actualCount = O->getAs<pvd::uint32>();
} catch(std::exception& e) {
std::ostringstream strm;
strm<<"invalid queueSize : "<<e.what();
requester->message(strm.str());
}
}
if(conf.actualCount==0)
conf.actualCount = conf.defCount;
if(conf.actualCount > conf.maxCount)
conf.actualCount = conf.maxCount;
O = pvRequest->getSubField<pvd::PVScalar>("record._options.pipeline");
if(O) {
try {
pipeline = O->getAs<pvd::boolean>();
} catch(std::exception& e) {
std::ostringstream strm;
strm<<"invalid pipeline : "<<e.what();
requester->message(strm.str());
}
}
setFreeHighMark(0.00);
if(inconf)
*inconf = conf;
}
MonitorFIFO::~MonitorFIFO() {
REFTRACE_DECREMENT(num_instances);
}
void MonitorFIFO::destroy()
{}
void MonitorFIFO::show(std::ostream& strm) const
{
// const (after ctor) bits
strm<<"MonitorFIFO"
" pipeline="<<pipeline
<<" size="<<conf.actualCount
<<" freeHighLevel="<<freeHighLevel
<<"\n";
Guard G(mutex);
strm<<" open="<<opened<<" running="<<running<<" finished="<<finished<<"\n";
strm<<" #empty="<<empty.size()<<" #returned="<<returned.size()<<" #inuse="<<inuse.size()<<" flowCount="<<flowCount<<"\n";
strm<<" events "<<(needConnected?'C':'_')<<(needEvent?'E':'_')<<(needUnlisten?'U':'_')<<(needClosed?'X':'_')
<<"\n";
}
void MonitorFIFO::setFreeHighMark(double level)
{
level = std::max(0.0, std::min(level, 1.0));
pvd::uint32 lvl = std::max(size_t(0), std::min(size_t(conf.actualCount * level), conf.actualCount-1));
Guard G(mutex);
freeHighLevel = lvl;
}
void MonitorFIFO::open(const pvd::StructureConstPtr& type)
{
Guard G(mutex);
if(opened)
throw std::logic_error("Monitor already open. Must close() before re-openning");
else if(needClosed)
throw std::logic_error("Monitor needs notify() between close() and open().");
else if(finished)
throw std::logic_error("Monitor finished. re-open() not possible");
// keep the code simpler.
// never try to re-use elements, even on re-open w/o type change.
empty.clear();
inuse.clear();
returned.clear();
// fill up empty.
pvd::PVDataCreatePtr create(pvd::getPVDataCreate());
while(empty.size() < conf.actualCount+1) {
MonitorElementPtr elem(new MonitorElement(create->createPVStructure(type)));
empty.push_back(elem);
}
opened = true;
needConnected = true;
assert(inuse.empty());
assert(empty.size()>=2);
assert(returned.empty());
assert(conf.actualCount>=1);
}
void MonitorFIFO::close()
{
Guard G(mutex);
if(!opened)
return; // no-op
opened = false;
needClosed = true;
}
void MonitorFIFO::finish()
{
Guard G(mutex);
if(!opened)
throw std::logic_error("Can not finish() a closed Monitor");
else if(finished)
return; // no-op
finished = true;
if(inuse.empty() && running)
needUnlisten = true;
}
bool MonitorFIFO::tryPost(const pvData::PVStructure& value,
const pvd::BitSet& changed,
const pvd::BitSet& overrun,
bool force)
{
Guard G(mutex);
assert(opened && !finished);
assert(!empty.empty() || !inuse.empty());
const bool havefree = _freeCount()>0u;
MonitorElementPtr elem;
if(havefree) {
// take an empty element
elem = empty.front();
empty.pop_front();
} else if(force) {
// allocate an extra element
elem.reset(new MonitorElement(pvd::getPVDataCreate()->createPVStructure(inuse.back()->pvStructurePtr->getStructure())));
}
if(elem) {
try {
assert(value.getStructure() == elem->pvStructurePtr->getStructure());
elem->pvStructurePtr->copyUnchecked(value, changed);
*elem->changedBitSet = changed;
*elem->overrunBitSet = overrun;
if(inuse.empty() && running)
needEvent = true;
inuse.push_back(elem);
}catch(...){
if(havefree) {
empty.push_front(elem);
}
throw;
}
if(pipeline)
flowCount--;
}
return _freeCount()>0u;
}
void MonitorFIFO::post(const pvData::PVStructure& value,
const pvd::BitSet& changed,
const pvd::BitSet& overrun)
{
Guard G(mutex);
assert(opened && !finished);
assert(!empty.empty() || !inuse.empty());
const bool use_empty = !empty.empty();
MonitorElementPtr elem;
if(use_empty) {
// space in window, or entering overflow, fill an empty element
assert(!empty.empty());
elem = empty.front();
} else {
// window full and already in overflow
// squash with last element
assert(!inuse.empty());
elem = inuse.back();
}
assert(value.getStructure() == elem->pvStructurePtr->getStructure());
elem->pvStructurePtr->copyUnchecked(value, changed);
if(use_empty) {
*elem->changedBitSet = changed;
*elem->overrunBitSet = overrun;
if(inuse.empty() && running)
needEvent = true;
inuse.push_back(elem);
empty.pop_front();
if(pipeline)
flowCount--;
} else {
// in overflow
// squash
elem->overrunBitSet->or_and(*elem->changedBitSet, changed);
*elem->overrunBitSet |= overrun;
*elem->changedBitSet |= changed;
// leave as inuse.back()
}
}
void MonitorFIFO::notify()
{
Monitor::shared_pointer self;
MonitorRequester::shared_pointer req;
pvd::StructureConstPtr type;
bool conn = false,
evt = false,
unl = false,
clo = false;
{
Guard G(mutex);
std::swap(conn, needConnected);
std::swap(evt, needEvent);
std::swap(unl, needUnlisten);
std::swap(clo, needClosed);
if(conn | evt | unl | clo) {
req = requester.lock();
self = shared_from_this();
}
if(conn)
type = (!inuse.empty() ? inuse.front() : empty.back())->pvStructurePtr->getStructure();
}
if(!req)
return;
if(conn)
req->monitorConnect(pvd::Status(), self, type);
if(evt)
req->monitorEvent(self);
if(unl)
req->unlisten(self);
if(clo)
req->channelDisconnect(false);
}
pvd::Status MonitorFIFO::start()
{
Monitor::shared_pointer self;
MonitorRequester::shared_pointer req;
{
Guard G(mutex);
if(!opened)
throw std::logic_error("Monitor can't start() before open()");
if(running)
return pvd::Status();
if(!inuse.empty()) {
self = shared_from_this();
req = requester.lock();
}
running = true;
}
if(req)
req->monitorEvent(self);
return pvd::Status();
}
pvd::Status MonitorFIFO::stop()
{
Guard G(mutex);
running = false;
return pvd::Status();
}
MonitorElementPtr MonitorFIFO::poll()
{
MonitorElementPtr ret;
Monitor::shared_pointer self;
MonitorRequester::shared_pointer req;
{
Guard G(mutex);
if(!inuse.empty() && inuse.size() + empty.size() > 1) {
ret = inuse.front();
inuse.pop_front();
if(inuse.empty() && finished) {
self = shared_from_this();
req = requester.lock();
}
}
assert(!inuse.empty() || !empty.empty());
}
if(req) {
req->unlisten(self);
}
return ret;
}
void MonitorFIFO::release(MonitorElementPtr const & elem)
{
size_t nempty;
{
Guard G(mutex);
assert(!inuse.empty() || !empty.empty());
const pvd::StructureConstPtr& type((!inuse.empty() ? inuse.front() : empty.back())->pvStructurePtr->getStructure());
if(elem->pvStructurePtr->getStructure() != type // return of old type
|| empty.size()+returned.size()>=conf.actualCount+1) // return of force'd
return; // ignore it
if(pipeline) {
// work done during reportRemoteQueueStatus()
returned.push_back(elem);
return;
}
bool below = _freeCount() <= freeHighLevel;
empty.push_front(elem);
bool above = _freeCount() > freeHighLevel;
if(!below || !above || !upstream)
return;
nempty = _freeCount();
}
upstream->freeHighMark(this, nempty);
notify();
}
void MonitorFIFO::getStats(Stats& s) const
{
Guard G(mutex);
s.nempty = empty.size() + returned.size();
s.nfilled = inuse.size();
s.noutstanding = conf.actualCount - s.nempty - s.nfilled;
}
void MonitorFIFO::reportRemoteQueueStatus(pvd::int32 nfree)
{
if(nfree<=0 || !pipeline)
return; // paranoia
size_t nempty;
{
Guard G(mutex);
bool below = _freeCount() <= freeHighLevel;
size_t nack = std::min(size_t(nfree), returned.size());
flowCount += nfree;
buffer_t::iterator end(returned.begin());
std::advance(end, nack);
// remove[0, nack) from returned and append to empty
empty.splice(empty.end(), returned, returned.begin(), end);
bool above = _freeCount() > freeHighLevel;
if(!below || !above || empty.size()<=1 || !upstream)
return;
nempty = _freeCount();
}
upstream->freeHighMark(this, nempty);
notify();
}
size_t MonitorFIFO::freeCount() const
{
Guard G(mutex);
return _freeCount();
}
// caller must hold lock
size_t MonitorFIFO::_freeCount() const
{
if(pipeline) {
return std::max(0, std::min(flowCount, epicsInt32(empty.size())));
} else {
return empty.empty() ? 0 : empty.size()-1;
}
}
}} // namespace epics::pvAccess

View File

@ -9,11 +9,15 @@
#ifndef MONITOR_H
#define MONITOR_H
#include <list>
#include <ostream>
#ifdef epicsExportSharedSymbols
# define monitorEpicsExportSharedSymbols
# undef epicsExportSharedSymbols
#endif
#include <epicsMutex.h>
#include <pv/status.h>
#include <pv/pvData.h>
#include <pv/sharedPtr.h>
@ -209,6 +213,196 @@ inline MonitorElement::Ref begin(Monitor& mon) { return MonitorElement::Ref(mon)
inline MonitorElement::Ref end(Monitor& mon) { return MonitorElement::Ref(); }
#endif // __cplusplus<201103L
/** Utility implementation of Monitor.
*
* The Monitor interface defines the downstream (consumer facing) side
* of a FIFO. This class is a concrete implementation of this FIFO,
* including the upstream (producer facing) side.
*
* In addition to MonitorRequester, which provides callbacks to the downstream side,
* The MonitorFIFO::Source class provides callbacks to the upstream side.
*
* The simplest usage is to create (as shown below), then put update into the FIFO
* using post() and tryPost(). These methods behave the same when the queue is
* not full, but differ when it is. Additionally, tryPost() has an argument 'force'.
* Together there are three actions
*
* # post(value, changed) - combines the new update with the last (most recent) in the FIFO.
* # tryPost(value, changed, ..., false) - Makes no change to the FIFO and returns false.
* # tryPost(value, changed, ..., true) - Over-fills the FIFO with the new element, then returns false.
*
* @note Calls to post() or tryPost() __must__ be followed with a call to notify().
* Callers of notify() __must__ not hold any locks, or a deadlock is possible.
*
* The intent of tryPost() with force=true is to aid code which is transferring values from
* some upstream buffer and this FIFO. Such code can be complicated if an item is removed
* from the upstream buffer, but can't be put into this downstream FIFO. Rather than
* being forced to effectivly maintain a third FIFO, code can use force=true.
*
* In either case, tryPost()==false indicates the the FIFO is full.
*
* eg. simple usage in a sub-class for Channel named MyChannel.
@code
pva::Monitor::shared_pointer
MyChannel::createMonitor(const pva::MonitorRequester::shared_pointer &requester,
const pvd::PVStructure::shared_pointer &pvRequest)
{
std::tr1::shared_ptr<pva::MonitorFIFO> ret(new pva::MonitorFIFO(requester, pvRequest));
ret->open(spamtype);
ret->notify();
// ret->post(...); // maybe initial update
}
@endcode
*/
class epicsShareClass MonitorFIFO : public Monitor,
public std::tr1::enable_shared_from_this<MonitorFIFO>
{
public:
POINTER_DEFINITIONS(MonitorFIFO);
//! Source methods may be called with downstream mutex locked.
//! Do not call notify(). This is done automatically after return in a way
//! which avoids locking and recursion problems.
struct epicsShareClass Source {
POINTER_DEFINITIONS(Source);
virtual ~Source();
//! Called when MonitorFIFO::freeCount() rises above the level computed
//! from MonitorFIFO::setFreeHighMark().
//! @param numEmpty The number of empty slots in the FIFO.
virtual void freeHighMark(MonitorFIFO *mon, size_t numEmpty) {}
};
struct Config {
size_t maxCount, //!< upper limit on requested FIFO size
defCount, //!< FIFO size when client makes no request
actualCount; //!< filled in with actual FIFO size
};
/**
* @param requester Downstream/consumer callbacks
* @param pvRequest Downstream provided options
* @param source Upstream/producer callbacks
* @param conf Upstream provided options. Updated with actual values used. May be NULL to use defaults.
*/
MonitorFIFO(const std::tr1::shared_ptr<MonitorRequester> &requester,
const pvData::PVStructure::const_shared_pointer &pvRequest,
const Source::shared_pointer& source = Source::shared_pointer(),
Config *conf=0);
virtual ~MonitorFIFO();
void show(std::ostream& strm) const;
virtual void destroy() OVERRIDE FINAL;
// configuration
//! Level, as a percentage of empty buffer slots, at which to call Source::freeHighMark().
//! Trigger condition is when number of free buffer slots goes above this level.
//! In range [0.0, 1.0)
void setFreeHighMark(double level);
// up-stream interface (putting data into FIFO)
//! Mark subscription as "open" with the associated structure type.
void open(const epics::pvData::StructureConstPtr& type);
//! Abnormal closure (eg. due to upstream dis-connection)
void close();
//! Successful closure (eg. RDB query done)
void finish();
//! Consume a free slot if available. otherwise ...
//! if !force take no action and return false.
//! if force then attempt to allocate and fill a new slot, then return false.
//! The extra slot will be free'd after it is consumed.
bool tryPost(const pvData::PVStructure& value,
const epics::pvData::BitSet& changed,
const epics::pvData::BitSet& overrun = epics::pvData::BitSet(),
bool force =false);
//! Consume a free slot if available, otherwise squash with most recent
void post(const pvData::PVStructure& value,
const epics::pvData::BitSet& changed,
const epics::pvData::BitSet& overrun = epics::pvData::BitSet());
//! Call after calling any other upstream interface methods (open()/close()/finish()/post()/...)
//! when no upstream mutexes are locked.
//! Do not call from Source::freeHighMark(). This is done automatically.
//! Call any MonitorRequester methods.
void notify();
// down-stream interface (taking data from FIFO)
virtual epics::pvData::Status start() OVERRIDE FINAL;
virtual epics::pvData::Status stop() OVERRIDE FINAL;
virtual MonitorElementPtr poll() OVERRIDE FINAL;
virtual void release(MonitorElementPtr const & monitorElement) OVERRIDE FINAL; // may call Source::freeHighMark()
virtual void getStats(Stats& s) const OVERRIDE FINAL;
virtual void reportRemoteQueueStatus(epics::pvData::int32 freeElements) OVERRIDE FINAL;
//! Number of unused FIFO slots at this moment, which may changed in the next.
size_t freeCount() const;
private:
size_t _freeCount() const;
friend void providerRegInit(void*);
static size_t num_instances;
// const after ctor
Config conf;
// locking here is complicated...
// our entry points which make callbacks are:
// notify() -> MonitorRequester::monitorConnect()
// -> MonitorRequester::monitorEvent()
// -> MonitorRequester::unlisten()
// -> ChannelBaseRequester::channelDisconnect()
// release() -> Source::freeHighMark()
// -> notify() -> ...
// reportRemoteQueueStatus() -> Source::freeHighMark()
// -> notify() -> ...
mutable epicsMutex mutex;
// ownership is archored at the downstream (consumer) end.
// strong refs are:
// downstream -> MonitorFIFO -> Source
// weak refs are:
// MonitorRequester <- MonitorFIFO <- upstream
// so we expect that downstream will hold a strong ref to us,
// and we keep a weak ref to downstream's MonitorRequester
const std::tr1::weak_ptr<MonitorRequester> requester;
// then we expect to keep a strong ref to upstream (Source)
// and expect that upstream will have only a weak ref to us.
const Source::shared_pointer upstream;
bool pipeline; // const after ctor
bool opened; // open() vs. close()
bool running; // start() vs. stop()
bool finished; // finish() called
bool needConnected;
bool needEvent;
bool needUnlisten;
bool needClosed;
size_t freeHighLevel;
epicsInt32 flowCount;
typedef std::list<MonitorElementPtr> buffer_t;
// we allocate one extra buffer element to hold data when post()
// while all elements poll()'d. So there will always be one
// element on either the empty or inuse lists
buffer_t inuse, empty, returned;
/* our elements are in one of 4 states
* Empty - on empty list
* In Use - on inuse list
* Polled - Returnedd from poll(). Not tracked
* Returned - only if pipeline==true, release()'d but not ack'd
*/
EPICS_NOT_COPYABLE(MonitorFIFO)
};
static inline
std::ostream& operator<<(std::ostream& strm, const MonitorFIFO& fifo) {
fifo.show(strm);
return strm;
}
}}
namespace epics { namespace pvData {

View File

@ -198,6 +198,8 @@ struct providerRegGbl_t {
epicsThreadOnceId providerRegOnce = EPICS_THREAD_ONCE_INIT;
} // namespace
void providerRegInit(void*)
{
epicsSignalInstallSigAlarmIgnore();
@ -217,10 +219,9 @@ void providerRegInit(void*)
registerRefCounter("ChannelBaseRequester (ABC)", &ChannelBaseRequester::num_instances);
registerRefCounter("ChannelRequest (ABC)", &ChannelRequest::num_instances);
registerRefCounter("ResponseHandler (ABC)", &ResponseHandler::num_instances);
registerRefCounter("MonitorFIFO", &MonitorFIFO::num_instances);
}
} // namespace
ChannelProviderRegistry::shared_pointer ChannelProviderRegistry::clients()
{
epicsThreadOnce(&providerRegOnce, &providerRegInit, 0);

View File

@ -27,6 +27,9 @@ TESTPROD_HOST += testServerContext
testServerContext_SRCS += testServerContext.cpp
TESTS += testServerContext
TESTPROD_HOST += testmonitorfifo
testmonitorfifo_SRCS += testmonitorfifo.cpp
TESTS += testmonitorfifo
PROD_HOST += testServer
testServer_SRCS += testServer.cpp

View File

@ -0,0 +1,781 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <vector>
#include <pv/pvUnitTest.h>
#include <testMain.h>
#include <epicsMutex.h>
#include <pv/pvAccess.h>
#include <pv/current_function.h>
#if __cplusplus>=201103L
#include <functional>
#include <initializer_list>
namespace pvd = epics::pvData;
namespace pva = epics::pvAccess;
typedef epicsGuard<epicsMutex> Guard;
typedef epicsGuardRelease<epicsMutex> UnGuard;
namespace {
struct Tester {
// we only have one thread, so no need for sync.
enum cb_t {
Connect,
Event,
Unlisten,
Close,
LowWater,
};
static const char* name(cb_t cb) {
switch(cb) {
#define CASE(NAME) case NAME: return #NAME
CASE(Connect);
CASE(Event);
CASE(Unlisten);
CASE(Close);
CASE(LowWater);
default: return "???";
}
}
typedef std::vector<cb_t> timeline_t;
static timeline_t timeline;
struct Requester : public pva::MonitorRequester {
POINTER_DEFINITIONS(Requester);
epicsMutex mutex; // not strictly needed, but a good simulation of usage
virtual ~Requester() {}
virtual std::string getRequesterName() OVERRIDE FINAL {return "Tester::Requester";}
virtual void channelDisconnect(bool destroy) OVERRIDE FINAL {
testDiag("In %s", CURRENT_FUNCTION);
Guard G(mutex);
Tester::timeline.push_back(Close);
}
virtual void monitorConnect(epics::pvData::Status const & status,
pva::MonitorPtr const & monitor, epics::pvData::StructureConstPtr const & structure) OVERRIDE FINAL {
testDiag("In %s", CURRENT_FUNCTION);
Guard G(mutex);
Tester::timeline.push_back(Connect);
}
virtual void monitorEvent(pva::MonitorPtr const & monitor) OVERRIDE FINAL {
testDiag("In %s", CURRENT_FUNCTION);
Guard G(mutex);
Tester::timeline.push_back(Event);
}
virtual void unlisten(pva::MonitorPtr const & monitor) OVERRIDE FINAL {
testDiag("In %s", CURRENT_FUNCTION);
Guard G(mutex);
Tester::timeline.push_back(Unlisten);
}
};
Requester::shared_pointer requester;
struct Handler : public pva::MonitorFIFO::Source {
POINTER_DEFINITIONS(Handler);
epicsMutex mutex; // not strictly needed, but a good simulation of usage
std::function<void(pva::MonitorFIFO *mon, size_t)> action;
virtual ~Handler() {}
virtual void freeHighMark(pva::MonitorFIFO *mon, size_t numEmpty) OVERRIDE FINAL {
testDiag("In %s", CURRENT_FUNCTION);
Guard G(mutex);
Tester::timeline.push_back(LowWater);
if(action)
action(mon, numEmpty);
}
};
Handler::weak_pointer handler;
void setAction(const std::function<void(pva::MonitorFIFO *mon, size_t)>& action) {
Handler::shared_pointer H(handler);
H->action = action;
}
pva::MonitorFIFO::shared_pointer mon;
pvd::StructureConstPtr type;
Tester(const pvd::PVStructure::const_shared_pointer& pvReq,
pva::MonitorFIFO::Config *conf)
{
Handler::shared_pointer H(new Handler);
handler = H;
requester.reset(new Requester);
mon.reset(new pva::MonitorFIFO(requester, pvReq, H, conf));
}
void reset() {
timeline.clear();
}
void testTimeline(std::initializer_list<cb_t> l) {
size_t i=0;
for(auto event : l) {
if(i >= timeline.size()) {
testFail("timeline ends early. Expected %s", name(event));
continue;
}
testOk(event==timeline[i], "%s == %s", name(event), name(timeline[i]));
i++;
}
for(; i<timeline.size(); i++) {
testFail("timeline ends late. Unexpected %s", name(timeline[i]));
}
if(i==0 && timeline.empty())
testPass("timeline no events as expected");
reset();
}
void connect(pvd::ScalarType t) {
testDiag("connect() with %s", pvd::ScalarTypeFunc::name(t));
type = pvd::getFieldCreate()->createFieldBuilder()
->add("value", t)
->createStructure();
mon->open(type);
}
void close() {
testDiag("close()");
type.reset();
mon->close();
}
template<typename T>
void post(T val)
{
testShow()<<"post("<<val<<")";
assert(!!type);
pvd::PVStructurePtr V(pvd::getPVDataCreate()->createPVStructure(type));
pvd::PVScalarPtr fld(V->getSubFieldT<pvd::PVScalar>("value"));
fld->putFrom(val);
pvd::BitSet changed;
changed.set(fld->getFieldOffset());
mon->post(*V, changed);
}
template<typename T>
void tryPost(T val, bool expect, bool force = false)
{
assert(!!type);
pvd::PVStructurePtr V(pvd::getPVDataCreate()->createPVStructure(type));
pvd::PVScalarPtr fld(V->getSubFieldT<pvd::PVScalar>("value"));
fld->putFrom(val);
pvd::BitSet changed;
changed.set(fld->getFieldOffset());
testEqual(mon->tryPost(*V, changed, pvd::BitSet(), force), expect)<<" value="<<val;
}
};
Tester::timeline_t Tester::timeline;
void testEmpty(pva::Monitor& mon)
{
pva::MonitorElement::Ref elem(mon);
testTrue(!elem)<<"Queue expected empty";
}
template<typename T>
void testPop(pva::Monitor& mon, T expected, bool overrun = false)
{
pva::MonitorElement::Ref elem(mon);
if(!elem) {
testFail("Queue unexpected empty");
return;
}
pvd::PVScalarPtr fld(elem->pvStructurePtr->getSubFieldT<pvd::PVScalar>("value"));
T actual(fld->getAs<T>());
bool changed = elem->changedBitSet->get(fld->getFieldOffset());
bool overran = elem->overrunBitSet->get(fld->getFieldOffset());
bool pop = actual==expected && changed && overran==overrun;
testTrue(pop)
<<" "<<actual<<" == "<<expected<<" changed="<<changed<<" "<<overran<<"=="<<overrun;
}
static const pvd::PVStructure::const_shared_pointer pvReqEmpty(pvd::getPVDataCreate()->createPVStructure(
pvd::getFieldCreate()->createFieldBuilder()
->createStructure()));
pvd::PVStructure::const_shared_pointer
pvReqPipelineBuild() {
pvd::PVStructure::shared_pointer ret(pvd::getPVDataCreate()->createPVStructure(
pvd::getFieldCreate()->createFieldBuilder()
->addNestedStructure("record")
->addNestedStructure("_options")
->add("pipeline", pvd::pvBoolean)
->add("queueSize", pvd::pvUInt)
->endNested()
->endNested()
->createStructure()));
ret->getSubFieldT<pvd::PVScalar>("record._options.pipeline")->putFrom<pvd::boolean>(true);
ret->getSubFieldT<pvd::PVScalar>("record._options.queueSize")->putFrom<pvd::uint32>(2);
return ret;
}
static const pvd::PVStructure::const_shared_pointer pvReqPipeline(pvReqPipelineBuild());
// Test the basic life cycle of a FIFO.
// post and pop single elements
void checkPlain()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
Tester tester(pvReqEmpty, 0);
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.mon->start();
tester.testTimeline({});
testEmpty(*tester.mon);
tester.post(5);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 5);
testEmpty(*tester.mon);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
// close() while FIFO not empty.
void checkAfterClose()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
Tester tester(pvReqEmpty, 0);
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.mon->start();
tester.testTimeline({});
testEmpty(*tester.mon);
tester.post(5);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Event, Tester::Close});
// FIFO not cleared by close
testPop(*tester.mon, 5);
testEmpty(*tester.mon);
tester.mon->notify();
tester.testTimeline({});
}
// close() while FIFO not empty, then re-open
void checkReOpenLost()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
Tester tester(pvReqEmpty, 0);
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.mon->start();
tester.testTimeline({});
testEmpty(*tester.mon);
tester.post(5);
tester.mon->stop();
tester.close();
tester.mon->notify();
// FIFO cleared by connect()
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.mon->start();
tester.testTimeline({Tester::Event, Tester::Close, Tester::Connect});
// update 5 was lost!
testEmpty(*tester.mon);
tester.mon->notify();
tester.testTimeline({});
}
// type change while FIFO is empty
void checkTypeChange()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
Tester tester(pvReqEmpty, 0);
tester.connect(pvd::pvInt);
tester.mon->start();
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.post(5);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 5);
testEmpty(*tester.mon);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
testDiag("Type change");
tester.connect(pvd::pvString);
tester.mon->start();
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.post(std::string("hello"));
tester.mon->notify();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, std::string("hello"));
testEmpty(*tester.mon);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
// post() until the FIFO is full, and keep going. Check overrun behavour
void checkFill()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
pva::MonitorFIFO::Config conf = {4, 2, 0};
Tester tester(pvReqEmpty, &conf);
testEqual(conf.actualCount, 2u);
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.mon->start();
testEqual(conf.actualCount, tester.mon->freeCount());
testShow()<<"Empty before "<<*tester.mon;
// fill up and overflow
tester.post(5);
testShow()<<"A5 "<<*tester.mon;
tester.post(6);
testShow()<<"Full "<<*tester.mon;
tester.post(7);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testShow()<<"Overrun1 "<<*tester.mon;
tester.post(8);
tester.mon->notify();
tester.testTimeline({});
testShow()<<"Overrun2 "<<*tester.mon;
testPop(*tester.mon, 5);
tester.testTimeline({});
testPop(*tester.mon, 6);
tester.testTimeline({Tester::LowWater});
testPop(*tester.mon, 8, true);
tester.testTimeline({});
testEmpty(*tester.mon);
testShow()<<"Empty again "<<*tester.mon;
// repeat
tester.post(15);
tester.post(16);
tester.post(17);
tester.post(18);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 15);
tester.testTimeline({});
testPop(*tester.mon, 16);
tester.testTimeline({Tester::LowWater});
testPop(*tester.mon, 18, true);
tester.testTimeline({});
testEmpty(*tester.mon);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
// post() until past full, then pop() and post() on a partially full queue
void checkSaturate()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
pva::MonitorFIFO::Config conf = {4, 2, 0};
Tester tester(pvReqEmpty, &conf);
testEqual(conf.actualCount, 2u);
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.mon->start();
testShow()<<"Empty before "<<*tester.mon;
// fill up and overflow
tester.post(5);
tester.post(6);
tester.post(7);
tester.post(8);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 5);
tester.testTimeline({});
tester.post(9);
tester.mon->notify();
tester.testTimeline({});
testPop(*tester.mon, 6);
tester.testTimeline({});
tester.post(10);
tester.mon->notify();
tester.testTimeline({});
testPop(*tester.mon, 8, true);
tester.testTimeline({});
tester.post(11);
tester.mon->notify();
tester.testTimeline({});
testShow()<<"Overrun2 "<<*tester.mon;
testPop(*tester.mon, 9);
tester.testTimeline({});
testPop(*tester.mon, 10);
tester.testTimeline({Tester::LowWater});
testPop(*tester.mon, 11);
tester.testTimeline({});
testEmpty(*tester.mon);
tester.testTimeline({});
testShow()<<"Empty again "<<*tester.mon;
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
// check handling of pipeline=true
void checkPipeline()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
pva::MonitorFIFO::Config conf = {4, 3, 0};
Tester tester(pvReqPipeline, &conf);
testEqual(conf.actualCount, 2u);
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
tester.mon->start();
testEqual(tester.mon->freeCount(), 0u);
tester.mon->reportRemoteQueueStatus(2);
testEqual(tester.mon->freeCount(), 2u);
tester.testTimeline({Tester::LowWater});
// fill up and overflow
tester.post(5);
tester.post(6);
tester.post(7);
tester.post(8);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testEqual(tester.mon->freeCount(), 0u);
testPop(*tester.mon, 5);
testPop(*tester.mon, 6);
testEmpty(*tester.mon);
testDiag("ACK 2");
testShow()<<"Before ACK "<<*tester.mon;
tester.mon->reportRemoteQueueStatus(2);
testShow()<<"After ACK "<<*tester.mon;
tester.testTimeline({Tester::LowWater});
testEqual(tester.mon->freeCount(), 1u);
// still have the overflow'd element on the queue
testPop(*tester.mon, 8, true);
testEmpty(*tester.mon);
tester.mon->reportRemoteQueueStatus(1);
testEqual(tester.mon->freeCount(), 2u);
tester.testTimeline({});
testShow()<<"Empty before re-fill "<<*tester.mon;
// fill up and overflow
tester.post(15);
tester.post(16);
tester.post(17);
tester.post(18);
tester.mon->notify();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 15);
testPop(*tester.mon, 16);
testEmpty(*tester.mon);
tester.testTimeline({});
testDiag("ACK 1");
testShow()<<"Before ACK "<<*tester.mon;
tester.mon->reportRemoteQueueStatus(1);
testShow()<<"After ACK "<<*tester.mon;
tester.testTimeline({});
// 1 inuse, 1 returned, 1 empty
tester.post(19);
tester.post(20);
testPop(*tester.mon, 18, true);
testEmpty(*tester.mon);
tester.post(21);
testEmpty(*tester.mon);
testShow()<<"Before final ACK "<<*tester.mon;
tester.mon->reportRemoteQueueStatus(2);
tester.testTimeline({Tester::LowWater});
testPop(*tester.mon, 21, true);
testEmpty(*tester.mon);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
// test the spam counter (keeps FIFO full) with pipeline=true
void checkSpam()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
pva::MonitorFIFO::Config conf = {4, 3, 0};
Tester tester(pvReqPipeline, &conf);
pvd::uint32 cnt = 0;
tester.setAction([&tester, &cnt] (pva::MonitorFIFO *mon, size_t nfree) {
testDiag("Spamming %zu", nfree);
while(nfree--) {
testDiag("nfree=%zu %c", nfree, (nfree>0)?'T':'F');
tester.tryPost(cnt++, nfree>0);
}
});
tester.mon->setFreeHighMark(0); // run action when all buffers free
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
testEqual(conf.actualCount, 2u);
testDiag("prime the pump");
tester.mon->reportRemoteQueueStatus(conf.actualCount);
tester.mon->notify();
tester.testTimeline({Tester::LowWater});
testShow()<<"Before start() "<<*tester.mon<<"\n";
tester.mon->start();
testShow()<<"After start() "<<*tester.mon<<"\n";
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 0);
testPop(*tester.mon, 1);
testEmpty(*tester.mon);
tester.mon->reportRemoteQueueStatus(2);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 2);
testPop(*tester.mon, 3);
testEmpty(*tester.mon);
tester.mon->reportRemoteQueueStatus(1);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 4);
testEmpty(*tester.mon);
tester.mon->reportRemoteQueueStatus(2);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 5);
testPop(*tester.mon, 6);
testEmpty(*tester.mon);
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
// check pipeline=true, watermark, and unlisten.
// a sequence with a definite end
void checkCountdown()
{
testDiag("==== %s ====", CURRENT_FUNCTION);
pva::MonitorFIFO::Config conf = {4, 3, 0};
Tester tester(pvReqPipeline, &conf);
pvd::int32 cnt = 10;
tester.setAction([&tester, &cnt] (pva::MonitorFIFO *mon, size_t nfree) {
testDiag("Spamming %zu", nfree);
while(cnt >= 0 && nfree--) {
pvd::int32 c = cnt--;
testDiag("Count %d nfree=%zu %c", c, nfree, (nfree>0)?'T':'F');
tester.tryPost(c, nfree>0);
}
if(cnt<0) {
testDiag("Finished!");
tester.mon->finish();
}
});
tester.mon->setFreeHighMark(0.5); // run action() when one of two buffer elements is free
tester.connect(pvd::pvInt);
tester.mon->notify();
tester.testTimeline({Tester::Connect});
testEqual(conf.actualCount, 2u);
testDiag("prime the pump");
tester.mon->reportRemoteQueueStatus(conf.actualCount);
tester.mon->notify();
tester.testTimeline({Tester::LowWater});
tester.mon->start();
tester.testTimeline({Tester::Event});
testPop(*tester.mon, 10);
testPop(*tester.mon, 9);
testEmpty(*tester.mon);
tester.testTimeline({});
tester.mon->reportRemoteQueueStatus(2);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 8);
testPop(*tester.mon, 7);
testEmpty(*tester.mon);
tester.testTimeline({});
tester.mon->reportRemoteQueueStatus(1);
tester.testTimeline({}); // nothing happens, watermark not reached
tester.mon->reportRemoteQueueStatus(1);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 6);
testPop(*tester.mon, 5);
testEmpty(*tester.mon);
tester.testTimeline({});
tester.mon->reportRemoteQueueStatus(1);
tester.testTimeline({});
tester.mon->reportRemoteQueueStatus(1);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 4);
testPop(*tester.mon, 3);
testEmpty(*tester.mon);
tester.testTimeline({});
tester.mon->reportRemoteQueueStatus(2);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 2);
testPop(*tester.mon, 1);
testEmpty(*tester.mon);
tester.testTimeline({});
tester.mon->reportRemoteQueueStatus(2);
tester.testTimeline({Tester::LowWater, Tester::Event});
testPop(*tester.mon, 0);
testEmpty(*tester.mon);
tester.testTimeline({Tester::Unlisten});
tester.mon->stop();
tester.close();
tester.mon->notify();
tester.testTimeline({Tester::Close});
}
} // namespace
MAIN(testmonitorfifo)
{
testPlan(184);
checkPlain();
checkAfterClose();
checkReOpenLost();
checkTypeChange();
checkFill();
checkSaturate();
checkPipeline();
checkSpam();
checkCountdown();
return testDone();
}
#else /* c++11 */
MAIN(testmonitorfifo)
{
testPlan(1);
testSkip(1, "no c++11");
return testDone();
}
#endif /* c++11 */