29template <
typename IoPolicy>
31: _commands (_queueSize)
32, _wakeup (
is_default_v<IoPolicy> ? eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC) : -1)
34 static_assert (has_spin_v<IoPolicy> || !has_sqpoll_v<IoPolicy>,
"spin required for sq poll policy");
36 io_uring_params params{};
37 params.flags = IoPolicy::flags;
39 if constexpr (has_cq_entries_v<IoPolicy>)
41 params.flags |= IORING_SETUP_CQSIZE;
42 params.cq_entries = IoPolicy::cqEntries;
45 if constexpr (has_sq_thread_idle_v<IoPolicy>)
47 params.sq_thread_idle = IoPolicy::sqThreadIdle;
50 if constexpr (has_sq_thread_cpu_v<IoPolicy>)
52 params.flags |= IORING_SETUP_SQ_AFF;
53 params.sq_thread_cpu = IoPolicy::sqThreadCpu;
56 if (io_uring_queue_init_params (IoPolicy::sqEntries, &_ring, ¶ms) < 0)
60 throw std::system_error (errno, std::system_category (),
"io_uring_queue_init failed");
64 if constexpr (is_default_v<IoPolicy>)
66 _wakeupOp = IoOperation::makeRead (_wakeup, &_wakeupBuf,
sizeof (_wakeupBuf),
nullptr);
69 _pendingOps.reserve (IoPolicy::sqEntries);
76template <
typename IoPolicy>
81 io_uring_queue_exit (&_ring);
93template <
typename IoPolicy>
96 if (isProactorThread ())
101 lastError = std::error_code (errno, std::system_category ());
108 std::atomic<bool> done{
false}, *pdone =
nullptr;
109 std::error_code errc, *perrc =
nullptr;
117 if (
JOIN_UNLIKELY (writeCommand ({CommandType::Flush,
nullptr,
false, pdone, perrc,
nullptr}) == -1))
125 while (!done.load (std::memory_order_acquire))
144template <
typename IoPolicy>
147 _threadId.store (Thread::currentId (), std::memory_order_release);
149 const int slack = prctl (PR_GET_TIMERSLACK, 0, 0, 0, 0);
150 prctl (PR_SET_TIMERSLACK, 1UL, 0, 0, 0);
151 _running.store (
true, std::memory_order_release);
153 prctl (PR_SET_TIMERSLACK,
static_cast<unsigned long> (slack), 0, 0, 0);
155 _threadId.store (Thread::invalidId, std::memory_order_release);
162template <
typename IoPolicy>
165 if (isProactorThread ())
167 _running.store (
false, std::memory_order_release);
168 cancelAllOperations ();
177 writeCommand ({CommandType::Stop,
nullptr, sync,
nullptr,
nullptr,
nullptr});
189template <
typename IoPolicy>
194 while (_threadId.load (std::memory_order_acquire) != Thread::invalidId)
204template <
typename IoPolicy>
205template <
size_t Count,
size_t... Sizes>
208 return registerFixedBuffers (arena, std::make_index_sequence<
sizeof...(Sizes)>{});
215template <
typename IoPolicy>
218 int ret = io_uring_unregister_buffers (&_ring);
221 lastError = std::error_code (-ret, std::system_category ());
232template <
typename IoPolicy>
233template <
size_t Count,
size_t Size>
237 std::error_code errc;
239 InvokeHandler fn = [
this, group, &arena, &result, &errc] () {
248 auto it = _bufferRings.emplace (group, &_ring).first;
249 if (
JOIN_UNLIKELY (it->second.registerBuffer (group, arena) == -1))
252 _bufferRings.erase (it);
282template <
typename IoPolicy>
283template <
size_t Count,
size_t Size>
287 std::error_code errc;
289 InvokeHandler fn = [
this, &arena, &result, &errc] () {
292 while ((group <= UINT16_MAX) && _bufferRings.count (
static_cast<uint16_t
> (group)))
307 auto it = _bufferRings.emplace (
static_cast<uint16_t
> (group), &_ring).first;
308 if (
JOIN_UNLIKELY (it->second.registerBuffer (
static_cast<uint16_t
> (group), arena) == -1))
311 _bufferRings.erase (it);
316 result =
static_cast<int> (group);
342template <
typename IoPolicy>
346 std::error_code errc;
348 InvokeHandler fn = [
this, group, &result, &errc] () {
349 auto it = _bufferRings.find (group);
375 _bufferRings.erase (it);
403template <
typename IoPolicy>
406 if (_wheel.mbind (numa) == -1)
411 return _commands.mbind (numa);
419template <
typename IoPolicy>
422 if (_wheel.mlock () == -1)
427 return _commands.
mlock ();
434template <
typename IoPolicy>
437 return _threadId.load (std::memory_order_acquire) != Thread::invalidId;
444template <
typename IoPolicy>
447 return _threadId.load (std::memory_order_acquire) == Thread::currentId ();
454template <
typename IoPolicy>
464template <
typename IoPolicy>
479template <
typename IoPolicy>
482 if constexpr (is_default_v<IoPolicy>)
486 std::atomic_thread_fence (std::memory_order_seq_cst);
488 WakeupState state = _wakeupState.load (std::memory_order_relaxed);
489 if (state != WakeupState::Sleeping)
494 if (_wakeupState.compare_exchange_strong (state, WakeupState::Waking, std::memory_order_seq_cst))
497 if (
JOIN_UNLIKELY (::write (_wakeup, &value,
sizeof (uint64_t)) == -1))
499 _wakeupState.store (WakeupState::Sleeping, std::memory_order_seq_cst);
511template <
typename IoPolicy>
515 while (_commands.tryPop (cmd) == 0)
517 processCommand (cmd);
525template <
typename IoPolicy>
532 case CommandType::Submit:
533 err = submitOperation (*cmd.op, cmd.flush);
535 (cmd.op->state.load (std::memory_order_relaxed) == IoOperation::State::Idle)))
537 dispatchOperation (*cmd.op, -lastError.default_error_condition ().value (),
false);
541 case CommandType::Cancel:
542 err = cancelOperation (*cmd.op, cmd.flush);
545 case CommandType::Invoke:
546 err = invokeFunction (cmd.fn);
549 case CommandType::Stop:
550 _running.store (
false, std::memory_order_release);
551 cancelAllOperations ();
554 io_uring_submit (&_ring);
558 case CommandType::Flush:
562 lastError = std::error_code (errno, std::system_category ());
574 if (cmd.errc && (err != 0))
576 *cmd.errc = lastError;
578 cmd.done->store (
true, std::memory_order_release);
586template <
typename IoPolicy>
589 if (
JOIN_UNLIKELY (!_running.load (std::memory_order_acquire)))
591 lastError = std::make_error_code (std::errc::operation_canceled);
596 while (
JOIN_UNLIKELY (op.state.load (std::memory_order_acquire) == IoOperation::State::Suspended))
601 IoOperation::State expected = IoOperation::State::Idle;
602 if (!op.state.compare_exchange_strong (expected, IoOperation::State::Submitted, std::memory_order_acquire,
603 std::memory_order_relaxed) &&
604 (expected == IoOperation::State::Busy))
606 op.state.store (IoOperation::State::Submitted, std::memory_order_release);
612 lastError = std::make_error_code (std::errc::bad_file_descriptor);
616 if (
JOIN_UNLIKELY ((op.index < _pendingOps.size ()) && (_pendingOps[op.index] == &op)))
622 IoRingBuffer* ring =
nullptr;
624 if (
JOIN_UNLIKELY (op.multishot && ((op.code ==
static_cast<uint8_t
> (IoOperation::Opcode::RecvMsg)) ||
625 (op.code ==
static_cast<uint8_t
> (IoOperation::Opcode::Recv)))))
627 auto it = _bufferRings.find (op.group);
638 io_uring_sqe* sqe = getSqe ();
648 prepareSqe (sqe, op);
649 op.index =
static_cast<uint32_t
> (_pendingOps.size ());
650 _pendingOps.push_back (&op);
660 io_uring_submit (&_ring);
670template <
typename IoPolicy>
675 lastError = std::make_error_code (std::errc::bad_file_descriptor);
679 if (
JOIN_UNLIKELY (op.state.load (std::memory_order_acquire) != IoOperation::State::Submitted))
685 if (
JOIN_UNLIKELY (op.index >= _pendingOps.size () || _pendingOps[op.index] != &op))
691 io_uring_sqe* sqe = getSqe ();
700 io_uring_prep_cancel (sqe, &op, 0);
701 io_uring_sqe_set_data (sqe,
nullptr);
705 io_uring_submit (&_ring);
715template <
typename IoPolicy>
718 for (IoOperation* op : _pendingOps)
721 while (op->state.load (std::memory_order_acquire) == IoOperation::State::Suspended)
725 cancelOperation (*op,
false);
733template <
typename IoPolicy>
736 if (
JOIN_LIKELY (op.index < _pendingOps.size () && _pendingOps[op.index] == &op))
738 IoOperation* last = _pendingOps.back ();
739 _pendingOps[op.index] = last;
740 last->index = op.index;
741 _pendingOps.pop_back ();
744 dispatchOperation (op, result, cancelled);
751template <
typename IoPolicy>
752template <
size_t Count,
size_t... Sizes,
size_t... Is>
754 std::index_sequence<Is...>)
noexcept
756 iovec iovecs[] = {iovec{arena.template getPtr<Is> (0), Count * Sizes}...};
758 int ret = io_uring_register_buffers (&_ring, iovecs,
sizeof...(Sizes));
761 lastError = std::error_code (-ret, std::system_category ());
772template <
typename IoPolicy>
775 io_uring_sqe* sqe = io_uring_get_sqe (&_ring);
778 io_uring_submit (&_ring);
779 sqe = io_uring_get_sqe (&_ring);
789template <
typename IoPolicy>
792 switch (
static_cast<IoOperation::Opcode
> (op.code))
794 case IoOperation::Opcode::Poll:
797 io_uring_prep_poll_multishot (sqe, op.data.poll.fd, op.data.poll.events);
801 io_uring_prep_poll_add (sqe, op.data.poll.fd, op.data.poll.events);
805 case IoOperation::Opcode::Accept:
808 io_uring_prep_multishot_accept (sqe, op.data.accept.fd, op.data.accept.addr, op.data.accept.addrlen,
809 op.data.accept.flags);
813 io_uring_prep_accept (sqe, op.data.accept.fd, op.data.accept.addr, op.data.accept.addrlen,
814 op.data.accept.flags);
818 case IoOperation::Opcode::Connect:
819 io_uring_prep_connect (sqe, op.data.connect.fd, op.data.connect.addr, op.data.connect.addrlen);
822 case IoOperation::Opcode::Read:
823 io_uring_prep_read (sqe, op.data.rw.fd, op.data.rw.buf, op.data.rw.len, 0);
826 case IoOperation::Opcode::Write:
827 io_uring_prep_write (sqe, op.data.rw.fd, op.data.rw.buf, op.data.rw.len, 0);
830 case IoOperation::Opcode::ReadFixed:
831 io_uring_prep_read_fixed (sqe, op.data.rw.fd, op.data.rw.buf, op.data.rw.len, 0, op.data.rw.index);
834 case IoOperation::Opcode::WriteFixed:
835 io_uring_prep_write_fixed (sqe, op.data.rw.fd, op.data.rw.buf, op.data.rw.len, 0, op.data.rw.index);
838 case IoOperation::Opcode::RecvMsg:
841 op.data.msg.msg->msg_namelen = op.data.msg.namelen;
842 op.data.msg.msg->msg_controllen = op.data.msg.controllen;
843 op.data.msg.msg->msg_iovlen = 0;
844 io_uring_prep_recvmsg_multishot (sqe, op.data.msg.fd, op.data.msg.msg, op.data.msg.flags);
845 sqe->flags |= IOSQE_BUFFER_SELECT;
846 sqe->buf_group = op.group;
850 io_uring_prep_recvmsg (sqe, op.data.msg.fd, op.data.msg.msg, op.data.msg.flags);
854 case IoOperation::Opcode::SendMsg:
855 io_uring_prep_sendmsg (sqe, op.data.msg.fd, op.data.msg.msg, op.data.msg.flags);
858 case IoOperation::Opcode::Recv:
861 io_uring_prep_recv_multishot (sqe, op.data.stream.fd,
nullptr, 0, op.data.stream.flags);
862 sqe->flags |= IOSQE_BUFFER_SELECT;
863 sqe->buf_group = op.group;
867 io_uring_prep_recv (sqe, op.data.stream.fd, op.data.stream.buf, op.data.stream.len,
868 op.data.stream.flags);
872 case IoOperation::Opcode::Send:
873 io_uring_prep_send (sqe, op.data.stream.fd, op.data.stream.buf, op.data.stream.len, op.data.stream.flags);
877 io_uring_prep_nop (sqe);
880 io_uring_sqe_set_data (sqe, &op);
884 sqe->flags |= IOSQE_IO_LINK;
892template <
typename IoPolicy>
895 IoOperation* op =
static_cast<IoOperation*
> (io_uring_cqe_get_data (cqe));
897 if constexpr (is_default_v<IoPolicy>)
901 endOperation (*op, cqe->res,
false);
902 if (
JOIN_LIKELY (_running.load (std::memory_order_acquire)))
904 submitOperation (_wakeupOp,
true);
920 IoOperation::State current = op->state.load (std::memory_order_acquire);
923 while (
JOIN_UNLIKELY (current == IoOperation::State::Suspended))
926 current = op->state.load (std::memory_order_acquire);
934 int result = cqe->res;
935 IoRingBuffer* br =
nullptr;
938 if ((cqe->flags & IORING_CQE_F_BUFFER) != 0)
940 bid =
static_cast<uint16_t
> (cqe->flags >> IORING_CQE_BUFFER_SHIFT);
943 if (op->code ==
static_cast<uint8_t
> (IoOperation::Opcode::RecvMsg))
945 msghdr reserved = {};
946 reserved.msg_namelen = op->data.msg.namelen;
947 reserved.msg_controllen = op->data.msg.controllen;
949 io_uring_recvmsg_out* out = io_uring_recvmsg_validate (br->get (bid), result, &reserved);
956 uint32_t payloadlen = io_uring_recvmsg_payload_length (out, result, &reserved);
958 op->data.msg.msg->msg_name = io_uring_recvmsg_name (out);
959 op->data.msg.msg->msg_control = io_uring_recvmsg_cmsg_firsthdr (out, &reserved);
960 op->data.msg.msg->msg_iov->iov_base = io_uring_recvmsg_payload (out, &reserved);
961 op->data.msg.msg->msg_iov->iov_len = payloadlen;
962 op->data.msg.msg->msg_iovlen = 1;
963 op->data.msg.msg->msg_namelen = out->namelen;
964 op->data.msg.msg->msg_controllen = out->controllen;
965 op->data.msg.msg->msg_flags =
static_cast<int> (out->flags);
967 result =
static_cast<int> (payloadlen);
972 op->data.stream.buf = br->get (bid);
976 if ((cqe->flags & IORING_CQE_F_MORE) != 0)
979 notifyOperation (*op, result,
false);
983 endOperation (*op, result, (result == -ECANCELED));
996template <
typename IoPolicy>
999 eventLoop (std::bool_constant<has_spin_v<IoPolicy>>{}, std::bool_constant<has_sqpoll_v<IoPolicy>>{});
1006template <
typename IoPolicy>
1009 if (
JOIN_LIKELY (_running.load (std::memory_order_acquire)))
1011 submitOperation (_wakeupOp,
true);
1017 while ((running = _running.load (std::memory_order_acquire)) || !_pendingOps.empty ())
1019 WheelPolicy::advance (_wheel);
1021 io_uring_submit (&_ring);
1023 io_uring_cqe* cqe =
nullptr;
1024 if (io_uring_peek_cqe (&_ring, &cqe) == 0)
1031 io_uring_cqe_seen (&_ring, cqe);
1033 while (io_uring_peek_cqe (&_ring, &cqe) == 0);
1038 if (!running || !backoff.spinExhausted ())
1044 _wakeupState.store (WakeupState::Sleeping, std::memory_order_relaxed);
1045 std::atomic_thread_fence (std::memory_order_seq_cst);
1047 WheelPolicy::advance (_wheel);
1049 io_uring_submit (&_ring);
1050 if (
JOIN_LIKELY (_running.load (std::memory_order_acquire)))
1052 if (io_uring_peek_cqe (&_ring, &cqe) != 0)
1054 __kernel_timespec ts;
1055 io_uring_wait_cqe_timeout (&_ring, &cqe, WheelPolicy::timeout (_wheel, ts));
1059 _wakeupState.store (WakeupState::Spinning, std::memory_order_seq_cst);
1067template <
typename IoPolicy>
1070 Backoff backoff{std::chrono::nanoseconds (spin_ns_v<IoPolicy>), std::chrono::nanoseconds (yield_ns_v<IoPolicy>),
1071 std::chrono::nanoseconds (sleep_ns_v<IoPolicy>)};
1074 while ((running = _running.load (std::memory_order_acquire)) || !_pendingOps.empty ())
1076 WheelPolicy::advance (_wheel);
1084 io_uring_submit (&_ring);
1087 io_uring_cqe* cqe =
nullptr;
1097 io_uring_cqe_seen (&_ring, cqe);
1099 while (io_uring_peek_cqe (&_ring, &cqe) == 0);
1109template <
typename IoPolicy>
1112 Backoff backoff{std::chrono::nanoseconds (spin_ns_v<IoPolicy>), std::chrono::nanoseconds (yield_ns_v<IoPolicy>),
1113 std::chrono::nanoseconds (sleep_ns_v<IoPolicy>)};
1116 while ((running = _running.load (std::memory_order_acquire)) || !_pendingOps.empty ())
1118 WheelPolicy::advance (_wheel);
1125 io_uring_submit (&_ring);
1129 io_uring_submit (&_ring);
1132 io_uring_cqe* cqe =
nullptr;
1142 io_uring_cqe_seen (&_ring, cqe);
1144 while (io_uring_peek_cqe (&_ring, &cqe) == 0);
basic proactor class.
Definition proactor.hpp:158
int mlock() const noexcept
lock proactor command queue and timer wheel memory in RAM.
Definition proactor_epoll_impl.hpp:288
int unregisterBufferRing(uint16_t group)
unregister a provided buffer ring.
Definition proactor_epoll_impl.hpp:213
bool isRunning() const noexcept
check if the event loop is running.
Definition proactor_epoll_impl.hpp:302
void run()
run the event loop (blocking).
Definition proactor_epoll_impl.hpp:61
~BasicProactor() noexcept
destroy instance.
Definition proactor_epoll_impl.hpp:50
int registerBufferRing(uint16_t group, LocalMem::SlabAllocator< Count, Size > &arena)
register a provided buffer ring.
Definition proactor_epoll_impl.hpp:107
Wheel & wheel() noexcept
get the timer wheel advanced by the event loop.
Definition proactor_epoll_impl.hpp:320
void stop(bool sync=true) noexcept
stop the event loop, ignored if no event loop is running.
Definition proactor_epoll_impl.hpp:71
bool isProactorThread() const noexcept
check if the calling thread is the proactor thread.
Definition proactor_epoll_impl.hpp:311
void waitStopped() const noexcept
wait for the event loop thread to terminate.
Definition proactor_epoll_impl.hpp:97
BasicProactor()
initialize the proactor and its I/O backend.
Definition proactor_epoll_impl.hpp:32
timing wheel.
Definition wheel.hpp:58
constexpr bool is_default_v
Definition io_policy.hpp:77
std::error_code make_error_code(join::Errc code) noexcept
Create an std::error_code object.
Definition error.cpp:195
#define JOIN_LIKELY(x)
Definition utils.hpp:50
#define JOIN_UNLIKELY(x)
Definition utils.hpp:51