artdaq_core  v3_01_04
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 "tracemf.h" // TRACE
12 
13 #include <chrono>
14 #include <condition_variable>
15 #include <mutex>
16 #include <type_traits>
17 
18 #undef TRACE_NAME
19 #define TRACE_NAME "ConcurrentQueue"
20 
21 // #include <boost/date_time/posix_time/posix_time_types.hpp>
22 // #include <boost/utility/enable_if.hpp>
23 // #include <boost/thread/condition.hpp>
24 // #include <boost/thread/mutex.hpp>
25 // #include <boost/thread/xtime.hpp>
26 
27 namespace artdaq
28 {
53  namespace detail
54  {
64  typedef std::chrono::duration<double> seconds;
65 
66  typedef size_t MemoryType;
67 
73  template <typename T>
75  {
76  typedef char TrueType;
77 
78  struct FalseType
79  {
80  TrueType _[2];
81  };
82 
83  template <MemoryType(T::*)() const>
84  struct TestConst;
85 
86  template <typename C>
87  static TrueType test(TestConst<&C::memoryUsed>*) { return 0; }
88 
89  template <typename C>
90  static FalseType test(...)
91  {
92  return {};
93  }
94 
95  public:
96 
102  static const bool value = (sizeof(test<T>(nullptr)) == sizeof(TrueType));
103  };
104 
111  template <typename T>
112  MemoryType
113  memoryUsage(const std::pair<T, size_t>& t)
114  {
115  MemoryType usage(0UL);
116  try
117  {
118  usage = t.first.memoryUsed();
119  }
120  catch (...) {}
121  return usage;
122  }
123 
130  template <typename T>
131  typename std::enable_if<hasMemoryUsed<T>::value, MemoryType>::type
132  memoryUsage(const T& t)
133  {
134  MemoryType usage(0UL);
135  try
136  {
137  usage = t.memoryUsed();
138  }
139  catch (...) {}
140  return usage;
141  }
142 
149  template <typename T>
150  typename std::enable_if<!hasMemoryUsed<T>::value, MemoryType>::type
151  memoryUsage(const T& t)
152  {
153  return sizeof(t);
154  }
155  }// end namespace detail
156 
157 
162  template <class T>
163  struct FailIfFull
164  {
165  typedef bool ReturnType;
166 
167  typedef T ValueType;
168  typedef std::list<T> SequenceType;
169  typedef typename SequenceType::size_type SizeType;
170 
174  static struct QueueIsFull : public std::exception
175  {
180  virtual const char* what() const throw()
181  {
182  return "Cannot add item to a full queue";
183  }
184  } queueIsFull;
185 
195  static void doInsert
196  (
197  T const& item,
198  SequenceType& elements,
199  SizeType& size,
200  detail::MemoryType const& itemSize,
201  detail::MemoryType& used,
202  std::condition_variable& nonempty
203  )
204  {
205  elements.push_back(item);
206  ++size;
207  used += itemSize;
208  nonempty.notify_one();
209  }
210 
224  static ReturnType doEnq
225  (
226  T const& item,
227  SequenceType& elements,
228  SizeType& size,
229  SizeType& capacity,
230  detail::MemoryType& used,
231  detail::MemoryType& memory,
232  size_t& elementsDropped,
233  std::condition_variable& nonempty
234  )
235  {
236  detail::MemoryType itemSize = detail::memoryUsage(item);
237  if (size >= capacity || used + itemSize > memory)
238  {
239  ++elementsDropped;
240  throw queueIsFull;
241  }
242  else
243  {
244  doInsert(item, elements, size, itemSize, used, nonempty);
245  }
246  return true;
247  }
248  };
249 
250  template <typename T>
252 
257  template <class T>
258  struct KeepNewest
259  {
260  typedef std::pair<T, size_t> ValueType;
261  typedef std::list<T> SequenceType;
262  typedef typename SequenceType::size_type SizeType;
264 
274  static void doInsert
275  (
276  T const& item,
277  SequenceType& elements,
278  SizeType& size,
279  detail::MemoryType const& itemSize,
280  detail::MemoryType& used,
281  std::condition_variable& nonempty
282  )
283  {
284  elements.push_back(item);
285  ++size;
286  used += itemSize;
287  nonempty.notify_one();
288  }
289 
302  static ReturnType doEnq
303  (
304  T const& item,
305  SequenceType& elements,
306  SizeType& size,
307  SizeType& capacity,
308  detail::MemoryType& used,
309  detail::MemoryType& memory,
310  size_t& elementsDropped,
311  std::condition_variable& nonempty
312  )
313  {
314  SizeType elementsRemoved(0);
315  detail::MemoryType itemSize = detail::memoryUsage(item);
316  while ((size == capacity || used + itemSize > memory) && !elements.empty())
317  {
318  SequenceType holder;
319  // Move the item out of elements in a manner that will not throw.
320  holder.splice(holder.begin(), elements, elements.begin());
321  // Record the change in the length of elements.
322  --size;
323  used -= detail::memoryUsage(holder.front());
324  ++elementsRemoved;
325  }
326  if (size < capacity && used + itemSize <= memory)
327  // we succeeded to make enough room for the new element
328  {
329  doInsert(item, elements, size, itemSize, used, nonempty);
330  }
331  else
332  {
333  // we cannot add the new element
334  ++elementsRemoved;
335  }
336  elementsDropped += elementsRemoved;
337  return elementsRemoved;
338  }
339  };
340 
345  template <class T>
347  {
348  typedef std::pair<T, size_t> ValueType;
349  typedef std::list<T> SequenceType;
350  typedef typename SequenceType::size_type SizeType;
352 
362  static void doInsert
363  (
364  T const& item,
365  SequenceType& elements,
366  SizeType& size,
367  detail::MemoryType const& itemSize,
368  detail::MemoryType& used,
369  std::condition_variable& nonempty
370  )
371  {
372  elements.push_back(item);
373  ++size;
374  used += itemSize;
375  nonempty.notify_one();
376  }
377 
390  static ReturnType doEnq
391  (
392  T const& item,
393  SequenceType& elements,
394  SizeType& size,
395  SizeType& capacity,
396  detail::MemoryType& used,
397  detail::MemoryType& memory,
398  size_t& elementsDropped,
399  std::condition_variable& nonempty
400  )
401  {
402  detail::MemoryType itemSize = detail::memoryUsage(item);
403  if (size < capacity && used + itemSize <= memory)
404  {
405  doInsert(item, elements, size, itemSize, used, nonempty);
406  return 0;
407  }
408  ++elementsDropped;
409  return 1;
410  }
411  };
412 
416  template <class T, class EnqPolicy = FailIfFull<T>>
418  {
419  public:
420  typedef typename EnqPolicy::ValueType ValueType;
421  typedef typename EnqPolicy::SequenceType SequenceType;
422  typedef typename SequenceType::size_type SizeType;
423 
430  explicit ConcurrentQueue
431  (
432  SizeType maxSize = std::numeric_limits<SizeType>::max(),
433  detail::MemoryType maxMemory = std::numeric_limits<detail::MemoryType>::max()
434  );
435 
443 
460  typename EnqPolicy::ReturnType enqNowait(T const& item);
461 
470  void enqWait(T const& item);
471 
484  bool enqTimedWait(T const& item, detail::seconds const& wait);
485 
498  bool deqNowait(ValueType& item);
499 
510  void deqWait(ValueType& item);
511 
525  bool deqTimedWait(ValueType& item, detail::seconds const& wait);
526 
531  bool empty() const;
532 
537  bool full() const;
538 
544  SizeType size() const;
545 
551  SizeType capacity() const;
552 
561 
566  detail::MemoryType used() const;
567 
573  detail::MemoryType memory() const;
574 
583  bool setMemory(detail::MemoryType maxMemory);
584 
590  SizeType clear();
591 
596  void addExternallyDroppedEvents(SizeType dropped);
597 
602  bool queueReaderIsReady() const { return readerReady_; }
603 
611  void setReaderIsReady(bool rdy = true)
612  {
613  readyTime_ = std::chrono::steady_clock::now();
614  readerReady_ = rdy;
615  }
616 
621  std::chrono::steady_clock::time_point getReadyTime() const { return readyTime_; }
622 
623  private:
624  typedef std::lock_guard<std::mutex> LockType;
625  typedef std::unique_lock<std::mutex> WaitLockType;
626 
627  mutable std::mutex protectElements_;
628  mutable std::condition_variable queueNotEmpty_;
629  mutable std::condition_variable queueNotFull_;
630 
631  std::chrono::steady_clock::time_point readyTime_;
632  bool readerReady_;
633  SequenceType elements_;
634  SizeType capacity_;
635  SizeType size_;
636  /*
637  N.B.: we rely on SizeType *not* being some synthesized large
638  type, so that reading the value is an atomic action, as is
639  incrementing or decrementing the value. We do *not* assume that
640  there is any atomic getAndIncrement or getAndDecrement
641  operation.
642  */
643  detail::MemoryType memory_;
644  detail::MemoryType used_;
645  size_t elementsDropped_;
646 
647  /*
648  These private member functions assume that whatever locks
649  necessary for safe operation have already been obtained.
650  */
651 
652  /*
653  Insert the given item into the list, if it is not already full,
654  and increment size. Return true if the item is inserted, and
655  false if not.
656  */
657  bool insertIfPossible(T const& item);
658 
672  bool removeHeadIfPossible(ValueType& item);
673 
685  void removeHead(ValueType& item);
686 
687  void assignItem(T& item, const T& element);
688 
689  void assignItem(std::pair<T, size_t>& item, const T& element);
690 
691  /*
692  Return false if the queue can accept new entries.
693  */
694  bool isFull() const;
695 
696  /*
697  These functions are declared private and not implemented to
698  prevent their use.
699  */
701 
702  ConcurrentQueue& operator=(ConcurrentQueue<T, EnqPolicy> const&) = delete;
703  };
704 
705  //------------------------------------------------------------------
706  // Implementation follows
707  //------------------------------------------------------------------
708 
709  template <class T, class EnqPolicy>
711  (
712  SizeType maxSize,
713  detail::MemoryType maxMemory
714  ) :
715  protectElements_()
716  , readyTime_(std::chrono::steady_clock::now())
717  , readerReady_(false)
718  , elements_()
719  , capacity_(maxSize)
720  , size_(0)
721  , memory_(maxMemory)
722  , used_(0)
723  , elementsDropped_(0) {}
724 
725  template <class T, class EnqPolicy>
727  {
728  LockType lock(protectElements_);
729  elements_.clear();
730  size_ = 0;
731  used_ = 0;
732  elementsDropped_ = 0;
733  }
734 
735 
736  // enqueue methods - 3 - enqNowait, enqWait, enqTimedWait
737 
738  template <class T, class EnqPolicy>
739  typename EnqPolicy::ReturnType ConcurrentQueue<T, EnqPolicy>::enqNowait(T const& item)
740  {
741  TLOG(12) << "enqNowait enter size=" << size_ << " capacity=" << capacity_ << " used=" << used_ << " memory=" << memory_ << TLOG_ENDL;
742  LockType lock(protectElements_);
743  auto retval = EnqPolicy::doEnq(item, elements_, size_, capacity_, used_, memory_,
744  elementsDropped_, queueNotEmpty_);
745  TLOG(12) << "enqNowait returning " << retval << TLOG_ENDL;
746  return retval;
747  }
748 
749  template <class T, class EnqPolicy>
751  {
752  TLOG(13) << "enqWait enter" << TLOG_ENDL;
753  WaitLockType lock(protectElements_);
754  while (isFull()) { queueNotFull_.wait(lock); }
755  EnqPolicy::doInsert(item, elements_, size_,
756  detail::memoryUsage(item), used_, queueNotEmpty_);
757  TLOG(13) << "enqWait returning" << TLOG_ENDL;
758  }
759 
760  template <class T, class EnqPolicy>
762  {
763  TLOG(14) << "ConcurrentQueue<T,EnqPolicy>::enqTimedWait enter with waitTime=" << std::chrono::duration_cast<std::chrono::milliseconds>(waitTime).count() << " ms size=" << size_
764  << " capacity=" << capacity_ << " used=" << used_ << " memory=" << memory_ << TLOG_ENDL;
765  WaitLockType lock(protectElements_);
766  if (isFull())
767  {
768  queueNotFull_.wait_for(lock, waitTime);
769  }
770  bool retval = insertIfPossible(item);
771  TLOG(14) << "ConcurrentQueue<T,EnqPolicy>::enqTimedWait returning " << retval << TLOG_ENDL;
772  return retval;
773  }
774 
775 
776  // dequeue methods - 3 - deqNowait, deqWait, deqTimedWait
777 
778  template <class T, class EnqPolicy>
780  {
781  TLOG(15) << "ConcurrentQueue<T, EnqPolicy>::deqNowait enter" << TLOG_ENDL;
782  LockType lock(protectElements_);
783  bool retval = removeHeadIfPossible(item);
784  TLOG(15) << "ConcurrentQueue<T, EnqPolicy>::deqNowait returning " << retval << TLOG_ENDL;
785  return retval;
786  }
787 
788  template <class T, class EnqPolicy>
790  {
791  TLOG(16) << "ConcurrentQueue<T, EnqPolicy>::deqWait enter" << TLOG_ENDL;
792  WaitLockType lock(protectElements_);
793  while (size_ == 0) { queueNotEmpty_.wait(lock); }
794  removeHead(item);
795  TLOG(16) << "ConcurrentQueue<T, EnqPolicy>::deqWait returning" << TLOG_ENDL;
796  }
797 
798  template <class T, class EnqPolicy>
800  {
801  TLOG(17) << "ConcurrentQueue<T, EnqPolicy>::deqTimedWait enter with waitTime=" << std::chrono::duration_cast<std::chrono::milliseconds>(waitTime).count() << " ms size=" << size_ << TLOG_ENDL;
802  WaitLockType lock(protectElements_);
803  if (size_ == 0)
804  {
805  queueNotEmpty_.wait_for(lock, waitTime);
806  }
807  bool retval = removeHeadIfPossible(item);
808  TLOG(17) << "ConcurrentQueue<T, EnqPolicy>::deqTimedWait returning " << retval << " size=" << size_ << TLOG_ENDL;
809  return retval;
810  }
811 
812 
813  template <class T, class EnqPolicy>
814  bool
816  {
817  // No lock is necessary: the read is atomic.
818  return size_ == 0;
819  }
820 
821  template <class T, class EnqPolicy>
822  bool
824  {
825  LockType lock(protectElements_);
826  return isFull();
827  }
828 
829  template <class T, class EnqPolicy>
832  {
833  // No lock is necessary: the read is atomic.
834  return size_;
835  }
836 
837  template <class T, class EnqPolicy>
840  {
841  // No lock is necessary: the read is atomic.
842  return capacity_;
843  }
844 
845  template <class T, class EnqPolicy>
846  bool
848  {
849  LockType lock(protectElements_);
850  bool isEmpty = (size_ == 0);
851  if (isEmpty) { capacity_ = newcapacity; }
852  return isEmpty;
853  }
854 
855  template <class T, class EnqPolicy>
858  {
859  // No lock is necessary: the read is atomic.
860  return used_;
861  }
862 
863  template <class T, class EnqPolicy>
866  {
867  // No lock is necessary: the read is atomic.
868  return memory_;
869  }
870 
871  template <class T, class EnqPolicy>
872  bool
874  {
875  LockType lock(protectElements_);
876  bool isEmpty = (size_ == 0);
877  if (isEmpty) { memory_ = newmemory; }
878  return isEmpty;
879  }
880 
881  template <class T, class EnqPolicy>
884  {
885  LockType lock(protectElements_);
886  SizeType clearedEvents = size_;
887  elementsDropped_ += size_;
888  elements_.clear();
889  size_ = 0;
890  used_ = 0;
891  return clearedEvents;
892  }
893 
894  template <class T, class EnqPolicy>
895  void
897  {
898  LockType lock(protectElements_);
899  elementsDropped_ += n;
900  }
901 
902  //-----------------------------------------------------------
903  // Private member functions
904  //-----------------------------------------------------------
905 
906  template <class T, class EnqPolicy>
907  bool
909  {
910  if (isFull())
911  {
912  ++elementsDropped_;
913  return false;
914  }
915  else
916  {
917  EnqPolicy::doInsert(item, elements_, size_,
918  detail::memoryUsage(item), used_, queueNotEmpty_);
919  return true;
920  }
921  }
922 
923  template <class T, class EnqPolicy>
924  bool
925  ConcurrentQueue<T, EnqPolicy>::removeHeadIfPossible(ValueType& item)
926  {
927  if (size_ == 0) { return false; }
928  removeHead(item);
929  return true;
930  }
931 
932  template <class T, class EnqPolicy>
933  void
934  ConcurrentQueue<T, EnqPolicy>::removeHead(ValueType& item)
935  {
936  SequenceType holder;
937  // Move the item out of elements_ in a manner that will not throw.
938  holder.splice(holder.begin(), elements_, elements_.begin());
939  // Record the change in the length of elements_.
940  --size_;
941  queueNotFull_.notify_one();
942  assignItem(item, holder.front());
943  used_ -= detail::memoryUsage(item);
944  }
945 
946  template <class T, class EnqPolicy>
947  void
948  ConcurrentQueue<T, EnqPolicy>::assignItem(T& item, const T& element)
949  {
950  item = element;
951  }
952 
953  template <class T, class EnqPolicy>
954  void
955  ConcurrentQueue<T, EnqPolicy>::assignItem(std::pair<T, size_t>& item, const T& element)
956  {
957  item.first = element;
958  item.second = elementsDropped_;
959  elementsDropped_ = 0;
960  }
961 
962  template <class T, class EnqPolicy>
963  bool
964  ConcurrentQueue<T, EnqPolicy>::isFull() const
965  {
966  if (size_ >= capacity_ || used_ >= memory_) { return true; }
967  return false;
968  }
969 } // namespace daqrate
970 
971 #endif /* artdaq_core_Core_ConcurrentQueue_hh */
972 
974 
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