From ad45016b87e98b570f7d983dca5f2a1e6a715af4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 Sep 2021 18:23:31 +0200 Subject: [PATCH] process write errors on complete or abort in transaction --- src/matrix/Sync.js | 10 +++----- src/matrix/storage/idb/Transaction.ts | 34 +++++++++++++++++++++++---- src/matrix/storage/idb/utils.ts | 17 ++------------ 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/matrix/Sync.js b/src/matrix/Sync.js index ed9889b1..bcd29ab4 100644 --- a/src/matrix/Sync.js +++ b/src/matrix/Sync.js @@ -296,14 +296,10 @@ export class Sync { // avoid corrupting state by only // storing the sync up till the point // the exception occurred - try { - syncTxn.abort(); - } catch (abortErr) { - log.set("couldNotAbortTxn", true); - } - throw err; + syncTxn.abort(log); + throw syncTxn.getCause(err); } - await syncTxn.complete(); + await syncTxn.complete(log); } _afterSync(sessionState, inviteStates, roomStates, archivedRoomStates, log) { diff --git a/src/matrix/storage/idb/Transaction.ts b/src/matrix/storage/idb/Transaction.ts index 291226c5..84247428 100644 --- a/src/matrix/storage/idb/Transaction.ts +++ b/src/matrix/storage/idb/Transaction.ts @@ -159,13 +159,39 @@ export class Transaction { return this._store(StoreNames.accountData, idbStore => new AccountDataStore(idbStore)); } - complete(): Promise { - return txnAsPromise(this._txn); + async complete(log?: LogItem): Promise { + try { + await txnAsPromise(this._txn); + } catch (err) { + if (this._writeErrors.length) { + this._logWriteErrors(log); + throw this._writeErrors[0].error; + } + throw err; + } } - abort(): void { + getCause(error: Error) { + if (error instanceof StorageError) { + if (error.errcode === "AbortError" && this._writeErrors.length) { + return this._writeErrors[0].error; + } + } + return error; + } + + abort(log?: LogItem): void { // TODO: should we wrap the exception in a StorageError? - this._txn.abort(); + try { + this._txn.abort(); + } catch (abortErr) { + log?.set("couldNotAbortTxn", true); + } + if (this._writeErrors.length) { + this._logWriteErrors(log); + } + } + addWriteError(error: StorageError, refItem: LogItem | undefined, operationName: string, key: IDBValidKey | IDBKeyRange | undefined) { // don't log subsequent `AbortError`s if (error.errcode !== "AbortError" || this._writeErrors.length === 0) { diff --git a/src/matrix/storage/idb/utils.ts b/src/matrix/storage/idb/utils.ts index bd1683ea..f9209f56 100644 --- a/src/matrix/storage/idb/utils.ts +++ b/src/matrix/storage/idb/utils.ts @@ -17,6 +17,7 @@ limitations under the License. import { IDBRequestError } from "./error"; import { StorageError } from "../common"; +import { AbortError } from "../../../utils/error.js"; let needsSyncPromise = false; @@ -112,22 +113,8 @@ export function txnAsPromise(txn): Promise { // @ts-ignore needsSyncPromise && Promise._flush && Promise._flush(); }); - txn.addEventListener("error", event => { - const request = event.target; - // catch first error here, but don't reject yet, - // as we don't have access to the failed request in the abort event handler - if (!error && request) { - error = new IDBRequestError(request); - } - }); txn.addEventListener("abort", event => { - if (!error) { - const txn = event.target; - const dbName = txn.db.name; - const storeNames = Array.from(txn.objectStoreNames).join(", ") - error = new StorageError(`Transaction on ${dbName} with stores ${storeNames} was aborted.`); - } - reject(error); + reject(new AbortError()); // @ts-ignore needsSyncPromise && Promise._flush && Promise._flush(); });