29template <
typename Policy>
31: _commands (_queueSize)
32, _wakeup (initWakeup (is_default<Policy>{}))
34 static_assert (has_spin<Policy>::value || !has_sqpoll<Policy>::value,
"spin required for sq poll policy");
36 io_uring_params params{};
37 params.flags = Policy::flags;
38 initCqEntries (params, has_cq_entries<Policy>{});
39 initSqThreadIdle (params, has_sq_thread_idle<Policy>{});
40 initSqThreadCpu (params, has_sq_thread_cpu<Policy>{});
42 if (io_uring_queue_init_params (Policy::sqEntries, &_ring, ¶ms) < 0)
46 throw std::system_error (errno, std::system_category (),
"io_uring_queue_init failed");
50 initWakeupOp (is_default<Policy>{});
51 _pendingOps.reserve (Policy::sqEntries);
58template <
typename Policy>
63 io_uring_queue_exit (&_ring);
75template <
typename Policy>
78 if (isProactorThread ())
83 lastError = std::error_code (errno, std::system_category ());
90 std::atomic<bool> done{
false}, *pdone =
nullptr;
91 std::error_code errc, *perrc =
nullptr;
99 if (
JOIN_UNLIKELY (writeCommand ({CommandType::Flush,
nullptr,
false, pdone, perrc,
nullptr}) == -1))
107 while (!done.load (std::memory_order_acquire))
126template <
typename Policy>
129 _threadId.store (pthread_self (), std::memory_order_release);
131 _running.store (
true, std::memory_order_release);
134 _threadId.store (_invalidThreadId, std::memory_order_release);
141template <
typename Policy>
144 if (isProactorThread ())
146 _running.store (
false, std::memory_order_release);
147 cancelAllOperations ();
156 writeCommand ({CommandType::Stop,
nullptr, sync,
nullptr,
nullptr,
nullptr});
168template <
typename Policy>
173 while (_threadId.load (std::memory_order_acquire) != _invalidThreadId)
183template <
typename Policy>
184template <
size_t Count,
size_t... Sizes>
187 return registerFixedBuffers (arena, std::make_index_sequence<
sizeof...(Sizes)>{});
194template <
typename Policy>
197 int ret = io_uring_unregister_buffers (&_ring);
200 lastError = std::error_code (-ret, std::system_category ());
211template <
typename Policy>
212template <
size_t Count,
size_t Size>
216 std::error_code errc;
218 InvokeHandler fn = [
this, group, &arena, &result, &errc] () {
227 auto it = _bufferRings.emplace (group, &_ring).first;
228 if (
JOIN_UNLIKELY (it->second.registerBuffer (group, arena) == -1))
231 _bufferRings.erase (it);
261template <
typename Policy>
265 std::error_code errc;
267 InvokeHandler fn = [
this, group, &result, &errc] () {
268 auto it = _bufferRings.find (group);
294 _bufferRings.erase (it);
322template <
typename Policy>
325 return _commands.mbind (numa);
333template <
typename Policy>
336 return _commands.
mlock ();
343template <
typename Policy>
346 return _threadId.load (std::memory_order_acquire) != _invalidThreadId;
353template <
typename Policy>
356 return _threadId.load (std::memory_order_acquire) == pthread_self ();
363template <
typename Policy>
366 return eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC);
373template <
typename Policy>
383template <
typename Policy>
386 _wakeupOp = IoOperation::makeRead (_wakeup, &_wakeupBuf,
sizeof (_wakeupBuf),
nullptr);
393template <
typename Policy>
403template <
typename Policy>
413template <
typename Policy>
416 params.flags |= IORING_SETUP_CQSIZE;
417 params.cq_entries = Policy::cqEntries;
424template <
typename Policy>
434template <
typename Policy>
437 params.sq_thread_idle = Policy::sqThreadIdle;
444template <
typename Policy>
454template <
typename Policy>
457 params.flags |= IORING_SETUP_SQ_AFF;
458 params.sq_thread_cpu = Policy::sqThreadCpu;
465template <
typename Policy>
468 return writeCommand (cmd, is_default<Policy>{});
475template <
typename Policy>
485 std::atomic_thread_fence (std::memory_order_seq_cst);
487 WakeupState state = _wakeupState.load (std::memory_order_relaxed);
488 if (state != WakeupState::Sleeping)
493 if (_wakeupState.compare_exchange_strong (state, WakeupState::Waking, std::memory_order_seq_cst))
496 if (
JOIN_UNLIKELY (::write (_wakeup, &value,
sizeof (uint64_t)) == -1))
498 _wakeupState.store (WakeupState::Sleeping, std::memory_order_seq_cst);
509template <
typename Policy>
512 return _commands.push (cmd);
519template <
typename Policy>
523 while (_commands.tryPop (cmd) == 0)
525 processCommand (cmd);
533template <
typename Policy>
540 case CommandType::Submit:
541 err = submitOperation (*cmd.op, cmd.flush);
543 (cmd.op->state.load (std::memory_order_relaxed) == IoOperation::State::Idle)))
545 dispatchOperation (cmd.op, -lastError.default_error_condition ().value (),
false);
549 case CommandType::Cancel:
550 err = cancelOperation (*cmd.op, cmd.flush);
553 case CommandType::Invoke:
554 err = invokeFunction (cmd.fn);
557 case CommandType::Stop:
558 _running.store (
false, std::memory_order_release);
559 cancelAllOperations ();
562 io_uring_submit (&_ring);
566 case CommandType::Flush:
570 lastError = std::error_code (errno, std::system_category ());
582 if (cmd.errc && (err != 0))
584 *cmd.errc = lastError;
586 cmd.done->store (
true, std::memory_order_release);
594template <
typename Policy>
599 lastError = std::make_error_code (std::errc::bad_file_descriptor);
604 while (
JOIN_UNLIKELY (op.state.load (std::memory_order_acquire) == IoOperation::State::Suspended))
609 IoOperation::State expected = IoOperation::State::Idle;
610 if (!op.state.compare_exchange_strong (expected, IoOperation::State::Submitted, std::memory_order_acquire,
611 std::memory_order_relaxed) &&
612 (expected == IoOperation::State::Busy))
614 op.state.store (IoOperation::State::Submitted, std::memory_order_release);
617 if (
JOIN_UNLIKELY ((op.index < _pendingOps.size ()) && (_pendingOps[op.index] == &op)))
623 IoRingBuffer* ring =
nullptr;
625 if (
JOIN_UNLIKELY (op.multishot && ((op.code ==
static_cast<uint8_t
> (IoOperation::Opcode::RecvMsg)) ||
626 (op.code ==
static_cast<uint8_t
> (IoOperation::Opcode::Recv)))))
628 auto it = _bufferRings.find (op.group);
639 io_uring_sqe* sqe = getSqe ();
649 prepareSqe (sqe, op);
650 op.index =
static_cast<uint32_t
> (_pendingOps.size ());
651 _pendingOps.push_back (&op);
661 io_uring_submit (&_ring);
671template <
typename Policy>
676 lastError = std::make_error_code (std::errc::bad_file_descriptor);
680 if (
JOIN_UNLIKELY (op.state.load (std::memory_order_acquire) != IoOperation::State::Submitted))
686 if (
JOIN_UNLIKELY (op.index >= _pendingOps.size () || _pendingOps[op.index] != &op))
692 io_uring_sqe* sqe = getSqe ();
701 io_uring_prep_cancel (sqe, &op, 0);
702 io_uring_sqe_set_data (sqe,
nullptr);
706 io_uring_submit (&_ring);
716template <
typename Policy>
719 for (IoOperation* op : _pendingOps)
722 while (op->state.load (std::memory_order_acquire) == IoOperation::State::Suspended)
726 cancelOperation (*op,
false);
734template <
typename Policy>
737 if (
JOIN_LIKELY (op.index < _pendingOps.size () && _pendingOps[op.index] == &op))
739 IoOperation* last = _pendingOps.back ();
740 _pendingOps[op.index] = last;
741 last->index = op.index;
742 _pendingOps.pop_back ();
745 dispatchOperation (&op, result, cancelled);
752template <
typename Policy>
753template <
size_t Count,
size_t... Sizes,
size_t... Is>
755 std::index_sequence<Is...>)
noexcept
757 iovec iovecs[] = {iovec{arena.template getPtr<Is> (0), Count * Sizes}...};
759 int ret = io_uring_register_buffers (&_ring, iovecs,
sizeof...(Sizes));
762 lastError = std::error_code (-ret, std::system_category ());
773template <
typename Policy>
776 io_uring_sqe* sqe = io_uring_get_sqe (&_ring);
779 io_uring_submit (&_ring);
780 sqe = io_uring_get_sqe (&_ring);
790template <
typename Policy>
793 switch (
static_cast<IoOperation::Opcode
> (op.code))
795 case IoOperation::Opcode::Poll:
798 io_uring_prep_poll_multishot (sqe, op.data.poll.fd, op.data.poll.events);
802 io_uring_prep_poll_add (sqe, op.data.poll.fd, op.data.poll.events);
806 case IoOperation::Opcode::Accept:
809 io_uring_prep_multishot_accept (sqe, op.data.accept.fd, op.data.accept.addr, op.data.accept.addrlen,
810 op.data.accept.flags);
814 io_uring_prep_accept (sqe, op.data.accept.fd, op.data.accept.addr, op.data.accept.addrlen,
815 op.data.accept.flags);
819 case IoOperation::Opcode::Connect:
820 io_uring_prep_connect (sqe, op.data.connect.fd, op.data.connect.addr, op.data.connect.addrlen);
823 case IoOperation::Opcode::Read:
824 io_uring_prep_read (sqe, op.data.rw.fd, op.data.rw.buf, op.data.rw.len, 0);
827 case IoOperation::Opcode::Write:
828 io_uring_prep_write (sqe, op.data.rw.fd, op.data.rw.buf, op.data.rw.len, 0);
831 case IoOperation::Opcode::ReadFixed:
832 io_uring_prep_read_fixed (sqe, op.data.rw.fd, op.data.rw.buf, op.data.rw.len, 0, op.data.rw.index);
835 case IoOperation::Opcode::WriteFixed:
836 io_uring_prep_write_fixed (sqe, op.data.rw.fd, op.data.rw.buf, op.data.rw.len, 0, op.data.rw.index);
839 case IoOperation::Opcode::RecvMsg:
842 op.data.msg.msg->msg_namelen = op.data.msg.namelen;
843 op.data.msg.msg->msg_controllen = op.data.msg.controllen;
844 op.data.msg.msg->msg_iovlen = 0;
845 io_uring_prep_recvmsg_multishot (sqe, op.data.msg.fd, op.data.msg.msg, op.data.msg.flags);
846 sqe->flags |= IOSQE_BUFFER_SELECT;
847 sqe->buf_group = op.group;
851 io_uring_prep_recvmsg (sqe, op.data.msg.fd, op.data.msg.msg, op.data.msg.flags);
855 case IoOperation::Opcode::SendMsg:
856 io_uring_prep_sendmsg (sqe, op.data.msg.fd, op.data.msg.msg, op.data.msg.flags);
859 case IoOperation::Opcode::Recv:
862 io_uring_prep_recv_multishot (sqe, op.data.stream.fd,
nullptr, 0, op.data.stream.flags);
863 sqe->flags |= IOSQE_BUFFER_SELECT;
864 sqe->buf_group = op.group;
868 io_uring_prep_recv (sqe, op.data.stream.fd, op.data.stream.buf, op.data.stream.len,
869 op.data.stream.flags);
873 case IoOperation::Opcode::Send:
874 io_uring_prep_send (sqe, op.data.stream.fd, op.data.stream.buf, op.data.stream.len, op.data.stream.flags);
878 io_uring_prep_nop (sqe);
881 io_uring_sqe_set_data (sqe, &op);
885 sqe->flags |= IOSQE_IO_LINK;
893template <
typename Policy>
896 dispatchCqe (cqe, is_default<Policy>{});
903template <
typename Policy>
906 IoOperation* op =
static_cast<IoOperation*
> (io_uring_cqe_get_data (cqe));
910 endOperation (*op, cqe->res,
false);
911 if (
JOIN_LIKELY (_running.load (std::memory_order_acquire)))
913 submitOperation (_wakeupOp,
true);
918 dispatchCqe (cqe, std::false_type{});
925template <
typename Policy>
928 IoOperation* op =
static_cast<IoOperation*
> (io_uring_cqe_get_data (cqe));
939 IoOperation::State current = op->state.load (std::memory_order_acquire);
942 while (
JOIN_UNLIKELY (current == IoOperation::State::Suspended))
945 current = op->state.load (std::memory_order_acquire);
953 int result = cqe->res;
954 IoRingBuffer* br =
nullptr;
957 if ((cqe->flags & IORING_CQE_F_BUFFER) != 0)
959 bid =
static_cast<uint16_t
> (cqe->flags >> IORING_CQE_BUFFER_SHIFT);
962 if (op->code ==
static_cast<uint8_t
> (IoOperation::Opcode::RecvMsg))
964 msghdr reserved = {};
965 reserved.msg_namelen = op->data.msg.namelen;
966 reserved.msg_controllen = op->data.msg.controllen;
968 io_uring_recvmsg_out* out = io_uring_recvmsg_validate (br->get (bid), result, &reserved);
975 uint32_t payloadlen = io_uring_recvmsg_payload_length (out, result, &reserved);
977 op->data.msg.msg->msg_name = io_uring_recvmsg_name (out);
978 op->data.msg.msg->msg_control = io_uring_recvmsg_cmsg_firsthdr (out, &reserved);
979 op->data.msg.msg->msg_iov->iov_base = io_uring_recvmsg_payload (out, &reserved);
980 op->data.msg.msg->msg_iov->iov_len = payloadlen;
981 op->data.msg.msg->msg_iovlen = 1;
982 op->data.msg.msg->msg_namelen = out->namelen;
983 op->data.msg.msg->msg_controllen = out->controllen;
984 op->data.msg.msg->msg_flags =
static_cast<int> (out->flags);
986 result =
static_cast<int> (payloadlen);
991 op->data.stream.buf = br->get (bid);
995 if ((cqe->flags & IORING_CQE_F_MORE) != 0)
998 notifyOperation (*op, result,
false);
1002 endOperation (*op, result, (result == -ECANCELED));
1015template <
typename Policy>
1018 eventLoop (has_spin<Policy>{}, has_sqpoll<Policy>{});
1025template <
typename Policy>
1028 if (
JOIN_LIKELY (_running.load (std::memory_order_acquire)))
1030 submitOperation (_wakeupOp,
true);
1036 while ((running = _running.load (std::memory_order_acquire)) || !_pendingOps.empty ())
1039 io_uring_submit (&_ring);
1041 io_uring_cqe* cqe =
nullptr;
1042 if (io_uring_peek_cqe (&_ring, &cqe) == 0)
1049 io_uring_cqe_seen (&_ring, cqe);
1051 while (io_uring_peek_cqe (&_ring, &cqe) == 0);
1056 if (!running || !backoff.spinExhausted ())
1062 _wakeupState.store (WakeupState::Sleeping, std::memory_order_relaxed);
1063 std::atomic_thread_fence (std::memory_order_seq_cst);
1066 io_uring_submit (&_ring);
1067 if (
JOIN_LIKELY (_running.load (std::memory_order_acquire)))
1069 if (io_uring_peek_cqe (&_ring, &cqe) != 0)
1071 io_uring_wait_cqe (&_ring, &cqe);
1075 _wakeupState.store (WakeupState::Spinning, std::memory_order_seq_cst);
1083template <
typename Policy>
1086 Backoff backoff (Policy::spin);
1089 while ((running = _running.load (std::memory_order_acquire)) || !_pendingOps.empty ())
1097 io_uring_submit (&_ring);
1100 io_uring_cqe* cqe =
nullptr;
1110 io_uring_cqe_seen (&_ring, cqe);
1112 while (io_uring_peek_cqe (&_ring, &cqe) == 0);
1122template <
typename Policy>
1125 Backoff backoff (Policy::spin);
1128 while ((running = _running.load (std::memory_order_acquire)) || !_pendingOps.empty ())
1135 io_uring_submit (&_ring);
1139 io_uring_submit (&_ring);
1142 io_uring_cqe* cqe =
nullptr;
1152 io_uring_cqe_seen (&_ring, cqe);
1154 while (io_uring_peek_cqe (&_ring, &cqe) == 0);
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() noexcept
destroy instance.
Definition proactor_epoll_impl.hpp:50
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
bool isProactorThread() const noexcept
check if the calling thread is the proactor thread.
Definition proactor_epoll_impl.hpp:252
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
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:45
#define JOIN_UNLIKELY(x)
Definition utils.hpp:46