taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 9e2409d738aebbc3273b730ad78c32939f18b681
parent 039ad74c8072ced8d2769b7753cac0aeb9be5ea0
Author: Florian Dold <florian@dold.me>
Date:   Tue, 16 Jul 2024 23:02:46 +0200

wallet-core: logging, possible fix for DB issue

Diffstat:
Mpackages/idb-bridge/src/backends.test.ts | 51+++++++++++++++++++++++++++++++++++++++++++++------
Mpackages/taler-wallet-core/src/query.ts | 29++++++++++++++++++++++-------
Mpackages/taler-wallet-core/src/shepherd.ts | 8+++++++-
3 files changed, 74 insertions(+), 14 deletions(-)

diff --git a/packages/idb-bridge/src/backends.test.ts b/packages/idb-bridge/src/backends.test.ts @@ -23,24 +23,23 @@ * Imports. */ import test from "ava"; +import { MemoryBackend } from "./MemoryBackend.js"; import { BridgeIDBCursorWithValue, BridgeIDBDatabase, BridgeIDBFactory, BridgeIDBKeyRange, - BridgeIDBTransaction, } from "./bridge-idb.js"; +import { promiseFromRequest, promiseFromTransaction } from "./idbpromutil.js"; import { IDBCursorDirection, IDBCursorWithValue, IDBDatabase, IDBKeyRange, - IDBRequest, IDBValidKey, } from "./idbtypes.js"; import { initTestIndexedDB, useTestIndexedDb } from "./testingdb.js"; -import { MemoryBackend } from "./MemoryBackend.js"; -import { promiseFromRequest, promiseFromTransaction } from "./idbpromutil.js"; +import { promiseForTransaction } from "./idb-wpt-ported/wptsupport.js"; test.before("test DB initialization", initTestIndexedDB); @@ -464,8 +463,7 @@ test("export", async (t) => { const exportedData2 = backend2.exportDump(); t.assert( - exportedData.databases[dbname].objectStores["books"].records.length === - 3, + exportedData.databases[dbname].objectStores["books"].records.length === 3, ); t.deepEqual(exportedData, exportedData2); @@ -738,3 +736,44 @@ test("range queries", async (t) => { t.pass(); }); + +test("idb: multiple transactions", async (t) => { + const idb = useTestIndexedDb(); + const dbname = "mtx-" + new Date().getTime() + Math.random(); + + const request = idb.open(dbname); + request.onupgradeneeded = () => { + const db = request.result as BridgeIDBDatabase; + const store = db.createObjectStore("books", { keyPath: "isbn" }); + const titleIndex = store.createIndex("by_title", "title", { unique: true }); + const authorIndex = store.createIndex("by_author", "author"); + + // Populate with initial data. + store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 }); + store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 }); + store.put({ title: "Bedrock Nights", author: "Barney", isbn: 345678 }); + }; + + const db: BridgeIDBDatabase = await promiseFromRequest(request); + + const tx1 = db.transaction(["books"], "readwrite"); + + tx1.oncomplete = () => { + t.log("first tx completed"); + }; + + tx1.onerror = () => { + t.log("first tx errored"); + }; + + tx1.abort(); + + const tx2 = db.transaction(["books"], "readwrite"); + tx2.commit(); + + const tx3 = db.transaction(["books"], "readwrite"); + + await promiseForTransaction(t, tx3); + + t.pass(); +}); diff --git a/packages/taler-wallet-core/src/query.ts b/packages/taler-wallet-core/src/query.ts @@ -576,7 +576,6 @@ function runTx<Arg, Res>( triggerContext: InternalTriggerContext, cancellationToken: CancellationToken, ): Promise<Res> { - cancellationToken.throwIfCancelled(); // Create stack trace in case we need to to print later where // the transaction was started. const stack = Error("Failed transaction was started here."); @@ -591,6 +590,7 @@ function runTx<Arg, Res>( let transactionException: any = undefined; let aborted = false; tx.oncomplete = () => { + logger.trace("transaction completed"); // This is a fatal error: The transaction completed *before* // the transaction function returned. Likely, the transaction // function waited on a promise that is *not* resolved in the @@ -610,13 +610,18 @@ function runTx<Arg, Res>( unregisterOnCancelled(); }; tx.onerror = () => { + logger.trace("transaction had error"); if (cancellationToken.isCancelled) { + reject( + new CancellationToken.CancellationError(cancellationToken.reason), + ); return; } logger.error("error in transaction"); logger.error(`${stack.stack}`); }; tx.onabort = () => { + logger.trace("transaction was aborted"); if (cancellationToken.isCancelled) { reject( new CancellationToken.CancellationError(cancellationToken.reason), @@ -651,13 +656,13 @@ function runTx<Arg, Res>( console.warn("got AbortError, transaction was aborted"); } else { transactionException = e; - console.error("Transaction failed:", e); + logger.error(`Transaction failed: ${safeStringifyException(e)}`); console.error(stack); tx.abort(); } }) .catch((e) => { - console.error("aborting failed:", safeStringifyException(e)); + logger.error(`aborting failed: ${safeStringifyException(e)}`); }); }); } @@ -971,7 +976,7 @@ export class DbAccessImpl<StoreMap> implements DbAccess<StoreMap> { return this.db; } - runAllStoresReadWriteTx<T>( + async runAllStoresReadWriteTx<T>( options: { label?: string; }, @@ -979,6 +984,7 @@ export class DbAccessImpl<StoreMap> implements DbAccess<StoreMap> { tx: DbReadWriteTransaction<StoreMap, Array<StoreNames<StoreMap>>>, ) => Promise<T>, ): Promise<T> { + this.cancellationToken.throwIfCancelled(); const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = {}; const strStoreNames: string[] = []; @@ -995,7 +1001,13 @@ export class DbAccessImpl<StoreMap> implements DbAccess<StoreMap> { ); const tx = this.db.transaction(strStoreNames, mode); const writeContext = makeWriteContext(tx, accessibleStores, triggerContext); - return runTx(tx, writeContext, txf, triggerContext, this.cancellationToken); + return await runTx( + tx, + writeContext, + txf, + triggerContext, + this.cancellationToken, + ); } async runAllStoresReadOnlyTx<T>( @@ -1006,6 +1018,7 @@ export class DbAccessImpl<StoreMap> implements DbAccess<StoreMap> { tx: DbReadOnlyTransaction<StoreMap, Array<StoreNames<StoreMap>>>, ) => Promise<T>, ): Promise<T> { + this.cancellationToken.throwIfCancelled(); const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = {}; const strStoreNames: string[] = []; @@ -1038,6 +1051,7 @@ export class DbAccessImpl<StoreMap> implements DbAccess<StoreMap> { }, txf: (tx: DbReadWriteTransaction<StoreMap, StoreNameArray>) => Promise<T>, ): Promise<T> { + this.cancellationToken.throwIfCancelled(); const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = {}; const strStoreNames: string[] = []; @@ -1064,12 +1078,13 @@ export class DbAccessImpl<StoreMap> implements DbAccess<StoreMap> { return res; } - runReadOnlyTx<T, StoreNameArray extends Array<StoreNames<StoreMap>>>( + async runReadOnlyTx<T, StoreNameArray extends Array<StoreNames<StoreMap>>>( opts: { storeNames: StoreNameArray; }, txf: (tx: DbReadOnlyTransaction<StoreMap, StoreNameArray>) => Promise<T>, ): Promise<T> { + this.cancellationToken.throwIfCancelled(); const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = {}; const strStoreNames: string[] = []; @@ -1086,7 +1101,7 @@ export class DbAccessImpl<StoreMap> implements DbAccess<StoreMap> { ); const tx = this.db.transaction(strStoreNames, mode); const readContext = makeReadContext(tx, accessibleStores, triggerContext); - const res = runTx( + const res = await runTx( tx, readContext, txf, diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts @@ -278,6 +278,7 @@ export class TaskSchedulerImpl implements TaskScheduler { this.startShepherdTask(taskId); } } + private async internalStartShepherdTask(taskId: TaskIdStr): Promise<void> { logger.trace(`Starting to shepherd task ${taskId}`); const oldShep = this.sheps.get(taskId); @@ -618,7 +619,12 @@ function getWalletExecutionContextForTask( }, }; - wex = getObservedWalletExecutionContext(ws, cancellationToken, undefined, oc); + wex = getObservedWalletExecutionContext( + ws, + cancellationToken, + undefined, + oc, + ); } else { oc = { observe(evt) {},