artdaq_core  v1_07_08
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Pages
ConcurrentQueue.hh
1 #ifndef artdaq_core_Core_ConcurrentQueue_hh
2 #define artdaq_core_Core_ConcurrentQueue_hh
3 
4 #include <algorithm>
5 #include <cstddef>
6 #include <exception>
7 #include <limits>
8 #include <list>
9 
10 #include <iostream> // debugging
11 #include "trace.h" // TRACE
12 
13 #include <chrono>
14 #include <condition_variable>
15 #include <mutex>
16 #include <type_traits>
17 
18 // #include <boost/date_time/posix_time/posix_time_types.hpp>
19 // #include <boost/utility/enable_if.hpp>
20 // #include <boost/thread/condition.hpp>
21 // #include <boost/thread/mutex.hpp>
22 // #include <boost/thread/xtime.hpp>
23 
24 namespace artdaq
25 {
50  namespace detail
51  {
61  typedef std::chrono::duration<double> seconds;
62 
63  typedef size_t MemoryType;
64 
70  template <typename T>
72  {
73  typedef char TrueType;
74 
75  struct FalseType
76  {
77  TrueType _[2];
78  };
79 
80  template <MemoryType(T::*)() const>
81  struct TestConst;
82 
83  template <typename C>
84  static TrueType test(TestConst<&C::memoryUsed>*) { return 0; }
85 
86  template <typename C>
87  static FalseType test(...)
88  {
89  return {};
90  }
91 
92  public:
93 
99  static const bool value = (sizeof(test<T>(nullptr)) == sizeof(TrueType));
100  };
101 
108  template <typename T>
109  MemoryType
110  memoryUsage(const std::pair<T, size_t>& t)
111  {
112  MemoryType usage(0UL);
113  try
114  {
115  usage = t.first.memoryUsed();
116  }
117  catch (...) {}
118  return usage;
119  }
120 
127  template <typename T>
128  typename std::enable_if<hasMemoryUsed<T>::value, MemoryType>::type
129  memoryUsage(const T& t)
130  {
131  MemoryType usage(0UL);
132  try
133  {
134  usage = t.memoryUsed();
135  }
136  catch (...) {}
137  return usage;
138  }
139 
146  template <typename T>
147  typename std::enable_if<!hasMemoryUsed<T>::value, MemoryType>::type
148  memoryUsage(const T& t)
149  {
150  return sizeof(t);
151  }
152  }// end namespace detail
153 
154 
159  template <class T>
160  struct FailIfFull
161  {
162  typedef bool ReturnType;
163 
164  typedef T ValueType;
165  typedef std::list<T> SequenceType;
166  typedef typename SequenceType::size_type SizeType;
167 
171  static struct QueueIsFull : public std::exception
172  {
177  virtual const char* what() const throw()
178  {
179  return "Cannot add item to a full queue";
180  }
181  } queueIsFull;
182 
192  static void doInsert
193  (
194  T const& item,
195  SequenceType& elements,
196  SizeType& size,
197  detail::MemoryType const& itemSize,
198  detail::MemoryType& used,
199  std::condition_variable& nonempty
200  )
201  {
202  elements.push_back(item);
203  ++size;
204  used += itemSize;
205  nonempty.notify_one();
206  }
207 
221  static ReturnType doEnq
222  (
223  T const& item,
224  SequenceType& elements,
225  SizeType& size,
226  SizeType& capacity,
227  detail::MemoryType& used,
228  detail::MemoryType& memory,
229  size_t& elementsDropped,
230  std::condition_variable& nonempty
231  )
232  {
233  detail::MemoryType itemSize = detail::memoryUsage(item);
234  if (size >= capacity || used + itemSize > memory)
235  {
236  ++elementsDropped;
237  throw queueIsFull;
238  }
239  else
240  {
241  doInsert(item, elements, size, itemSize, used, nonempty);
242  }
243  return true;
244  }
245  };
246 
247  template <typename T>
249 
254  template <class T>
255  struct KeepNewest
256  {
257  typedef std::pair<T, size_t> ValueType;
258  typedef std::list<T> SequenceType;
259  typedef typename SequenceType::size_type SizeType;
261 
271  static void doInsert
272  (
273  T const& item,
274  SequenceType& elements,
275  SizeType& size,
276  detail::MemoryType const& itemSize,
277  detail::MemoryType& used,
278  std::condition_variable& nonempty
279  )
280  {
281  elements.push_back(item);
282  ++size;
283  used += itemSize;
284  nonempty.notify_one();
285  }
286 
299  static ReturnType doEnq
300  (
301  T const& item,
302  SequenceType& elements,
303  SizeType& size,
304  SizeType& capacity,
305  detail::MemoryType& used,
306  detail::MemoryType& memory,
307  size_t& elementsDropped,
308  std::condition_variable& nonempty
309  )
310  {
311  SizeType elementsRemoved(0);
312  detail::MemoryType itemSize = detail::memoryUsage(item);
313  while ((size == capacity || used + itemSize > memory) && !elements.empty())
314  {
315  SequenceType holder;
316  // Move the item out of elements in a manner that will not throw.
317  holder.splice(holder.begin(), elements, elements.begin());
318  // Record the change in the length of elements.
319  --size;
320  used -= detail::memoryUsage(holder.front());
321  ++elementsRemoved;
322  }
323  if (size < capacity && used + itemSize <= memory)
324  // we succeeded to make enough room for the new element
325  {
326  doInsert(item, elements, size, itemSize, used, nonempty);
327  }
328  else
329  {
330  // we cannot add the new element
331  ++elementsRemoved;
332  }
333  elementsDropped += elementsRemoved;
334  return elementsRemoved;
335  }
336  };
337 
342  template <class T>
344  {
345  typedef std::pair<T, size_t> ValueType;
346  typedef std::list<T> SequenceType;
347  typedef typename SequenceType::size_type SizeType;
349 
359  static void doInsert
360  (
361  T const& item,
362  SequenceType& elements,
363  SizeType& size,
364  detail::MemoryType const& itemSize,
365  detail::MemoryType& used,
366  std::condition_variable& nonempty
367  )
368  {
369  elements.push_back(item);
370  ++size;
371  used += itemSize;
372  nonempty.notify_one();
373  }
374 
387  static ReturnType doEnq
388  (
389  T const& item,
390  SequenceType& elements,
391  SizeType& size,
392  SizeType& capacity,
393  detail::MemoryType& used,
394  detail::MemoryType& memory,
395  size_t& elementsDropped,
396  std::condition_variable& nonempty
397  )
398  {
399  detail::MemoryType itemSize = detail::memoryUsage(item);
400  if (size < capacity && used + itemSize <= memory)
401  {
402  doInsert(item, elements, size, itemSize, used, nonempty);
403  return 0;
404  }
405  ++elementsDropped;
406  return 1;
407  }
408  };
409 
413  template <class T, class EnqPolicy = FailIfFull<T>>
415  {
416  public:
417  typedef typename EnqPolicy::ValueType ValueType;
418  typedef typename EnqPolicy::SequenceType SequenceType;
419  typedef typename SequenceType::size_type SizeType;
420 
427  explicit ConcurrentQueue
428  (
429  SizeType maxSize = std::numeric_limits<SizeType>::max(),
430  detail::MemoryType maxMemory = std::numeric_limits<detail::MemoryType>::max()
431  );
432 
440 
457  typename EnqPolicy::ReturnType enqNowait(T const& item);
458 
467  void enqWait(T const& item);
468 
481  bool enqTimedWait(T const& item, detail::seconds const& wait);
482 
495  bool deqNowait(ValueType& item);
496 
507  void deqWait(ValueType& item);
508 
522  bool deqTimedWait(ValueType& item, detail::seconds const& wait);
523 
528  bool empty() const;
529 
534  bool full() const;
535 
541  SizeType size() const;
542 
548  SizeType capacity() const;
549 
558 
563  detail::MemoryType used() const;
564 
570  detail::MemoryType memory() const;
571 
580  bool setMemory(detail::MemoryType maxMemory);
581 
587  SizeType clear();
588 
593  void addExternallyDroppedEvents(SizeType dropped);
594 
599  bool queueReaderIsReady() const { return readerReady_; }
600 
608  void setReaderIsReady(bool rdy = true)
609  {
610  readyTime_ = std::chrono::steady_clock::now();
611  readerReady_ = rdy;
612  }
613 
618  std::chrono::steady_clock::time_point getReadyTime() const { return readyTime_; }
619 
620  private:
621  typedef std::lock_guard<std::mutex> LockType;
622  typedef std::unique_lock<std::mutex> WaitLockType;
623 
624  mutable std::mutex protectElements_;
625  mutable std::condition_variable queueNotEmpty_;
626  mutable std::condition_variable queueNotFull_;
627 
628  std::chrono::steady_clock::time_point readyTime_;
629  bool readerReady_;
630  SequenceType elements_;
631  SizeType capacity_;
632  SizeType size_;
633  /*
634  N.B.: we rely on SizeType *not* being some synthesized large
635  type, so that reading the value is an atomic action, as is
636  incrementing or decrementing the value. We do *not* assume that
637  there is any atomic getAndIncrement or getAndDecrement
638  operation.
639  */
640  detail::MemoryType memory_;
641  detail::MemoryType used_;
642  size_t elementsDropped_;
643 
644  /*
645  These private member functions assume that whatever locks
646  necessary for safe operation have already been obtained.
647  */
648 
649  /*
650  Insert the given item into the list, if it is not already full,
651  and increment size. Return true if the item is inserted, and
652  false if not.
653  */
654  bool insertIfPossible(T const& item);
655 
669  bool removeHeadIfPossible(ValueType& item);
670 
682  void removeHead(ValueType& item);
683 
684  void assignItem(T& item, const T& element);
685 
686  void assignItem(std::pair<T, size_t>& item, const T& element);
687 
688  /*
689  Return false if the queue can accept new entries.
690  */
691  bool isFull() const;
692 
693  /*
694  These functions are declared private and not implemented to
695  prevent their use.
696  */
698 
699  ConcurrentQueue& operator=(ConcurrentQueue<T, EnqPolicy> const&) = delete;
700  };
701 
702  //------------------------------------------------------------------
703  // Implementation follows
704  //------------------------------------------------------------------
705 
706  template <class T, class EnqPolicy>
708  (
709  SizeType maxSize,
710  detail::MemoryType maxMemory
711  ) :
712  protectElements_()
713  , readyTime_(std::chrono::steady_clock::now())
714  , readerReady_(false)
715  , elements_()
716  , capacity_(maxSize)
717  , size_(0)
718  , memory_(maxMemory)
719  , used_(0)
720  , elementsDropped_(0) {}
721 
722  template <class T, class EnqPolicy>
724  {
725  LockType lock(protectElements_);
726  elements_.clear();
727  size_ = 0;
728  used_ = 0;
729  elementsDropped_ = 0;
730  }
731 
732 
733  // enqueue methods - 3 - enqNowait, enqWait, enqTimedWait
734 
735  template <class T, class EnqPolicy>
736  typename EnqPolicy::ReturnType ConcurrentQueue<T, EnqPolicy>::enqNowait(T const& item)
737  {
738  TRACE(12, "ConcurrentQueue<T,EnqPolicy>::enqNowait enter size=%zu capacity=%zu used=%zu memory=%zu", size_, capacity_, used_, memory_);
739  LockType lock(protectElements_);
740  auto retval = EnqPolicy::doEnq(item, elements_, size_, capacity_, used_, memory_,
741  elementsDropped_, queueNotEmpty_);
742  TRACE(12, "ConcurrentQueue<T,EnqPolicy>::enqNowait returning %zu", (SizeType)retval);
743  return retval;
744  }
745 
746  template <class T, class EnqPolicy>
748  {
749  TRACE(12, "ConcurrentQueue<T,EnqPolicy>::enqWait enter");
750  WaitLockType lock(protectElements_);
751  while (isFull()) { queueNotFull_.wait(lock); }
752  EnqPolicy::doInsert(item, elements_, size_,
753  detail::memoryUsage(item), used_, queueNotEmpty_);
754  TRACE(12, "ConcurrentQueue<T,EnqPolicy>::enqWait returning");
755  }
756 
757  template <class T, class EnqPolicy>
759  {
760  TRACE(12, "ConcurrentQueue<T,EnqPolicy>::enqTimedWait enter with waitTime=%ld ms size=%zu capacity=%zu used=%zu memory=%zu"
761  , std::chrono::duration_cast<std::chrono::milliseconds>(waitTime).count(), size_, capacity_, used_, memory_);
762  WaitLockType lock(protectElements_);
763  if (isFull())
764  {
765  queueNotFull_.wait_for(lock, waitTime);
766  }
767  bool retval = insertIfPossible(item);
768  TRACE(12, "ConcurrentQueue<T,EnqPolicy>::enqTimedWait returning %d", retval);
769  return retval;
770  }
771 
772 
773  // dequeue methods - 3 - deqNowait, deqWait, deqTimedWait
774 
775  template <class T, class EnqPolicy>
777  {
778  TRACE(12, "ConcurrentQueue<T, EnqPolicy>::deqNowait enter");
779  LockType lock(protectElements_);
780  bool retval = removeHeadIfPossible(item);
781  TRACE(12, "ConcurrentQueue<T, EnqPolicy>::deqNowait returning %d", retval);
782  return retval;
783  }
784 
785  template <class T, class EnqPolicy>
787  {
788  TRACE(12, "ConcurrentQueue<T, EnqPolicy>::deqWait enter");
789  WaitLockType lock(protectElements_);
790  while (size_ == 0) { queueNotEmpty_.wait(lock); }
791  removeHead(item);
792  TRACE(12, "ConcurrentQueue<T, EnqPolicy>::deqWait returning");
793  }
794 
795  template <class T, class EnqPolicy>
797  {
798  TRACE(12, "ConcurrentQueue<T, EnqPolicy>::deqTimedWait enter with waitTime=%ld ms size=%zu"
799  , std::chrono::duration_cast<std::chrono::milliseconds>(waitTime).count(), size_);
800  WaitLockType lock(protectElements_);
801  if (size_ == 0)
802  {
803  queueNotEmpty_.wait_for(lock, waitTime);
804  }
805  bool retval = removeHeadIfPossible(item);
806  TRACE(12, "ConcurrentQueue<T, EnqPolicy>::deqTimedWait returning %d size=%zu", retval, size_);
807  return retval;
808  }
809 
810 
811  template <class T, class EnqPolicy>
812  bool
814  {
815  // No lock is necessary: the read is atomic.
816  return size_ == 0;
817  }
818 
819  template <class T, class EnqPolicy>
820  bool
822  {
823  LockType lock(protectElements_);
824  return isFull();
825  }
826 
827  template <class T, class EnqPolicy>
830  {
831  // No lock is necessary: the read is atomic.
832  return size_;
833  }
834 
835  template <class T, class EnqPolicy>
838  {
839  // No lock is necessary: the read is atomic.
840  return capacity_;
841  }
842 
843  template <class T, class EnqPolicy>
844  bool
846  {
847  LockType lock(protectElements_);
848  bool isEmpty = (size_ == 0);
849  if (isEmpty) { capacity_ = newcapacity; }
850  return isEmpty;
851  }
852 
853  template <class T, class EnqPolicy>
856  {
857  // No lock is necessary: the read is atomic.
858  return used_;
859  }
860 
861  template <class T, class EnqPolicy>
864  {
865  // No lock is necessary: the read is atomic.
866  return memory_;
867  }
868 
869  template <class T, class EnqPolicy>
870  bool
872  {
873  LockType lock(protectElements_);
874  bool isEmpty = (size_ == 0);
875  if (isEmpty) { memory_ = newmemory; }
876  return isEmpty;
877  }
878 
879  template <class T, class EnqPolicy>
882  {
883  LockType lock(protectElements_);
884  SizeType clearedEvents = size_;
885  elementsDropped_ += size_;
886  elements_.clear();
887  size_ = 0;
888  used_ = 0;
889  return clearedEvents;
890  }
891 
892  template <class T, class EnqPolicy>
893  void
895  {
896  LockType lock(protectElements_);
897  elementsDropped_ += n;
898  }
899 
900  //-----------------------------------------------------------
901  // Private member functions
902  //-----------------------------------------------------------
903 
904  template <class T, class EnqPolicy>
905  bool
907  {
908  if (isFull())
909  {
910  ++elementsDropped_;
911  return false;
912  }
913  else
914  {
915  EnqPolicy::doInsert(item, elements_, size_,
916  detail::memoryUsage(item), used_, queueNotEmpty_);
917  return true;
918  }
919  }
920 
921  template <class T, class EnqPolicy>
922  bool
923  ConcurrentQueue<T, EnqPolicy>::removeHeadIfPossible(ValueType& item)
924  {
925  if (size_ == 0) { return false; }
926  removeHead(item);
927  return true;
928  }
929 
930  template <class T, class EnqPolicy>
931  void
932  ConcurrentQueue<T, EnqPolicy>::removeHead(ValueType& item)
933  {
934  SequenceType holder;
935  // Move the item out of elements_ in a manner that will not throw.
936  holder.splice(holder.begin(), elements_, elements_.begin());
937  // Record the change in the length of elements_.
938  --size_;
939  queueNotFull_.notify_one();
940  assignItem(item, holder.front());
941  used_ -= detail::memoryUsage(item);
942  }
943 
944  template <class T, class EnqPolicy>
945  void
946  ConcurrentQueue<T, EnqPolicy>::assignItem(T& item, const T& element)
947  {
948  item = element;
949  }
950 
951  template <class T, class EnqPolicy>
952  void
953  ConcurrentQueue<T, EnqPolicy>::assignItem(std::pair<T, size_t>& item, const T& element)
954  {
955  item.first = element;
956  item.second = elementsDropped_;
957  elementsDropped_ = 0;
958  }
959 
960  template <class T, class EnqPolicy>
961  bool
962  ConcurrentQueue<T, EnqPolicy>::isFull() const
963  {
964  if (size_ >= capacity_ || used_ >= memory_) { return true; }
965  return false;
966  }
967 } // namespace daqrate
968 
969 #endif /* artdaq_core_Core_ConcurrentQueue_hh */
970 
972 
std::chrono::steady_clock::time_point getReadyTime() const
Gets the time at which the queue became ready.
std::list< T > SequenceType
Type of sequences of items.
T ValueType
Type of values stored in queue.
static ReturnType doEnq(T const &item, SequenceType &elements, SizeType &size, SizeType &capacity, detail::MemoryType &used, detail::MemoryType &memory, size_t &elementsDropped, std::condition_variable &nonempty)
Attempts to enqueue an item.
SizeType clear()
Remove all items from the queue. This changes the size to zero but does not change the capacity...
detail::MemoryType used() const
Return the memory in bytes used by items in the queue.
std::pair< T, size_t > ValueType
Type of elements stored in the queue.
virtual const char * what() const
Describe exception.
detail::MemoryType memory() const
Return the memory of the queue in bytes, that is, the maximum memory the items in the queue may occup...
SequenceType::size_type SizeType
Size type of seqeuences.
MemoryType memoryUsage(const std::pair< T, size_t > &t)
Returns the memory used by an object.
std::pair< T, size_t > ValueType
Type of elements stored in the queue.
std::chrono::duration< double > seconds
EnqPolicy::SequenceType SequenceType
Type of sequence used by ConcurrentQueue.
EnqPolicy::ReturnType enqNowait(T const &item)
Add a copy if item to the queue, according to the rules determined by the EnqPolicy.
SequenceType::size_type SizeType
Size type of seqeuences.
static void doInsert(T const &item, SequenceType &elements, SizeType &size, detail::MemoryType const &itemSize, detail::MemoryType &used, std::condition_variable &nonempty)
Inserts element into the ConcurrentQueue.
std::list< T > SequenceType
Type of sequences of items.
bool setCapacity(SizeType capacity)
SizeType ReturnType
Type returned by doEnq.
SequenceType::size_type SizeType
Size type of seqeuences.
ConcurrentQueue policy to throw an exception when the queue is full.
size_t MemoryType
Basic unit of data storage and pointer types.
Exception thrown by FailIfFull policy when an enqueue operation is attempted on a full queue...
ConcurrentQueue policy to discard oldest elements when the queue is full.
static const bool value
Use SFINAE to figure out if the class used to instantiate the ConcurrentQueue template has a method m...
ConcurrentQueue policy to discard new elements when the queue is full.
static void doInsert(T const &item, SequenceType &elements, SizeType &size, detail::MemoryType const &itemSize, detail::MemoryType &used, std::condition_variable &nonempty)
Inserts element into the ConcurrentQueue.
bool deqNowait(ValueType &item)
Assign the value at the head of the queue to item and then remove the head of the queue...
bool setMemory(detail::MemoryType maxMemory)
Reset the memory usage in bytes of the queue. A value of 0 disabled the memory check. This can only be done if the queue is empty.
SizeType ReturnType
Type returned by doEnq.
artdaq::FailIfFull::QueueIsFull queueIsFull
Instance of QueueIsFull exception.
ConcurrentQueue(SizeType maxSize=std::numeric_limits< SizeType >::max(), detail::MemoryType maxMemory=std::numeric_limits< detail::MemoryType >::max())
ConcurrentQueue is always bounded. By default, the bound is absurdly large.
static ReturnType doEnq(T const &item, SequenceType &elements, SizeType &size, SizeType &capacity, detail::MemoryType &used, detail::MemoryType &memory, size_t &elementsDropped, std::condition_variable &nonempty)
Attempts to enqueue an item.
void enqWait(T const &item)
Add a copy of item to the queue.
bool deqTimedWait(ValueType &item, detail::seconds const &wait)
Assign the value at the head of the queue to item and then remove the head of the queue...
bool enqTimedWait(T const &item, detail::seconds const &wait)
Add a copy of item to the queue, waiting for the queue to be non-full.
std::list< T > SequenceType
Type of seqeuences of values.
void addExternallyDroppedEvents(SizeType dropped)
Adds the passed count to the counter of dropped events.
static void doInsert(T const &item, SequenceType &elements, SizeType &size, detail::MemoryType const &itemSize, detail::MemoryType &used, std::condition_variable &nonempty)
Inserts element into the ConcurrentQueue.
void deqWait(ValueType &item)
Assign the value of the head of the queue to item and then remove the head of the queue...
SizeType capacity() const
Return the capacity of the queue, that is, the maximum number of items it can contain.
void setReaderIsReady(bool rdy=true)
Set the ready flag for the reader.
EnqPolicy::ValueType ValueType
Type of values stored in ConcurrentQueue.
SequenceType::size_type SizeType
Type for indexes in sequence.
static ReturnType doEnq(T const &item, SequenceType &elements, SizeType &size, SizeType &capacity, detail::MemoryType &used, detail::MemoryType &memory, size_t &elementsDropped, std::condition_variable &nonempty)
Attempts to enqueue an item.
bool queueReaderIsReady() const
Is the reader connected and ready for items to appear on the queue?
bool ReturnType
Type returned by doEnq.
SizeType size() const