ChimeraTK-cppext 01.07.01
Loading...
Searching...
No Matches
future_queue.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3#pragma once
4
5#include "semaphore.hpp"
6
7#include <atomic>
8#include <cassert>
9#include <future> // just for std::launch
10#include <vector>
11
12namespace cppext {
13
14 /*********************************************************************************************************************/
15
18 class MOVE_DATA {};
19
22 class SWAP_DATA {};
23
24 namespace detail {
29 } // namespace detail
30
31 /*********************************************************************************************************************/
32
33 namespace detail {
34 struct shared_state_base;
35
36 template<typename T>
37 struct shared_state;
38
39 template<typename T, typename FEATURES, typename TOUT, typename CALLABLE>
40 struct continuation_process_async;
41
50
53
56
59
61 template<typename T>
62 void make_new(size_t length);
63
66 const shared_state_base* operator->() const;
67
69 template<typename T>
71
73 operator bool() const;
74
76 bool operator==(const shared_state_ptr& other) const;
77
79 shared_state_base* get() const;
80
83
84 private:
86 void free();
87
90 };
91
92 template<typename T>
95 p.make_new<T>(length);
96 return p;
97 }
98
99 } // namespace detail
100
101 template<typename T, typename FEATURES>
102 class future_queue;
103
104 /*********************************************************************************************************************/
105
109 public:
112 size_t write_available() const;
113
118 size_t read_available() const;
119
123 bool push_exception(std::exception_ptr exception);
124
127 bool push_overwrite_exception(std::exception_ptr exception);
128
133 bool empty();
134
138 void wait();
139
141 size_t size() const;
142
145 bool operator==(const future_queue_base& other) const;
146 bool operator!=(const future_queue_base& other) const;
147
148 protected:
150
152
155 bool obtain_write_slot(size_t& index);
156
159
163
171
178
181
185
186 template<typename T, typename FEATURES>
187 friend class ::cppext::future_queue;
188
189 template<typename ITERATOR_TYPE>
191
192 template<typename ITERATOR_TYPE>
194
197
198 template<typename T, typename FEATURES, typename TOUT, typename CALLABLE>
200 };
201
202 /*********************************************************************************************************************/
203
228 template<typename T, typename FEATURES = MOVE_DATA>
230 public:
236
240
243 future_queue(const future_queue& other) = default;
244
248
251 template<typename U = T,
252 typename std::enable_if<std::is_same<T, U>::value && !std::is_same<U, void>::value, int>::type = 0>
253 bool push(U&& t);
254 template<typename U = T,
255 typename std::enable_if<!std::is_same<U, void>::value && std::is_copy_constructible<T>::value, int>::type = 0>
256 bool push(const U& t);
257
259 bool push(void);
260
270 template<typename U = T,
271 typename std::enable_if<std::is_same<T, U>::value && !std::is_same<U, void>::value, int>::type = 0>
273
274 template<typename U = T,
275 typename std::enable_if<!std::is_same<U, void>::value && std::is_copy_constructible<T>::value, int>::type = 0>
276 bool push_overwrite(const U& t);
277
280
283 template<typename U = T,
284 typename std::enable_if<std::is_same<T, U>::value && !std::is_same<U, void>::value, int>::type = 0>
285 bool pop(U& t);
286
287 bool pop();
288
291 template<typename U = T,
292 typename std::enable_if<std::is_same<T, U>::value && !std::is_same<U, void>::value, int>::type = 0>
293 void pop_wait(U& t);
294
295 void pop_wait();
296
303 template<typename U = T,
304 typename std::enable_if<std::is_same<T, U>::value && !std::is_same<U, void>::value, int>::type = 0>
306
307 template<typename U = T,
308 typename std::enable_if<std::is_same<T, U>::value && std::is_same<U, void>::value, int>::type = 0>
309 void front() const;
310
329 template<typename T2, typename FEATURES2 = MOVE_DATA, typename CALLABLE>
330 future_queue<T2, FEATURES2> then(CALLABLE callable, std::launch policy = std::launch::async);
331
332 typedef T value_type;
333 };
334
335 /*********************************************************************************************************************/
336
337 namespace detail {
338
347
360
364
367 inline void free();
368
370 std::atomic<size_t> reference_count{0};
371
373 std::atomic<size_t> when_any_index;
374
376 size_t nBuffers;
377
380 std::vector<semaphore> semaphores;
381
384 std::vector<std::exception_ptr> exceptions;
385
388 std::atomic<size_t> writeIndex;
389
393 std::atomic<size_t> readIndexMax;
394
396 std::atomic<size_t> readIndex;
397
401
405
409
414
417
420
424
428
432
433 std::atomic<when_any_notification_info> when_any_notification{when_any_notification_info()};
434 };
435
439 template<typename T>
442
444 std::vector<T> buffers;
445 };
446
448 template<>
452
454 // Reduce reference count but atomically keep the old reference counter. Note
455 // that the std::memory_order_relaxed refers to the access to the pointer not
456 // to the reference counter.
457 size_t oldCount = this->reference_count--;
458
459 // Determine whether we need to destroy the shared state depending on possible
460 // internal references.
461 bool executeDelete = false;
462
463 // Standard case: no continuation. If the last user is just destroying its
464 // reference we delete the shared state.
465 if(oldCount == 1 && !this->is_continuation_async && !this->is_continuation_deferred &&
467 executeDelete = true;
468 }
469 // Deferred continuations (incl. when_all) have two internal use counts due to
470 // the two std::functions, so we need to remove those functions first.
471 else if(oldCount == 3 && (this->is_continuation_deferred || this->is_continuation_when_all)) {
474 executeDelete = true;
475 }
476 // Async continuations have one internal use count inside their thread, so we
477 // need to terminate the thread first.
478 else if(oldCount == 2 && this->is_continuation_async) {
479 if(this->continuation_process_async.joinable()) {
480 // Signal termination to internal thread and wait until thread has been
481 // terminated
482 while(this->continuation_process_async_terminated == false) {
483 // Push a detail::TerminateInternalThread exception into the queue which
484 // the internal thread is potentially waiting on.
485 try {
487 }
488 catch(...) {
489 // Special case: the origin queue is a continuation itself (deferred
490 // or when_all) - we need to push the exception to the origin of the
491 // origin to actually reach the internal thread, since
492 // deferred/when_all continuations do not really use their own queue
493 if(this->continuation_origin.d->is_continuation_deferred ||
494 this->continuation_origin.d->is_continuation_when_all) {
495 this->continuation_origin.d->continuation_origin.push_exception(std::current_exception());
496 }
497 // Standard case: just push the exception to the origin queue of the
498 // continuation
499 else {
500 this->continuation_origin.push_exception(std::current_exception());
501 }
502 } // end catch
503 } // end while
504
505 this->continuation_process_async.join();
506 }
507 executeDelete = true;
508 }
509
510 if(executeDelete) {
511 // Now that all potential internal references have been cleared the
512 // reference count must be 0
514
515 // the when_any_notification notifyerQueue may have it's reference count
516 // manually incremented by setNotificationQueue, and must be freed if it exists.
517 if(when_any_notification.load().notifyerQueue) {
518 when_any_notification.load().notifyerQueue->free();
519 }
520 delete this;
521 } // end if executeDelete
522
523 } // end shared_state_base::free()
524
525 } // namespace detail
526
527 /*********************************************************************************************************************/
528 /*********************************************************************************************************************/
530 /*********************************************************************************************************************/
531 /*********************************************************************************************************************/
532
561 template<typename ITERATOR_TYPE>
563 // Add lengthes of all queues - this will be the length of the notification
564 // queue
565 size_t summedLength = 0;
566 for(ITERATOR_TYPE it = begin; it != end; ++it) summedLength += it->size();
567
568 // Create a notification queue, so we can hand it on to the queues
569 future_queue<size_t> notifyerQueue(summedLength);
570
571 // Distribute the pointer to the notification queue to all participating
572 // queues
573 size_t index = 0;
574 for(ITERATOR_TYPE it = begin; it != end; ++it) {
575 size_t nPreviousValues = it->setNotificationQueue(notifyerQueue, index);
576 for(size_t i = 0; i < nPreviousValues; ++i) notifyerQueue.push(index);
577 ++index;
578 }
579
580 return notifyerQueue;
581 }
582
583 /*********************************************************************************************************************/
584
589 template<typename ITERATOR_TYPE>
591 // Create a notification queue in a shared pointer, so we can hand it on to
592 // the queues
593 future_queue<void> notifyerQueue(1);
594
595 // copy the list of participating queues
596 std::vector<future_queue_base> participants;
597 for(auto it = begin; it != end; ++it) participants.push_back(*it);
598
599 // obtain notification queue for any update to any queue
600 auto anyNotify = when_any(begin, end);
601
602 // define function to be executed (inside the notifyer queue) on non-blocking
603 // functions like pop() or empty()
604 notifyerQueue.d->continuation_process_deferred = std::function<void(void)>([notifyerQueue, participants]() mutable {
605 bool empty = false;
606 for(auto& q : participants) {
607 if(q.empty()) {
608 empty = true;
609 break;
610 }
611 }
612 if(!empty) notifyerQueue.push();
613 });
614
615 // define function to be executed (inside the notifyer queue) on blocking
616 // functions like pop_wait() or wait()
617 notifyerQueue.d->continuation_process_deferred_wait =
618 std::function<void(void)>([notifyerQueue, participants, anyNotify]() mutable {
619 while(true) {
620 anyNotify.pop_wait();
621 bool empty = false;
622 for(auto& q : participants) {
623 if(q.empty()) {
624 empty = true;
625 break;
626 }
627 }
628 if(!empty) break;
629 }
630 notifyerQueue.push();
631 });
632
633 // set flag marking the notifyerQueue a when_all continuation and save the
634 // notification queue of the when_any as the origin.
635 notifyerQueue.d->is_continuation_when_all = true;
636 notifyerQueue.d->continuation_origin = anyNotify;
637
638 return notifyerQueue;
639 }
640
641 /*********************************************************************************************************************/
642
643 namespace detail {
644
649 template<typename T>
650 void data_assign(T& a, T&& b, MOVE_DATA) {
651 // in order not to depend on the move assignment operator, which might not
652 // always be available, we perform an in-place destruction followed by an
653 // in-place move construction.
654 a.~T();
655 new(&a) T(std::move(b));
656 }
657
658 template<typename T>
659 void data_assign(T& a, T&& b, SWAP_DATA) {
660 std::swap(a, b);
661 }
662
663 } // namespace detail
664
665 /*********************************************************************************************************************/
666 /*********************************************************************************************************************/
668 /*********************************************************************************************************************/
669 /*********************************************************************************************************************/
670
671 namespace detail {
672
674
676 // Copy the pointer and increase the reference count
677 set(other.get());
678 if(get() != nullptr) get()->reference_count++;
679 }
680
682 // Free previous target, copy the new pointer and increase its reference count
683 free();
684 set(other.get());
685 if(get() != nullptr) get()->reference_count++;
686 return *this;
687 }
688
690 free();
691 }
692
694 return ptr;
695 }
696
698 ptr = ptr_;
699 }
700
701 inline void shared_state_ptr::free() {
702 // Don't do anything if called on a nullptr (i.e. default constructed or
703 // already destroyed)
704 if(get() == nullptr) {
705 return;
706 }
707
708 get()->free();
709 set(nullptr);
710 }
711
712 template<typename T>
714 free();
715 ptr = new shared_state<T>(length);
716 get()->reference_count = 1;
717 }
718
720 assert(get() != nullptr);
721 return get();
722 }
723
725 assert(get() != nullptr);
726 return get();
727 }
728
729 template<typename T>
731 assert(get() != nullptr);
732 return static_cast<shared_state<T>*>(get());
733 }
734
735 inline shared_state_ptr::operator bool() const {
736 return get() != nullptr;
737 }
738
740 return get() == other.get();
741 }
742
743 } // namespace detail
744
745 /*********************************************************************************************************************/
746 /*********************************************************************************************************************/
748 /*********************************************************************************************************************/
749 /*********************************************************************************************************************/
750
752 // Obtain indices in this particular order to ensure consistency. Result might
753 // be too small (but not too big) if writing happens concurrently.
754 size_t l_writeIndex = d->writeIndex;
755 size_t l_readIndex = d->readIndex;
756 if(l_writeIndex - l_readIndex < d->nBuffers - 1) {
757 return d->nBuffers - (l_writeIndex - l_readIndex) - 1;
758 }
759 else {
760 return 0;
761 }
762 }
763
764 inline size_t future_queue_base::read_available() const {
765 // Single consumer, so atomicity doesn't matter
766 return d->readIndexMax - d->readIndex;
767 }
768
770 // if there is no notification queue, atomically increment counter while making sure now notification queue is
771 // placed concurrently
773 do {
774 info = d->when_any_notification.load(std::memory_order_acquire);
775 if(info.notifyerQueue) break;
776 info_n = info;
777 ++info_n.notifyerQueue_previousData;
778 } while(!d->when_any_notification.compare_exchange_weak(info, info_n));
779 return info.notifyerQueue;
780 }
781
783 // if there is a notification queue, push to it
786 n.d.set(notification_queue);
787 bool nret = n.push(d->when_any_index);
788 n.d.set(nullptr); // prevent reference count from being decremented
789 (void)nret;
790 // This assert doesn't really hold. It might spuriously fail during destruction of certain combinations of
791 // continuations and when_any/when_all.
792 // assert(nret == true);
793 }
794 }
795
798 do {
799 info = d->when_any_notification.load(std::memory_order_acquire);
800 if(info.notifyerQueue) break; // no need to deal with this counter if notification queue present
801 info_n = info;
802 assert(info_n.notifyerQueue_previousData > 0);
803 --info_n.notifyerQueue_previousData;
804 } while(!d->when_any_notification.compare_exchange_weak(info, info_n));
805 }
806
807 inline bool future_queue_base::push_exception(std::exception_ptr exception) {
808 // obtain index to write to
809 size_t myIndex;
810 if(!obtain_write_slot(myIndex)) return false;
811
812 // assign the payload data (data buffer is ignored if exception is set)
813 d->exceptions[myIndex % d->nBuffers] = exception;
814
815 // obtain notification queue or increment previous data counter (for when_any)
817
818 // signal receiving end
819 assert(!d->semaphores[myIndex % d->nBuffers].is_ready());
820 d->semaphores[myIndex % d->nBuffers].unlock();
822
823 // deal with when_any notifications
825 return true;
826 }
827
828 inline bool future_queue_base::push_overwrite_exception(std::exception_ptr exception) {
829 assert(d->nBuffers - 1 > 1);
830 bool ret = true;
831
832 // obtain index to write to, if necessary remove old data first
833 size_t myIndex;
835 if(d->semaphores[(myIndex - 1) % d->nBuffers].is_ready_and_reset()) {
836 size_t expectedIndex = myIndex;
837 bool success = d->writeIndex.compare_exchange_strong(expectedIndex, myIndex - 1);
838 if(!success) {
839 // in case of a concurrent push_overwrite(), our data effectively just got overwritten by the other thread
840 // even before writing it...
841 d->semaphores[(myIndex - 1) % d->nBuffers].unlock();
842 return false;
843 }
844 ret = false;
845 }
846 else {
847 return false;
848 }
849 if(!obtain_write_slot(myIndex)) return false;
850 }
851
852 // assign the payload data (data buffer is ignored if exception is set)
853 d->exceptions[myIndex % d->nBuffers] = exception;
854
855 // obtain notification queue or increment previous data counter (for when_any) (unless data was overwritten)
857 if(ret) {
859 }
860 else {
861 notification_queue = nullptr;
862 }
863
864 // obtain notification queue or increment previous data counter (for when_any)
865 assert(!d->semaphores[myIndex % d->nBuffers].is_ready());
866 d->semaphores[myIndex % d->nBuffers].unlock();
868
869 // deal with when_any notifications (unless data was overwritten)
870 if(ret) {
872 }
873 return ret;
874 }
875
877 if(d->hasFrontOwnership) return false;
878 if(d->is_continuation_deferred || d->is_continuation_when_all) d->continuation_process_deferred();
879 if(d->semaphores[d->readIndex % d->nBuffers].is_ready_and_reset()) {
880 d->hasFrontOwnership = true;
881 return false;
882 }
883 return true;
884 }
885
887 if(d->hasFrontOwnership) return;
888 if(d->is_continuation_deferred || d->is_continuation_when_all) d->continuation_process_deferred_wait();
889 d->semaphores[d->readIndex % d->nBuffers].wait_and_reset();
890 d->hasFrontOwnership = true;
891 }
892
893 inline size_t future_queue_base::size() const {
894 if(!d->is_continuation_deferred) {
895 return d->nBuffers - 1;
896 }
897 else {
898 return d->continuation_origin.size();
899 }
900 }
901
903 return d == other.d;
904 }
905
907 return !(d == other.d);
908 }
909
911
913
915 index = d->writeIndex;
916 while(true) {
917 if(index >= d->readIndex + d->nBuffers - 1) return false; // queue is full
918 bool success = d->writeIndex.compare_exchange_weak(index, index + 1);
919 if(success) break;
920 }
921 return true;
922 }
923
925 size_t l_readIndex = d->readIndex;
926 size_t l_writeIndex = d->writeIndex;
927 size_t l_readIndexMax = d->readIndexMax;
928 if(l_writeIndex >= l_readIndex + d->nBuffers) l_writeIndex = l_readIndex + d->nBuffers - 1;
930 do {
931 for(size_t index = l_readIndexMax; index <= l_writeIndex - 1; ++index) {
932 if(!d->semaphores[index % d->nBuffers].is_ready()) break;
934 }
935 d->readIndexMax.compare_exchange_weak(l_readIndexMax, newReadIndexMax);
936 } while(d->readIndexMax < newReadIndexMax);
937 }
938
941 if(!d->is_continuation_deferred) {
942 d->when_any_index = indexToSend;
943
944 // create new info struct with notification queue
946 info.notifyerQueue = notificationQueue.d.get();
947 info.notifyerQueue_previousData = 0;
948
949 // atomically exchange info struct while making sure it has not been altered at the target in the mean time
951 do {
952 info_o = d->when_any_notification;
953 } while(!d->when_any_notification.compare_exchange_weak(info_o, info));
954
955 // artificially increment the reference count of the notification queue, since we have to store a plain pointer
956 // in the info struct rather than a shared_state_ptr (which is not trivially copyable).
957 info.notifyerQueue->reference_count++;
958
959 return info_o.notifyerQueue_previousData;
960 }
961 else {
962 return d->continuation_origin.setNotificationQueue(notificationQueue, indexToSend);
963 }
964 }
965
966 /*********************************************************************************************************************/
967 /*********************************************************************************************************************/
969 /*********************************************************************************************************************/
970 /*********************************************************************************************************************/
971
972 template<typename T, typename FEATURES>
973 future_queue<T, FEATURES>::future_queue(size_t length) : future_queue_base(detail::make_shared_state<T>(length)) {}
974
975 template<typename T, typename FEATURES>
977
978 /*********************************************************************************************************************/
982 template<typename T, typename FEATURES>
983 template<typename U, typename std::enable_if<std::is_same<T, U>::value && !std::is_same<U, void>::value, int>::type>
985 // obtain index to write to
986 size_t myIndex;
987 if(!obtain_write_slot(myIndex)) return false;
988
989 // assign the payload data
990 detail::data_assign(future_queue_base::d.cast<T>()->buffers[myIndex % d->nBuffers], std::move(t), FEATURES());
991 d->exceptions[myIndex % d->nBuffers] = nullptr;
992
993 // obtain notification queue or increment previous data counter (for when_any)
994 auto notification_queue = get_notification_queue();
995
996 // signal receiving end
997 assert(!d->semaphores[myIndex % d->nBuffers].is_ready());
998 d->semaphores[myIndex % d->nBuffers].unlock();
999 update_read_index_max(); // basically only for read_available()
1000
1001 // deal with when_any notifications
1002 send_notification(notification_queue);
1003 return true;
1004 }
1005
1007 template<typename T, typename FEATURES>
1008 template<typename U,
1009 typename std::enable_if<!std::is_same<U, void>::value && std::is_copy_constructible<T>::value, int>::type>
1011 // Create copy and pass this copy as an Rvalue reference to the other
1012 // implementation
1013 return push(T(t));
1014 }
1015
1017 template<typename T, typename FEATURES>
1019 static_assert(
1020 std::is_same<T, void>::value, "future_queue<T,FEATURES>::push(void) may only be called for T = void.");
1021 // obtain index to write to
1022 size_t myIndex;
1023 if(!obtain_write_slot(myIndex)) return false;
1024
1025 // assign the payload data
1026 d->exceptions[myIndex % d->nBuffers] = nullptr;
1027
1028 // obtain notification queue or increment previous data counter (for when_any)
1029 auto notification_queue = get_notification_queue();
1030
1031 // signal receiving end
1032 assert(!d->semaphores[myIndex % d->nBuffers].is_ready());
1033 d->semaphores[myIndex % d->nBuffers].unlock();
1034 update_read_index_max(); // basically only for read_available()
1035
1036 // deal with when_any notifications
1037 send_notification(notification_queue);
1038 return true;
1039 }
1040
1043 template<typename T, typename FEATURES>
1044 template<typename U, typename std::enable_if<std::is_same<T, U>::value && !std::is_same<U, void>::value, int>::type>
1046 assert(d->nBuffers - 1 > 1);
1047 bool ret = true;
1048
1049 // obtain index to write to, if necessary remove old data first
1050 size_t myIndex;
1051 if(!obtain_write_slot(myIndex)) {
1052 if(d->semaphores[(myIndex - 1) % d->nBuffers].is_ready_and_reset()) {
1053 size_t expectedIndex = myIndex;
1054 bool success = d->writeIndex.compare_exchange_strong(expectedIndex, myIndex - 1);
1055 if(!success) {
1056 // in case of a concurrent push_overwrite(), our data effectively just got overwritten by the other thread
1057 // even before writing it...
1058 d->semaphores[(myIndex - 1) % d->nBuffers].unlock();
1059 return false;
1060 }
1061 ret = false;
1062 }
1063 else {
1064 return false;
1065 }
1066 if(!obtain_write_slot(myIndex)) return false;
1067 }
1068
1069 // assign the payload data
1070 detail::data_assign(future_queue_base::d.cast<T>()->buffers[myIndex % d->nBuffers], std::move(t), FEATURES());
1071 d->exceptions[myIndex % d->nBuffers] = nullptr;
1072
1073 // obtain notification queue or increment previous data counter (for when_any) (unless data was overwritten)
1075 if(ret) {
1076 notification_queue = get_notification_queue();
1077 }
1078
1079 // signal receiving end
1080 assert(!d->semaphores[myIndex % d->nBuffers].is_ready());
1081 d->semaphores[myIndex % d->nBuffers].unlock();
1082 update_read_index_max();
1083
1084 // deal with when_any notifications (unless data was overwritten)
1085 if(ret) {
1086 send_notification(notification_queue);
1087 }
1088 return ret;
1089 }
1090
1093 template<typename T, typename FEATURES>
1094 template<typename U,
1095 typename std::enable_if<!std::is_same<U, void>::value && std::is_copy_constructible<T>::value, int>::type>
1097 // Create copy and pass this copy as an Rvalue reference to the other
1098 // implementation
1099 return push_overwrite(T(t));
1100 }
1101
1102 /*********************************************************************************************************************/
1106 template<typename T, typename FEATURES>
1107 template<typename U, typename std::enable_if<std::is_same<T, U>::value && !std::is_same<U, void>::value, int>::type>
1109 if((d->is_continuation_deferred || d->is_continuation_when_all) && !d->hasFrontOwnership) {
1110 d->continuation_process_deferred();
1111 }
1112 if(d->hasFrontOwnership || d->semaphores[d->readIndex % d->nBuffers].is_ready_and_reset()) {
1113 std::exception_ptr e;
1114 if(d->exceptions[d->readIndex % d->nBuffers]) {
1115 e = d->exceptions[d->readIndex % d->nBuffers];
1116 }
1117 else {
1119 t, std::move(future_queue_base::d.cast<T>()->buffers[d->readIndex % d->nBuffers]), FEATURES());
1120 }
1121 assert(d->readIndex < d->writeIndex);
1122 d->readIndex++;
1123 d->hasFrontOwnership = false;
1124 decrement_previous_data_counter();
1125 if(e) std::rethrow_exception(e);
1126 return true;
1127 }
1128 else {
1129 return false;
1130 }
1131 }
1132
1135 template<typename T, typename FEATURES>
1137 if((d->is_continuation_deferred || d->is_continuation_when_all) && !d->hasFrontOwnership) {
1138 d->continuation_process_deferred();
1139 }
1140 if(d->hasFrontOwnership || d->semaphores[d->readIndex % d->nBuffers].is_ready_and_reset()) {
1141 std::exception_ptr e;
1142 if(d->exceptions[d->readIndex % d->nBuffers]) {
1143 e = d->exceptions[d->readIndex % d->nBuffers];
1144 }
1145 assert(d->readIndex < d->writeIndex);
1146 d->readIndex++;
1147 d->hasFrontOwnership = false;
1148 decrement_previous_data_counter();
1149 if(e) std::rethrow_exception(e);
1150 return true;
1151 }
1152 else {
1153 return false;
1154 }
1155 }
1156
1158 template<typename T, typename FEATURES>
1159 template<typename U, typename std::enable_if<std::is_same<T, U>::value && !std::is_same<U, void>::value, int>::type>
1161 if(!d->hasFrontOwnership) {
1162 if(d->is_continuation_deferred || d->is_continuation_when_all) d->continuation_process_deferred_wait();
1163 d->semaphores[d->readIndex % d->nBuffers].wait_and_reset();
1164 }
1165 else {
1166 d->hasFrontOwnership = false;
1167 }
1168 std::exception_ptr e;
1169 if(d->exceptions[d->readIndex % d->nBuffers]) {
1170 e = d->exceptions[d->readIndex % d->nBuffers];
1171 }
1172 else {
1174 t, std::move(future_queue_base::d.cast<U>()->buffers[d->readIndex % d->nBuffers]), FEATURES());
1175 }
1176 assert(d->readIndex < d->writeIndex);
1177 d->readIndex++;
1178 decrement_previous_data_counter();
1179 if(e) std::rethrow_exception(e);
1180 }
1181
1184 template<typename T, typename FEATURES>
1186 if(!d->hasFrontOwnership) {
1187 if(d->is_continuation_deferred || d->is_continuation_when_all) d->continuation_process_deferred_wait();
1188 d->semaphores[d->readIndex % d->nBuffers].wait_and_reset();
1189 }
1190 else {
1191 d->hasFrontOwnership = false;
1192 }
1193 std::exception_ptr e;
1194 if(d->exceptions[d->readIndex % d->nBuffers]) {
1195 e = d->exceptions[d->readIndex % d->nBuffers];
1196 }
1197 assert(d->readIndex < d->writeIndex);
1198 d->readIndex++;
1199 decrement_previous_data_counter();
1200 if(e) std::rethrow_exception(e);
1201 }
1202
1203 /*********************************************************************************************************************/
1207 template<typename T, typename FEATURES>
1208 template<typename U, typename std::enable_if<std::is_same<T, U>::value && !std::is_same<U, void>::value, int>::type>
1210 assert(d->hasFrontOwnership);
1211 if(d->exceptions[d->readIndex % d->nBuffers]) std::rethrow_exception(d->exceptions[d->readIndex % d->nBuffers]);
1212 return future_queue_base::d.cast<T>()->buffers[d->readIndex % d->nBuffers];
1213 }
1214
1216 template<typename T, typename FEATURES>
1217 template<typename U, typename std::enable_if<std::is_same<T, U>::value && std::is_same<U, void>::value, int>::type>
1219 assert(d->hasFrontOwnership);
1220 if(d->exceptions[d->readIndex % d->nBuffers]) std::rethrow_exception(d->exceptions[d->readIndex % d->nBuffers]);
1221 }
1222
1223 /*********************************************************************************************************************/
1227 namespace detail {
1228 // ----------------------------------------------------------------------------------------------------------------
1229 // ----------------------------------------------------------------------------------------------------------------
1230 // helper functions used inside future_queue::then()
1231
1232 // ----------------------------------------------------------------------------------------------------------------
1233 // continuation_process_deferred: function to be executed in a deferred
1234 // continuation in non-blocking functions
1235
1236 // continuation_process_deferred for non-void data types
1237 template<typename T, typename FEATURES, typename TOUT, typename CALLABLE>
1242 void operator()() {
1243 // written this way so the callable is able to swap with the internal buffer
1244 if(q_input.empty()) return;
1245 try {
1247 }
1248 catch(...) {
1249 q_output.push_exception(std::current_exception());
1250 }
1251 try {
1252 q_input.pop();
1253 }
1254 catch(...) {
1255 // exception already pushed to the output queue, so ignore here
1256 }
1257 }
1261 };
1262
1263 // continuation_process_deferred for void input and non-void output data types
1264 template<typename FEATURES, typename TOUT, typename CALLABLE>
1277
1278 // continuation_process_deferred for non-void input and void output data types
1279 template<typename T, typename FEATURES, typename CALLABLE>
1284 void operator()() {
1285 // written this way so the callable is able to swap with the internal buffer
1286 if(q_input.empty()) return;
1287 try {
1289 q_output.push();
1290 }
1291 catch(...) {
1292 q_output.push_exception(std::current_exception());
1293 }
1294 try {
1295 q_input.pop();
1296 }
1297 catch(...) {
1298 // exception already pushed to the output queue, so ignore here
1299 }
1300 }
1304 };
1305
1306 // continuation_process_deferred for void input and void output data types
1307 template<typename FEATURES, typename CALLABLE>
1328
1329 // factory for continuation_process_deferred
1330 template<typename T, typename FEATURES, typename TOUT, typename CALLABLE>
1335
1336 // ----------------------------------------------------------------------------------------------------------------
1337 // continuation_process_deferred_wait: function to be executed in a deferred
1338 // continuation in blocking functions
1339
1340 // continuation_process_deferred_wait for non-void data types
1341 template<typename T, typename FEATURES, typename TOUT, typename CALLABLE>
1346 void operator()() {
1347 // written this way so the callable is able to swap with the internal buffer
1348 try {
1349 q_input.wait();
1351 }
1352 catch(...) {
1353 q_output.push_exception(std::current_exception());
1354 }
1355 try {
1356 q_input.pop();
1357 }
1358 catch(...) {
1359 // exception already pushed to the output queue, so ignore here
1360 }
1361 }
1365 };
1366
1367 // continuation_process_deferred_wait for void input and non-void output data
1368 // types
1369 template<typename FEATURES, typename TOUT, typename CALLABLE>
1387
1388 // continuation_process_deferred_wait for non-void input and void output data
1389 // types
1390 template<typename T, typename FEATURES, typename CALLABLE>
1395 void operator()() {
1396 // written this way so the callable is able to swap with the internal buffer
1397 try {
1398 q_input.wait();
1400 q_output.push();
1401 }
1402 catch(...) {
1403 q_output.push_exception(std::current_exception());
1404 }
1405 try {
1406 q_input.pop();
1407 }
1408 catch(...) {
1409 // exception already pushed to the output queue, so ignore here
1410 }
1411 }
1415 };
1416
1417 // continuation_process_deferred_wait for void input and void output data types
1418 template<typename FEATURES, typename CALLABLE>
1437
1438 // factory for continuation_process_deferred_wait
1439 template<typename T, typename FEATURES, typename TOUT, typename CALLABLE>
1444
1445 // ----------------------------------------------------------------------------------------------------------------
1446 // continuation_process_async: function to be executed in the internal thread of
1447 // a async continuation
1448
1449 // continuation_process_async for non-void data types
1450 template<typename T, typename FEATURES, typename TOUT, typename CALLABLE>
1454 void operator()() {
1455 while(true) {
1456 // written this way so the callable is able to swap with the internal
1457 // buffer
1458 q_input.wait();
1459 T* v;
1460 try {
1461 v = &(q_input.front());
1462 // TODO how to handle full output queues?
1464 }
1466 q_output.d->continuation_process_async_terminated = true;
1467 return;
1468 }
1469 catch(...) {
1470 // TODO how to handle full output queues?
1471 q_output.push_exception(std::current_exception());
1472 }
1473 try {
1474 q_input.pop();
1475 }
1476 catch(...) {
1477 // exception already pushed to the output queue, so ignore here
1478 }
1479 }
1480 }
1484 };
1485
1486 // continuation_process_async for void input and non-void output data types
1487 template<typename FEATURES, typename TOUT, typename CALLABLE>
1492 void operator()() {
1493 while(true) {
1494 try {
1495 q_input.pop_wait();
1496 // TODO how to handle full output queues?
1498 }
1500 q_output.d->continuation_process_async_terminated = true;
1501 return;
1502 }
1503 catch(...) {
1504 // TODO how to handle full output queues?
1505 q_output.push_exception(std::current_exception());
1506 }
1507 }
1508 }
1512 };
1513
1514 // continuation_process_async for non-void input and void output data types
1515 template<typename T, typename FEATURES, typename CALLABLE>
1519 void operator()() {
1520 while(true) {
1521 // written this way so the callable is able to swap with the internal
1522 // buffer
1523 q_input.wait();
1524 T* v;
1525 try {
1526 v = &(q_input.front());
1527 callable(*v);
1528 // TODO how to handle full output queues?
1529 q_output.push();
1530 }
1532 q_output.d->continuation_process_async_terminated = true;
1533 return;
1534 }
1535 catch(...) {
1536 // TODO how to handle full output queues?
1537 q_output.push_exception(std::current_exception());
1538 }
1539 try {
1540 q_input.pop();
1541 }
1542 catch(...) {
1543 // exception already pushed to the output queue, so ignore here
1544 }
1545 }
1546 }
1550 };
1551
1552 // continuation_process_async for void input and void output data types
1553 template<typename FEATURES, typename CALLABLE>
1558 void operator()() {
1559 while(true) {
1560 try {
1561 q_input.pop_wait();
1562 callable();
1563 // TODO how to handle full output queues?
1564 q_output.push();
1565 }
1567 q_output.d->continuation_process_async_terminated = true;
1568 return;
1569 }
1570 catch(...) {
1571 // TODO how to handle full output queues?
1572 q_output.push_exception(std::current_exception());
1573 }
1574 }
1575 }
1579 };
1580
1581 // factory for continuation_process_async
1582 template<typename T, typename FEATURES, typename TOUT, typename CALLABLE>
1587 } // namespace detail
1588
1589 // ----------------------------------------------------------------------------------------------------------------
1590 // ----------------------------------------------------------------------------------------------------------------
1591 // actual implementation of future_queue::then()
1592
1593 template<typename T, typename FEATURES>
1594 template<typename T2, typename FEATURES2, typename CALLABLE>
1596 future_queue<T, FEATURES> q_input(*this);
1597 if(policy == std::launch::deferred) {
1598 future_queue<T2, FEATURES2> q_output(1);
1599 q_output.d->continuation_process_deferred =
1600 detail::make_continuation_process_deferred(q_input, q_output, callable);
1601 q_output.d->continuation_process_deferred_wait =
1602 detail::make_continuation_process_deferred_wait(q_input, q_output, callable);
1603 q_output.d->continuation_origin = *this;
1604 q_output.d->is_continuation_deferred = true;
1605 return q_output;
1606 }
1607 else {
1608 future_queue<T2, FEATURES2> q_output(size());
1609 q_output.d->continuation_process_async =
1610 std::thread(detail::make_continuation_process_async(q_input, q_output, callable));
1611 q_output.d->continuation_origin = *this;
1612 q_output.d->is_continuation_async = true;
1613 return q_output;
1614 }
1615 }
1616
1617} // namespace cppext
Feature tag for future_queue: use std::move to store and retreive data to/from the queue.
Feature tag for future_queue: use std::swap to store and retreive data to/from the queue.
Exception to be pushed into the queue to signal a termination request for the internal thread of an a...
Type-independent base class for future_queue which does not depend on the template argument.
detail::shared_state_ptr d
pointer to data used to allow sharing the queue (create multiple copies which all refer to the same q...
bool operator==(const future_queue_base &other) const
Check whether two future_queue instances use the same shared state, i.e.
size_t setNotificationQueue(future_queue< size_t, MOVE_DATA > &notificationQueue, size_t indexToSend)
Set the notification queue in the shared state, as done in when_any.
void decrement_previous_data_counter()
Decrement the "previous data" counter used in when_any().
bool operator!=(const future_queue_base &other) const
void wait()
Wait until the queue is not empty.
bool push_exception(std::exception_ptr exception)
Push an exception pointer (inplace of a value) into the queue.
void update_read_index_max()
update readIndexMax after a write operation was completed
void send_notification(cppext::detail::shared_state_base *notification_queue)
Send notification to notification queue (if not nullptr).
cppext::detail::shared_state_base * get_notification_queue()
Atomically return the notification queue or increment the "previous data" counter (for wait_any).
friend future_queue< void, MOVE_DATA > when_all(ITERATOR_TYPE begin, ITERATOR_TYPE end)
This function expects two forward iterators pointing to a region of a container of future_queue objec...
size_t write_available() const
Number of push operations which can be performed before the queue is full.
bool empty()
Check if there is currently no data on the queue.
size_t read_available() const
Number of pop operations which can be performed before the queue is empty.
bool push_overwrite_exception(std::exception_ptr exception)
Like push_exception() but overwrite the last pushed value in case the queue is full.
bool obtain_write_slot(size_t &index)
reserve next available write slot.
friend future_queue< size_t, MOVE_DATA > when_any(ITERATOR_TYPE begin, ITERATOR_TYPE end)
Implementations of non-member functions.
size_t size() const
return length of the queue
A lockfree multi-producer single-consumer queue of a fixed length which the receiver can wait on in c...
void front() const
This front() is for void data types.
void pop_wait()
This pop_wait() is for all data types (for non-void data types the value will be discarded)
bool push_overwrite(U &&t)
Push object t to the queue.
future_queue(const future_queue &other)=default
Copy constructor: After copying the object both *this and the other object will refer to the same que...
bool push(U &&t)
Push object t to the queue.
bool pop()
This pop() is for all data types (for non-void data types the value will be discarded)
future_queue(size_t length)
The length specifies how many objects the queue can contain at a time.
future_queue< T2, FEATURES2 > then(CALLABLE callable, std::launch policy=std::launch::async)
Add continuation: Whenever there is a new element in the queue, process it with the callable and put ...
bool pop(U &t)
Pop object off the queue and store it in t.
future_queue & operator=(const future_queue &other)=default
Copy assignment operator: After the assignment both *this and the other object will refer to the same...
bool push_overwrite()
This version of push_overwrite() is valid only for T=void.
bool push(const U &t)
This push() is for non-void data types passed by Lvalue reference.
void pop_wait(U &t)
Pop object off the queue and store it in t.
U & front()
Obtain the front element of the queue without removing it.
future_queue()
The default constructor creates only a place holder which can later be assigned with a properly const...
bool push(void)
This version of push() is valid only for T=void.
bool push_overwrite(const U &t)
This push_overwrite() is for non-void data types passed by Lvalue reference.
continuation_process_async< T, FEATURES, TOUT, CALLABLE > make_continuation_process_async(future_queue< T, FEATURES > q_input, future_queue< TOUT > q_output, CALLABLE callable)
continuation_process_deferred< T, FEATURES, TOUT, CALLABLE > make_continuation_process_deferred(future_queue< T, FEATURES > q_input, future_queue< TOUT > q_output, CALLABLE callable)
void data_assign(T &a, T &&b, MOVE_DATA)
Helper function to realise the data assignment depending on the selected FEATURES tags.
continuation_process_deferred_wait< T, FEATURES, TOUT, CALLABLE > make_continuation_process_deferred_wait(future_queue< T, FEATURES > q_input, future_queue< TOUT > q_output, CALLABLE callable)
shared_state_ptr make_shared_state(size_t length)
future_queue< size_t > when_any(ITERATOR_TYPE begin, ITERATOR_TYPE end)
Implementations of non-member functions.
future_queue< void > when_all(ITERATOR_TYPE begin, ITERATOR_TYPE end)
This function expects two forward iterators pointing to a region of a container of future_queue objec...
continuation_process_async(future_queue< T, FEATURES > q_input_, future_queue< void > q_output_, CALLABLE callable_)
continuation_process_async(future_queue< void, FEATURES > q_input_, future_queue< TOUT > q_output_, CALLABLE callable_)
continuation_process_async(future_queue< void, FEATURES > q_input_, future_queue< void > q_output_, CALLABLE callable_)
continuation_process_async(future_queue< T, FEATURES > q_input_, future_queue< TOUT > q_output_, CALLABLE callable_)
continuation_process_deferred(future_queue< T, FEATURES > q_input_, future_queue< void > q_output_, CALLABLE callable_)
continuation_process_deferred(future_queue< void, FEATURES > q_input_, future_queue< TOUT > q_output_, CALLABLE callable_)
continuation_process_deferred(future_queue< void, FEATURES > q_input_, future_queue< void > q_output_, CALLABLE callable_)
continuation_process_deferred_wait(future_queue< T, FEATURES > q_input_, future_queue< void > q_output_, CALLABLE callable_)
continuation_process_deferred_wait(future_queue< void, FEATURES > q_input_, future_queue< TOUT > q_output_, CALLABLE callable_)
continuation_process_deferred_wait(future_queue< void, FEATURES > q_input_, future_queue< void > q_output_, CALLABLE callable_)
continuation_process_deferred_wait(future_queue< T, FEATURES > q_input_, future_queue< TOUT > q_output_, CALLABLE callable_)
continuation_process_deferred(future_queue< T, FEATURES > q_input_, future_queue< TOUT > q_output_, CALLABLE callable_)
Internal base class for holding the data which is shared between multiple instances of the same queue...
std::atomic< size_t > writeIndex
index of the element which will be next written
bool is_continuation_when_all
Flag whether this future_queue is a when_all-type continuation (of many other)
std::vector< semaphore > semaphores
vector of semaphores corresponding to the buffers which allows the receiver to wait for new data
future_queue_base continuation_origin
If either is_continuation_deferred or is_continuation_async is true, this will point to the original ...
size_t nBuffers
the number of buffers we have allocated
bool is_continuation_deferred
Flag whether this future_queue is a deferred-type continuation of another.
std::atomic< size_t > when_any_index
index used in wait_any to identify the queue
std::atomic< when_any_notification_info > when_any_notification
std::atomic< bool > continuation_process_async_terminated
Flag whether the internal thread continuation_process_async has been terminated.
std::vector< std::exception_ptr > exceptions
vector of exception pointers, can be set instead of values through push_exception()
std::atomic< size_t > reference_count
reference count.
std::function< void(void)> continuation_process_deferred
Function to be called for deferred evaulation of a single value if this queue is a continuation.
std::atomic< size_t > readIndex
index of the element which will be next read
bool is_continuation_async
Flag whether this future_queue is a async-type continuation of another.
bool hasFrontOwnership
Flag if the receiver has already ownership over the front element.
std::thread continuation_process_async
Thread handling async-type continuations.
void free()
Decreaces the reference count and calls "delete this" where appropriate.
std::atomic< size_t > readIndexMax
maximum index which the receiver is currently allowed to read (after checking it semaphore).
virtual ~shared_state_base()
Destructor must be virtual so the destructor of the derived class gets called.
std::function< void(void)> continuation_process_deferred_wait
Function to be called for deferred evaulation of a single value if this queue is a continuation.
shared_ptr-like smart pointer type for referencing the shared_state.
void make_new(size_t length)
Create new shared_state for type T.
shared_state_base * operator->()
Dereferencing operator.
shared_state< T > * cast()
Cast into shared state for type T.
shared_state_ptr & operator=(const shared_state_ptr &other)
Copy by assignment.
shared_state_base * get() const
Obtain the target pointer.
void set(shared_state_base *ptr_)
Set the target pointer without incrementing the reference counter.
shared_state_ptr()
Default constructor: create empty pointer.
bool operator==(const shared_state_ptr &other) const
Check if two pointers are identical.
Internal class for holding the data which is shared between multiple instances of the same queue.
std::vector< T > buffers
vector of buffers - allocation is done in the constructor
cppext::detail::shared_state_base * notifyerQueue
Notification queue used to realise a wait_any logic.
size_t notifyerQueue_previousData
counter for the number of elements in the queue before when_any has added the notifyerQueue