| 288 | } |
| 289 | |
| 290 | private onMessage(w: ParsePoolWorker, m: ParseWorkerMessage): void { |
| 291 | if (m.type === 'grammars-loaded') { |
| 292 | if (!this.workers.has(w)) return; // recycled/destroyed before ready |
| 293 | this.pending.delete(w); |
| 294 | this.idle.push(w); |
| 295 | this.drain(); |
| 296 | return; |
| 297 | } |
| 298 | if (m.type === 'parse-result') { |
| 299 | const job = this.inflight.get(w); |
| 300 | if (!job || (m.id !== undefined && m.id !== job.id)) return; // stale (post-recycle) |
| 301 | this.inflight.delete(w); |
| 302 | if (job.timerExpired) { |
| 303 | // The base timer fired before this result was processed. That almost |
| 304 | // always means the MAIN THREAD was stalled (sync SQLite store on slow |
| 305 | // disks) while the parse itself finished long ago — the worker's own |
| 306 | // clock (parseMs) tells the two apart. Either way the result is here |
| 307 | // and valid: accept it instead of the old behaviour (kill worker + |
| 308 | // reject), which turned every main-thread stall into false timeouts |
| 309 | // and dropped files (issue #1231). |
| 310 | const parseMs = typeof m.parseMs === 'number' ? Math.round(m.parseMs) : undefined; |
| 311 | const detail = parseMs === undefined |
| 312 | ? '' |
| 313 | : parseMs < (job.budgetMs ?? this.parseTimeoutMs) |
| 314 | ? ` (parse took ${parseMs}ms in-worker — the main thread was stalled, not the parse)` |
| 315 | : ` (parse genuinely took ${parseMs}ms)`; |
| 316 | this.log(`Late parse-result accepted: ${job.task.filePath}${detail}`); |
| 317 | } |
| 318 | // Recycle the worker once it's done enough parses to have grown its WASM |
| 319 | // heap; otherwise return it to the idle set for the next job. |
| 320 | if ((this.parseCounts.get(w) ?? 0) >= this.recycleInterval) { |
| 321 | this.recycle(w); |
| 322 | } else { |
| 323 | this.idle.push(w); |
| 324 | } |
| 325 | this.settle(job, m.result); |
| 326 | this.drain(); |
| 327 | } |
| 328 | } |
| 329 | |
| 330 | /** A worker died (crash hook / OOM exit / spawn error). Reject its in-flight |
| 331 | * parse so the caller's retry pass can re-attempt it, then respawn. */ |