commit 75f1058c4555854ee3718f8c3c7bb80b48feec07
parent 03b7cfb3310b0e29e26c0d760ab60e3a14c32098
Author: Florian Dold <dold@taler.net>
Date: Thu, 3 Sep 2026 15:44:44 +0200
wallet-core: move resource sequentialization into its own class
Diffstat:
2 files changed, 65 insertions(+), 29 deletions(-)
diff --git a/packages/taler-wallet-core/src/sequential-locks.ts b/packages/taler-wallet-core/src/sequential-locks.ts
@@ -0,0 +1,59 @@
+/*
+ This file is part of GNU Taler
+ (C) 2026 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+
+import { OpenedPromise, openPromise } from "@gnu-taler/taler-util";
+
+/**
+ * Exclusive execution of asynchronous work on named resources.
+ */
+export class SequentialLocks {
+ private waiters: Record<string, OpenedPromise<void>[]> = {};
+ private locks: Set<string> = new Set();
+
+ /**
+ * Run `f` while holding the locks named by `tokens`.
+ */
+ async run<T>(tokens: string[], f: () => Promise<T>): Promise<T> {
+ // Make sure locks are always acquired in the same order
+ tokens = [...tokens].sort();
+
+ for (const token of tokens) {
+ if (this.locks.has(token)) {
+ const p = openPromise<void>();
+ let waitList = this.waiters[token];
+ if (!waitList) {
+ waitList = this.waiters[token] = [];
+ }
+ waitList.push(p);
+ await p.promise;
+ }
+ this.locks.add(token);
+ }
+
+ try {
+ return await f();
+ } finally {
+ for (const token of tokens) {
+ this.locks.delete(token);
+ const waiter = (this.waiters[token] ?? []).shift();
+ if (waiter) {
+ waiter.resolve();
+ }
+ }
+ }
+ }
+}
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -112,6 +112,7 @@ import {
import { ProgressContext } from "./progress.js";
import { TransactionAbortedError } from "./db/query.js";
import { dispatchRequestInternal, isWalletInitOperation } from "./requests.js";
+import { SequentialLocks } from "./sequential-locks.js";
import { TaskScheduler, TaskSchedulerImpl } from "./shepherd.js";
import { rematerializeTransactions } from "./transactions.js";
import {
@@ -1325,12 +1326,11 @@ export class InternalWalletState {
/**
* Promises that are waiting for a particular resource.
*/
- private resourceWaiters: Record<string, OpenedPromise<void>[]> = {};
/**
* Resources that are currently locked.
*/
- private resourceLocks: Set<string> = new Set();
+ private sequentialLocks = new SequentialLocks();
taskScheduler: TaskScheduler = new TaskSchedulerImpl(this);
@@ -1815,35 +1815,12 @@ export class InternalWalletState {
tokens: string[],
f: () => Promise<T>,
): Promise<T> {
- // Make sure locks are always acquired in the same order
- tokens = [...tokens].sort();
-
- for (const token of tokens) {
- if (this.resourceLocks.has(token)) {
- const p = openPromise<void>();
- let waitList = this.resourceWaiters[token];
- if (!waitList) {
- waitList = this.resourceWaiters[token] = [];
- }
- waitList.push(p);
- await p.promise;
- }
- this.resourceLocks.add(token);
- }
-
+ logger.trace(`begin exclusive execution on ${JSON.stringify(tokens)}`);
try {
- logger.trace(`begin exclusive execution on ${JSON.stringify(tokens)}`);
- const result = await f();
- logger.trace(`end exclusive execution on ${JSON.stringify(tokens)}`);
- return result;
+ return await this.sequentialLocks.run(tokens, f);
} finally {
- for (const token of tokens) {
- this.resourceLocks.delete(token);
- const waiter = (this.resourceWaiters[token] ?? []).shift();
- if (waiter) {
- waiter.resolve();
- }
- }
+ logger.trace(`end exclusive execution on ${JSON.stringify(tokens)}`);
}
}
+
}