dexie / Dexie.js

A Minimalistic Wrapper for IndexedDB
https://dexie.org
Apache License 2.0
11.75k stars 642 forks source link

Nested transactions cause PrematureCommitError #725

Open ThomasGreiner opened 6 years ago

ThomasGreiner commented 6 years ago

Environment Dexie 2.0.4 (in a browserify bundle) Chrome 67.0.3396.99 Ubuntu 16.04

Steps to reproduce Run the following code:

"use strict";

const Dexie = require("dexie");

const db = new Dexie("foo");
db.version(1).stores({foo: "++,value"});

const db2 = new Dexie("bar");
db2.version(1).stores({bar: "++,value"});

db.transaction("rw", db.foo, () =>
{
  console.log("foo");
  return db2.transaction("rw", db2.bar, () =>
  {
    console.log("bar");
  });
});

Observed behavior "foo", "bar" and error are shown:

Unhandled rejection: PrematureCommitError: Transaction committed too early. See http://bit.ly/2kdckMn

Expected behavior Only "foo" and "bar" are shown.

Further information When adding db2.bar to the list of tables in the first transaction no output is shown.

dfahlander commented 6 years ago

This may seem weird, but is actually due to how IndexedDB transactions are designed. An IDB transaction will commit as soon as there are no operations on it. As you are using two different database connections (db and db2), their transactions are separate. The same reason you cannot await non-indexedDb operations (such as fetch() or setTimeout()) without loosing the transaction, you won't either be able to await a transaction from a different database.

It would be logical to include db2.bar in the first list, to acquire a lock on both databases, but this is not possible with indexedDB. What would be expected here, would be an Error thrown when including db2.bar in the first list, as it is not a valid table of the "foo" database.

ThomasGreiner commented 6 years ago

Thanks for the detailed explanation.

It would be logical to include db2.bar in the first list, to acquire a lock on both databases, but this is not possible with indexedDB.

So the only way to resolve this would be to move both tables into the same database or is there a way to somehow make the first transaction wait for the second? (similar to how you can make a transaction wait for fetch() to finish by wrapping it inside Dexie.Promise)

What would be expected here, would be an Error thrown when including db2.bar in the first list, as it is not a valid table of the "foo" database.

I agree that it'd be nice if an error were thrown to clarify that this is an IndexedDB limitation.

dfahlander commented 6 years ago

Absolutely best to include all tables into same DB if you need consistency / isolation etc. That said, it would be possible to solve your exact scenario using [Dexie.waitFor()](http://dexie.org/docs/Dexie/Dexie.waitFor()) but I would not recommend it, as it can have performance implications.