From c1df371a141a47dff15855549023a803015dfe01 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 1 Oct 2020 16:14:06 +0200 Subject: [PATCH] add some documentation for our idb investigations --- doc/INDEXEDDB.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++ src/matrix/Sync.js | 1 + 2 files changed, 74 insertions(+) create mode 100644 doc/INDEXEDDB.md diff --git a/doc/INDEXEDDB.md b/doc/INDEXEDDB.md new file mode 100644 index 00000000..ebfa6580 --- /dev/null +++ b/doc/INDEXEDDB.md @@ -0,0 +1,73 @@ +## Promises, async/await and indexedDB + +Doesn't indexedDB close your transaction if you don't queue more requests from an idb event handler? +So wouldn't that mean that you can't use promises and async/await when using idb? + +It used to be like this, and for IE11 on Win7 (not on Windows 10 strangely enough), it still is like this. +Here we manually flush the promise queue synchronously at the end of an idb event handler. + +In modern browsers, indexedDB transactions should only be closed after flushing the microtask queue of the event loop, +which is where promises run. + +Keep in mind that indexedDB events, just like any other DOM event, are fired as macro tasks. +Promises queue micro tasks, of which the queue is drained before proceeding to the next macro task. +This also means that if a transaction is completed, you will only receive the event once you are ready to process the next macro tasks. +That doesn't prevent any placed request from throwing TransactionInactiveError though. + +## TransactionInactiveError in Safari + +Safari doesn't fully follow the rules above, in that if you open a transaction, +you need to "use" (not sure if this means getting a store or actually placing a request) it straight away, +without waiting for any *micro*tasks. See comments about Safari at https://github.com/dfahlander/Dexie.js/issues/317#issue-178349994. + +Another failure mode perceived in Hydrogen on Safari is that when the (readonly) prepareTxn in sync wasn't awaited to be completed before opening and using the syncTxn. +I haven't found any documentation online about this at all. Awaiting prepareTxn.complete() fixed the issue below. It's strange though the put does not fail. + +What is happening below is: + - in the sync loop: + - we first open a readonly txn on inboundGroupSessions, which we don't use in the example below + - we then open a readwrite txn on session, ... (does not overlap with first txn) + - first the first incremental sync on a room (!YxKeAxtNcDZDrGgaMF:matrix.org) it seems to work well + - on a second incremental sync for that same room, the first get throws TransactionInactiveError for some reason. + - the put in the second incremental sync somehow did not throw. + +So it looks like safari doesn't like (some) transactions still being active while a second one is being openened, even with non-overlapping stores. +For now I haven't awaited every read txn in the app, as this was the only place it fails, but if this pops up again in safari, we might have to do that. + +Keep in mind that the `txn ... inactive` logs are only logged when the "complete" or "abort" events are processed, +which happens in a macro task, as opposed to all of our promises, which run in a micro task. +So the transaction is likely to have closed before it appears in the logs. + +``` +[Log] txn 4504181722375185 active on inboundGroupSessions +[Log] txn 861052256474256 active on session, roomSummary, roomState, roomMembers, timelineEvents, timelineFragments, pendingEvents, userIdentities, groupSessionDecryptions, deviceIdentities, outboundGroupSessions, operations, accountData +[Info] hydrogen_session_5286139994689036.session.put({"key":"sync","value":{"token":"s1572540047_757284957_7660701_602588550_435736037_1567300_101589125_347651623_132704","filterId":"2"}}) +[Info] hydrogen_session_5286139994689036.userIdentities.get("@bwindels:matrix.org") +[Log] txn 4504181722375185 inactive +[Log] * applying sync response to room !YxKeAxtNcDZDrGgaMF:matrix.org ... +[Info] hydrogen_session_5286139994689036.roomMembers.put({"roomId":"!YxKeAxtNcDZDrGgaMF:matrix.org","userId":"@bwindels:matrix.org","membership":"join","avatarUrl":"mxc://matrix.org/aerWVfICBMcyFcEyREcivLuI","displayName":"Bruno","key":"!YxKeAxtNcDZDrGgaMF:matrix.org|@bwindels:matrix.org"}) +[Info] hydrogen_session_5286139994689036.roomMembers.get("!YxKeAxtNcDZDrGgaMF:matrix.org|@bwindels:matrix.org") +[Info] hydrogen_session_5286139994689036.timelineEvents.add({"fragmentId":0,"eventIndex":2147483658,"roomId":"!YxKeAxtNcDZDrGgaMF:matrix.org","event":{"content":{"body":"haha","msgtype":"m.text"},"origin_server_ts":1601457573756,"sender":"@bwindels:matrix.org","type":"m.room.message","unsigned":{"age":8360},"event_id":"$eD9z73-lCpXBVby5_fKqzRZzMVHiPzKbE_RSZzqRKx0"},"displayName":"Bruno","avatarUrl":"mxc://matrix.org/aerWVfICBMcyFcEyREcivLuI","key":"!YxKeAxtNcDZDrGgaMF:matrix.org|00000000|8000000a","eventIdKey":"!YxKeAxtNcDZDrGgaMF:matrix.org|$eD9z73-lCpXBVby5_fKqzRZzMVHiPzKbE_RSZzqRKx0"}) +[Info] hydrogen_session_5286139994689036.roomSummary.put({"roomId":"!YxKeAxtNcDZDrGgaMF:matrix.org","name":"!!!test8!!!!!!","lastMessageBody":"haha","lastMessageTimestamp":1601457573756,"isUnread":true,"encryption":null,"lastDecryptedEventKey":null,"isDirectMessage":false,"membership":"join","inviteCount":0,"joinCount":2,"heroes":null,"hasFetchedMembers":false,"isTrackingMembers":false,"avatarUrl":null,"notificationCount":5,"highlightCount":0,"tags":{"m.lowpriority":{}}}) +[Log] txn 861052256474256 inactive +[Info] syncTxn committed!! + +... two more unrelated sync responses ... + +[Log] starting sync request with since s1572540191_757284957_7660742_602588567_435736063_1567300_101589126_347651632_132704 ... +[Log] txn 8104296957004707 active on inboundGroupSessions +[Log] txn 2233038992157489 active on session, roomSummary, roomState, roomMembers, timelineEvents, timelineFragments, pendingEvents, userIdentities, groupSessionDecryptions, deviceIdentities, outboundGroupSessions, operations, accountData +[Info] hydrogen_session_5286139994689036.session.put({"key":"sync","value":{"token":"s1572540223_757284957_7660782_602588579_435736078_1567300_101589130_347651633_132704","filterId":"2"}}) +[Log] * applying sync response to room !YxKeAxtNcDZDrGgaMF:matrix.org ... +[Info] hydrogen_session_5286139994689036.roomMembers.get("!YxKeAxtNcDZDrGgaMF:matrix.org|@bwindels:matrix.org") +[Warning] stopping sync because of error +[Error] StorageError: get("!YxKeAxtNcDZDrGgaMF:matrix.org|@bwindels:matrix.org") failed on txn with stores accountData, deviceIdentities, groupSessionDecryptions, operations, outboundGroupSessions, pendingEvents, roomMembers, roomState, roomSummary, session, timelineEvents, timelineFragments, userIdentities on hydrogen_session_5286139994689036.roomMembers: (name: TransactionInactiveError) (code: 0) Failed to execute 'get' on 'IDBObjectStore': The transaction is inactive or finished. + (anonymous function) + asyncFunctionResume + (anonymous function) + promiseReactionJobWithoutPromise + promiseReactionJob +[Log] newStatus – "SyncError" +[Log] txn 8104296957004707 inactive +[Log] txn 2233038992157489 inactive +``` diff --git a/src/matrix/Sync.js b/src/matrix/Sync.js index 2d9780b1..81c9d649 100644 --- a/src/matrix/Sync.js +++ b/src/matrix/Sync.js @@ -235,6 +235,7 @@ export class Sync { await Promise.all(roomStates.map(async rs => { rs.preparation = await rs.room.prepareSync(rs.roomResponse, rs.membership, prepareTxn); })); + // This is needed for safari to not throw TransactionInactiveErrors on the syncTxn. See docs/INDEXEDDB.md await prepareTxn.complete(); await Promise.all(roomStates.map(rs => rs.room.afterPrepareSync(rs.preparation))); }