taler-typescript-core

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

commit a84803970b1e6147e4508542b730c7f1a0ba2606
parent 9d4254f9708c83bc4771ff7ac0690295a0fa760f
Author: Florian Dold <dold@taler.net>
Date:   Thu,  3 Sep 2026 15:33:41 +0200

idb-bridge: fail requests when the sqlite helper process exits

Without this every statement waiting on a dead helper, and every later
one, hangs forever.

Diffstat:
Mpackages/idb-bridge/src/node-helper-sqlite3-impl.ts | 34++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+), 0 deletions(-)

diff --git a/packages/idb-bridge/src/node-helper-sqlite3-impl.ts b/packages/idb-bridge/src/node-helper-sqlite3-impl.ts @@ -64,6 +64,7 @@ function concatArr(as: Uint8Array[]): Uint8Array { interface ReqInfo { resolve: (x: Uint8Array) => void; + reject: (err: Error) => void; } class Helper { @@ -77,6 +78,11 @@ class Helper { public proc: ChildProcessByStdio<stream.Writable, stream.Readable, null>; private promStarted: Promise<void>; private shutdownPromise: Promise<void> | undefined; + /** + * Set once the helper process is gone. Every request still waiting + * for a response is rejected with it, and so is every later request. + */ + private dead: Error | undefined; constructor(opts?: { enableTracing: boolean }) { this.enableTracing = opts?.enableTracing ?? false; @@ -91,12 +97,36 @@ class Helper { this.proc.on("spawn", () => { startedPromcap.resolve(); }); + this.proc.on("exit", (code, signal) => { + this.markDead( + Error( + `taler-helper-sqlite3 exited with ${ + signal ? `signal ${signal}` : `status ${code}` + }`, + ), + ); + }); + this.proc.on("error", (err: Error) => { + this.markDead(Error(`taler-helper-sqlite3 failed: ${err.message}`)); + }); // Make sure that the process is not blocking the parent process // from exiting. // When we are actively waiting for a response, we ref it again. this.unrefProc(); } + private markDead(err: Error): void { + if (this.dead) { + return; + } + this.dead = err; + const pending = [...this.reqMap.values()]; + this.reqMap.clear(); + for (const ri of pending) { + ri.reject(err); + } + } + private unrefProc() { this.proc.unref(); try { @@ -180,6 +210,9 @@ class Helper { async communicate(cmd: number, payload: Uint8Array): Promise<Uint8Array> { await this.promStarted; + if (this.dead) { + throw this.dead; + } if (!this.isListening) { this.startListening(); } @@ -188,6 +221,7 @@ class Helper { const reqNum = ++this.reqCounter; this.reqMap.set(reqNum, { resolve: prom.resolve, + reject: prom.reject, }); // len, reqId, reqType, payload const bufLen = 4 + 4 + 1 + payload.length;