()
| 392 | } |
| 393 | |
| 394 | private drain(): void { |
| 395 | // Grow toward maxSize while queued work outstrips workers that are idle OR |
| 396 | // already on their way up — throttled so we never cold-start the whole pool |
| 397 | // at once. |
| 398 | while ( |
| 399 | this.queue.length > this.idle.length + this.pending.size && |
| 400 | this.workers.size < this.maxSize && |
| 401 | this.pending.size < MAX_CONCURRENT_SPAWN && |
| 402 | !this.destroyed && |
| 403 | this.healthy |
| 404 | ) { |
| 405 | this.spawnOne(); |
| 406 | } |
| 407 | // Dispatch queued jobs to idle workers. |
| 408 | while (this.idle.length && this.queue.length) { |
| 409 | let job: ParseJob | undefined; |
| 410 | while (this.queue.length && (job = this.queue.shift()) && job.settled) job = undefined; |
| 411 | if (!job || job.settled) break; |
| 412 | const w = this.idle.pop()!; |
| 413 | this.dispatch(w, job); |
| 414 | } |
| 415 | // Hang-prevention: if there's queued work but nothing can ever run it (no |
| 416 | // idle workers, none spawning, none alive), fail it instead of hanging |
| 417 | // forever. Reached only when the crash budget is exhausted or after destroy. |
| 418 | if (this.queue.length && this.idle.length === 0 && this.pending.size === 0 && this.workers.size === 0) { |
| 419 | const reason = this.destroyed ? 'parse pool destroyed' : 'parse pool exhausted its worker crash budget'; |
| 420 | for (const job of this.queue.splice(0)) this.settle(job, undefined, new Error(reason)); |
| 421 | } |
| 422 | } |
| 423 | |
| 424 | private settle(job: ParseJob, result?: ExtractionResult, err?: Error): void { |
| 425 | if (job.settled) return; |
no test coverage detected