instant client · indexeddb lock investigation
Why a frozen tab can block Instant auth
One tab is saving pending mutations. Android freezes it at the wrong moment. Another tab tries to load the current user and waits on the first tab's database lock. This is the mechanism and the smallest useful fix.
01The page and the database manager are separate
Chrome does not run everything for a tab in one place. The tab's code runs in a renderer process. Think of that as the worker responsible for the page: it runs JavaScript, timers, and browser API callbacks.
The actual IndexedDB engine lives in Chrome's shared browser process. Think of that as the database manager. It owns the data on disk and decides which tab holds each database lock. The page worker and database manager communicate by sending messages to each other.
02One local write requires a round trip
Instant calls store.put(value). The page worker first
serializes the value, then sends it to the database manager. The database
manager performs the write and sends a result back to the page worker.
IndexedDB normally waits for that result to be processed before deciding
that the transaction is finished.
Why wait? A result handler is allowed to add another write to the same transaction. Until the page worker processes the result, IndexedDB cannot assume that no more work is coming.
03Automatic commit depends on the writer tab
A readwrite transaction gets exclusive access to the object
stores it touches. Later transactions that need the same store, including
transactions from another tab, wait behind it.
With normal automatic commit, the database manager waits for the writer tab to process its request results. Only then does the page worker send the final message that no requests remain. The database manager commits the transaction and releases the lock.
That creates the dangerous dependency: releasing a shared database lock can require one particular tab to run again.
04A freeze turns that dependency into a cross-tab stall
Android can suspend a background tab's page worker at an arbitrary instruction. The tab has not closed, so Chrome still has a live database transaction associated with it. But the tab is no longer processing the result that normally leads to automatic commit.
Current Chromium has an internal recovery watchdog for some stalled, blocking transactions. In one local run it released the sibling tab after about 60 seconds. That watchdog is heuristic, browser-specific, and not a timeout Instant can configure or depend on. The user can still experience a long auth stall, and some freeze points appear to fall outside the watchdog's useful state.
Instant's client makes the window easier to hit under write load. A dirty
local value schedules persistence after a 100ms throttle, followed by a
bounded idle callback. This only batches local persistence; each
db.transact() is still sent to the server independently.
When persistence runs, the whole pendingMutations map is
rewritten into the kv store. Then a new tab boots, and
subscribeAuth waits on a read of currentUser
from that same store:
05Explicit commit removes the largest waiting window
IDBTransaction.commit() tells the database manager:
all requests have already been queued; no more are coming.
Instant knows this is true because multiSet() adds every
put() synchronously in one loop.
Once that commit message has been sent, the database manager can finish the queued writes and release the lock without waiting for the writer tab to process each result first.
The change in IndexedDBStorage is small because every
method already issues all its requests up front:
const tx = db.transaction(['kv'], 'readwrite');
const store = tx.objectStore('kv');
for (const [k, v] of pairs) store.put(v, k);
tx.commit?.(); // backend may now commit without us
06What remains after the small fix
Explicit commit does not eliminate every possible freeze point. The value still has to be serialized in the page worker, sent to the database manager, and followed by the commit call. Android could freeze the tab before it reaches that line.
That gives us a staged plan. First, add explicit commit because it is a small change that removes the largest known wait. Second, stop rewriting one ever-growing mutation value, so serialization stays small. Third, isolate auth from mutation persistence so a stuck mutation writer does not own the resource auth needs.
A timeout in the active tab can stop the UI from waiting forever, but it cannot release a lock owned by the frozen tab. Only Chrome's database manager can abort that transaction.