rexie icon indicating copy to clipboard operation
rexie copied to clipboard

TransactionInactiveError

Open anlumo opened this issue 2 years ago • 13 comments

I have code that tries to read/modify/write entries in a table. It basically looks like this:

async fn modify(ids: &[Uuid]) {
    let transaction = database.transaction(&["foo"], TransactionMode::ReadWrite)?;
    let table = transaction.store("foo")?;
    for id in ids {
        let obj = table.get(&id.to_string()).await?;
        // modify object here
        table.put(&obj, None).await?;
    }
    transaction.done().await?;
}

This code results in the error failed to execute indexed db request: TransactionInactiveError: Failed to execute 'put' on 'IDBObjectStore': The transaction is not active.

My research (source) indicates that the transaction is inactive right when control is given back to the JS event loop and active again only in the callback of the IndexedDB operation (the get in this case).

This is inherently incompatible with the way async/await works in Rust, since you don't get control to exactly when the Future is polled (only that it won't happen before the operation finishes).

I don't quite get how others get this to work, since this doesn't seem to be such an uncommon operation to do, and I can't find any hints of problems reported by others over this?

This was tested on Chromium 108.

I've now moved to using the idb-sys crate that doesn't use async, and there it works fine. The code is just way more convoluted, unfortunately.

anlumo avatar Jan 24 '23 18:01 anlumo

I've now moved to using the idb-sys crate that doesn't use async, and there it works fine. The code is just way more convoluted, unfortunately.

Thanks for creating this issue. Can you also try with idb crate which uses async/await syntax?

devashishdxt avatar Jan 31 '23 06:01 devashishdxt

I've added tests in idb crate to test this and it seems to work: https://github.com/devashishdxt/idb/blob/750d6d3a3e4c2e0a0e0c821c853e5e8b1db8d0c0/idb/tests/transaction.rs#L167

devashishdxt avatar Feb 01 '23 03:02 devashishdxt

I suspect that if there's no Future to process other than the idb one, it's always going to bounce to that one when the transaction is active again, making this impossible to test in isolation. You'd have to schedule something else at the same time that's not the same IndexedDB transaction.

I've looked through the dexie code to see how they're doing it, and it appears that they've implemented their own transactions internally that are just a queue that gets filled up and then flushed to the database all at once once it's done.

anlumo avatar Feb 01 '23 03:02 anlumo

Can you provide an example of what you're trying to do in // modify object here?

devashishdxt avatar Feb 01 '23 03:02 devashishdxt

I'm recording the last access date, so I'm updating the timestamp there.

anlumo avatar Feb 01 '23 03:02 anlumo

It is not possible for me to debug this without a reproducible example.

devashishdxt avatar Feb 01 '23 03:02 devashishdxt

I'll try to come up with something.

anlumo avatar Feb 01 '23 03:02 anlumo

I ran int the same issue and was able to reproduce the problem in your idb test suite by adding some sleeps:

panicked at 'id should be ok: failed to add a value: TransactionInactiveError: Failed to execute 'add' on 'IDBObjectStore': The transaction has finished.', idb/tests/transaction.rs:199:9

If anyone else in this thread found a solution since then please lmk, I'm contemplating building my own transaction system on top :scream:

elsirion avatar Aug 28 '23 19:08 elsirion

@elsirion Thanks for sharing an example. IndexedDB transaction work a little different as compared to transactions in other databases. Any transaction with no outstanding request gets committed when the control of event loop is given back to the browser. In your example, sleep returns the control of event loop to the browser and as there aren't any more pending requests on transaction, it is auto-committed.

If you want long-lived transaction across the event-loop control boundaries, you'll have to add some logic to do that yourself.

Making a network call is another example of surrendering the control of event loop to the browser. So, the transaction may get auto-committed if you make a network call.

Here are some notes on IndexedDB's transaction lifecycle: https://www.w3.org/TR/IndexedDB/#transaction-lifecycle

Here's a potential approach that may be used to handle this: https://jlongster.com/future-sql-web (refer to Long-lived IndexedDB transactions section in this article).

devashishdxt avatar Aug 29 '23 01:08 devashishdxt

Yeah, it's not a bug in this crate, just insane semantics of IndexedDB transactions :cry:

My best bet is probably to build an ACID-compliant transaction abstraction on top and then only use the IndexedDB transactions at commit time so that there are no interruptions in execution.

elsirion avatar Aug 29 '23 09:08 elsirion

@elsirion that's what Dexie is doing.

This ticket is about Rust Futures being inherently incompatible with indexeddb API. It's not possible to correctly convert from the transactions in indexeddb to Futures.

anlumo avatar Aug 29 '23 09:08 anlumo

This ticket is about Rust Futures being inherently incompatible with indexeddb API.

I agree, given the lack of guarantees about rust future execution and the IndexedDB behavior I don't really see how these can be made to work together reliably. It might work by accident most of the time or for limited use cases where rexie futures always return Ready on first poll and never yield to the executor, but I'd be very uncomfortable building a bigger application on top.

That's why I think the two problems are related. Having a proper transaction implementation on top of idb-sys in Rexie would solve the problem and is likely the only way to do so. Would such a patch be welcome @devashishdxt?

elsirion avatar Aug 29 '23 13:08 elsirion

@elsirion Sure. You can try adding a transaction implementation (like other JS libraries do) on top. I think this'll greatly increase usability of indexedDB transaction in wasm.

devashishdxt avatar Aug 31 '23 03:08 devashishdxt