stdb: hold frames issued before the socket opens #113
No reviewers
Labels
No labels
area:defold
area:docs
area:infra
area:protocol
area:testing
kind:bug
kind:chore
phase:breadth
phase:core
phase:hardening
phase:slice
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
yuris/spacetimedb-defold!113
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/hold-pre-open-frames"
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?
Calling the SDK on the line after
connectcrashed with an error message from anative module the caller did not write:
connectreturns before the websocket handshake completes, but the send closurewas installed immediately after
websocket.connectreturned, so a frame issuedin that window reached extension-websocket's own
LuaSendguard(
websocket.cpp:562), which raises for any state that is notSTATE_CONNECTED.This was the only mistake in this SDK that did not explain itself. Forgetting
the websocket dependency, leaving off
ws://, and using a stale generatedmodule all produce messages naming the actual problem; this one named neither
the SDK nor readiness.
init.lua'sREADINESSnote documented the constraint,but documentation only helps someone who read it before writing the line, and
this is the line people write on instinct.
What changed
Frames issued before
EVENT_CONNECTEDare held and flushed, in order, when itarrives.
Nothing competes for that window:
settle_pendingre-sends nothing.ANSWERED_BY_REPLAYmeans stay quiet,something else will re-issue this — not re-issue it.
resubscribe_allruns off theInitialConnectionframe, which by definitionarrives on an open socket.
The caller is tracked either way:
connection.lua:747parks every request inpendingbefore callingsend, so a socket that dies without ever openingstill settles each held call through the same
"connection lost"sweep as anyother drop. The invariant at
connection.lua:876continues to hold.The flush is guarded because HTML5 delivers
EVENT_CONNECTEDtwice for onesocket — the emscripten
onopenhandler fires it directly(
emscripten_callbacks.cpp:11) and the shared update loop fires it again offthe same state transition (
websocket.cpp:871). Unguarded, the second flushwould re-send what the first one sent.
ready/outboxare per-socket locals insideopen(), so frames held for asocket that never opened cannot leak onto its replacement.
Verification
End to end, real engine, live 2.x server. A throwaway edit to
dev/example-chatmoved asubscribe_queryto the line afterconnect, thenreverted.
Before:
The raise also killed the socket, so the run reconnected and then timed out.
After:
This is what settles the one thing static reading could not:
EVENT_CONNECTEDdoes arrive in-engine, and the held frame flushes and gets answered.
Unit tests. Four new assertions in
test_init.luapin the hold, the flush,the order, and the double-
EVENT_CONNECTEDguard. Confirmed to fail without thefix:
With the fix:
13 passed, 0 failed.stylua --checkclean.The existing
connect sends BINARY framestest now opens the socket beforeasserting, since the frame no longer reaches the binding synchronously.
Behaviour change worth noting
A too-early call's error moves from an immediate raise at the callsite to the
handle or callback it already returns. The outcome still arrives — that is the
connection.lua:876invariant — but the stack trace at the offending line isgone. For the beginner this fix targets that is a trade rather than a
regression, and it is the reason
READINESSininit.luawas rewritten ratherthan deleted:
"connected"is still where anything depending on identitybelongs.