Bug 226547 - indexedDB.open() sometimes hangs forever in pending state on first page load in iOS 14.6
Summary: indexedDB.open() sometimes hangs forever in pending state on first page load ...
Status: RESOLVED DUPLICATE of bug 224623
Alias: None
Product: WebKit
Classification: Unclassified
Component: New Bugs (show other bugs)
Version: Safari 14
Hardware: iPhone / iPad iOS 14
: P2 Normal
Assignee: Sihui Liu
URL:
Keywords: InRadar
Depends on:
Blocks: 224305
  Show dependency treegraph
 
Reported: 2021-06-02 11:52 PDT by Simon Taylor
Modified: 2021-07-21 16:11 PDT (History)
25 users (show)

See Also:


Attachments
QR code to help reproducing the bug: https://tango-bravo.net/safari-indexeddb-bug/bug.html (5.35 KB, image/png)
2021-06-02 11:52 PDT, Simon Taylor
no flags Details
QR code for the "workaround" page: https://tango-bravo.net/safari-indexeddb-bug/workaround.html (5.37 KB, image/png)
2021-06-02 11:53 PDT, Simon Taylor
no flags Details
test case, html (780 bytes, text/html)
2021-06-02 11:54 PDT, Simon Taylor
no flags Details
test case, js (20 bytes, application/x-javascript)
2021-06-02 11:54 PDT, Simon Taylor
no flags Details
test case,"workaround" (777 bytes, text/html)
2021-06-02 11:55 PDT, Simon Taylor
no flags Details
Screen recording showing the bug in action (5.56 MB, video/quicktime)
2021-06-02 11:59 PDT, Simon Taylor
no flags Details
Screen recording showing the bug affects macOS Safari too (1.11 MB, video/mp4)
2021-06-03 01:24 PDT, Simon Taylor
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Simon Taylor 2021-06-02 11:52:07 PDT
The first indexedDB.open() call made by a page appears to be flaky on first load in iOS 14.6.

Its readyState stays as "pending" forever, and none of the event handlers (including "onblocked") are called.

I can easily reproduce on iOS 14.6 on an iPhone 12 mini when loading a URL from scanning a QR code.

1) Clear all tabs in Safari
2) Swipe up to return to home screen
3) Open camera, scan QR code for https://tango-bravo.net/safari-indexeddb-bug/bug.html (will add a QR as an attachment)
4) Note that no onsuccess etc logs are displayed (can connect inspector at this point to verify readyState is pending)
5) Hit refresh. This time the handlers are called correctly.

A workaround appears to be just to attempt to open a dummy indexedDB first that we don't care about in the main page. That one sometimes fails too (less reproducible for me) but the second one always appears to work.

https://tango-bravo.net/safari-indexeddb-bug/workaround.html has a demo page of that, QR code attached here too.

The closest existing bug I could find was Bug 171049, which I note saw some recent activity. However for me this doesn't depend on user state at all - reproduces even after clearing website data. This one appears to be new with iOS 14.6 (a lot of our sites broke 100% reproducibly when freshly launched from a QR code due to this underlying issue).
Comment 1 Simon Taylor 2021-06-02 11:52:46 PDT
Created attachment 430380 [details]
QR code to help reproducing the bug: https://tango-bravo.net/safari-indexeddb-bug/bug.html
Comment 2 Simon Taylor 2021-06-02 11:53:34 PDT
Created attachment 430381 [details]
QR code for the "workaround" page: https://tango-bravo.net/safari-indexeddb-bug/workaround.html
Comment 3 Simon Taylor 2021-06-02 11:54:33 PDT
Created attachment 430382 [details]
test case, html
Comment 4 Simon Taylor 2021-06-02 11:54:59 PDT
Created attachment 430383 [details]
test case, js
Comment 5 Simon Taylor 2021-06-02 11:55:22 PDT
Created attachment 430384 [details]
test case,"workaround"
Comment 6 Simon Taylor 2021-06-02 11:59:20 PDT
Created attachment 430385 [details]
Screen recording showing the bug in action
Comment 7 Radar WebKit Bug Importer 2021-06-02 18:31:08 PDT
<rdar://problem/78792951>
Comment 8 Simon Taylor 2021-06-03 01:21:36 PDT
I've also just successfully reproduced this in the latest macOS Safari 14.1.1, on macOS Big Sur 11.4.

For that make sure Safari isn't running and then run `open https://tango-bravo.net/safari-indexeddb-bug/bug.html` in a terminal. Not 100% reproducible for me, but relatively frequent.

Attaching a screen recording.
Comment 9 Simon Taylor 2021-06-03 01:24:37 PDT
Created attachment 430448 [details]
Screen recording showing the bug affects macOS Safari too
Comment 10 Simon Taylor 2021-06-03 01:30:42 PDT
One more thing of interest - repeatedly looping through quitting Safari and re-opening from a terminal sometimes shows "onupgradeneeded" callback, even without clearing the website data, and without changing the version being requested.

So either the hanging requests sometimes corrupt the on-disk database, leading to an "upgradeneeded" on the next attempt, or it's another symptom of the general flakiness at early startup around indexedDB.
Comment 11 Simon Taylor 2021-06-03 02:10:15 PDT
Unable to reproduce in Safari Technology Preview 125, so perhaps this is already fixed upstream?
Comment 12 Jason 2021-06-03 11:39:52 PDT
I'm seeing this issue as well on Safari 14.1.1 Big Sur and also seeing it fixed in the Tech Preview 125. 

Thank you so much for posting your workaround! I have been pulling my hair out trying to figure out what's going on.
Comment 13 Olivier 2021-06-04 19:47:15 PDT
Thank you so much for posting this workaround. That bug drove my crazy for a couple days... Couldn't figure out what was going on !
Comment 14 Simon Bluhm 2021-06-06 14:41:29 PDT
I've encountered the same problem with my web app on iOS 14.7 beta. IndexedDB is not opening when Safari is loaded for the first time. The same is true for web apps added to the home screen. A page refresh fixes this problem. The problem re-occurs when Safari is closed and then re-opened.

Would be great to see an urgent fix as my web app is working offline first and depends on IndexedDB to load data.
Comment 15 Simon Taylor 2021-06-07 01:46:30 PDT
> I've encountered the same problem with my web app on iOS 14.7 beta.
> IndexedDB is not opening when Safari is loaded for the first time. The same
> is true for web apps added to the home screen. A page refresh fixes this
> problem. The problem re-occurs when Safari is closed and then re-opened.
> 
> Would be great to see an urgent fix as my web app is working offline first
> and depends on IndexedDB to load data.

Does the workaround work for you too? It basically boils down to adding another open call for a database that you don't care about before the one that you need to work. Something like indexedDB.open("dummy", 1) appears to be enough for me, but I'd be interested if that works for PWA added to the home screen too.

Of course I'd also welcome an update to fix this ASAP but as Safari is built into the iOS image, it means that updates are dependent on OS updates, so workarounds are usually required in practice for these kind of things. Apple also only tends to pull updates from the upstream webkit project every 6 months or so I believe.

On a separate note, I thought I'd add that for us this was triggered by Emscripten's filesystem support that uses indexedDB - took a while to discover the hang on load was due to that run dependency never being resolved. It might not affect pages built with the current Emscripten (we're using quite an old version) but thought I'd mention it explicitly in case anyone else is searching for Emscipten WebAssembly builds hanging on first page load.
Comment 16 Simon Taylor 2021-06-07 01:54:55 PDT
(In reply to Simon Taylor from comment #15)
> On a separate note, I thought I'd add that for us this was triggered by
> Emscripten's filesystem support that uses indexedDB

Quick correction, the problematic Emscripten indexedDB usage was actually the implement of its Fetch API, that uses indexedDB for caching and persistent storage:
https://github.com/emscripten-core/emscripten/blob/e333edadae94c34829a25c7f930937b0421fc73b/src/Fetch.js#L28

One nice feature of the workaround is we could add it to the main page, which avoided the need to modify the Emscripten runtime code.
Comment 17 Simon Bluhm 2021-06-07 11:59:12 PDT
(In reply to Simon Taylor from comment #15)
> Does the workaround work for you too?

Yep the workaround works for me as well in my PWA. It really confused me first because I use IndexedDB on the main thread and in a worker. The one in the worker was always working fine as it always opens after the one from the main thread. I first thought it was due to changes in my code but then found your bug report with the workaround and it made a lot more sense, so thanks for posting that otherwise I would probably still be debugging my code!!
Comment 18 John Hiesey 2021-06-14 15:28:18 PDT
I can also reproduce this on Safari 14.1.1 on macOS 11.4

Here's a minimal reproduction html file:

<!doctype html>
<html>
<head>
	<title>indexedDB fails to open</title>
</head>
<body>
	<script>
		const request = indexedDB.open('testdb', 1)
		request.addEventListener('success', () => {
			alert('success')
		})
		request.addEventListener('error', () => {
			alert('error')
		})
	</script>
</body>
</html>

Expected behavior: A "success" alert appears
Actual behavior: Nothing happens

This definitely should work! I confirmed that it works in current versions of Chrome and Firefox.
Comment 19 Feross Aboukhadijeh 2021-06-14 15:50:54 PDT
This IndexedDB bug broke our website https://wormhole.app for all Safari users. 

The most basic "hello world" usage of IndexedDB appears to not work on the first page load. If you refresh it appears to start to working. 

Since this bug is so easy to trigger by just opening a database, it likely affects *many* other sites as well.
Comment 20 Chris Dumez 2021-06-14 20:13:50 PDT
I have bisected this to:
http://trac.webkit.org/changeset/276556/webkit
Comment 21 Jake Archibald 2021-06-16 04:52:40 PDT
Is there a workaround? I've got multiple sites and libraries impacted by this.

The first comment mentions "the second [connection] always appears to work", is this true, or is it also a race?
Comment 22 Thomas Steiner 2021-06-16 04:56:36 PDT
"The workaround is referencing window.indexedDB before opening it."—https://twitter.com/html5test/status/1404687119570903041
Comment 23 Jake Archibald 2021-06-16 05:10:42 PDT
Right, but I'm looking for someone from the WebKit/Safari team to confirm that. 

I'm worried it's just another race, and I'll go to the effort of patching libraries and sites and still be left with the problem.
Comment 24 Jake Archibald 2021-06-16 05:14:23 PDT
Like, does:

(indexedDB && indexedDB).open("foo");

…100% work around the issue?
Comment 25 John Hiesey 2021-06-16 10:09:59 PDT
No, just referencing indexedDB right before opening doesn't work around the issue. Setting a timer between referencing and opening also doesn't work reliably, even if the timer runs for several seconds.

I haven't yet seen any failures when referencing indexedDB in the top level scope of a module (i.e. when a script is loaded) and then opening a db after network requests or user interaction. But I have no idea if it's truly reliable, and isn't a general purpose workaround.
Comment 26 Sihui Liu 2021-06-16 10:50:39 PDT
Hi,

Thanks for the bug report! The issue is that some IndexedDB messages are received in another process (network process) before it is ready to handle them. This is an issue in macOS Big Sur 11.4 and iOS 14.6 and we are working on a fix.
 
I will make an update if the fix is available in build.

To work around it, one way is to start some IndexedDB activities like deleting a database that does not exist, so another process will be notified and start preparation for IDB activities ahead. If this still not works, you may try to add some delay between dummy operation and your real database operations, like:

indexedDB.deleteDatabase('dummy-database');
setTimeout(indexedDBFunction, 1000);
Comment 27 Jake Archibald 2021-06-17 04:10:25 PDT
Will indexedDB.databases() send the same signal?

My current draft workaround is:

async function workaroundSafariBug() {
  if (
    !(
      navigator.userAgent.includes('Safari/') &&
      navigator.userAgent.includes('Version/')
    )
  ) {
    // No point putting other browsers through this mess.
    return;
  }

  let intervalId;

  await new Promise((resolve, reject) => {
    const tryIdb = () => indexedDB.databases().then(resolve, reject);
    intervalId = setInterval(tryIdb, 100);
    tryIdb();
  });

  clearInterval(intervalId);
}
Comment 28 Jake Archibald 2021-06-17 06:53:03 PDT
I can confirm the workaround works. It's better than polling open/delete, since it doesn't have side effects. Slightly more readable version:

async function workaroundSafariBug() {
  const isSafari =
    /Safari\//.test(navigator.userAgent) &&
    !/Chrom(e|ium)\//.test(navigator.userAgent);

  // No point putting other browsers through this mess.
  if (!isSafari) return;

  let intervalId;

  await new Promise((resolve, reject) => {
    const tryIdb = () => indexedDB.databases().then(resolve, reject);
    intervalId = setInterval(tryIdb, 100);
    tryIdb();
  });

  clearInterval(intervalId);
}
Comment 29 Simon Taylor 2021-06-17 07:00:44 PDT
Thanks for posting that workaround Jake, nice idea to avoid side effects and wait until it succeeds.

I haven't verified this myself, but wWorth noting on iOS this bug may well apply to all browsers as they will all be based on the same WebKit engine under the hood, so the Safari test may be a bit too restrictive.
Comment 30 Jake Archibald 2021-06-17 09:17:05 PDT
Chrome on iOS doesn't add "Chrome/" or "Chromium/" to the user agent string, so it should be fine.
Comment 31 Jake Archibald 2021-06-17 09:34:34 PDT
Here's a microlibrary with the fix https://www.npmjs.com/package/safari-14-idb-fix
Comment 32 Sihui Liu 2021-06-17 09:39:14 PDT
Thanks for posting the workaround; indexedDB.databases() will work too.

The issue should be fixed in macOS Monterey and iOS 15 developer beta. Please test on that. For builds before that and after iOS 14.6 (or macOS 11.4), you may use workarounds posted above.
Comment 33 Sihui Liu 2021-06-17 10:08:05 PDT

*** This bug has been marked as a duplicate of bug 224623 ***
Comment 34 Paolo Macco 2021-06-18 02:44:32 PDT
The provided workarounds don't work for me.
I'm using indexedDB through the localforage library and the Promise is never resolved, even if the workaround methods have been passed.
Moreover, if I try to access the db content through the "Storage" tab in the developer console, it is empty (it doesn't load anything with no messages/spinners visible) even if I'm 100% sure it isn't (the rare cases in which the indexedDB works, in fact, I see there are data in the db)
Comment 35 Jake Archibald 2021-06-18 03:11:38 PDT
Are you seeing the bug on https://static-misc-3.glitch.me/safari-idb-bug-workaround/fixed.html ?
Comment 36 Jake Archibald 2021-06-18 03:31:05 PDT
Paolo Macco: Looking at localForage, I think it creates the idb connection eagerly, so a 'wait' workaround doesn't help, since it isn't waiting.

You could use https://www.npmjs.com/package/idb-keyval as an alternative (disclaimer: I'm the maintainer). It's much smaller than localForage (250 bytes vs 7kB), and it doesn't try to connect until you call the first method, so the workaround I posted here works fine.
Comment 37 Paolo Macco 2021-06-18 07:26:55 PDT
After further investigations on Safari 14.1.1 on MacOS, I've found some more interesting details if you use the same indexedDB in multiple pages of your domain (note that I've tested all this scenarios with the latest workaround provided):

1) If you change page with the developer console open, the indexedDB is forever pending (you can verify that the db content in the storage tab is not loaded...as if it is empty). You have to close the tab to let it work again.
1b) Sometimes the issue described above happens even if you reload the same page, but this is not deterministic...it needs more investigations
2) If you WRITE on the indexedDB and then you change page, the indexedDB is forever pending. You have to change again url (if you are in localhost/index.html page, you can navigate to localhost to let the indexeddb work, even if they are the same file...). I think this issue happens with other actions like clearing the db or removing an entry, but I'm not sure.

In other scenario, it seems that the workaround provided works properly.

@Jake Archibald: I've tried replacing localforage with idb-keyval but I've obatined the same results, so the issues are not related to the library used.
Comment 38 Jake Archibald 2021-06-18 07:40:31 PDT
I can't recreate those issues. Do you have a test page and some concrete steps to follow?
Comment 39 Paolo Macco 2021-06-18 09:07:18 PDT
@Jake: I'm trying to isolate the issue in a sample project, because until now I've tried only in my live project and I think there is something else that triggers the problem.... not clear what 😅
Comment 40 Jason 2021-06-18 09:28:44 PDT
This fix worked for me. It's essentially the same idea that Simon Taylor posted to do some dummy idb interaction before your real work. Run this before doing any other idb work:

private fixSafariIndexedDB(): void {
  if (!window.indexedDB) return;
  const dummyDbName = 'safariIdbFix';
  window.indexedDB.open(dummyDbName);
  window.indexedDB.deleteDatabase(dummyDbName);
}
Comment 41 Lee Robertson 2021-06-18 09:30:50 PDT
Does anyone feel https://bugs.webkit.org/show_bug.cgi?id=197050 is related?
Comment 42 Paolo Macco 2021-06-21 04:30:59 PDT
I've found what the problem is.
If you change page while the indexedDB has not finished writing on the store, the browser doesn't load the indexedb and the request is forever pending.

You can try this example https://60d07525d461cb0007612310--distracted-ride-d4bea8.netlify.app/
If you try this on Chrome browser, for example, it works properly (after you are redirected to the landing.html page, you receive an alert). On Safari it doesn't work. You don't see that alert and you can't logout because the indexedDB hasn't been loaded.

You are in the scenario I described in my previous comment (comment 37).

To recreate the issue, I've put a call to the indexedDB (wrapped in a localforage call) inside the onPause listened, which is fired when you change page.

I've not found (yet) any workaround to avoid the issue.
Comment 43 Binyamin 2021-06-21 22:17:52 PDT
Have you tried to wrap indexedDB inside requestAnimationFrame() and see if it workarounds the issue?
Comment 44 Paolo Macco 2021-06-22 02:33:21 PDT
With windwos.requestAnimationFrame wrapping the indexedDB operation, it seems to work...

Here the example https://60d1a9b8e42616000856891f--distracted-ride-d4bea8.netlify.app/

Thank you @Binyamin.


However, I noticed that the 'pause' event is not fired on Chrome when changing/reloading page, but on Safari it is.
Comment 45 Binyamin 2021-06-22 04:44:10 PDT
Can we expect patch for this bug fix?
Comment 46 Sam Sneddon [:gsnedders] 2021-07-13 07:40:03 PDT
(In reply to Binyamin from comment #45)
> Can we expect patch for this bug fix?

Apple does not comment on future releases.
Comment 47 Leo Balter 2021-07-21 15:42:06 PDT
This bug is flagged as resolved being a duplicate of bug 224623, which has a patch already in the nightly version. Seems also that this blocked bug 224305, which is now set as fixed with a patch.

I quickly borrowed an example from a comment above and it doesn't seem to work in the latest Safari Technology Preview (nightly) build. Maybe that's something else? https://60d07525d461cb0007612310--distracted-ride-d4bea8.netlify.app/login.html

Can anyone confirm if  the Nightly version is addressing this bug?
Comment 48 Chris Dumez 2021-07-21 16:11:10 PDT
(In reply to Leo Balter from comment #47)
> This bug is flagged as resolved being a duplicate of bug 224623, which has a
> patch already in the nightly version. Seems also that this blocked bug
> 224305, which is now set as fixed with a patch.
> 
> I quickly borrowed an example from a comment above and it doesn't seem to
> work in the latest Safari Technology Preview (nightly) build. Maybe that's
> something else?
> https://60d07525d461cb0007612310--distracted-ride-d4bea8.netlify.app/login.
> html
> 
> Can anyone confirm if  the Nightly version is addressing this bug?

As far as I know, this bug never impacted Safari Technology Preview because STP is based on trunk and this bug never impacted trunk. The bug only occurred on the product branch, in a bugfix update, due to us cherry-picking a change which worked fine on trunk but not on the branch (due to a missing dependency).