flaky: GameServer WebSocket integration tests time out under parallel load #43
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#43
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?
GameServerIntegrationTestsoccasionally fail withOperationCanceledExceptionfromTestWebSocket.ReceiveAsync(a receive stalls past the 20s timeout) — seen on CI run 39 (Item_EquipAndUnequip_UpdateTheSlot) and once locally in a full paralleldotnet test. Passing in isolation and on a re-run confirms it is load-induced flakiness, not a logic bug (CI run 40, a superset of the same code, is green).Likely root cause: thread-pool starvation. xUnit runs test classes in parallel; each
GameServerFactoryspins up a full host with a 100msGameLoopServicetick loop (+ the 15sPersistenceService). Many concurrent hosts starve the pool, so a broadcast (InventoryState) is delivered later than the receive window.Scope (investigate, then pick)
PersistenceServicein tests that dont exercise it).MaxParallelThreads/ a dedicated non-parallel collection).ReceiveUntilAsyncresilient to a slow first message rather than throwing on the inner per-receive timeout.Definition of Done
Stopgap shipped in #40 (commit
7f8d609): raised the WebSocket receive timeout from 20s to 60s. Confirmed the failure isItem_EquipAndUnequip_UpdateTheSlottiming out (OperationCanceledExceptionfromTestWebSocket.ReceiveAsync) under load on the shared runner — CI run 39/41 red, run 42 green after the bump. Could not reproduce in 38 local runs (macOS + Linux down to 0.5 CPU), so this is a busy-shared-box flake, not a product bug.The 60s ceiling only widens the window; this issue stays open for the real fix — stop depending on wall-clock delivery (e.g. poll a server-side condition, or reduce concurrent test-host pressure). A reliable reproduction (a genuinely CPU-starved runner) is the first task.
Root cause found — and it was NOT thread starvation (my earlier hypothesis was wrong). Fixed in #40, commit
ee7a00e.The real bug: on connect,
GameSessionHandlercalledinterest.Enter(making the connection visible to the tick's AoI reconcile) before sendingWelcome. Under load, the handler thread could be preempted betweenEnterand theWelcomesend, letting a tick broadcastItemSpawned/PlayerJoinedto the connection ahead of itsWelcome. The integration test (and a real client) treatsWelcomeas the first message and discards anything before it — dropping the sword'sItemSpawned. Because the AoI reconcile marks entities as known when it emits them, the dropped spawn is never re-sent, so the client hangs until the receive timeout (the 60s bump just made it wait longer).Proven deterministically: widening the Enter→Welcome window with a 300ms delay makes
Item_EquipAndUnequip_UpdateTheSlotfail every time with the exact CIOperationCanceledException; movingEnterafterWelcomemakes it pass even with that delay.Fix: send the initial snapshot (
Welcome+InventoryState) first, join the AoI second — soWelcomeis always the client's first message. Also reverted the 20s→60s receive-timeout stopgap. Closing when #40 merges.