Skip to content

[Due for payment 2026-04-14] [$250] [Sentry] fix The database connection is closing issue #84192

@mountiny

Description

@mountiny

Sentry issue https://expensify.sentry.io/issues/7092239780

Thread https://expensify.slack.com/archives/C05LX9D6E07/p1770496102994319

Please re-state the problem that we are trying to solve in this issue.

When Onyx operations attempt to create an IndexedDB transaction, the browser may have already closed the database connection, causing:
InvalidStateError: Failed to execute 'transaction' on 'IDBDatabase': The database connection is closing.

This affects ~58,700 occurrences across 430 users on all browsers (Chrome: 31K, Safari: 18K, Firefox: 5K, Mobile Safari: 3K, Edge: 1K). The error is an unhandled promise rejection.

What is the root cause of that problem?

The existing db.onclose handler (line 22) sets dbp = undefined when the browser closes the connection, which protects future calls to getDB(). However, any operations that have already resolved the dbp promise still hold a stale db reference and proceed to call db.transaction() at line 59 — which throws InvalidStateError on the now-closing connection.

This can be triggered by:

• The browser closing idle IDB connections (Safari is especially aggressive, but Chrome/Firefox do this too under memory pressure or tab suspension)
• Another tab triggering a version upgrade (versionchange event — currently unhandled)
• The verifyStoreExists path closing the DB (line 39) while concurrent operations still hold the old reference

What changes do you think we should make in order to solve the problem?

All changes are in lib/storage/providers/IDBKeyValProvider/createStore.ts:

  1. Retry mechanism with a single retry on InvalidStateError
       — Extract the transaction chain (getDB → verifyStoreExists → db.transaction) into openTransactionAndExecute()
       — Wrap the call in .catch(): if the error is InvalidStateError, reset dbp = undefined and retry once with a fresh connection
       — If the retry also fails, the error is thrown normally and still surfaces in Sentry exactly as it does today

  2. Diagnostic logging on every retry (does NOT hide the issue)
       — Log at alert level with: dbName, storeName, txMode, and closedBy
       — closedBy tracks why the connection was closed: 'browser' (user-agent closed it), 'versionchange' (cross-tab upgrade), 'verifyStoreExists' (missing store path), or 'unknown'
       — This gives us more visibility than today (where we only get a crash count with no context)

  3. onversionchange handler
       — When another tab triggers a DB version upgrade, proactively close the connection and reset dbp
       — Without this, cross-tab upgrades can block indefinitely and in-flight operations crash on the stale connection

  4. onclose handler now logs
       — The existing onclose handler now logs when the browser unexpectedly closes the connection, giving us data on browser-initiated closures even when no retry is needed

Key safety guarantees:

• Data is never lost — the transaction fails before any data is written; the retry completes successfully with a valid connection
• Only one retry is attempted — no infinite loops
• If the retry fails, the error propagates normally and Sentry captures it as before
• Normal operations (no InvalidStateError) are completely unaffected — zero performance impact
• The closedBy logging gives us instrumentation to continue investigating the root cause of the spike

cc @fabioh8010

Issue OwnerCurrent Issue Owner: @mkhutornyi
Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~022029317821194916452
  • Upwork Job ID: 2029317821194916452
  • Last Price Increase: 2026-03-04

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.ExternalAdded to denote the issue can be worked on by a contributorWeeklyKSv2

Type

No type
No fields configured for issues without a type.

Projects

Status

LOW

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions