Commit a5845d7a authored by jan.koester's avatar jan.koester
Browse files

test

parent 199b9956
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -199,6 +199,15 @@ namespace netplus {
            // to the owning poll instance.
            int ownerPollFd = -1;

            // Opaque per-backend bookkeeping pointer, the IOCP-backend analogue of
            // ownerPollFd above -- IOCP has no per-connection "poll instance fd" (every
            // connection for one event instance shares a single completion port), but
            // requestWritablePoll()/detachConnection()/reattachConnection() still need to
            // find that instance's connection registry from just a con&. Set once at
            // accept time (event/iocp.cpp's accept_loop) to that translation unit's
            // EventState*; unused (stays null) on backends that use ownerPollFd instead.
            void* backendState = nullptr;

            // Proactively write out whatever is currently queued in SendData,
            // instead of waiting for the event loop's post-RequestEvent drain.
            // Safe to call from within a RequestEvent handler on the same
+82 −15
Original line number Diff line number Diff line
@@ -262,13 +262,27 @@ namespace netplus {
        } catch (NetException& e) {
            c.slots[0].WritePending.store(false);
            if (e.getErrorType() != NetException::Note) throw;
            // EWOULDBLOCK: data remains in SendData but write is not pending.
            // Post a zero-byte completion to wake the worker and retry later.
            if (st && c.SendData.pos < c.SendData.size()) {
                SOCKET cs = (SOCKET)c.slots[0].csock->fd();
                PostQueuedCompletionStatus(st->iocp, 0, (ULONG_PTR)c.slots[0].csock.get(),
                    &c.slots[0].csock->_ReadBuffer->overlapped);
            }
            // tcp::sendData()'s IOCP-associated branch only ever throws Note for one
            // reason: another overlapped write is already in flight on this socket
            // (_pendingIocpWrite already true when we tried to post this one). That
            // write's own OP_WRITE completion -- guaranteed to arrive, whether it
            // finishes synchronously or not (Windows still queues a completion packet
            // either way) -- already re-invokes try_post_send() for whatever is left in
            // SendData once it lands (see the OP_WRITE handler below), so there is
            // nothing left to wake here.
            //
            // This used to PostQueuedCompletionStatus() a synthetic zero-byte
            // completion via c.slots[0].csock->_ReadBuffer->overlapped to force a
            // retry. That buffer's `operation` field is always OP_READ (post_recv() /
            // prime_read() are the only things that set it), so the synthetic
            // completion was misdelivered to the OP_READ branch above, where
            // bytes==0 && ok==true is indistinguishable from a real zero-byte read
            // completion -- i.e. the peer gracefully closing the connection. That
            // silently tore down the connection (PeerClosed -> mark_closing -> close())
            // while its real write was still in flight, cutting the response short
            // for any multi-chunk (>BLOCKSIZE) send that raced a second try_post_send()
            // call (e.g. via con::flushSendData(), which -- unlike this function --
            // doesn't check hasPendingWrite() between chunks) against this one.
            return false;
        }
    }
@@ -396,6 +410,12 @@ namespace netplus {
            c->slots[0].csock->setIocpAssociated(true);

            register_con(st, accepted, c);
            // See connection.h's doc comment on backendState: lets
            // requestWritablePoll()/detachConnection()/reattachConnection() find this
            // instance's EventState (and hence its iocp handle + sockToCon registry) from
            // just a con&, mirroring how epoll.cpp/kqueue.cpp use ownerPollFd for the same
            // purpose.
            c->backendState = st;

            ev->ConnectEvent(*c, 0, 0);

@@ -1026,15 +1046,62 @@ namespace netplus {
    // down — not a crash, not silent corruption, just the wakeup this
    // feature relies on not being available on IOCP yet.
    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; }
    // Unlike epoll/kqueue, IOCP has no "arm for writable" step to redo -- a socket is
    // always eligible to have I/O posted on it, the only thing that ever blocked a send
    // was another overlapped write already in flight, and that write's own completion
    // already re-invokes try_post_send() for whatever's left in SendData (see the
    // OP_WRITE handler in EventWorker::operator() above). So the IOCP equivalent of "please
    // re-check this connection's writability" is simply: try the send right now.
    //
    // Caller must hold c.event_mutex (see connection.h's doc comment) -- same precondition
    // epoll.cpp's requestWritablePoll() has, since try_post_send() itself takes no lock.
    void requestWritablePoll(con &c) {
        EventState* st = reinterpret_cast<EventState*>(c.backendState);
        if (!st || c.slots.empty() || !c.slots[0].csock) return;
        try_post_send(nullptr, st, c, 0);
    }

    // Caller must hold c.event_mutex (see connection.h's doc comment). Removing the
    // sockToCon entry is enough to make this fd invisible to every worker thread from here
    // on: find_con() in EventWorker::operator() is the only thing that ever resolves a
    // completion's SOCKET back to a con, and it already drops anything it can't find
    // (`if (!conPtr) continue;`) -- there is no separate kernel-side interest to
    // deregister the way epoll_ctl(EPOLL_CTL_DEL) is needed on that backend, since IOCP
    // was never "polling" this fd in the first place, just waiting on whatever I/O had
    // actually been posted.
    std::unique_ptr<socket> detachConnection(con &c) {
        if (c.slots.empty() || !c.slots[0].csock) return nullptr;
        EventState* st = reinterpret_cast<EventState*>(c.backendState);
        if (!st) return nullptr;
        remove_con(st, (SOCKET)c.slots[0].csock->fd());
        return std::move(c.slots[0].csock);
    }

    // Unlike detachConnection()/requestWritablePoll(), the CALLER MUST NOT hold
    // c->event_mutex (see connection.h's doc comment) -- this takes it itself.
    bool reattachConnection(std::shared_ptr<con> c) {
        if (!c || c->slots.empty() || !c->slots[0].csock) return false;
        EventState* st = reinterpret_cast<EventState*>(c->backendState);
        if (!st) return false;

        std::lock_guard<std::recursive_mutex> evlk(c->event_mutex);

        // The socket's CreateIoCompletionPort() association from accept_loop() is
        // permanent for the handle's lifetime (unlike epoll's fd interest, IOCP gives no
        // way to dissociate it) -- so all that's needed is making it a tracked connection
        // again and priming the next read, mirroring accept_loop()'s own post-registration
        // sequence.
        register_con(st, (SOCKET)c->slots[0].csock->fd(), c);
        try { post_recv(st, *c); } catch (...) { return false; }

        // Anything queued into SendData while detached gets its first send attempt kicked
        // off here too, same as requestWritablePoll().
        if (c->SendData.pos < c->SendData.size()) {
            try { try_post_send(nullptr, st, *c, 0); } catch (...) {}
        }

    // 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; }
        return true;
    }

} // namespace netplus