join 1.0
lightweight network framework library
Loading...
Searching...
No Matches
proactor.hpp
Go to the documentation of this file.
1
25#ifndef JOIN_CORE_PROACTOR_HPP
26#define JOIN_CORE_PROACTOR_HPP
27
28// libjoin.
29#include <join/io_operation.hpp>
30#include <join/allocator.hpp>
31#include <join/function.hpp>
32#ifdef JOIN_HAS_IO_URING
33#include <join/io_policy.hpp>
34#else
35#include <join/reactor.hpp>
36#endif
37#include <join/backoff.hpp>
38#include <join/thread.hpp>
39#include <join/memory.hpp>
40#include <join/queue.hpp>
41
42// C++.
43#include <unordered_map>
44#include <utility>
45#include <vector>
46
47// C.
48#include <sys/eventfd.h>
49#include <cerrno>
50
51namespace join
52{
53 class CompletionHandler;
54#ifdef JOIN_HAS_IO_URING
55 template <typename Policy>
56 class BasicProactor;
57 template <typename Policy>
58 class BasicProactorThread;
59
60 using Proactor = BasicProactor<IoDefaultPolicy>;
61 using HybridProactor = BasicProactor<IoHybridPolicy>;
62 using SqpollProactor = BasicProactor<IoSqpollPolicy>;
63 using ProactorThread = BasicProactorThread<IoDefaultPolicy>;
64 using HybridProactorThread = BasicProactorThread<IoHybridPolicy>;
65 using SqpollProactorThread = BasicProactorThread<IoSqpollPolicy>;
66#else
67 class BasicProactor;
68 class BasicProactorThread;
69
72#endif
73}
74
79{
81#ifdef JOIN_HAS_IO_URING
82 template <typename Policy>
83 friend class join::BasicProactor;
84#else
85 friend class join::BasicProactor;
86#endif
87
88public:
92 CompletionHandler () = default;
93
98 CompletionHandler (const CompletionHandler& other) = default;
99
106
112
119
123 virtual ~CompletionHandler () = default;
124
125protected:
131 virtual void onComplete ([[maybe_unused]] IoOperation& op, [[maybe_unused]] int result)
132 {
133 // do nothing.
134 }
135
141 virtual void onCancel ([[maybe_unused]] IoOperation& op, [[maybe_unused]] int result)
142 {
143 // do nothing.
144 }
145};
146
150#ifdef JOIN_HAS_IO_URING
151template <typename Policy = join::IoDefaultPolicy>
153#else
155#endif
156{
157public:
159 using InvokeHandler = Function<void (), 64>;
160
164 explicit BasicProactor ();
165
170 BasicProactor (const BasicProactor& other) = delete;
171
177 BasicProactor& operator= (const BasicProactor& other) = delete;
178
183 BasicProactor (BasicProactor&& other) = delete;
184
191
195 ~BasicProactor () noexcept;
196
204 int submit (IoOperation& op, bool flush = false, bool sync = false) noexcept;
205
213 int cancel (IoOperation& op, bool flush = false, bool sync = false) noexcept;
214
221 int invoke (InvokeHandler* fn, bool sync = true) noexcept;
222
223#ifdef JOIN_HAS_IO_URING
229 int flush (bool sync = false) noexcept;
230#endif
231
236 void suspend (IoOperation* op) noexcept;
237
243 void resume (IoOperation* op, CompletionHandler* handler) noexcept;
244
248 void run ();
249
254 void stop (bool sync = true) noexcept;
255
259 void waitStopped () const noexcept;
260
261#ifdef JOIN_HAS_IO_URING
267 template <size_t Count, size_t... Sizes>
268 int registerFixedBuffers (LocalMem::Allocator<Count, Sizes...>& arena) noexcept;
269
274 int unregisterFixedBuffers () noexcept;
275#endif
276
284 template <size_t Count, size_t Size>
285 int registerBufferRing (uint16_t group, LocalMem::Allocator<Count, Size>& arena);
286
292 int unregisterBufferRing (uint16_t group);
293
294#ifdef JOIN_HAS_NUMA
300 int mbind (int numa) const noexcept;
301#endif
302
307 int mlock () const noexcept;
308
313 bool isRunning () const noexcept;
314
319 bool isProactorThread () const noexcept;
320
321private:
322#ifdef JOIN_HAS_IO_URING
327 int initWakeup (std::true_type) noexcept;
328
333 int initWakeup (std::false_type) noexcept;
334
338 void initWakeupOp (std::true_type) noexcept;
339
343 void initWakeupOp (std::false_type) noexcept;
344
349 void initCqEntries (io_uring_params&, std::false_type) noexcept;
350
355 void initCqEntries (io_uring_params& params, std::true_type) noexcept;
356
361 void initSqThreadIdle (io_uring_params&, std::false_type) noexcept;
362
367 void initSqThreadIdle (io_uring_params& params, std::true_type) noexcept;
368
373 void initSqThreadCpu (io_uring_params&, std::false_type) noexcept;
374
379 void initSqThreadCpu (io_uring_params& params, std::true_type) noexcept;
380#endif
381
385 enum class WakeupState
386 {
387 Sleeping,
388 Waking,
389 Spinning,
390 };
391
395 enum class CommandType
396 {
397 Submit,
398 Cancel,
399 Invoke,
400 Stop,
401#ifdef JOIN_HAS_IO_URING
402 Flush,
403#endif
404 };
405
409 struct alignas (64) Command
410 {
411 CommandType type;
412 IoOperation* op;
413 bool flush;
414 std::atomic<bool>* done;
415 std::error_code* errc;
416 InvokeHandler* fn;
417 };
418
424 int writeCommand (const Command& cmd) noexcept;
425
426#ifdef JOIN_HAS_IO_URING
432 int writeCommand (const Command& cmd, std::true_type) noexcept;
433
439 int writeCommand (const Command& cmd, std::false_type) noexcept;
440#endif
441
445 void readCommands () noexcept;
446
451 void processCommand (const Command& cmd) noexcept;
452
459 int submitOperation (IoOperation& op, bool flush) noexcept;
460
467 int cancelOperation (IoOperation& op, bool flush) noexcept;
468
472 void cancelAllOperations () noexcept;
473
479 int invokeFunction (InvokeHandler* fn) noexcept;
480
487 void notifyOperation (IoOperation& op, int result, bool cancelled) noexcept;
488
495 void dispatchOperation (IoOperation* op, int result, bool cancelled) noexcept;
496
503 void endOperation (IoOperation& op, int result, bool cancelled = false) noexcept;
504
509 void resetOperation (IoOperation& op) noexcept;
510
511#ifdef JOIN_HAS_IO_URING
518 template <size_t Count, size_t... Sizes, size_t... Is>
519 int registerFixedBuffers (LocalMem::Allocator<Count, Sizes...>& arena, std::index_sequence<Is...>) noexcept;
520
525 io_uring_sqe* getSqe () noexcept;
526
532 void prepareSqe (io_uring_sqe* sqe, IoOperation& op) noexcept;
533
538 void dispatchCqe (io_uring_cqe* cqe) noexcept;
539
544 void dispatchCqe (io_uring_cqe* cqe, std::true_type) noexcept;
545
550 void dispatchCqe (io_uring_cqe* cqe, std::false_type) noexcept;
551
555 void eventLoop () noexcept;
556
560 void eventLoop (std::false_type, std::false_type) noexcept;
561
565 void eventLoop (std::true_type, std::false_type) noexcept;
566
570 void eventLoop (std::true_type, std::true_type) noexcept;
571#else
577 static bool isWriteOp (const IoOperation& op) noexcept;
578
585 static int executeOp (IoOperation& op, uint32_t revents) noexcept;
586
592 void onEvent (int fd, uint32_t revents) noexcept override;
593#endif
594
596 static constexpr size_t _queueSize = 1024;
597
600
602#ifdef JOIN_HAS_IO_URING
603 alignas (64) std::atomic<WakeupState> _wakeupState{WakeupState::Spinning};
604#else
605 alignas (64) std::atomic<WakeupState> _wakeupState{WakeupState::Sleeping};
606#endif
607
609 int _wakeup = -1;
610
611#ifdef JOIN_HAS_IO_URING
613 alignas (64) uint64_t _wakeupBuf = 0;
614
616 IoOperation _wakeupOp = {};
617
619 io_uring _ring = {};
620
622 std::vector<IoOperation*> _pendingOps;
623
625 static constexpr pthread_t _invalidThreadId = static_cast<pthread_t> (-1);
626
628 std::atomic<pthread_t> _threadId{_invalidThreadId};
629
631 std::atomic<bool> _running{false};
632#else
634 std::vector<IoOperation*> _readOps;
635
637 std::vector<IoOperation*> _writeOps;
638
640 Reactor _reactor;
641#endif
642
644 std::unordered_map<uint16_t, IoRingBuffer> _bufferRings;
645};
646
647// =========================================================================
648// CLASS : BasicProactor
649// METHOD : submit
650// =========================================================================
651#ifdef JOIN_HAS_IO_URING
652template <typename Policy>
653int join::BasicProactor<Policy>::submit (IoOperation& op, bool flush, bool sync) noexcept
654#else
655inline int join::BasicProactor::submit (IoOperation& op, bool flush, bool sync) noexcept
656#endif
657{
658 if (isProactorThread ())
659 {
660 return submitOperation (op, flush);
661 }
662
663 std::atomic<bool> done{false}, *pdone = nullptr;
664 std::error_code errc, *perrc = nullptr;
665
666 if (JOIN_UNLIKELY (sync))
667 {
668 pdone = &done;
669 perrc = &errc;
670 }
671
672 if (JOIN_UNLIKELY (writeCommand ({CommandType::Submit, &op, flush, pdone, perrc, nullptr}) == -1))
673 {
674 return -1; // LCOV_EXCL_LINE
675 }
676
677 if (JOIN_UNLIKELY (sync))
678 {
679 Backoff backoff;
680 while (!done.load (std::memory_order_acquire))
681 {
682 backoff ();
683 }
684
685 if (JOIN_UNLIKELY (errc))
686 {
687 lastError = errc;
688 return -1;
689 }
690 }
691
692 return 0;
693}
694
695// =========================================================================
696// CLASS : BasicProactor
697// METHOD : cancel
698// =========================================================================
699#ifdef JOIN_HAS_IO_URING
700template <typename Policy>
701int join::BasicProactor<Policy>::cancel (IoOperation& op, bool flush, bool sync) noexcept
702#else
703inline int join::BasicProactor::cancel (IoOperation& op, bool flush, bool sync) noexcept
704#endif
705{
706 if (isProactorThread ())
707 {
708 return cancelOperation (op, flush);
709 }
710
711 std::atomic<bool> done{false}, *pdone = nullptr;
712 std::error_code errc, *perrc = nullptr;
713
714 if (JOIN_UNLIKELY (sync))
715 {
716 pdone = &done;
717 perrc = &errc;
718 }
719
720 if (JOIN_UNLIKELY (writeCommand ({CommandType::Cancel, &op, flush, pdone, perrc, nullptr}) == -1))
721 {
722 return -1; // LCOV_EXCL_LINE
723 }
724
725 if (JOIN_UNLIKELY (sync))
726 {
727 Backoff backoff;
728 while (!done.load (std::memory_order_acquire))
729 {
730 backoff ();
731 }
732
733 if (JOIN_UNLIKELY (errc))
734 {
735 lastError = errc;
736 return -1;
737 }
738 }
739
740 return 0;
741}
742
743// =========================================================================
744// CLASS : BasicProactor
745// METHOD : invoke
746// =========================================================================
747#ifdef JOIN_HAS_IO_URING
748template <typename Policy>
749int join::BasicProactor<Policy>::invoke (InvokeHandler* fn, bool sync) noexcept
750#else
751inline int join::BasicProactor::invoke (InvokeHandler* fn, bool sync) noexcept
752#endif
753{
754 if (isProactorThread ())
755 {
756 return invokeFunction (fn);
757 }
758
759 std::atomic<bool> done{false}, *pdone = nullptr;
760 std::error_code errc, *perrc = nullptr;
761
762 if (JOIN_LIKELY (sync))
763 {
764 pdone = &done;
765 perrc = &errc;
766 }
767
768 if (JOIN_UNLIKELY (writeCommand ({CommandType::Invoke, nullptr, false, pdone, perrc, fn}) == -1))
769 {
770 return -1; // LCOV_EXCL_LINE
771 }
772
773 if (JOIN_LIKELY (sync))
774 {
775 Backoff backoff;
776 while (!done.load (std::memory_order_acquire))
777 {
778 backoff ();
779 }
780
781 if (JOIN_UNLIKELY (errc))
782 {
783 lastError = errc;
784 return -1;
785 }
786 }
787
788 return 0;
789}
790
791// =========================================================================
792// CLASS : BasicProactor
793// METHOD : suspend
794// =========================================================================
795#ifdef JOIN_HAS_IO_URING
796template <typename Policy>
798#else
800#endif
801{
802 if (JOIN_UNLIKELY (op == nullptr))
803 {
804 return;
805 }
806
807 Backoff backoff;
808 for (;;)
809 {
810 IoOperation::State expected = op->state.load (std::memory_order_acquire);
811 if ((expected == IoOperation::State::Idle) || (expected == IoOperation::State::Submitted))
812 {
813 if (op->state.compare_exchange_strong (expected, IoOperation::State::Suspended, std::memory_order_acquire,
814 std::memory_order_relaxed))
815 {
816 op->resume = expected;
817 return;
818 }
819 }
820 else if ((expected == IoOperation::State::Busy) && isProactorThread ())
821 {
822 return;
823 }
824
825 backoff ();
826 }
827}
828
829// =========================================================================
830// CLASS : BasicProactor
831// METHOD : resume
832// =========================================================================
833#ifdef JOIN_HAS_IO_URING
834template <typename Policy>
836#else
838#endif
839{
840 if (JOIN_UNLIKELY (op == nullptr))
841 {
842 return;
843 }
844
845 op->handler = handler;
846 IoOperation::State expected = IoOperation::State::Suspended;
847 op->state.compare_exchange_strong (expected, op->resume, std::memory_order_release, std::memory_order_relaxed);
848}
849
850// =========================================================================
851// CLASS : BasicProactor
852// METHOD : invokeFunction
853// =========================================================================
854#ifdef JOIN_HAS_IO_URING
855template <typename Policy>
856int join::BasicProactor<Policy>::invokeFunction (InvokeHandler* fn) noexcept
857#else
858inline int join::BasicProactor::invokeFunction (InvokeHandler* fn) noexcept
859#endif
860{
861 if (JOIN_UNLIKELY ((fn == nullptr) || !*fn))
862 {
863 lastError = make_error_code (Errc::InvalidParam);
864 return -1;
865 }
866
867 (*fn) ();
868
869 return 0;
870}
871
872// =========================================================================
873// CLASS : BasicProactor
874// METHOD : notifyOperation
875// =========================================================================
876#ifdef JOIN_HAS_IO_URING
877template <typename Policy>
878void join::BasicProactor<Policy>::notifyOperation (IoOperation& op, int result, bool cancelled) noexcept
879#else
880inline void join::BasicProactor::notifyOperation (IoOperation& op, int result, bool cancelled) noexcept
881#endif
882{
883 if (JOIN_LIKELY (op.handler))
884 {
885 if (cancelled)
886 {
887 op.handler->onCancel (op, result);
888 }
889 else
890 {
891 op.handler->onComplete (op, result);
892 }
893 }
894}
895
896// =========================================================================
897// CLASS : BasicProactor
898// METHOD : dispatchOperation
899// =========================================================================
900#ifdef JOIN_HAS_IO_URING
901template <typename Policy>
902void join::BasicProactor<Policy>::dispatchOperation (IoOperation* op, int result, bool cancelled) noexcept
903#else
904inline void join::BasicProactor::dispatchOperation (IoOperation* op, int result, bool cancelled) noexcept
905#endif
906{
907 if (JOIN_UNLIKELY (op == nullptr))
908 {
909 return; // LCOV_EXCL_LINE
910 }
911
912 Backoff backoff;
913 for (;;)
914 {
915 IoOperation::State expected = op->state.load (std::memory_order_relaxed);
916 if (expected != IoOperation::State::Suspended)
917 {
918 if (op->state.compare_exchange_strong (expected, IoOperation::State::Busy, std::memory_order_acquire,
919 std::memory_order_relaxed))
920 {
921 break;
922 }
923 }
924 backoff (); // LCOV_EXCL_LINE
925 }
926
927 if (op->ring != nullptr)
928 {
929 op->ring->unbind ();
930 op->ring = nullptr;
931 }
932
933 op->more = false;
934
935 notifyOperation (*op, result, cancelled);
936}
937
938// =========================================================================
939// CLASS : BasicProactor
940// METHOD : resetOperation
941// =========================================================================
942#ifdef JOIN_HAS_IO_URING
943template <typename Policy>
944void join::BasicProactor<Policy>::resetOperation (IoOperation& op) noexcept
945#else
946inline void join::BasicProactor::resetOperation (IoOperation& op) noexcept
947#endif
948{
949 IoOperation::State expected = IoOperation::State::Submitted;
950 if (!op.state.compare_exchange_strong (expected, IoOperation::State::Idle, std::memory_order_release,
951 std::memory_order_relaxed) &&
952 (expected != IoOperation::State::Busy)) // LCOV_EXCL_LINE
953 {
954 op.resume = IoOperation::State::Idle; // LCOV_EXCL_LINE
955 }
956}
957
958#ifdef JOIN_HAS_IO_URING
960#else
962#endif
963
967#ifdef JOIN_HAS_IO_URING
968template <typename Policy = join::IoDefaultPolicy>
970#else
972#endif
973{
974public:
979#ifdef JOIN_HAS_IO_URING
981#else
983#endif
984 {
985 return instance ()._proactor;
986 }
987
993 static int affinity (int core)
994 {
995 return instance ()._dispatcher.affinity (core);
996 }
997
1002 static int affinity () noexcept
1003 {
1004 return instance ()._dispatcher.affinity ();
1005 }
1006
1012 static int priority (int prio)
1013 {
1014 return instance ()._dispatcher.priority (prio);
1015 }
1016
1021 static int priority () noexcept
1022 {
1023 return instance ()._dispatcher.priority ();
1024 }
1025
1030 static pthread_t handle () noexcept
1031 {
1032 return instance ()._dispatcher.handle ();
1033 }
1034
1035#ifdef JOIN_HAS_NUMA
1041 static int mbind (int numa) noexcept
1042 {
1043 return instance ()._proactor.mbind (numa);
1044 }
1045#endif
1046
1051 static int mlock () noexcept
1052 {
1053 return instance ()._proactor.mlock ();
1054 }
1055
1056private:
1061 static BasicProactorThread& instance ()
1062 {
1063 static BasicProactorThread proactorThread;
1064 return proactorThread;
1065 }
1066
1070 BasicProactorThread ()
1071 {
1072 _dispatcher = Thread ([this] () {
1073 _proactor.run ();
1074 });
1075
1076 Backoff backoff;
1077 while (!_proactor.isRunning ())
1078 {
1079 backoff ();
1080 }
1081 }
1082
1087 BasicProactorThread (const BasicProactorThread&) = delete;
1088
1094 BasicProactorThread& operator= (const BasicProactorThread&) = delete;
1095
1100 BasicProactorThread (BasicProactorThread&&) = delete;
1101
1107 BasicProactorThread& operator= (BasicProactorThread&&) = delete;
1108
1112 ~BasicProactorThread ()
1113 {
1114 _proactor.stop ();
1115 _dispatcher.join ();
1116 }
1117
1118#ifdef JOIN_HAS_IO_URING
1120 BasicProactor<Policy> _proactor;
1121#else
1123 BasicProactor _proactor;
1124#endif
1125
1127 Thread _dispatcher;
1128};
1129
1130#endif
adaptive backoff strategy for busy-wait loops.
Definition backoff.hpp:43
memory arena owning backend and managing one or more pools.
Definition memory.hpp:53
Convenience class that owns a Proactor running on a dedicated background thread.
Definition proactor.hpp:973
static int affinity() noexcept
get proactor thread affinity.
Definition proactor.hpp:1002
static int priority(int prio)
set proactor thread scheduling priority.
Definition proactor.hpp:1012
static int mlock() noexcept
lock proactor command queue memory in RAM.
Definition proactor.hpp:1051
static int affinity(int core)
set proactor thread affinity.
Definition proactor.hpp:993
static pthread_t handle() noexcept
get the native handle of the proactor thread.
Definition proactor.hpp:1030
static BasicProactor & proactor()
get the Proactor instance owned by the singleton ProactorThread.
Definition proactor.hpp:982
static int priority() noexcept
get proactor thread scheduling priority.
Definition proactor.hpp:1021
basic proactor class.
Definition proactor.hpp:156
int mlock() const noexcept
lock proactor command queue memory in RAM.
Definition proactor_epoll_impl.hpp:229
int unregisterBufferRing(uint16_t group)
unregister a provided buffer ring.
Definition proactor_epoll_impl.hpp:154
bool isRunning() const noexcept
check if the event loop is running.
Definition proactor_epoll_impl.hpp:243
void run()
run the event loop (blocking).
Definition proactor_epoll_impl.hpp:61
BasicProactor(const BasicProactor &other)=delete
copy constructor.
BasicProactor(BasicProactor &&other)=delete
move constructor.
~BasicProactor() noexcept
destroy instance.
Definition proactor_epoll_impl.hpp:50
int invoke(InvokeHandler *fn, bool sync=true) noexcept
invoke a function from the proactor thread.
Definition proactor.hpp:751
void suspend(IoOperation *op) noexcept
suspend an I/O operation.
Definition proactor.hpp:799
BasicProactor & operator=(const BasicProactor &other)=delete
copy assignment operator.
Function< void(), 64 > InvokeHandler
function invoked on the proactor thread.
Definition proactor.hpp:159
void stop(bool sync=true) noexcept
stop the event loop, ignored if no event loop is running.
Definition proactor_epoll_impl.hpp:71
int registerBufferRing(uint16_t group, LocalMem::Allocator< Count, Size > &arena)
register a provided buffer ring.
Definition proactor_epoll_impl.hpp:107
int submit(IoOperation &op, bool flush=false, bool sync=false) noexcept
submit an asynchronous operation to the proactor.
Definition proactor.hpp:655
bool isProactorThread() const noexcept
check if the calling thread is the proactor thread.
Definition proactor_epoll_impl.hpp:252
void resume(IoOperation *op, CompletionHandler *handler) noexcept
resume an I/O operation.
Definition proactor.hpp:837
void waitStopped() const noexcept
wait for the event loop thread to terminate.
Definition proactor_epoll_impl.hpp:97
int cancel(IoOperation &op, bool flush=false, bool sync=false) noexcept
cancel an in-flight operation.
Definition proactor.hpp:703
BasicProactor()
initialize the proactor and its I/O backend.
Definition proactor_epoll_impl.hpp:32
completion handler interface class.
Definition proactor.hpp:79
virtual void onComplete(IoOperation &op, int result)
method called when an operation completes successfully.
Definition proactor.hpp:131
CompletionHandler()=default
create instance.
virtual ~CompletionHandler()=default
destroy instance.
CompletionHandler(CompletionHandler &&other)=default
move constructor.
CompletionHandler & operator=(const CompletionHandler &other)=default
copy assignment operator.
CompletionHandler(const CompletionHandler &other)=default
copy constructor.
virtual void onCancel(IoOperation &op, int result)
method called when an operation is cancelled.
Definition proactor.hpp:141
Event handler interface class.
Definition reactor.hpp:48
friend class Reactor
friendship with reactor.
Definition reactor.hpp:149
fixed-capacity move-only allocation-free alternative to std::function.
Definition function.hpp:44
BasicArena< LocalMem, Count, Sizes... > Allocator
Definition memory.hpp:125
void join() noexcept
block the current thread until the running thread finishes its execution.
Definition thread.cpp:252
int affinity(int core)
set thread affinity.
Definition thread.cpp:106
int priority(int prio)
set thread priority.
Definition thread.cpp:169
pthread_t handle() const noexcept
get the handle of the thread of execution. @retunr thread of execution handle.
Definition thread.cpp:301
Definition acceptor.hpp:32
BasicProactorThread ProactorThread
Definition proactor.hpp:71
std::error_code make_error_code(join::Errc code) noexcept
Create an std::error_code object.
Definition error.cpp:195
BasicProactor Proactor
Definition proactor.hpp:70
Definition error.hpp:144
Describes a single asynchronous operation submitted to the Proactor.
Definition io_operation.hpp:47
State
operation lifecycle state.
Definition io_operation.hpp:52
BasicQueue< Type, Backend, SyncPolicy< Type, Backend > > Queue
queue type alias combining backend and policy.
Definition queue.hpp:1054
#define JOIN_LIKELY(x)
Definition utils.hpp:45
#define JOIN_UNLIKELY(x)
Definition utils.hpp:46