Repository navigation
Dexie RxStorage: reference counting in dexie-helper never closes Dexie connections â reopening a database name after deletion fails (DatabaseClosedError + DB8)Â #8793
Description
Affected versions
17.3.0 and 17.4.0 (current), Dexie-based RxStorage. See the analysis below for why
this is version-independent.
Summary
While debugging a DatabaseClosedError after a close â delete â reopen cycle in our
app, I ended up in src/plugins/storage-dexie/dexie-helper.ts and - unless I'm
misreading the code - the reference counting around DEXIE_STATE_DB_BY_NAME /
REF_COUNT_PER_DEXIE_DB seems to be broken in two independent ways:
-
The counter is never incremented anywhere. The only writes to
REF_COUNT_PER_DEXIE_DBin the entire package areset(x, 0)at creation and
set(x, count - 1)incloseDexieDb(). Starting from 0, the counter can only
ever be 0, -1, -2, ... so the conditionnewCount === 0(which guards
state.dexieDb.close()and the cache eviction) can never become true. The close
branch is unreachable. -
The initial counter is stored under the wrong key. Inside the
getFromMapOrCreatefactory callback,REF_COUNT_PER_DEXIE_DB.set(state, 0)
runs beforeconst state = ...has been assigned (the callback executes
synchronously during that assignment), so the key is effectivelyundefined.
closeDexieDb()later looks up the actual state promise, getsundefined, and
computesundefined - 1 = NaN. The(prevCount as any) - 1cast in the source
silences the TypeScript error that would have flagged this. This part appears to
have changed with the "REFACTOR Map cache code" commit (REFACTOR Map cache code #4604), which moved those
lines into the factory callback.
Consequences
RxStorageInstanceDexie.close()never closes the underlying Dexie/IndexedDB
connections (connection leak), and theDEXIE_STATE_DB_BY_NAMEentry is never
removed.- If the application afterwards deletes the IndexedDB databases (e.g.
indexedDB.deleteDatabase(...)on logout, to remove user data from a shared
device), the delete request is blocked by the leaked connections; Dexie
force-closes them ("Another connection wants to delete database ..."). The module
cache now holds force-closed Dexie instances. - Re-creating an RxDatabase with the same name in the same JS context then receives
the closed Dexie instance from the cache: the first storage operation throws
Dexie'sDatabaseClosedError("Database has been closed"), and a retry fails with
RxDB error DB8 because the half-created database is still registered.
Reproduction (browser, dexie storage, no plugins required)
import { createRxDatabase, removeRxDatabase } from 'rxdb';
import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie';
const schema = {
version: 0, primaryKey: 'id', type: 'object',
properties: { id: { type: 'string', maxLength: 100 } }, required: ['id']
};
const open = async (name) => {
const db = await createRxDatabase({ name, storage: getRxStorageDexie(), multiInstance: true });
await db.addCollections({ docs: { schema } });
return db;
};
const name = 'repro' + Date.now();
let db = await open(name);
await db.docs.insert({ id: 'a' });
await db.close(); // leaks the Dexie connections
await removeRxDatabase(name, getRxStorageDexie());
for (const d of await indexedDB.databases()) { // app-level cleanup of the per-collection DBs
if (d.name?.includes(name)) {
await new Promise(res => {
const req = indexedDB.deleteDatabase(d.name);
req.onsuccess = req.onerror = () => res();
// logs "Another connection wants to delete database ..." for every sub-DB,
// which shows that close() did not release the connections
});
}
}
db = await open(name); // throws DatabaseClosedErrorRuntime trace of the counter
To verify the analysis at runtime, I instrumented Map.prototype.set/delete in
the reproduction and logged every access whose call stack originates in the dexie
helper (function names containing DexieDb).
Trace with 17.3.0 as published (reproduction fails with DatabaseClosedError)
set(key="rxdb-dexie-reproâĶ--0--_rxdb_internal", value=undefined) in getDexieDbWithTables
set(key=undefined, value=0) in getDexieDbWithTables
set(key="rxdb-dexie-reproâĶ--0--_rxdb_internal", value=<Promise>) in getDexieDbWithTables
set(key="rxdb-dexie-reproâĶ--0--docs", value=undefined) in getDexieDbWithTables
set(key=undefined, value=0) in getDexieDbWithTables
set(key="rxdb-dexie-reproâĶ--0--docs", value=<Promise>) in getDexieDbWithTables
set(key=<Promise>, value=NaN) in closeDexieDb
set(key=<Promise>, value=NaN) in closeDexieDb
set(key=<Promise>, value=NaN) in closeDexieDb
set(key=<Promise>, value=NaN) in closeDexieDb
set(key=<Promise>, value=NaN) in closeDexieDb
The initial count is stored under the key undefined (defect 2). Every
closeDexieDb() call writes NaN (undefined - 1), meaning the else-branch ran
and dexieDb.close() was never reached. No increment and no delete appear
anywhere in the trace: connections and cache entries are never released.
Same trace with the suggested fix applied (reproduction passes)
set(key=<Promise>, value=0) in getDexieDbWithTables
set(key="rxdb-dexie-reproâĶ--0--_rxdb_internal", value=<Promise>) in getDexieDbWithTables
set(key=<Promise>, value=1) in getDexieDbWithTables
set(key=<Promise>, value=0) in getDexieDbWithTables
set(key="rxdb-dexie-reproâĶ--0--docs", value=<Promise>) in getDexieDbWithTables
set(key=<Promise>, value=1) in getDexieDbWithTables
delete(key=<Promise>) in closeDexieDb
delete(key="rxdb-dexie-reproâĶ--0--docs") in closeDexieDb
delete(key=<Promise>) in closeDexieDb
delete(key="rxdb-dexie-reproâĶ--0--_rxdb_internal") in closeDexieDb
âĶ (same balanced cycle repeats for removeRxDatabase and for the reopen)
Counts go 0 â 1 per storage instance, and on close both the counter and the
name-cache entry are removed. The "Another connection wants to delete" warnings
disappear as well, since the connections are now released before the delete.
Bug checklist
- Different browsers: reproduced in both Firefox and Chromium.
- Different storages: not applicable - the defect is located in
dexie-helper.ts, which only the Dexie storage executes. eventReduce: false: still fails with the same error.- Key-compression: not used (default off).
multiInstance: false: still fails with the same error.
Suggested fix
- Key the initial count on
value(not the still-unassignedstate). - Increment the count on every
getDexieDbWithTables()call. It is called exactly
once per storage instance (createDexieStorageInstance), andcloseDexieDbruns
exactly once per instance close, so the counting stays balanced. - When the last reference is released, also delete the
DEXIE_STATE_DB_BY_NAME
entry â otherwise the next open of the same name still receives the closed
instance.
I have this change running as a patch-package patch in our app, and with it the
scenario above works. I can follow up with a PR containing the fix and a regression
test based on test/unit/bug-report.test.ts.
Activity
pubkey commentedon Jul 15, 2026
Can you make a PR with a test case that reproduces your problem?
You can start with this template.
Cr4ftingMine commentedon Jul 15, 2026
Done: #8797 - the first commit contains the failing test, the second one a suggested fix.
stale commentedon Jul 22, 2026
This issue has been automatically marked as stale because it has not had recent activity. It will be closed soon. Please update it or it may be closed to keep our repository organized. The best way is to add some more information or make a pull request with a test case. Also you might get help in fixing it at the RxDB Community Chat If you know you will continue working on this, just write any message to the issue (like "ping") to remove the stale tag.
Move the dexie connection leak test to rx-storage-dexie.test.ts
FIX dexie storage: close the connections and evict the state cache onâĶ
FIX dexie storage: close the connections and evict the state cache onâĶ