@instantdb/core boot (subscribeAuth, getAuth, first query) waits unboundedly
on reading the kv object store of instant_<appId>_<v>. If any other browsing
context on the same origin holds a readwrite transaction on that store, boot
blocks with no timeout and no fallback. A tab that the OS process-freezes
mid-transaction (Android's cached-app freezer; SIGSTOP on desktop) holds that
lock indefinitely — the browser never aborts it, because the transaction still
has a request in flight.
Observed in production on Android Chrome: user requests a magic link, the tab
sits in background, Android freezes it mid-kv-write; user opens the link in a
new tab; the new tab authenticates over the network fine but subscribeAuth
never fires — endless spinner until the frozen tab is killed or thawed.
Everything in the page (repro.html) uses only public APIs: init,
transact, tx, id, subscribeAuth, getAuth. Any random UUID works as
appId — the client creates its IndexedDB store and resolves auth locally
before/without network. The freeze is simulated with SIGSTOP, which is what
Android's app freezer effectively does to a cached renderer.
python3 -m http.server 8734
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--user-data-dir=/tmp/instant-repro-profile --remote-debugging-port=9223 \
http://127.0.0.1:8734/repro.html "http://127.0.0.1:8734/repro.html?role=holder"-
victim tab (
repro.html): logssubscribeAuth fired … boot completein ~30 ms. Baseline. -
holder tab (
?role=holder): an ordinary Instant client issuingtransact()every 25 ms. Pending mutations are persisted to thekvobject store on a ~100 ms throttle (PersistedObject,saveThrottleMs: 100). Each mutation carries a 64 KB payload so the persisted map — and with it each readwrite transaction — grows quickly; this widens the window you need to hit, it does not create it. Keep this tab focused for ~30 s so its timers aren't throttled. -
Find the holder's renderer PID (Chrome task manager, or
SIGSTOPeach renderer of the debug profile and see which tab stops responding), then:kill -STOP <holder-renderer-pid>
and reload the victim tab. The stop must land while a
kvsave is in flight — with the fat payloads that's roughly 1 in 5 attempts (kill -CONT, wait a second,-STOPagain, reload victim, repeat). -
Result when caught: the victim logs
… subscribeAuth still hasn't fired (auth boot blocked on kv)forever. In our run it stayed blocked for 198 s with no browser-side abort, then resolved within ~1 s ofkill -CONT.
- Graceful lifecycle freeze doesn't reproduce this. 30 attempts with
Page.setWebLifecycleState {state: "frozen"}(thechrome://discardsfreeze) never parked the lock — Chrome appears to let in-flight IndexedDB transactions settle before/while freezing. The indefinite variant needs a process-level freeze, which is what Android does to cached tabs. - An idle frozen tab is harmless. Only a freeze landing mid-transaction
parks the lock. Real tabs write
kvon every pending-mutation change, auth change, etc., so over hours of background life the window gets hit — as our production incident shows. - If the parked transaction has no request in flight (callback queued but unexecuted), Chrome aborts it after ~60 s ("Transaction timed out due to inactivity") and the victim recovers. With a request in flight there is no timeout at all.
@instantdb/core0.21.26 (pinned inrepro.htmlvia esm.sh)- Chrome 150.0.7871.182 (macOS, desktop repro), Chrome 150.0.7871.126 (Android 16, field incident + partial verification)
- Boot reads of
kv(auth, pendingMutations) should have a timeout + degraded path (e.g. proceed with network auth / in-memory storage and reconcile when the lock frees) instead of blockingsubscribeAuthindefinitely. - Expose
Storageinjection in@instantdb/react'sinit(core already accepts it;InMemoryStorageexists but isn't exported) so apps can opt out of shared IDB state where cross-tab locking is a hazard.