-
Notifications
You must be signed in to change notification settings - Fork 95
[HOLD] feat: add visibilitychange probe for proactive IDB health check #788
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
a4ebed8
74d04d6
b63de8b
74a7370
f492eac
00e8667
af905f9
7ff3bac
e3a8cc5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,11 @@ function getBudgetedHealErrorLabel(error: unknown): string { | |
| return 'unknown'; | ||
| } | ||
|
|
||
| /** Union of all error types indicating a stale/dead IDB connection. Used by the visibilitychange probe. */ | ||
| function isStaleConnectionError(error: unknown): boolean { | ||
| return isInvalidStateError(error) || isBackingStoreError(error) || isConnectionLostError(error); | ||
| } | ||
|
|
||
| // This is a copy of the createStore function from idb-keyval, we need a custom implementation | ||
| // because we need to create the database manually in order to ensure that the store exists before we use it. | ||
| // If the store does not exist, idb-keyval will throw an error | ||
|
|
@@ -127,6 +132,53 @@ function createStore(dbName: string, storeName: string): UseStore { | |
| return result; | ||
| } | ||
|
|
||
| // Proactive IDB health check when tab returns to foreground. | ||
| // Safari kills IDB connections for backgrounded tabs. By probing before | ||
| // the ReconnectApp write storm hits, we drop the stale dbp early so the | ||
| // first real operation opens a fresh connection instead of failing. | ||
| if (typeof document !== 'undefined') { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to have this condition? As IDB is used in web platforms only this is supposed to be always defined, right? |
||
| document.addEventListener('visibilitychange', () => { | ||
| if (document.visibilityState !== 'visible' || !dbp) { | ||
| return; | ||
| } | ||
|
|
||
| Logger.logInfo('IDB visibilitychange probe: tab became visible, checking connection health', {dbName, storeName}); | ||
|
|
||
| const probePromise = dbp; | ||
|
|
||
| const dropCacheIfStale = (error: unknown) => { | ||
| if (dbp !== probePromise || !isStaleConnectionError(error)) { | ||
| return; | ||
| } | ||
| Logger.logAlert('IDB visibilitychange probe: stale connection detected, dropping cached connection', { | ||
| dbName, | ||
| storeName, | ||
| errorMessage: error instanceof Error ? error.message : String(error), | ||
| }); | ||
| dbp = undefined; | ||
| }; | ||
|
|
||
| probePromise.then((db) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the tab becomes visible while Useful? React with 👍 / 👎.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @elirangoshen have you checked this comment? Is it still relevant? |
||
| if (dbp !== probePromise) { | ||
| return; | ||
| } | ||
| try { | ||
| const tx = db.transaction(storeName, 'readonly'); | ||
| const probeStore = tx.objectStore(storeName); | ||
| const req = probeStore.count(); | ||
| req.onsuccess = () => { | ||
| Logger.logInfo('IDB visibilitychange probe: connection is healthy', {dbName, storeName}); | ||
| }; | ||
| req.onerror = () => { | ||
| dropCacheIfStale(req.error); | ||
| }; | ||
| } catch (error) { | ||
| dropCacheIfStale(error); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| // Handles three recoverable error classes: | ||
| // 1. InvalidStateError — connection closed between getDB() resolving and db.transaction(). | ||
| // Retry once with a fresh connection. No budget limit (transient, always worth one reopen). | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove mentions of ReconnectApp