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:
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) {},