| 74 | private belowWaiters: Array<{ limit: number; resolve: () => void }> = []; |
| 75 | |
| 76 | constructor(workerScriptPath: string, dbPath: string, fastInit: boolean) { |
| 77 | this.worker = new Worker(workerScriptPath); |
| 78 | let readyResolve!: () => void; |
| 79 | let readyReject!: (e: Error) => void; |
| 80 | this.readyPromise = new Promise<void>((resolve, reject) => { |
| 81 | readyResolve = resolve; |
| 82 | readyReject = reject; |
| 83 | }); |
| 84 | |
| 85 | this.worker.on('message', (msg: { type: string; id?: number; message?: string }) => { |
| 86 | if (msg.type === 'ready') { |
| 87 | readyResolve(); |
| 88 | } else if (msg.type === 'ack') { |
| 89 | this.settleOne(); |
| 90 | } else if (msg.type === 'drained' && msg.id !== undefined) { |
| 91 | const waiter = this.drainWaiters.get(msg.id); |
| 92 | this.drainWaiters.delete(msg.id); |
| 93 | if (!waiter) return; |
| 94 | if (this.firstError) waiter.reject(this.firstError); |
| 95 | else waiter.resolve(); |
| 96 | } else if (msg.type === 'error') { |
| 97 | if (!this.firstError) this.firstError = new Error(`store worker: ${msg.message}`); |
| 98 | this.settleOne(); // the error reply is also the failed bundle's ack |
| 99 | } |
| 100 | }); |
| 101 | this.worker.on('error', (err) => { |
| 102 | this.failAll(err instanceof Error ? err : new Error(String(err))); |
| 103 | readyReject(this.firstError!); |
| 104 | }); |
| 105 | this.worker.on('exit', (code) => { |
| 106 | this.exited = true; |
| 107 | if (code !== 0) { |
| 108 | this.failAll(new Error(`store worker exited with code ${code}`)); |
| 109 | readyReject(this.firstError!); |
| 110 | } else if (this.drainWaiters.size > 0 || this.belowWaiters.length > 0) { |
| 111 | // A clean exit with waiters pending is a protocol violation (only |
| 112 | // close() should end the worker) — settle the waiters instead of |
| 113 | // hanging the index forever. |
| 114 | this.failAll(new Error('store worker exited before drain completed')); |
| 115 | } |
| 116 | }); |
| 117 | |
| 118 | this.worker.postMessage({ type: 'open', dbPath, fastInit }); |
| 119 | // The worker holds the event loop open only until close(); don't unref — |
| 120 | // bundles must never be dropped because main ran out of work. |
| 121 | } |
| 122 | |
| 123 | private failAll(err: Error): void { |
| 124 | if (!this.firstError) this.firstError = err; |