fix(net): a node-interest burst overflows the outbound queue and silently disconnects the player #291
Labels
No labels
alpha:wave-0
alpha:wave-1
alpha:wave-2
alpha:wave-3
area:assets
area:combat
area:ecology
area:infra
area:render
area:scripting
area:ui
area:world
enhancement
epic
migration
post-alpha
roadmap
tech-debt
type:bug
type:chore
type:design
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
marco/IsoMmo#291
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A long
/tpdrops the connection. Reproduced 3x live, then instrumented to root cause (instrumentation reverted; the tree is clean).Symptom
Client:
Connection lost: The remote party closed the WebSocket connection without completing the close handshake.Server: a plain "disconnected", no error, no metric. The teleport commits — you log back in at the destination.
Root cause
Instrumented, teleporting to (2192,2560) (
northern_grassland):349
NaturalNodeSpawnedin ONE tick (WorldTicksends one message per node).WebSocketConnectionqueues intoChannel.CreateBounded(OutboundCapacity)withOutboundCapacity = 256and treats a full queue as "client hopelessly behind" ->Close(), which cancels the receive loop and kills the socket with no close frame.The 349 is structural, not incidental:
EcologyScatteryields one candidate perspacing x spacingslot, sotemperate_pine spacing 6= 1/36 = 2.78%; the node view block isSpawnerChunkSize 16xNodeViewChunkRadius 3= 7x7 chunks = 112x112 = 12,544 cells. 12,544/36 = 348.4.It is intermittent because the send pump drains concurrently: it survives when the client is idle (a login in the same block survived 3/3) and fails when the client is busy rebuilding terrain mesh — i.e. exactly during a teleport. Walking is safe: the block shifts by one chunk, not a whole block.
Why it matters beyond
/tpThe same reconcile runs on connect (
InterestManager.Enterstarts the known set empty), so the burst is a coin flip on login too. Any of these makes it universal rather than intermittent: filling the empty biomes with species rows, moving the spawn intograss/northern_grassland, raisingNodeViewChunkRadius(which its own comment invites for 4K), or a second player in the same block.Scope
NaturalNodesSpawned(IReadOnlyList<NaturalNodeState>)/NaturalNodesRemoved(IReadOnlyList<Guid>), one guarded send each, mirrored in the client dispatcher. BumpProtocolVersion.Current(21 today) in the SAME change.isommo_outbound_overflow_totalcounter next to the malformed-message one.EcologyOptions.NodeViewChunkRadius's comment that a block is now one message, so raising it is safe.Do NOT switch the channel to
Waitmode — that blocks the sim thread, which is the exact thing the class exists to prevent.Definition of Done
WorldTickburst test: an observer standing in a saturated node block produces at most oneNaturalNodesSpawned, and the whole reconcile emits fewer messages than the queue capacity.