Loading src/connection.h +20 −0 Original line number Diff line number Diff line Loading @@ -260,4 +260,24 @@ namespace netplus { // handler, which is invoked with it already held -- see requestWritablePoll's // identical precondition). std::unique_ptr<socket> detachConnection(con &c); // Re-registers a connection previously removed via detachConnection() with the same // epoll/kqueue instance it originally belonged to (c->ownerPollFd, set once at accept // time and never cleared by detachConnection). For a caller that finished background // work on a connection's socket -- already restored into c->slots[0].csock and // switched back to non-blocking, since the event loop always runs non-blocking // sockets -- and wants normal HttpEvent dispatch (further reads, keep-alive) to // resume. // // Unlike detachConnection/requestWritablePoll, the CALLER MUST NOT hold // c->event_mutex -- this takes the lock itself, since it's meant to be called from a // background thread once it's done with the connection (mirroring // ConnectEventHandler's own locking around initial registration), not from within an // already-locked RequestEvent handler. // // Returns false if there's no socket to reattach, or ownerPollFd was never recorded // (shouldn't happen for a connection that reached here via a prior successful // detachConnection()). Implemented per event backend like detachConnection/ // requestWritablePoll above; same select-based poll.cpp/iocp.cpp no-op caveat. bool reattachConnection(std::shared_ptr<con> c); }; src/event/epoll.cpp +29 −0 Original line number Diff line number Diff line Loading @@ -1173,4 +1173,33 @@ namespace netplus { return std::move(c.slots[0].csock); } bool reattachConnection(std::shared_ptr<con> c) { if (!c || c->slots.empty() || !c->slots[0].csock || c->ownerPollFd < 0) return false; int fd = c->slots[0].csock->fd(); if (fd < 0) return false; { std::unique_lock<std::shared_mutex> lk(POLL_HANDLER_MUTEX); CONNECTIONS[fd] = c; } std::lock_guard<std::recursive_mutex> evlk(c->event_mutex); epoll_event ev{}; ev.data.fd = fd; ev.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT; if (c->SendData.pos < c->SendData.size() || c->slots[0].csock->hasPendingWrite()) ev.events |= EPOLLOUT; // Unlike ConnectEventHandler's fresh accept(), this fd may still be registered with // the kernel from before detachConnection() -- EPOLLONESHOT only disarms interest, it // doesn't deregister (see detachConnection's comment above). MOD is therefore the // common case; ADD is the fallback for the rare case the fd was never registered // (shouldn't happen for a connection that went through a real detachConnection(), but // mirrors requestWritablePoll's own MOD-then-ADD-on-ENOENT idiom rather than assuming). if (epoll_ctl(c->ownerPollFd, EPOLL_CTL_MOD, fd, &ev) < 0 && errno == ENOENT) epoll_ctl(c->ownerPollFd, EPOLL_CTL_ADD, fd, &ev); return true; } } // namespace netplus src/event/iocp.cpp +5 −0 Original line number Diff line number Diff line Loading @@ -1032,4 +1032,9 @@ namespace netplus { // documented no-op, matching requestWritablePoll() above. std::unique_ptr<socket> detachConnection(con &) { return nullptr; } // detachConnection() never hands out a real socket on this backend (always nullptr // above), so no caller can ever have one to reattach -- documented no-op for the same // reason. bool reattachConnection(std::shared_ptr<con>) { return false; } } // namespace netplus src/event/kqueue.cpp +25 −0 Original line number Diff line number Diff line Loading @@ -1226,4 +1226,29 @@ namespace netplus { return std::move(c.slots[0].csock); } bool reattachConnection(std::shared_ptr<con> c) { if (!c || c->slots.empty() || !c->slots[0].csock || c->ownerPollFd < 0) return false; int fd = c->slots[0].csock->fd(); if (fd < 0) return false; { std::unique_lock<std::shared_mutex> lk(POLL_HANDLER_MUTEX); CONNECTIONS[fd] = c; } std::lock_guard<std::recursive_mutex> evlk(c->event_mutex); // Unlike epoll's EPOLLONESHOT, a kqueue EV_ONESHOT filter is removed by the kernel // the instant it fires (see detachConnection's comment above) -- there is nothing // left to MOD, so this is always a fresh EV_ADD, exactly like ConnectEventHandler's // initial registration. struct kevent changes[2]; int n = 0; EV_SET(&changes[n++], fd, EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, nullptr); if (c->SendData.pos < c->SendData.size() || c->slots[0].csock->hasPendingWrite()) EV_SET(&changes[n++], fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, nullptr); kevent(c->ownerPollFd, changes, n, nullptr, 0, nullptr); return true; } } // namespace netplus src/event/poll.cpp +5 −0 Original line number Diff line number Diff line Loading @@ -1099,4 +1099,9 @@ namespace netplus { // documented no-op, matching requestWritablePoll() above. std::unique_ptr<socket> detachConnection(con &) { return nullptr; } // detachConnection() never hands out a real socket on this backend (always nullptr // above), so no caller can ever have one to reattach -- documented no-op for the same // reason. bool reattachConnection(std::shared_ptr<con>) { return false; } } // namespace netplus Loading
src/connection.h +20 −0 Original line number Diff line number Diff line Loading @@ -260,4 +260,24 @@ namespace netplus { // handler, which is invoked with it already held -- see requestWritablePoll's // identical precondition). std::unique_ptr<socket> detachConnection(con &c); // Re-registers a connection previously removed via detachConnection() with the same // epoll/kqueue instance it originally belonged to (c->ownerPollFd, set once at accept // time and never cleared by detachConnection). For a caller that finished background // work on a connection's socket -- already restored into c->slots[0].csock and // switched back to non-blocking, since the event loop always runs non-blocking // sockets -- and wants normal HttpEvent dispatch (further reads, keep-alive) to // resume. // // Unlike detachConnection/requestWritablePoll, the CALLER MUST NOT hold // c->event_mutex -- this takes the lock itself, since it's meant to be called from a // background thread once it's done with the connection (mirroring // ConnectEventHandler's own locking around initial registration), not from within an // already-locked RequestEvent handler. // // Returns false if there's no socket to reattach, or ownerPollFd was never recorded // (shouldn't happen for a connection that reached here via a prior successful // detachConnection()). Implemented per event backend like detachConnection/ // requestWritablePoll above; same select-based poll.cpp/iocp.cpp no-op caveat. bool reattachConnection(std::shared_ptr<con> c); };
src/event/epoll.cpp +29 −0 Original line number Diff line number Diff line Loading @@ -1173,4 +1173,33 @@ namespace netplus { return std::move(c.slots[0].csock); } bool reattachConnection(std::shared_ptr<con> c) { if (!c || c->slots.empty() || !c->slots[0].csock || c->ownerPollFd < 0) return false; int fd = c->slots[0].csock->fd(); if (fd < 0) return false; { std::unique_lock<std::shared_mutex> lk(POLL_HANDLER_MUTEX); CONNECTIONS[fd] = c; } std::lock_guard<std::recursive_mutex> evlk(c->event_mutex); epoll_event ev{}; ev.data.fd = fd; ev.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT; if (c->SendData.pos < c->SendData.size() || c->slots[0].csock->hasPendingWrite()) ev.events |= EPOLLOUT; // Unlike ConnectEventHandler's fresh accept(), this fd may still be registered with // the kernel from before detachConnection() -- EPOLLONESHOT only disarms interest, it // doesn't deregister (see detachConnection's comment above). MOD is therefore the // common case; ADD is the fallback for the rare case the fd was never registered // (shouldn't happen for a connection that went through a real detachConnection(), but // mirrors requestWritablePoll's own MOD-then-ADD-on-ENOENT idiom rather than assuming). if (epoll_ctl(c->ownerPollFd, EPOLL_CTL_MOD, fd, &ev) < 0 && errno == ENOENT) epoll_ctl(c->ownerPollFd, EPOLL_CTL_ADD, fd, &ev); return true; } } // namespace netplus
src/event/iocp.cpp +5 −0 Original line number Diff line number Diff line Loading @@ -1032,4 +1032,9 @@ namespace netplus { // documented no-op, matching requestWritablePoll() above. std::unique_ptr<socket> detachConnection(con &) { return nullptr; } // detachConnection() never hands out a real socket on this backend (always nullptr // above), so no caller can ever have one to reattach -- documented no-op for the same // reason. bool reattachConnection(std::shared_ptr<con>) { return false; } } // namespace netplus
src/event/kqueue.cpp +25 −0 Original line number Diff line number Diff line Loading @@ -1226,4 +1226,29 @@ namespace netplus { return std::move(c.slots[0].csock); } bool reattachConnection(std::shared_ptr<con> c) { if (!c || c->slots.empty() || !c->slots[0].csock || c->ownerPollFd < 0) return false; int fd = c->slots[0].csock->fd(); if (fd < 0) return false; { std::unique_lock<std::shared_mutex> lk(POLL_HANDLER_MUTEX); CONNECTIONS[fd] = c; } std::lock_guard<std::recursive_mutex> evlk(c->event_mutex); // Unlike epoll's EPOLLONESHOT, a kqueue EV_ONESHOT filter is removed by the kernel // the instant it fires (see detachConnection's comment above) -- there is nothing // left to MOD, so this is always a fresh EV_ADD, exactly like ConnectEventHandler's // initial registration. struct kevent changes[2]; int n = 0; EV_SET(&changes[n++], fd, EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, nullptr); if (c->SendData.pos < c->SendData.size() || c->slots[0].csock->hasPendingWrite()) EV_SET(&changes[n++], fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, nullptr); kevent(c->ownerPollFd, changes, n, nullptr, 0, nullptr); return true; } } // namespace netplus
src/event/poll.cpp +5 −0 Original line number Diff line number Diff line Loading @@ -1099,4 +1099,9 @@ namespace netplus { // documented no-op, matching requestWritablePoll() above. std::unique_ptr<socket> detachConnection(con &) { return nullptr; } // detachConnection() never hands out a real socket on this backend (always nullptr // above), so no caller can ever have one to reattach -- documented no-op for the same // reason. bool reattachConnection(std::shared_ptr<con>) { return false; } } // namespace netplus