Comlink RPC promises are orphaned when worker.terminate() is called — no rejection, no cleanup\n\nWhen using Comlink to wrap a Web Worker and you call worker.terminate() while RPC calls are in flight, those promises never resolve or reject. They leak forever. Comlink's internal message routing relies on the worker being alive to send responses back. If the worker dies, the main-thread Proxy's pending promises are permanently stuck.\n\nThis is especially problematic for timeout/recovery scenarios (e.g., killing a hung Pyodide worker), where you need callers to know the operation failed.
Track pending RPC calls externally with a Set of reject callbacks. Reject all pending before terminating.\n\nts\ntype PendingEntry = { reject: (err: Error) => void };\nconst pending = new Set<PendingEntry>();\n\nasync function run_rpc(input: Input): Promise<Result> {\n let entry: PendingEntry | null = null;\n try {\n const result = await new Promise<Result>((resolve, reject) => {\n entry = { reject };\n pending.add(entry);\n proxy!.someMethod(input).then(resolve, reject);\n });\n return result;\n } finally {\n if (entry) pending.delete(entry);\n }\n}\n\nfunction hard_restart(): void {\n // Reject all pending BEFORE terminating\n const err = new Error('Worker terminated');\n for (const entry of pending) {\n entry.reject(err);\n }\n pending.clear();\n\n worker?.terminate();\n worker = null;\n proxy = null;\n // ... reinitialize\n}\n\n\nThis pattern wraps each Comlink call in a Promise with an externally-accessible reject function. On termination, all pending calls fail immediately with a descriptive error that callers can handle (retry, show UI error, etc.).