()
| 411 | } |
| 412 | |
| 413 | private drain(): void { |
| 414 | // Grow toward maxSize while queued work outstrips workers that are idle OR |
| 415 | // already on their way up — throttled so we never cold-start the whole pool |
| 416 | // at once. |
| 417 | while ( |
| 418 | this.queue.length > this.idle.length + this.pending.size && |
| 419 | this.workers.size < this.maxSize && |
| 420 | this.pending.size < MAX_CONCURRENT_SPAWN && |
| 421 | !this.destroyed && |
| 422 | this.healthy |
| 423 | ) { |
| 424 | this.spawnOne(); |
| 425 | } |
| 426 | // Dispatch queued jobs to idle workers. |
| 427 | while (this.idle.length && this.queue.length) { |
| 428 | let job: ParseJob | undefined; |
| 429 | while (this.queue.length && (job = this.queue.shift()) && job.settled) job = undefined; |
| 430 | if (!job || job.settled) break; |
| 431 | const w = this.idle.pop()!; |
| 432 | this.dispatch(w, job); |
| 433 | } |
| 434 | // Hang-prevention: if there's queued work but nothing can ever run it (no |
| 435 | // idle workers, none spawning, none alive), fail it instead of hanging |
| 436 | // forever. Reached only when the crash budget is exhausted or after destroy. |
| 437 | if (this.queue.length && this.idle.length === 0 && this.pending.size === 0 && this.workers.size === 0) { |
| 438 | const reason = this.destroyed ? 'parse pool destroyed' : 'parse pool exhausted its worker crash budget'; |
| 439 | for (const job of this.queue.splice(0)) this.settle(job, undefined, new Error(reason)); |
| 440 | } |
| 441 | } |
| 442 | |
| 443 | private settle(job: ParseJob, result?: ExtractionResult, err?: Error): void { |
| 444 | if (job.settled) return; |
no test coverage detected