feat: seed the reconnect spread from the persisted identity #72
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!72
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/65-cold-start-spread"
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?
Closes #65.
connectalready spreads each reconnect delay by a deterministic 50–99.95% keyed on the identity, so a server restart does not bring the whole player base back in one instant. Butbackoff_spreadwas fedconn.identity, which is nil until a socket has succeeded — so the one case the spread exists for, a fleet relaunching into an outage, got no spread at all and retried on the flat 1/2/4/8/16/30 ladder in lockstep.persisted.identitywas already being written byconnection.lua'sIdentityTokenhandler and never read back. This reads it back, for this one purpose:The judgement call
The persisted identity is not server-confirmed at that point — nothing has connected yet. Using it anyway is deliberate. The failure mode of a wrong identity is a wrong-but-deterministic slot, which is strictly better than every client in the fleet sharing one ladder; the spread only asks for a value that differs between clients, not for a correct one. The
orordering means the wire identity takes over the momentIdentityTokenlands, and nothing else in the SDK readspersisted_identity— the comment on the line says so, because that is the constraint that keeps this safe.A genuinely fresh install has nothing persisted and keeps the exact ladder. That is correct, not a gap: one new client is not a herd.
Tests
dev/test/test_init.lua, reusing the file's existingwebsocket/timerstubs:a persisted identity spreads the very first reconnect, before any socket has succeeded— storage holds an identity, the socket errors immediately, and thereconnectingdelay equalsbackoff_delay(1, base, cap, backoff_spread("0xabc…", 1))exactly. The expectation is computed by calling the real functions, not hardcoded.which is not the lockstep ladder slot the whole fleet would share— and it differs from the unspread attempt-1 value.nothing persisted keeps attempt 1 on the exact ladder value—storage = false→ exactly1.the live wire identity outranks the persisted one once IdentityToken lands/and the persisted identity is genuinely no longer the one being keyed on— storage identity ≠ wire identity, asserted after a successful connect so theorordering cannot silently flip.Proof the tests discriminate — reverting the callsite to
conn.identityalone:Restored:
7 passed, 0 failed.stylua --checkclean.README
The reconnect prose said a cold-start fleet "retries on the exact 1s/2s/4s ladder until one attempt succeeds (see #65)" — now stale. Rewritten to state that the spread seeds from the persisted identity, that the saved identity is not server-confirmed and why that is still fine, and that only a fresh client (nothing persisted, or
storage = false) uses the exact ladder.The
connection.backoff_spreadrow in the API table repeated the stale claim as "nilbefore an identity exists"; that clause now says it is nil only for a client that has never connected and saved one.connection.lua's own docstring for the pure function was left alone — "returns nil before an identity exists" is a true statement about nil-in/nil-out, and that file was out of scope.All three findings actioned. The first was a real crash path and the review is right that the rigor already existed one file over.
A non-string persisted identity raised out of the reconnect callback.
backoff_spreaddoes#identitythenidentity:byte(i), so a save file holdingidentity = 12345turned every socket drop into a raise from insideschedule_reconnect— i.e. out of the engine's websocket callback. Reproduced exactly as reported:Fixed by type-checking at the read, following
auth.lua:149-152, which guards the same shared slot for the same reason and whose comment names it:A non-string now degrades to the exact ladder, which is what a client with no identity gets anyway. Two tests added —
a number identity degrades to the ladder, not a raiseandand neither does a table identity reach the spread— and they fail with the type check removed (output above), so they discriminate.The
sys_storagecomment premise was falsified 58 lines below by this patch. Corrected to name both fields, since the conclusion (an empty table yields nil for both) still holds.backoff_spread's docstring — you are right that I defended only the first clause. "The first connect is a cold start, not a herd" is the policy #65 exists to overturn, and the README this patch rewrites now says the opposite. Replaced with wording that describes the pure function's contract without asserting the policy:Being in a file the PR otherwise didn't touch was not a good enough reason to leave a false sentence in place.
Suite
7 passed, 0 failed,stylua --checkclean.