artdaq_core  v3_06_01
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 - note: no #define TRACE_NAME in .hh files
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 {
48 namespace detail {
58 typedef std::chrono::duration<double> seconds;
59 
60 typedef size_t MemoryType;
61 
67 template<typename T>
69 {
70  typedef char TrueType;
71 
72  struct FalseType
73  {
74  TrueType _[2];
75  };
76 
77  template<MemoryType (T::*)() const>
78  struct TestConst;
79 
80  template<typename C>
81  static TrueType test(TestConst<&C::memoryUsed>*)
82  {
83  return 0;
84  }
85 
86  template<typename C>
87  static FalseType test(...)
88  {
89  return {};
90  }
91 
92 public:
98  static const bool value = (sizeof(test<T>(nullptr)) == sizeof(TrueType)); // NOLINT(cert-err58-cpp)
99 };
100 
107 template<typename T>
109 memoryUsage(const std::pair<T, size_t>& t)
110 {
111  MemoryType usage(0UL);
112  try
113  {
114  usage = t.first.memoryUsed();
115  }
116  catch (...)
117  {}
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  {}
138  return usage;
139 }
140 
147 template<typename T>
148 typename std::enable_if<!hasMemoryUsed<T>::value, MemoryType>::type
149 memoryUsage(const T& t)
150 {
151  return sizeof(t);
152 }
153 } // end namespace detail
154 
159 template<class T>
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  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  };
182 
192  static void doInsert(
193  T const& item,
194  SequenceType& elements,
195  SizeType& size,
196  detail::MemoryType const& itemSize,
197  detail::MemoryType& used,
198  std::condition_variable& nonempty)
199  {
200  elements.push_back(item);
201  ++size;
202  used += itemSize;
203  nonempty.notify_one();
204  }
205 
220  T const& item,
221  SequenceType& elements,
222  SizeType& size,
223  SizeType& capacity,
224  detail::MemoryType& used,
225  detail::MemoryType& memory,
226  size_t& elementsDropped,
227  std::condition_variable& nonempty)
228  {
229  detail::MemoryType itemSize = detail::memoryUsage(item);
230  if (size >= capacity || used + itemSize > memory)
231  {
232  ++elementsDropped;
233  throw QueueIsFull();
234  }
235  else
236  {
237  doInsert(item, elements, size, itemSize, used, nonempty);
238  }
239  return true;
240  }
241 };
242 
247 template<class T>
249 {
250  typedef std::pair<T, size_t> ValueType;
251  typedef std::list<T> SequenceType;
252  typedef typename SequenceType::size_type SizeType;
254 
264  static void doInsert(
265  T const& item,
266  SequenceType& elements,
267  SizeType& size,
268  detail::MemoryType const& itemSize,
269  detail::MemoryType& used,
270  std::condition_variable& nonempty)
271  {
272  elements.push_back(item);
273  ++size;
274  used += itemSize;
275  nonempty.notify_one();
276  }
277 
291  T const& item,
292  SequenceType& elements,
293  SizeType& size,
294  SizeType& capacity,
295  detail::MemoryType& used,
296  detail::MemoryType& memory,
297  size_t& elementsDropped,
298  std::condition_variable& nonempty)
299  {
300  SizeType elementsRemoved(0);
301  detail::MemoryType itemSize = detail::memoryUsage(item);
302  while ((size == capacity || used + itemSize > memory) && !elements.empty())
303  {
304  SequenceType holder;
305  // Move the item out of elements in a manner that will not throw.
306  holder.splice(holder.begin(), elements, elements.begin());
307  // Record the change in the length of elements.
308  --size;
309  used -= detail::memoryUsage(holder.front());
310  ++elementsRemoved;
311  }
312  if (size < capacity && used + itemSize <= memory)
313  // we succeeded to make enough room for the new element
314  {
315  doInsert(item, elements, size, itemSize, used, nonempty);
316  }
317  else
318  {
319  // we cannot add the new element
320  ++elementsRemoved;
321  }
322  elementsDropped += elementsRemoved;
323  return elementsRemoved;
324  }
325 };
326 
331 template<class T>
333 {
334  typedef std::pair<T, size_t> ValueType;
335  typedef std::list<T> SequenceType;
336  typedef typename SequenceType::size_type SizeType;
338 
348  static void doInsert(
349  T const& item,
350  SequenceType& elements,
351  SizeType& size,
352  detail::MemoryType const& itemSize,
353  detail::MemoryType& used,
354  std::condition_variable& nonempty)
355  {
356  elements.push_back(item);
357  ++size;
358  used += itemSize;
359  nonempty.notify_one();
360  }
361 
375  T const& item,
376  SequenceType& elements,
377  SizeType& size,
378  SizeType& capacity,
379  detail::MemoryType& used,
380  detail::MemoryType& memory,
381  size_t& elementsDropped,
382  std::condition_variable& nonempty)
383  {
384  detail::MemoryType itemSize = detail::memoryUsage(item);
385  if (size < capacity && used + itemSize <= memory)
386  {
387  doInsert(item, elements, size, itemSize, used, nonempty);
388  return 0;
389  }
390  ++elementsDropped;
391  return 1;
392  }
393 };
394 
398 template<class T, class EnqPolicy = FailIfFull<T>>
400 {
401 public:
402  typedef typename EnqPolicy::ValueType ValueType;
403  typedef typename EnqPolicy::SequenceType SequenceType;
404  typedef typename SequenceType::size_type SizeType;
405 
412  explicit ConcurrentQueue(
413  SizeType maxSize = std::numeric_limits<SizeType>::max(),
414  detail::MemoryType maxMemory = std::numeric_limits<detail::MemoryType>::max());
415 
423 
440  typename EnqPolicy::ReturnType enqNowait(T const& item);
441 
450  void enqWait(T const& item);
451 
464  bool enqTimedWait(T const& item, detail::seconds const& wait);
465 
478  bool deqNowait(ValueType& item);
479 
490  void deqWait(ValueType& item);
491 
505  bool deqTimedWait(ValueType& item, detail::seconds const& wait);
506 
511  bool empty() const;
512 
517  bool full() const;
518 
524  SizeType size() const;
525 
531  SizeType capacity() const;
532 
541 
546  detail::MemoryType used() const;
547 
553  detail::MemoryType memory() const;
554 
563  bool setMemory(detail::MemoryType maxMemory);
564 
570  SizeType clear();
571 
576  void addExternallyDroppedEvents(SizeType dropped);
577 
582  bool queueReaderIsReady() const { return readerReady_; }
583 
591  void setReaderIsReady(bool rdy = true)
592  {
593  readyTime_ = std::chrono::steady_clock::now();
594  readerReady_ = rdy;
595  }
596 
601  std::chrono::steady_clock::time_point getReadyTime() const { return readyTime_; }
602 
603 private:
604  typedef std::lock_guard<std::mutex> LockType;
605  typedef std::unique_lock<std::mutex> WaitLockType;
606 
607  mutable std::mutex protectElements_;
608  mutable std::condition_variable queueNotEmpty_;
609  mutable std::condition_variable queueNotFull_;
610 
611  std::chrono::steady_clock::time_point readyTime_;
612  bool readerReady_{false};
613  SequenceType elements_;
614  SizeType capacity_;
615  SizeType size_;
616  /*
617  N.B.: we rely on SizeType *not* being some synthesized large
618  type, so that reading the value is an atomic action, as is
619  incrementing or decrementing the value. We do *not* assume that
620  there is any atomic getAndIncrement or getAndDecrement
621  operation.
622  */
623  detail::MemoryType memory_;
624  detail::MemoryType used_{0};
625  size_t elementsDropped_{0};
626 
627  /*
628  These private member functions assume that whatever locks
629  necessary for safe operation have already been obtained.
630  */
631 
632  /*
633  Insert the given item into the list, if it is not already full,
634  and increment size. Return true if the item is inserted, and
635  false if not.
636  */
637  bool insertIfPossible(T const& item);
638 
652  bool removeHeadIfPossible(ValueType& item);
653 
665  void removeHead(ValueType& item);
666 
667  void assignItem(T& item, const T& element);
668 
669  void assignItem(std::pair<T, size_t>& item, const T& element);
670 
671  /*
672  Return false if the queue can accept new entries.
673  */
674  bool isFull() const;
675 
676  /*
677  These functions are declared private and not implemented to
678  prevent their use.
679  */
680  ConcurrentQueue(ConcurrentQueue<T, EnqPolicy> const&) = delete;
681 
682  ConcurrentQueue& operator=(ConcurrentQueue<T, EnqPolicy> const&) = delete;
683  ConcurrentQueue(ConcurrentQueue<T, EnqPolicy>&&) = default;
684  ConcurrentQueue& operator=(ConcurrentQueue<T, EnqPolicy>&&) = default;
685 };
686 
687 //------------------------------------------------------------------
688 // Implementation follows
689 //------------------------------------------------------------------
690 
691 template<class T, class EnqPolicy>
693  SizeType maxSize,
694  detail::MemoryType maxMemory)
695  : protectElements_()
696  , readyTime_(std::chrono::steady_clock::now())
697  , elements_()
698  , capacity_(maxSize)
699  , size_(0)
700  , memory_(maxMemory)
701 {}
702 
703 template<class T, class EnqPolicy>
705 {
706  LockType lock(protectElements_);
707  elements_.clear();
708  size_ = 0;
709  used_ = 0;
710  elementsDropped_ = 0;
711 }
712 
713 // enqueue methods - 3 - enqNowait, enqWait, enqTimedWait
714 
715 template<class T, class EnqPolicy>
716 typename EnqPolicy::ReturnType ConcurrentQueue<T, EnqPolicy>::enqNowait(T const& item)
717 {
718  TLOG(12, "ConcurrentQueue") << "enqNowait enter size=" << size_ << " capacity=" << capacity_ << " used=" << used_ << " memory=" << memory_;
719  LockType lock(protectElements_);
720  auto retval = EnqPolicy::doEnq(item, elements_, size_, capacity_, used_, memory_,
721  elementsDropped_, queueNotEmpty_);
722  TLOG(12, "ConcurrentQueue") << "enqNowait returning " << retval;
723  return retval;
724 }
725 
726 template<class T, class EnqPolicy>
728 {
729  TLOG(13, "ConcurrentQueue") << "enqWait enter";
730  WaitLockType lock(protectElements_);
731  while (isFull()) { queueNotFull_.wait(lock); }
732  EnqPolicy::doInsert(item, elements_, size_,
733  detail::memoryUsage(item), used_, queueNotEmpty_);
734  TLOG(13, "ConcurrentQueue") << "enqWait returning";
735 }
736 
737 template<class T, class EnqPolicy>
739 {
740  TLOG(14, "ConcurrentQueue") << "ConcurrentQueue<T,EnqPolicy>::enqTimedWait enter with waitTime=" << std::chrono::duration_cast<std::chrono::milliseconds>(waitTime).count() << " ms size=" << size_
741  << " capacity=" << capacity_ << " used=" << used_ << " memory=" << memory_;
742  WaitLockType lock(protectElements_);
743  if (isFull())
744  {
745  queueNotFull_.wait_for(lock, waitTime);
746  }
747  bool retval = insertIfPossible(item);
748  TLOG(14, "ConcurrentQueue") << "ConcurrentQueue<T,EnqPolicy>::enqTimedWait returning " << retval;
749  return retval;
750 }
751 
752 // dequeue methods - 3 - deqNowait, deqWait, deqTimedWait
753 
754 template<class T, class EnqPolicy>
756 {
757  TLOG(15, "ConcurrentQueue") << "ConcurrentQueue<T, EnqPolicy>::deqNowait enter";
758  LockType lock(protectElements_);
759  bool retval = removeHeadIfPossible(item);
760  TLOG(15, "ConcurrentQueue") << "ConcurrentQueue<T, EnqPolicy>::deqNowait returning " << retval;
761  return retval;
762 }
763 
764 template<class T, class EnqPolicy>
766 {
767  TLOG(16, "ConcurrentQueue") << "ConcurrentQueue<T, EnqPolicy>::deqWait enter";
768  WaitLockType lock(protectElements_);
769  while (size_ == 0) { queueNotEmpty_.wait(lock); }
770  removeHead(item);
771  TLOG(16, "ConcurrentQueue") << "ConcurrentQueue<T, EnqPolicy>::deqWait returning";
772 }
773 
774 template<class T, class EnqPolicy>
776 {
777  TLOG(17, "ConcurrentQueue") << "ConcurrentQueue<T, EnqPolicy>::deqTimedWait enter with waitTime=" << std::chrono::duration_cast<std::chrono::milliseconds>(waitTime).count() << " ms size=" << size_;
778  WaitLockType lock(protectElements_);
779  if (size_ == 0)
780  {
781  queueNotEmpty_.wait_for(lock, waitTime);
782  }
783  bool retval = removeHeadIfPossible(item);
784  TLOG(17, "ConcurrentQueue") << "ConcurrentQueue<T, EnqPolicy>::deqTimedWait returning " << retval << " size=" << size_;
785  return retval;
786 }
787 
788 template<class T, class EnqPolicy>
790 {
791  // No lock is necessary: the read is atomic.
792  return size_ == 0;
793 }
794 
795 template<class T, class EnqPolicy>
797 {
798  LockType lock(protectElements_);
799  return isFull();
800 }
801 
802 template<class T, class EnqPolicy>
805 {
806  // No lock is necessary: the read is atomic.
807  return size_;
808 }
809 
810 template<class T, class EnqPolicy>
813 {
814  // No lock is necessary: the read is atomic.
815  return capacity_;
816 }
817 
818 template<class T, class EnqPolicy>
820 {
821  LockType lock(protectElements_);
822  bool isEmpty = (size_ == 0);
823  if (isEmpty) { capacity_ = newcapacity; }
824  return isEmpty;
825 }
826 
827 template<class T, class EnqPolicy>
830 {
831  // No lock is necessary: the read is atomic.
832  return used_;
833 }
834 
835 template<class T, class EnqPolicy>
838 {
839  // No lock is necessary: the read is atomic.
840  return memory_;
841 }
842 
843 template<class T, class EnqPolicy>
845 {
846  LockType lock(protectElements_);
847  bool isEmpty = (size_ == 0);
848  if (isEmpty) { memory_ = maxMemory; }
849  return isEmpty;
850 }
851 
852 template<class T, class EnqPolicy>
855 {
856  LockType lock(protectElements_);
857  SizeType clearedEvents = size_;
858  elementsDropped_ += size_;
859  elements_.clear();
860  size_ = 0;
861  used_ = 0;
862  return clearedEvents;
863 }
864 
865 template<class T, class EnqPolicy>
867 {
868  LockType lock(protectElements_);
869  elementsDropped_ += dropped;
870 }
871 
872 //-----------------------------------------------------------
873 // Private member functions
874 //-----------------------------------------------------------
875 
876 template<class T, class EnqPolicy>
878 {
879  if (isFull())
880  {
881  ++elementsDropped_;
882  return false;
883  }
884  else
885  {
886  EnqPolicy::doInsert(item, elements_, size_,
887  detail::memoryUsage(item), used_, queueNotEmpty_);
888  return true;
889  }
890 }
891 
892 template<class T, class EnqPolicy>
893 bool ConcurrentQueue<T, EnqPolicy>::removeHeadIfPossible(ValueType& item)
894 {
895  if (size_ == 0) { return false; }
896  removeHead(item);
897  return true;
898 }
899 
900 template<class T, class EnqPolicy>
901 void ConcurrentQueue<T, EnqPolicy>::removeHead(ValueType& item)
902 {
903  SequenceType holder;
904  // Move the item out of elements_ in a manner that will not throw.
905  holder.splice(holder.begin(), elements_, elements_.begin());
906  // Record the change in the length of elements_.
907  --size_;
908  queueNotFull_.notify_one();
909  assignItem(item, holder.front());
910  used_ -= detail::memoryUsage(item);
911 }
912 
913 template<class T, class EnqPolicy>
914 void ConcurrentQueue<T, EnqPolicy>::assignItem(T& item, const T& element)
915 {
916  item = element;
917 }
918 
919 template<class T, class EnqPolicy>
920 void ConcurrentQueue<T, EnqPolicy>::assignItem(std::pair<T, size_t>& item, const T& element)
921 {
922  item.first = element;
923  item.second = elementsDropped_;
924  elementsDropped_ = 0;
925 }
926 
927 template<class T, class EnqPolicy>
928 bool ConcurrentQueue<T, EnqPolicy>::isFull() const
929 {
930  if (size_ >= capacity_ || used_ >= memory_) { return true; }
931  return false;
932 }
933 } // namespace artdaq
934 
935 #endif /* artdaq_core_Core_ConcurrentQueue_hh */
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.
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