`Failed to initialize IdxdbStore`: main thread and worker race each other during the client-version store reset
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 21
- Avg merge
- 12h 14m
- Merged PRs (30d)
- 41
Description
### Packages versions
- `@miden-sdk/miden-sdk`: 0.15.9
- `@miden-sdk/react`: 0.15.9
- Not browser-specific (reported on Chrome and Safari)
### Bug description
Creating a `WebClient` with the default `useWorker: true` initializes WASM twice — once on the main thread and once in the web worker — and both instances call `openDatabase(storeName, clientVersion)` concurrently against the **same** database. With `@miden-sdk/react`'s `MidenProvider` no `storeName` is passed, so both open `MidenClientDB`.
That is harmless until `MidenDatabase.ensureClientVersion` (idxdb-store, `schema.ts`) takes its reset path, which happens when the stored `clientVersion` differs from the shipped one in major or minor:
```js
this.dexie.close();
await this.dexie.delete();
await this.dexie.open();
await this.persistClientVersion(clientVersion);
```
`dexie.delete()` fires `versionchange` on every *other* open connection, and Dexie's default handler closes it ("Another connection wants to delete database … Closing db now"). With both instances inside `ensureClientVersion` at the same time, whichever reaches `delete()` second closes the connection the first one just reopened, and any `settings` read/write in flight at that moment rejects with `DatabaseClosedError`. `logWebStoreError` re-throws it and the Rust wrapper reports `Failed to initialize IdxdbStore`.
Two consequences:
- `MidenProvider` has no retry — it stores the error in `initError` and renders `errorComponent` for good, so the app stays dead until the user reloads.
- Both instances run `delete()`, so one can drop the database the other just recreated and populated.
Only users with a store written by an older client are affected, which is why this surfaces in production but never on a fresh profile.
**Possible fix.** The exclusive section only needs to run once per database. Holding a Web Lock (`navigator.locks.request`) around `MidenDatabase.open()` / `ensureClientVersion` would make the second caller wait and then observe the already-updated `clientVersion`, skipping the reset entirely. The SDK already depends on Web Locks in `syncLock.js`, and since they serialize across tabs too, this would also cover the multi-tab variant of the same race.
### How can this be reproduced?
Precondition: an existing store whose `clientVersion` differs in major/minor from the SDK build being loaded.
**How users hit it?** Open an app on 0.14.x so the store is created, upgrade to 0.15.x, reload.
On reload both instances enter the reset path together. It is a race, so it does not fail on every attempt — the window is between one instance's `dexie.delete()` and the other's `settings` access. Passing `useWorker: false` (a single WASM instance, so a single connection) never reproduces it.
### Relevant log output
```shell
Failed to initialize: Failed to initialize IdxdbStore
```
Contributor guide
Research direction
The issue is in the idxdb-store schema.ts file, specifically the ensureClientVersion method. Look at how WebClient initializes with useWorker: true, causing concurrent database opens. Examine the syncLock.js for Web Lock usage. The fix involves adding a lock around MidenDatabase.open() to serialize access. Test by simulating a version mismatch and reloading with multiple instances.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, typescript, wasm
- Domain
- backend, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100