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:
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;