Files
pvAccess/src/utils/pv/fairQueue.h
2016-03-10 23:36:16 +01:00

204 lines
5.0 KiB
C++

/**
* Copyright - See the COPYRIGHT that is included with this distribution.
* pvAccessCPP is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*/
#ifndef FAIRQUEUE_H
#define FAIRQUEUE_H
#ifdef epicsExportSharedSymbols
# define fairQueueExportSharedSymbols
# undef epicsExportSharedSymbols
#endif
#include <epicsEvent.h>
#include <epicsMutex.h>
#include <epicsGuard.h>
#include <ellLib.h>
#include <dbDefs.h>
#include <pv/sharedPtr.h>
#ifdef fairQueueExportSharedSymbols
# define epicsExportSharedSymbols
# undef fairQueueExportSharedSymbols
#endif
#include <shareLib.h>
namespace epics {
namespace pvAccess {
/** @brief An intrusive, loss-less, unbounded, round-robin queue
*
* The parameterized type 'T' must be a sub-class of @class fair_queue<T>::entry
*
* @li Intrusive. Entries in the queue must derive from @class entry
*
* @li Loss-less. An entry will be returned by pop_front() corresponding to
* each call to push_back().
*
* @li Un-bounded. There is no upper limit to the number of times an entry
* may be queued other than machine constraints.
*
* @li Round robin. The order that entries are returned may not match
* the order they were added in. "Fairness" is achived by returning
* entries in a rotating fashion based on the order in which they were
* first added. Re-adding the same entry before it is popped does not change
* this order.
* Adding [A, A, B, A, C, C] would give out [A, B, C, A, C, A].
*
* @warning Only one thread should call pop_front()
* as push_back() does not broadcast (only wakes up one waiter)
*/
template<typename T>
class fair_queue
{
typedef epicsGuard<epicsMutex> guard_t;
public:
typedef std::tr1::shared_ptr<T> value_type;
class entry {
/* In c++, use of ellLib (which implies offsetof()) should be restricted
* to POD structs. So enode_t exists as a POD struct for which offsetof()
* is safe and well defined. enode_t::self is used in place of
* casting via CONTAINER(penode, entry, enode)
*/
struct enode_t {
ELLNODE node;
entry *self;
} enode;
unsigned Qcnt;
value_type holder;
#ifndef NDEBUG
fair_queue *owner;
#endif
friend class fair_queue;
entry(const entry&);
entry& operator=(const entry&);
public:
entry() :Qcnt(0), holder()
#ifndef NDEBUG
, owner(NULL)
#endif
{
enode.node.next = enode.node.previous = NULL;
enode.self = this;
}
~entry() {
// nodes should be removed from the list before deletion
assert(!enode.node.next && !enode.node.previous);
#ifndef NDEBUG
assert(!owner);
#endif
}
};
fair_queue()
{
ellInit(&list);
}
~fair_queue()
{
clear();
assert(ellCount(&list)==0);
}
void clear()
{
value_type C;
guard_t G(mutex);
do {
pop_front_try(C);
} while(C);
}
bool empty() const {
guard_t G(mutex);
return ellFirst(&list)==NULL;
}
void push_back(const value_type& ent)
{
bool wake;
entry *P = ent.get();
{
guard_t G(mutex);
wake = ellFirst(&list)==NULL; // empty queue
if(P->Qcnt++==0) {
// not in list
assert(P->owner==NULL);
P->owner = this;
P->holder = ent; // the list will hold a reference
ellAdd(&list, &P->enode.node); // push_back
} else
assert(P->owner==this);
}
if(wake) wakeup.signal();
}
bool pop_front_try(value_type& ret)
{
guard_t G(mutex);
ELLNODE *cur = ellGet(&list); // pop_front
if(cur) {
typedef typename entry::enode_t enode_t;
enode_t *PN = CONTAINER(cur, enode_t, node);
entry *P = PN->self;
assert(P->owner==this);
assert(P->Qcnt>0);
if(--P->Qcnt==0) {
PN->node.previous = PN->node.next = NULL;
P->owner = NULL;
ret.swap(P->holder);
} else {
ellAdd(&list, &P->enode.node); // push_back
ret = P->holder;
}
return true;
} else {
ret.reset();
return false;
}
}
void pop_front(value_type& ret)
{
while(1) {
pop_front_try(ret);
if(ret)
break;
wakeup.wait();
}
}
bool pop_front(value_type& ret, double timeout)
{
while(1) {
pop_front_try(ret);
if(ret)
return true;
if(!wakeup.wait(timeout))
return false;
}
}
private:
ELLLIST list;
mutable epicsMutex mutex;
mutable epicsEvent wakeup;
};
}
} // namespace
#endif // FAIRQUEUE_H