Files
pvAccess/src/client/clientpvt.h
T
Michael Davidsaver 855335f3b1 client.h guard concurrent callbacks on cancel
cancel() blocks until concurrent callbacks have
completed.  Also serializes any callbacks for each
Operation/Monitor.
2018-09-26 10:28:18 -07:00

168 lines
4.3 KiB
C++

#ifndef CLIENTPVT_H
#define CLIENTPVT_H
#include <epicsEvent.h>
#include <epicsThread.h>
#include <pv/sharedPtr.h>
#include <pva/client.h>
namespace pvd = epics::pvData;
namespace pva = epics::pvAccess;
typedef epicsGuard<epicsMutex> Guard;
typedef epicsGuardRelease<epicsMutex> UnGuard;
namespace pvac{namespace detail{
/* Like std::tr1::enable_shared_from_this
* with the notion of internal vs. external references.
* External references wrap an internal reference.
* When the last external reference is dropped,
* then Base::cancel() is called, but the object isn't free'd
* until all internal references are dropped as well.
*/
template<typename Base>
struct wrapped_shared_from_this {
private:
// const after build()
std::tr1::weak_ptr<Base> myselfptr;
struct canceller {
std::tr1::shared_ptr<Base> ptr;
canceller(const std::tr1::shared_ptr<Base>& ptr) :ptr(ptr) {}
void operator()(Base *) {
std::tr1::shared_ptr<Base> P;
P.swap(ptr);
P->cancel();
}
};
public:
std::tr1::shared_ptr<Base> internal_shared_from_this() {
std::tr1::shared_ptr<Base> ret(myselfptr);
if(!ret)
throw std::tr1::bad_weak_ptr();
return ret;
}
static
std::tr1::shared_ptr<Base> build() {
std::tr1::shared_ptr<Base> inner(new Base),
ret(inner.get(), canceller(inner));
inner->myselfptr = inner;
return ret;
}
template<typename A>
static
std::tr1::shared_ptr<Base> build(A a) {
std::tr1::shared_ptr<Base> inner(new Base(a)),
ret(inner.get(), canceller(inner));
inner->myselfptr = inner;
return ret;
}
template<typename A, typename B>
static
std::tr1::shared_ptr<Base> build(A a, B b) {
std::tr1::shared_ptr<Base> inner(new Base(a, b)),
ret(inner.get(), canceller(inner));
inner->myselfptr = inner;
return ret;
}
};
/** Safe use of raw callback pointer while unlocked.
* clear pointer and then call CallbackGuard::wait() to ensure that concurrent
* callback have completed.
*
* Prototype usage
@code
* struct mycb : public CallbackStorage {
* void (*ptr)();
* };
* // make a callback
* void docb(mycb& cb) {
* CallbackGuard G(cb); // lock
* // decide whether to make CB
* if(P){
* void (*P)() = ptr; // copy for use while unlocked
* CallbackUse U(G); // unlock
* (*P)();
* // automatic re-lock
* }
* // automatic final unlock
* }
* void cancelop(mycb& cb) {
* CallbackGuard G(cb);
* ptr = 0; // prevent further callbacks from starting
* G.wait(); // wait for inprogress callbacks to complete
* }
@endcode
*/
struct CallbackStorage {
mutable epicsMutex mutex;
epicsEvent wakeup;
size_t nwaitcb;
epicsThreadId incb;
CallbackStorage() :nwaitcb(0u), incb(0) {}
};
// analogous to epicsGuard
struct CallbackGuard {
CallbackStorage& store;
epicsThreadId self;
explicit CallbackGuard(CallbackStorage& store) :store(store), self(0) {
store.mutex.lock();
}
~CallbackGuard() {
bool notify = store.nwaitcb!=0;
store.mutex.unlock();
if(notify)
store.wakeup.signal();
}
void ensureself() {
if(!self)
self = epicsThreadGetIdSelf();
}
// unlock and block until no in-progress callbacks
void wait() {
if(!store.incb) return;
ensureself();
store.nwaitcb++;
while(store.incb && store.incb!=self) {
store.mutex.unlock();
store.wakeup.wait();
store.mutex.lock();
}
store.nwaitcb--;
}
};
// analogous to epicsGuardRelease
struct CallbackUse {
CallbackGuard& G;
explicit CallbackUse(CallbackGuard& G) :G(G) {
G.wait(); // serialize callbacks
G.ensureself();
G.store.incb=G.self;
G.store.mutex.unlock();
}
~CallbackUse() {
G.store.mutex.lock();
G.store.incb=0;
}
};
void registerRefTrack();
void registerRefTrackGet();
void registerRefTrackPut();
void registerRefTrackMonitor();
void registerRefTrackRPC();
}} // namespace pvac::detail
#endif // CLIENTPVT_H