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