RESOLVED FIXED288682
IndexedDB in Worker commits when thread is terminated
https://bugs.webkit.org/show_bug.cgi?id=288682
Summary IndexedDB in Worker commits when thread is terminated
neil
Reported 2025-02-26 20:29:12 PST
Created attachment 474365 [details] Testcase to reproduce transaction commit on worker termination # Summary When a worker thread running an indexedDB transaction is terminated, WebKit can *commit* the half-complete transaction rather than aborting. This makes transactions, well, non-transactional, which can cause severe data corruption. # Background We are building full offline support into the [Fastmail](https://www.fastmail.com/) web app. Our iOS app is essentially a WKWebView-based wrapper around the same. Data is stored in indexedDB, which runs inside a web worker (shared worker for web browsers, normal worker for our app where we know there can only ever be one UI instance). If interested/relevant, more details of the architecture can be found in [this blog post](https://www.fastmail.com/blog/offline-architecture/). We have noticed some iOS beta testers getting corrupt databases. Inspecting the data we found it was consistent with a transaction having been abandoned half-way through, but the data written up to that point being committed. To test this theory we added a patch so that for every transaction: 1. We insert a sentinel record into the DB 2. We do the operations in the transaction 3. We remove the sentinel record from the DB 4. We commit the transaction This should mean the sentinel record is never committed to the DB. We then added a check before (1) for the sentinel record. If it was present, we knew a transaction had been abandoned but committed. Sure enough, we started getting occasional hits, solely from WebKit. Looking at the logs, it seemed to coincide with the page being reloaded. It looks like the worker is being terminated while in the middle of a transaction, but rather than aborting the transaction as it should it's *committing* what's been written so far. I have managed to produce a test case that shows this behaviour. Note, in this test case I am explicitly terminating the worker, which makes it very reproducible, but we were seeing this sporadically even when not doing so — I presume because the OS was terminating the worker on page reload, perhaps with some short timeout. Not too, if you *remove* the `worker.terminate()` from the test case you see another bug: on the page after reload we can never establish a successful connection to the DB. All requests to it hang. # Steps to reproduce 1. Open https://testcases.fastmail.io/webkit-commit/idb-bug.html 2. Click "Run test" 3. Expected result: "Passed". Actual result: "Failed". I've also attached the testcase; the files are the same. # Request for advice on workaround Obviously we would like to see this fixed, however given the release cycle of iOS we don't want to have to wait for this fix to be present before releasing our offline support. So far we are struggling to find a 100% reliable way of avoiding the bug, as the OS can terminate the worker and cause the issue. I'm hoping that someone with understanding of the WebKit code and the root cause of this might be able to suggest some way we can avoid this issue in the meantime. Thanks for any help!
Attachments
Testcase to reproduce transaction commit on worker termination (2.55 KB, application/zip)
2025-02-26 20:29 PST, neil
no flags
Radar WebKit Bug Importer
Comment 1 2025-03-05 20:30:14 PST
John Wilander
Comment 2 2025-03-21 16:01:53 PDT
Thanks for the report, Neil! I'll ping the right folks.
Sihui Liu
Comment 3 2025-03-24 23:12:55 PDT
Basically transaction will be automatically committed when there is no more request scheduled on the transaction, and requests can be scheduled when transaction is created and when even is being dispatched. That is, if transaction has been created (control has returned to event loop after transaction creation) and there is no more event to be dispatched, the transaction will be committed automatically. Technically new requests can be scheduled within macro task (e.g. event handlers like `onsuccess`) and micro task (e.g. Promise resolve function). Looks like there is an issue in the Worker termination case where WebKit does not perform micro tasks (not create requests in resolve function of Promise), so it ends up thinking there is no more request and automatically committing transaction. I think a workaround should be to use event handler to schedule request directly instead of relying on Promise. For example, you may use a helper function like: function keepTransactionActive(transaction) { if (!shouldKeepTransactionActive) return; const request = transaction.objectStore("foo").get("test"); request.onsuccess = (event) => { keepTransactionActive(event.target.transaction); }; }
neil
Comment 4 2025-03-25 17:54:56 PDT
Ah, interesting, thanks for the detail. I'll give that workaround a go and see if that can prevent the issue until the underlying bug is fixed.
Sihui Liu
Comment 5 2025-03-26 12:04:10 PDT
EWS
Comment 6 2025-03-27 15:36:38 PDT
Committed 292795@main (b9d8178a0635): <https://commits.webkit.org/292795@main> Reviewed commits have been landed. Closing PR #43050 and removing active labels.
Note You need to log in before you can comment on or make changes to this bug.