33: _commands (_queueSize)
34, _wakeup (eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC))
35, _readOps (256, nullptr)
36, _writeOps (256, nullptr)
40 throw std::system_error (errno, std::system_category (),
"eventfd failed");
43 _reactor.
addHandler (_wakeup,
this,
true,
false,
false);
63 _wakeupState.store (WakeupState::Sleeping, std::memory_order_seq_cst);
73 if (isProactorThread ())
75 cancelAllOperations ();
76 _reactor.stop (
false);
85 writeCommand ({CommandType::Stop,
nullptr, sync,
nullptr,
nullptr,
nullptr});
99 _reactor.waitStopped ();
106template <
size_t Count,
size_t Size>
110 std::error_code errc;
112 InvokeHandler fn = [
this, group, &arena, &result, &errc] () {
121 if (
JOIN_UNLIKELY (_bufferRings[group].registerBuffer (group, arena) == -1))
124 _bufferRings.erase (group);
154template <
size_t Count,
size_t Size>
158 std::error_code errc;
163 while ((group <= UINT16_MAX) && _bufferRings.count (
static_cast<uint16_t
> (group)))
178 if (
JOIN_UNLIKELY (_bufferRings[
static_cast<uint16_t
> (group)].registerBuffer (
static_cast<uint16_t
> (group),
182 _bufferRings.erase (
static_cast<uint16_t
> (group));
187 result =
static_cast<int> (group);
216 std::error_code errc;
219 auto it = _bufferRings.find (group);
245 _bufferRings.erase (it);
273inline int join::BasicProactor::mbind (
int numa)
const noexcept
275 if (_commands.mbind (numa) == -1)
280 return _reactor.mbind (numa);
290 if (_commands.mlock () == -1)
295 return _reactor.mlock ();
304 return _reactor.isRunning ();
313 return _reactor.isReactorThread ();
322 return _reactor.wheel ();
329inline int join::BasicProactor::writeCommand (
const Command& cmd)
noexcept
338 std::atomic_thread_fence (std::memory_order_seq_cst);
340 WakeupState state = _wakeupState.load (std::memory_order_relaxed);
341 if (state != WakeupState::Sleeping)
346 if (_wakeupState.compare_exchange_strong (state, WakeupState::Waking, std::memory_order_seq_cst))
349 if (
JOIN_UNLIKELY (::write (_wakeup, &value,
sizeof (uint64_t)) == -1))
351 _wakeupState.store (WakeupState::Sleeping, std::memory_order_seq_cst);
362inline void join::BasicProactor::readCommands () noexcept
365 [[maybe_unused]] ssize_t nread = ::read (_wakeup, &count,
sizeof (count));
366 _wakeupState.store (WakeupState::Sleeping, std::memory_order_relaxed);
367 std::atomic_thread_fence (std::memory_order_seq_cst);
370 while (_commands.tryPop (cmd) == 0)
372 processCommand (cmd);
380inline void join::BasicProactor::processCommand (
const Command& cmd)
noexcept
386 case CommandType::Submit:
387 err = submitOperation (*cmd.op, cmd.flush);
390 dispatchOperation (*cmd.op, -lastError.default_error_condition ().value (),
false);
394 case CommandType::Cancel:
395 err = cancelOperation (*cmd.op, cmd.flush);
398 case CommandType::Invoke:
399 err = invokeFunction (cmd.fn);
402 case CommandType::Stop:
403 cancelAllOperations ();
404 _reactor.stop (
false);
413 if (cmd.errc && (err != 0))
417 cmd.done->store (
true, std::memory_order_release);
425inline int join::BasicProactor::submitOperation (IoOperation& op, [[maybe_unused]]
bool flush)
noexcept
435 std::memory_order_relaxed) &&
444 lastError = std::make_error_code (std::errc::bad_file_descriptor);
448 IoRingBuffer* ring =
nullptr;
453 auto it = _bufferRings.find (op.group);
466 if (
JOIN_UNLIKELY (::connect (op.data.connect.fd, op.data.connect.addr, op.data.connect.addrlen) == -1 &&
467 errno != EINPROGRESS))
469 lastError = std::error_code (errno, std::system_category ());
475 if (
JOIN_UNLIKELY (
static_cast<size_t> (op.fd ()) >= _readOps.size ()))
477 size_t newSize =
static_cast<size_t> (op.fd ()) + 1;
478 _readOps.resize (newSize,
nullptr);
479 _writeOps.resize (newSize,
nullptr);
488 IoOperation*& last = isWriteOperation (op) ? _writeOps[op.fd ()] : _readOps[op.fd ()];
498 op.next = last->next;
507 int err = _reactor.addHandler (op.fd (),
this, _readOps[op.fd ()] !=
nullptr, _writeOps[op.fd ()] !=
nullptr);
529inline int join::BasicProactor::cancelOperation (IoOperation& op, [[maybe_unused]]
bool flush)
noexcept
533 lastError = std::make_error_code (std::errc::bad_file_descriptor);
543 if (
JOIN_UNLIKELY (
static_cast<size_t> (op.fd ()) >= _readOps.size ()))
545 lastError = std::make_error_code (std::errc::bad_file_descriptor);
557 if (_readOps[op.fd ()] ==
nullptr && _writeOps[op.fd ()] ==
nullptr)
559 ret = _reactor.delHandler (op.fd ());
561 else if ((isWriteOperation (op) ? _writeOps[op.fd ()] : _readOps[op.fd ()]) ==
nullptr)
563 ret = _reactor.addHandler (op.fd (),
this, _readOps[op.fd ()] !=
nullptr, _writeOps[op.fd ()] !=
nullptr);
566 dispatchOperation (op, -ECANCELED,
true);
575inline void join::BasicProactor::cancelAllOperations () noexcept
577 for (
size_t fd = 0; fd < _readOps.size (); ++fd)
579 IoOperation* rOp = std::exchange (_readOps[fd],
nullptr);
580 IoOperation* wOp = std::exchange (_writeOps[fd],
nullptr);
583 _reactor.delHandler (fd);
587 drainQueue (*rOp, -ECANCELED, 0,
true);
591 drainQueue (*wOp, -ECANCELED, 0,
true);
600inline void join::BasicProactor::endOperation (IoOperation& op,
int result,
bool cancelled)
noexcept
604 if (
JOIN_UNLIKELY (fd < 0 ||
static_cast<size_t> (fd) >= _readOps.size ()))
609 unqueueOperation (op);
611 if (_readOps[fd] ==
nullptr && _writeOps[fd] ==
nullptr)
613 _reactor.delHandler (fd);
615 else if ((isWriteOperation (op) ? _writeOps[fd] : _readOps[fd]) ==
nullptr)
617 _reactor.addHandler (fd,
this, _readOps[fd] !=
nullptr, _writeOps[fd] !=
nullptr);
620 dispatchOperation (op, result, cancelled);
627inline void join::BasicProactor::onEvent (
int fd, uint32_t revents)
noexcept
635 if (
JOIN_UNLIKELY (revents & (EPOLLERR | EPOLLRDHUP | EPOLLHUP)))
637 IoOperation* rOp = std::exchange (_readOps[fd],
nullptr);
638 IoOperation* wOp = std::exchange (_writeOps[fd],
nullptr);
641 _reactor.delHandler (fd);
644 int result = (revents & EPOLLERR) ? -ECONNRESET : 0;
648 drainQueue (*rOp, result, revents,
false);
652 drainQueue (*wOp, result, revents,
false);
658 if (revents & EPOLLIN)
660 processQueue (fd,
false, revents);
663 if (revents & EPOLLOUT)
665 processQueue (fd,
true, revents);
673inline void join::BasicProactor::drainQueue (IoOperation& tail,
int result, uint32_t revents,
bool cancelled)
noexcept
675 IoOperation* op = tail.next;
678 while (op !=
nullptr)
680 IoOperation* next = op->next;
687 res = executeOperation (*op, revents);
690 dispatchOperation (*op, res, cancelled);
700inline void join::BasicProactor::processQueue (
int fd,
bool write, uint32_t revents)
noexcept
702 IoOperation* last = write ? _writeOps[fd] : _readOps[fd];
709 for (IoOperation* it = last->next; it != last; it = it->next)
714 IoOperation* op = last->next;
715 bool consumed =
false;
717 for (
size_t i = 0; (op !=
nullptr) && (i < count); ++i)
719 IoOperation* following = (op == last) ?
nullptr : op->next;
722 if (poll || !consumed)
724 consumed = consumed || !poll;
725 processOperation (*op, revents);
728 last = write ? _writeOps[fd] : _readOps[fd];
731 if ((following !=
nullptr) && (last !=
nullptr))
733 for (IoOperation* it = last->next;; it = it->next)
754inline void join::BasicProactor::processOperation (IoOperation& op, uint32_t revents)
noexcept
762 current = op.state.load (std::memory_order_acquire);
770 IoRingBuffer* br =
nullptr;
773 if (op.ring !=
nullptr)
776 bid =
static_cast<uint16_t
> (br->select ());
780 uint32_t reserved = op.data.msg.namelen + op.data.msg.controllen;
783 endOperation (op, -EFAULT,
false);
788 char*
base =
static_cast<char*
> (br->get (bid));
790 op.data.msg.msg->msg_name =
base;
791 op.data.msg.msg->msg_namelen = op.data.msg.namelen;
792 op.data.msg.msg->msg_control =
base + op.data.msg.namelen;
793 op.data.msg.msg->msg_controllen = op.data.msg.controllen;
794 op.data.msg.msg->msg_iov->iov_base =
base + reserved;
795 op.data.msg.msg->msg_iov->iov_len = br->size () - reserved;
796 op.data.msg.msg->msg_iovlen = 1;
797 op.data.msg.msg->msg_flags = 0;
801 op.data.stream.buf = br->get (bid);
802 op.data.stream.len = br->size ();
806 int result = executeOperation (op, revents);
819 op.data.msg.msg->msg_iov->iov_len =
static_cast<size_t> (result);
821 if (op.data.msg.msg->msg_controllen < sizeof (cmsghdr))
823 op.data.msg.msg->msg_control =
nullptr;
831 notifyOperation (op, result,
false);
835 endOperation (op, result,
false);
848inline int join::BasicProactor::executeOperation (IoOperation& op, uint32_t revents)
noexcept
855 return static_cast<int> (revents & (op.data.poll.events | POLLERR | POLLHUP | POLLRDHUP));
859 int fd = ::accept4 (op.data.accept.fd, op.data.accept.addr, op.data.accept.addrlen,
860 op.data.accept.flags);
865 return (fd == -1) ? -errno : fd;
871 socklen_t len =
sizeof (err);
872 if (
JOIN_UNLIKELY (::getsockopt (op.data.connect.fd, SOL_SOCKET, SO_ERROR, &err, &len) == -1))
882 ssize_t n = ::read (op.data.rw.fd, op.data.rw.buf, op.data.rw.len);
887 return (n == -1) ? -errno :
static_cast<int> (n);
893 ssize_t n = ::write (op.data.rw.fd, op.data.rw.buf, op.data.rw.len);
898 return (n == -1) ? -errno :
static_cast<int> (n);
903 ssize_t n = ::recvmsg (op.data.msg.fd, op.data.msg.msg, op.data.msg.flags);
908 return (n == -1) ? -errno :
static_cast<int> (n);
913 ssize_t n = ::sendmsg (op.data.msg.fd, op.data.msg.msg, op.data.msg.flags);
918 return (n == -1) ? -errno :
static_cast<int> (n);
924 ::recv (op.data.stream.fd, op.data.stream.buf, op.data.stream.len, op.data.stream.flags);
929 return (n == -1) ? -errno :
static_cast<int> (n);
935 ::send (op.data.stream.fd, op.data.stream.buf, op.data.stream.len, op.data.stream.flags);
940 return (n == -1) ? -errno :
static_cast<int> (n);
953inline bool join::BasicProactor::unqueueOperation (IoOperation& op)
noexcept
955 if (op.next ==
nullptr)
960 IoOperation*& last = isWriteOperation (op) ? _writeOps[op.fd ()] : _readOps[op.fd ()];
961 IoOperation* prev = last;
963 while (prev->next != &op)
974 prev->next = op.next;
991inline bool join::BasicProactor::isWriteOperation (
const IoOperation& op)
noexcept
996 return (op.data.poll.events & POLLIN) == 0;
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
slab allocator owning a backend and managing one or more pools.
Definition slab_allocator.hpp:339
timing wheel.
Definition wheel.hpp:58
fixed-capacity move-only allocation-free alternative to std::function.
Definition function.hpp:44
int addHandler(int fd, EventHandler *handler, bool wantRead=true, bool wantWrite=false, bool sync=true) noexcept
add handler to reactor.
Definition reactor.cpp:101
std::string base(const std::string &filepath)
get base path of the specified file.
Definition filesystem.hpp:41
std::error_code make_error_code(join::Errc code) noexcept
Create an std::error_code object.
Definition error.cpp:195
thread_local std::error_code lastError
last error.
Definition error.cpp:32
State
operation lifecycle state.
Definition io_operation.hpp:53
Opcode
operation code.
Definition io_operation.hpp:64
#define JOIN_LIKELY(x)
Definition utils.hpp:50
#define JOIN_UNLIKELY(x)
Definition utils.hpp:51