perf(net): own one pooled async receive
Replace timeout-polled in-world UDP receives with one cancellable caller-buffered socket operation. Transfer only right-sized pooled datagrams through the FIFO, return every ownership edge deterministically, and send caller spans without a transport copy while preserving handshake pacing and ACK order.
This commit is contained in:
parent
a2a1e5916d
commit
7211bb1bf7
10 changed files with 547 additions and 87 deletions
70
docs/research/2026-07-25-slice-h-c1-pooled-receive-owner.md
Normal file
70
docs/research/2026-07-25-slice-h-c1-pooled-receive-owner.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Slice H-c1 — cancellable pooled receive owner
|
||||
|
||||
## Result
|
||||
|
||||
The in-world transport now has one asynchronous socket receive owner:
|
||||
|
||||
```text
|
||||
one retained full-size socket buffer
|
||||
-> ReceiveFromAsync(cancellation token)
|
||||
-> copy actual datagram length into a right-sized ArrayPool bucket
|
||||
-> FIFO Channel<PooledInboundDatagram>
|
||||
-> render-thread decode/dispatch
|
||||
-> return bucket exactly once
|
||||
```
|
||||
|
||||
The synchronous `PumpOnce` handshake path keeps its existing 250 ms pacing,
|
||||
but receives into a temporarily rented caller buffer rather than accepting a
|
||||
fresh `UdpClient.Receive` array. The normal in-world path no longer wakes four
|
||||
times per idle second and no longer uses `SocketException(TimedOut)` as its
|
||||
heartbeat.
|
||||
|
||||
`NetClient.Send` also consumes its caller's `ReadOnlySpan<byte>` directly
|
||||
through `Socket.SendTo`; the transport-only `datagram.ToArray()` copy is gone.
|
||||
Higher-level outbound framing allocations are intentionally left to H-c3.
|
||||
|
||||
## Retail and protocol boundary
|
||||
|
||||
Behavioral invariants remain unchanged:
|
||||
|
||||
- one outstanding receive;
|
||||
- kernel arrival order is FIFO channel order;
|
||||
- decode, ISAAC, fragment assembly, ACK decisions, and event dispatch remain
|
||||
on the existing render/update thread;
|
||||
- ACK generation remains inside `ProcessDatagram`, immediately after decode
|
||||
acceptance;
|
||||
- `LinkStatusHolder::OnHeartbeat` (`0x004113D0`) last-heard placement remains
|
||||
immediately after successful decode;
|
||||
- blocking `PumpOnce` handshake cadence is unchanged.
|
||||
|
||||
The initial design review caught that 484 bytes is a fragment limit, not a
|
||||
safe UDP receive limit. Named-retail `CNetLayerPacket` (type 3705 in
|
||||
`acclient.h`) contains a separate `ProtoHeader` plus `m_Data[65484]`.
|
||||
Accordingly the socket edge retains a full UDP buffer. Only the received byte
|
||||
count crosses the queue boundary, preventing small queued packets from each
|
||||
pinning a 64 KiB bucket.
|
||||
|
||||
Holtburger's receive/finalize path remains the cross-reference for
|
||||
ACK-per-accepted-packet ordering. The transport ownership mechanism is a
|
||||
modern .NET adaptation; it does not change AC wire behavior.
|
||||
|
||||
## Deterministic evidence
|
||||
|
||||
- real loopback `ReceiveAsync` writes a 1,500-byte datagram into caller-owned
|
||||
memory;
|
||||
- cancelling an idle receive terminates the one outstanding operation;
|
||||
- a scripted recoverable `SocketException` does not kill the receive owner;
|
||||
- three accepted packets retain arrival order and produce ACKs in the exact
|
||||
sequence `41, 42, 43`;
|
||||
- blocking handshake timeout behavior and cached socket-timeout tests remain
|
||||
unchanged;
|
||||
- graceful confirmation drain releases every consumed pooled datagram, and
|
||||
shutdown cancels/joins the receive task before returning unread buffers.
|
||||
|
||||
## Remaining H-c work
|
||||
|
||||
H-c2 removes `Packet`, `BodyBytes`, `PacketHeaderOptional`, `List`, and
|
||||
single-fragment payload copies from the production decode path using borrowed
|
||||
memory with an explicit lifetime boundary. H-c3 writes fragment and packet
|
||||
framing directly into caller storage.
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue