taler-typescript-core

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

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:
Apackages/taler-wallet-core/src/sequential-locks.ts | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-wallet-core/src/wallet.ts | 35++++++-----------------------------
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)}`); } } + }