reconnect: a mass cold start still retries in lockstep #65
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#65
Loading…
Add table
Add a link
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?
Found by review of #62 (closes #19), and out of scope for it.
#19 spreads the reconnect backoff per client, keyed on the identity. But the spread comes from conn.identity, which is nil until a socket succeeds — so the one case it does not cover is the synchronised cold start: the server goes down, the whole player base relaunches, and every client retries at the flat 1, 2, 4, 8, 16, 30.
Measured on the #19 branch:
The data needed is already loaded three lines above the callsite. connect reads persisted.token from storage but never persisted.identity, even though the identity IS written to storage (dev/test/test_connection.lua asserts saved.identity), and connection.new initialises identity = nil.
So the fix looks like a one-liner: read the persisted identity and pass it to spread_for for the pre-connection attempts. The reason it was not folded into #19 is that it is a real judgement call rather than an oversight — the on-disk identity is not server-confirmed at that point, so this means spreading on an unverified value. Cheap, but it deserves deciding on its own terms.
Worth noting this is arguably the MORE important herd: a server restart that drops live sockets is covered by #19, but a server outage that makes everyone relaunch is not, and it is the one that coincides with the server being least able to absorb it.
Closed by #72.
connectnow seeds the reconnect spread from the persisted identity when there is one, so a fleet cold-starting into an outage scatters instead of sharing the flat 1/2/4/8/16/30 ladder.persisted.identitywas already being written byconnection.lua'sIdentityTokenhandler and never read back; it is now read for this one purpose and confined to thebackoff_spreadargument.On the judgement call this issue named: the identity is used although it is not server-confirmed at that point. A wrong identity gives a wrong-but-deterministic slot, which is strictly better than every client sharing one ladder — the spread only needs a value that differs between clients, not a correct one. The wire identity takes over the moment
IdentityTokenlands, viaconn.identity or persisted_identity. A genuinely fresh install has nothing persisted and keeps the exact ladder, which is right: one new client is not a herd.One real bug came out of review.
backoff_spreaddoes#identitythenidentity:byte(i), and the storage slot is shared (seesys_storage), so a caller's ownidentitykey or a hand-edited save could put a number or a table there — turning every socket drop into a raise from insideschedule_reconnect, i.e. out of the engine's websocket callback:Now type-checked at the read, following
auth.lua's guard on the same seam; a non-string degrades to the exact ladder. Tests cover the number and table cases and fail without the check.Also corrected two now-false statements this change created:
sys_storage's comment claimingconnectreads onlypersisted.token, andbackoff_spread's docstring clause "the first connect is a cold start, not a herd" — which was the policy this issue exists to overturn.Verified on
main@52dd72d:8 passed, 0 failed,stylua --checkclean, native and HTML5 conformance pass. Reconnect prose and theconnection.backoff_spreadAPI row in the README updated.