Loading src/connection.h +20 −0 Original line number Diff line number Diff line Loading @@ -240,4 +240,24 @@ namespace netplus { // waiting on this wakeup on those backends only resumes once some // other, unrelated event touches the connection. void requestWritablePoll(con &c); // Removes a connection from the event loop's fd tracking and returns its primary // socket WITHOUT closing it -- for a request handler that needs to take over a // connection's socket for something outside the normal read/dispatch/write cycle // (e.g. a WebSocket tunnel: once upgraded, bytes need to be relayed raw for the // connection's lifetime instead of being parsed as further HTTP). After this call // the event loop will never dispatch another event for this fd -- no more // IoEventHandler invocations, no re-arming. The caller owns the returned socket and // is responsible for eventually closing it. // // Returns nullptr if the connection has no socket (already closed/detached) or the // current backend doesn't implement this yet -- same per-backend caveat as // requestWritablePoll() above (epoll and kqueue implement it; select-based poll.cpp // and iocp.cpp are documented no-ops for now, since the one caller of this is new // and opt-in). // // PRECONDITION: caller holds c.event_mutex (true from within any RequestEvent // handler, which is invoked with it already held -- see requestWritablePoll's // identical precondition). std::unique_ptr<socket> detachConnection(con &c); }; src/event/epoll.cpp +26 −0 Original line number Diff line number Diff line Loading @@ -424,6 +424,14 @@ namespace netplus { } else if (rcv > 0) { c->RecvData.append(recvBuf.data.buf, rcv); evconnection->RequestEvent(*c, tid, args); if (!c->slots[0].csock) { // RequestEvent took ownership of this connection's // socket via detachConnection() (e.g. a WebSocket // tunnel handoff) -- nothing left here to read, // flush, or re-arm; EpollArmGuard's destructor // already no-ops on a null csock too. return; } } if (wouldBlock) { Loading Loading @@ -1074,4 +1082,22 @@ namespace netplus { epoll_ctl(c.ownerPollFd, EPOLL_CTL_ADD, fd, &ev); } std::unique_ptr<socket> detachConnection(con &c) { // Caller must hold c.event_mutex (see declaration in connection.h). if (c.slots.empty() || !c.slots[0].csock) return nullptr; int fd = c.slots[0].csock->fd(); { // Once erased, no future epoll_wait() delivery for this fd can // find a connection to dispatch to (mirrors kqueue.cpp's // detachConnection -- see its longer comment for why no // explicit deregistration call is needed either: EPOLLONESHOT // already disabled the fd after the event we're currently // inside of, and EpollArmGuard's destructor already skips // re-arming once slots[0].csock is null). std::unique_lock<std::shared_mutex> lock(POLL_HANDLER_MUTEX); CONNECTIONS.erase(fd); } return std::move(c.slots[0].csock); } } // namespace netplus src/event/iocp.cpp +4 −0 Original line number Diff line number Diff line Loading @@ -947,4 +947,8 @@ namespace netplus { std::shared_ptr<con> lookupConnection(int /*fd*/) { return nullptr; } void requestWritablePoll(con &) {} // Not yet implemented for this backend (see connection.h's doc comment) -- // documented no-op, matching requestWritablePoll() above. std::unique_ptr<socket> detachConnection(con &) { return nullptr; } } // namespace netplus src/event/kqueue.cpp +29 −0 Original line number Diff line number Diff line Loading @@ -564,6 +564,14 @@ namespace netplus { } else if (rcv > 0) { c->RecvData.append(recvBuf.data.buf, rcv); evconnection->RequestEvent(*c, tid, args); if (!c->slots[0].csock) { // RequestEvent took ownership of this connection's // socket via detachConnection() (e.g. a WebSocket // tunnel handoff) -- nothing left here to read, // flush, or re-arm; KqueueArmGuard's destructor // already no-ops on a null csock too. return; } } if (wouldBlock) { Loading Loading @@ -1133,4 +1141,25 @@ namespace netplus { kevent(c.ownerPollFd, changes, 2, nullptr, 0, nullptr); } std::unique_ptr<socket> detachConnection(con &c) { // Caller must hold c.event_mutex (see declaration in connection.h). if (c.slots.empty() || !c.slots[0].csock) return nullptr; int fd = c.slots[0].csock->fd(); { // Once erased, getConByFd() can never find this fd again, so // IoEventHandler's own entry guard (`if (!c || !c->slots[0].csock) // return;`) makes any *future* kevent for this fd (there // shouldn't be one -- EVFILT_READ/WRITE are EV_ONESHOT and we're // called from within the one that already fired, before it gets // re-armed) a safe no-op. No explicit EV_DELETE needed for the // same reason: a ONESHOT filter that already fired has nothing // left to delete, and KqueueArmGuard's destructor (which decides // whether to re-arm) already checks slots[0].csock and skips // re-arming once we null it below. std::unique_lock<std::shared_mutex> lock(POLL_HANDLER_MUTEX); CONNECTIONS.erase(fd); } return std::move(c.slots[0].csock); } } // namespace netplus src/event/poll.cpp +4 −0 Original line number Diff line number Diff line Loading @@ -1090,4 +1090,8 @@ namespace netplus { // feature that relies on this is new and opt-in. void requestWritablePoll(con &) {} // Not yet implemented for this backend (see connection.h's doc comment) -- // documented no-op, matching requestWritablePoll() above. std::unique_ptr<socket> detachConnection(con &) { return nullptr; } } // namespace netplus Loading
src/connection.h +20 −0 Original line number Diff line number Diff line Loading @@ -240,4 +240,24 @@ namespace netplus { // waiting on this wakeup on those backends only resumes once some // other, unrelated event touches the connection. void requestWritablePoll(con &c); // Removes a connection from the event loop's fd tracking and returns its primary // socket WITHOUT closing it -- for a request handler that needs to take over a // connection's socket for something outside the normal read/dispatch/write cycle // (e.g. a WebSocket tunnel: once upgraded, bytes need to be relayed raw for the // connection's lifetime instead of being parsed as further HTTP). After this call // the event loop will never dispatch another event for this fd -- no more // IoEventHandler invocations, no re-arming. The caller owns the returned socket and // is responsible for eventually closing it. // // Returns nullptr if the connection has no socket (already closed/detached) or the // current backend doesn't implement this yet -- same per-backend caveat as // requestWritablePoll() above (epoll and kqueue implement it; select-based poll.cpp // and iocp.cpp are documented no-ops for now, since the one caller of this is new // and opt-in). // // PRECONDITION: caller holds c.event_mutex (true from within any RequestEvent // handler, which is invoked with it already held -- see requestWritablePoll's // identical precondition). std::unique_ptr<socket> detachConnection(con &c); };
src/event/epoll.cpp +26 −0 Original line number Diff line number Diff line Loading @@ -424,6 +424,14 @@ namespace netplus { } else if (rcv > 0) { c->RecvData.append(recvBuf.data.buf, rcv); evconnection->RequestEvent(*c, tid, args); if (!c->slots[0].csock) { // RequestEvent took ownership of this connection's // socket via detachConnection() (e.g. a WebSocket // tunnel handoff) -- nothing left here to read, // flush, or re-arm; EpollArmGuard's destructor // already no-ops on a null csock too. return; } } if (wouldBlock) { Loading Loading @@ -1074,4 +1082,22 @@ namespace netplus { epoll_ctl(c.ownerPollFd, EPOLL_CTL_ADD, fd, &ev); } std::unique_ptr<socket> detachConnection(con &c) { // Caller must hold c.event_mutex (see declaration in connection.h). if (c.slots.empty() || !c.slots[0].csock) return nullptr; int fd = c.slots[0].csock->fd(); { // Once erased, no future epoll_wait() delivery for this fd can // find a connection to dispatch to (mirrors kqueue.cpp's // detachConnection -- see its longer comment for why no // explicit deregistration call is needed either: EPOLLONESHOT // already disabled the fd after the event we're currently // inside of, and EpollArmGuard's destructor already skips // re-arming once slots[0].csock is null). std::unique_lock<std::shared_mutex> lock(POLL_HANDLER_MUTEX); CONNECTIONS.erase(fd); } return std::move(c.slots[0].csock); } } // namespace netplus
src/event/iocp.cpp +4 −0 Original line number Diff line number Diff line Loading @@ -947,4 +947,8 @@ namespace netplus { std::shared_ptr<con> lookupConnection(int /*fd*/) { return nullptr; } void requestWritablePoll(con &) {} // Not yet implemented for this backend (see connection.h's doc comment) -- // documented no-op, matching requestWritablePoll() above. std::unique_ptr<socket> detachConnection(con &) { return nullptr; } } // namespace netplus
src/event/kqueue.cpp +29 −0 Original line number Diff line number Diff line Loading @@ -564,6 +564,14 @@ namespace netplus { } else if (rcv > 0) { c->RecvData.append(recvBuf.data.buf, rcv); evconnection->RequestEvent(*c, tid, args); if (!c->slots[0].csock) { // RequestEvent took ownership of this connection's // socket via detachConnection() (e.g. a WebSocket // tunnel handoff) -- nothing left here to read, // flush, or re-arm; KqueueArmGuard's destructor // already no-ops on a null csock too. return; } } if (wouldBlock) { Loading Loading @@ -1133,4 +1141,25 @@ namespace netplus { kevent(c.ownerPollFd, changes, 2, nullptr, 0, nullptr); } std::unique_ptr<socket> detachConnection(con &c) { // Caller must hold c.event_mutex (see declaration in connection.h). if (c.slots.empty() || !c.slots[0].csock) return nullptr; int fd = c.slots[0].csock->fd(); { // Once erased, getConByFd() can never find this fd again, so // IoEventHandler's own entry guard (`if (!c || !c->slots[0].csock) // return;`) makes any *future* kevent for this fd (there // shouldn't be one -- EVFILT_READ/WRITE are EV_ONESHOT and we're // called from within the one that already fired, before it gets // re-armed) a safe no-op. No explicit EV_DELETE needed for the // same reason: a ONESHOT filter that already fired has nothing // left to delete, and KqueueArmGuard's destructor (which decides // whether to re-arm) already checks slots[0].csock and skips // re-arming once we null it below. std::unique_lock<std::shared_mutex> lock(POLL_HANDLER_MUTEX); CONNECTIONS.erase(fd); } return std::move(c.slots[0].csock); } } // namespace netplus
src/event/poll.cpp +4 −0 Original line number Diff line number Diff line Loading @@ -1090,4 +1090,8 @@ namespace netplus { // feature that relies on this is new and opt-in. void requestWritablePoll(con &) {} // Not yet implemented for this backend (see connection.h's doc comment) -- // documented no-op, matching requestWritablePoll() above. std::unique_ptr<socket> detachConnection(con &) { return nullptr; } } // namespace netplus