velocious 1.0.528 → 1.0.530

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +12 -1
  2. package/build/background-jobs/forked-runner-child.js +2 -1
  3. package/build/background-jobs/job-runner.js +19 -5
  4. package/build/background-jobs/json-socket.js +2 -0
  5. package/build/background-jobs/main.js +47 -5
  6. package/build/background-jobs/pooled-runner-child.js +115 -0
  7. package/build/background-jobs/runner-process-title.js +6 -0
  8. package/build/background-jobs/scheduler.js +28 -5
  9. package/build/background-jobs/store.js +79 -15
  10. package/build/background-jobs/types.js +5 -5
  11. package/build/background-jobs/worker.js +272 -5
  12. package/build/configuration-types.js +5 -0
  13. package/build/configuration.js +53 -4
  14. package/build/src/background-jobs/forked-runner-child.js +3 -2
  15. package/build/src/background-jobs/job-runner.d.ts +10 -1
  16. package/build/src/background-jobs/job-runner.d.ts.map +1 -1
  17. package/build/src/background-jobs/job-runner.js +21 -6
  18. package/build/src/background-jobs/json-socket.d.ts +2 -0
  19. package/build/src/background-jobs/json-socket.d.ts.map +1 -1
  20. package/build/src/background-jobs/json-socket.js +3 -1
  21. package/build/src/background-jobs/main.d.ts +16 -0
  22. package/build/src/background-jobs/main.d.ts.map +1 -1
  23. package/build/src/background-jobs/main.js +46 -5
  24. package/build/src/background-jobs/pooled-runner-child.d.ts +2 -0
  25. package/build/src/background-jobs/pooled-runner-child.d.ts.map +1 -0
  26. package/build/src/background-jobs/pooled-runner-child.js +108 -0
  27. package/build/src/background-jobs/runner-process-title.d.ts +3 -0
  28. package/build/src/background-jobs/runner-process-title.d.ts.map +1 -0
  29. package/build/src/background-jobs/runner-process-title.js +6 -0
  30. package/build/src/background-jobs/scheduler.d.ts +16 -2
  31. package/build/src/background-jobs/scheduler.d.ts.map +1 -1
  32. package/build/src/background-jobs/scheduler.js +27 -6
  33. package/build/src/background-jobs/store.d.ts +24 -0
  34. package/build/src/background-jobs/store.d.ts.map +1 -1
  35. package/build/src/background-jobs/store.js +81 -15
  36. package/build/src/background-jobs/types.d.ts +10 -8
  37. package/build/src/background-jobs/types.d.ts.map +1 -1
  38. package/build/src/background-jobs/types.js +6 -6
  39. package/build/src/background-jobs/worker.d.ts +116 -1
  40. package/build/src/background-jobs/worker.d.ts.map +1 -1
  41. package/build/src/background-jobs/worker.js +276 -6
  42. package/build/src/configuration-types.d.ts +25 -0
  43. package/build/src/configuration-types.d.ts.map +1 -1
  44. package/build/src/configuration-types.js +6 -1
  45. package/build/src/configuration.d.ts +6 -0
  46. package/build/src/configuration.d.ts.map +1 -1
  47. package/build/src/configuration.js +54 -4
  48. package/build/tsconfig.tsbuildinfo +1 -1
  49. package/package.json +1 -1
  50. package/src/background-jobs/forked-runner-child.js +2 -1
  51. package/src/background-jobs/job-runner.js +19 -5
  52. package/src/background-jobs/json-socket.js +2 -0
  53. package/src/background-jobs/main.js +47 -5
  54. package/src/background-jobs/pooled-runner-child.js +115 -0
  55. package/src/background-jobs/runner-process-title.js +6 -0
  56. package/src/background-jobs/scheduler.js +28 -5
  57. package/src/background-jobs/store.js +79 -15
  58. package/src/background-jobs/types.js +5 -5
  59. package/src/background-jobs/worker.js +272 -5
  60. package/src/configuration-types.js +5 -0
  61. package/src/configuration.js +53 -4
@@ -19,6 +19,7 @@ const FORKED_CHILD_SIGKILL_GRACE_MS = 5000
19
19
  */
20
20
  const MAX_FORKED_JOB_TIMEOUT_MS = 2_147_483_647
21
21
  const FORKED_RUNNER_ENTRY_PATH = fileURLToPath(new URL("./forked-runner-child.js", import.meta.url))
22
+ const POOLED_RUNNER_ENTRY_PATH = fileURLToPath(new URL("./pooled-runner-child.js", import.meta.url))
22
23
  /** How often the worker sends a liveness heartbeat to the main. */
23
24
  const HEARTBEAT_INTERVAL_MS = 15000
24
25
  /** TCP keepalive so a half-open connection to the main surfaces as a close. */
@@ -26,7 +27,25 @@ const SOCKET_KEEPALIVE_MS = 10000
26
27
  /**
27
28
  * Execution modes.
28
29
  * @type {import("./types.js").BackgroundJobExecutionMode[]} */
29
- const EXECUTION_MODES = ["inline", "forked", "spawned"]
30
+ const EXECUTION_MODES = ["inline", "forked", "pooled", "spawned"]
31
+
32
+ /**
33
+ * Normalizes a candidate pooled-runner count or job limit.
34
+ * @param {number | undefined} value - Candidate positive integer.
35
+ * @returns {number | undefined} - Normalized value.
36
+ */
37
+ function positiveInteger(value) {
38
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined
39
+ }
40
+
41
+ /**
42
+ * Normalizes a candidate pooled-runner resource limit.
43
+ * @param {number | undefined} value - Candidate positive number.
44
+ * @returns {number | undefined} - Normalized value.
45
+ */
46
+ function positiveNumber(value) {
47
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined
48
+ }
30
49
 
31
50
  /**
32
51
  * Per-forked-child timeout bookkeeping.
@@ -46,11 +65,16 @@ export default class BackgroundJobsWorker {
46
65
  * @param {number} [args.port] - Port.
47
66
  * @param {number} [args.maxConcurrentForkedJobs] - Override the process runner concurrency cap from `configuration.getBackgroundJobsConfig()`.
48
67
  * @param {number} [args.maxConcurrentInlineJobs] - Override the inline-job concurrency cap from `configuration.getBackgroundJobsConfig()`.
68
+ * @param {number} [args.pooledRunnerCount] - Override the pooled runner count.
69
+ * @param {number} [args.pooledRunnerConcurrency] - Override the per-runner concurrency.
70
+ * @param {number} [args.pooledRunnerMaxJobs] - Override the per-runner recycle job count.
71
+ * @param {number} [args.pooledRunnerMaxRssBytes] - Override the per-runner recycle RSS limit.
72
+ * @param {number} [args.pooledRunnerMaxLifetimeMs] - Override the per-runner recycle lifetime.
49
73
  * @param {number} [args.forkedChildSigkillGraceMs] - Override the grace period between SIGTERM and SIGKILL when reaping lingering process runners on stop.
50
74
  * @param {number} [args.heartbeatIntervalMs] - Override the liveness heartbeat interval (default 15000ms).
51
75
  * @param {number} [args.jobTimeoutMs] - Override the forked-job wall-clock timeout from `configuration.getBackgroundJobsConfig()`. `0` disables it.
52
76
  */
53
- constructor({configuration, host, port, maxConcurrentForkedJobs, maxConcurrentInlineJobs, forkedChildSigkillGraceMs, heartbeatIntervalMs, jobTimeoutMs} = {}) {
77
+ constructor({configuration, host, port, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerConcurrency, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, forkedChildSigkillGraceMs, heartbeatIntervalMs, jobTimeoutMs} = {}) {
54
78
  /**
55
79
  * Narrows the runtime value to the documented type.
56
80
  * @type {Promise<import("../configuration.js").default>} */
@@ -86,6 +110,16 @@ export default class BackgroundJobsWorker {
86
110
  * Narrows the runtime value to the documented type.
87
111
  * @type {number} */
88
112
  this.maxConcurrentForkedJobs = this.maxConcurrentForkedJobsOverride || 4
113
+ this.pooledRunnerCountOverride = positiveInteger(pooledRunnerCount)
114
+ this.pooledRunnerConcurrencyOverride = positiveInteger(pooledRunnerConcurrency)
115
+ this.pooledRunnerMaxJobsOverride = positiveInteger(pooledRunnerMaxJobs)
116
+ this.pooledRunnerMaxRssBytesOverride = positiveNumber(pooledRunnerMaxRssBytes)
117
+ this.pooledRunnerMaxLifetimeMsOverride = positiveNumber(pooledRunnerMaxLifetimeMs)
118
+ this.pooledRunnerCount = this.pooledRunnerCountOverride || 4
119
+ this.pooledRunnerConcurrency = this.pooledRunnerConcurrencyOverride || 1
120
+ this.pooledRunnerMaxJobs = this.pooledRunnerMaxJobsOverride || 100
121
+ this.pooledRunnerMaxRssBytes = this.pooledRunnerMaxRssBytesOverride || 512 * 1024 * 1024
122
+ this.pooledRunnerMaxLifetimeMs = this.pooledRunnerMaxLifetimeMsOverride || 60 * 60 * 1000
89
123
  /**
90
124
  * Grace period between SIGTERM and SIGKILL when reaping process runners that
91
125
  * outlast a bounded shutdown drain.
@@ -148,6 +182,12 @@ export default class BackgroundJobsWorker {
148
182
  * @type {Set<import("node:child_process").ChildProcess>}
149
183
  */
150
184
  this.inflightProcessChildren = new Set()
185
+ /** @type {Set<Promise<void>>} */
186
+ this.inflightPooledJobs = new Set()
187
+ /** @type {Set<import("node:child_process").ChildProcess>} */
188
+ this.pooledChildren = new Set()
189
+ /** @type {Map<import("node:child_process").ChildProcess, {createdAtMs: number, jobsRun: number, inflight: Map<string, {payload: import("./types.js").BackgroundJobPayload & {id: string}, resolve?: (value: void) => void}>, retiring: boolean, settling?: boolean}>} */
190
+ this.pooledChildStates = new Map()
151
191
  }
152
192
 
153
193
  /**
@@ -171,6 +211,12 @@ export default class BackgroundJobsWorker {
171
211
 
172
212
  this.maxConcurrentForkedJobs = config.maxConcurrentForkedJobs || this.maxConcurrentForkedJobs
173
213
  }
214
+ const poolConfig = this.configuration.getBackgroundJobsConfig()
215
+ if (typeof this.pooledRunnerCountOverride !== "number") this.pooledRunnerCount = poolConfig.pooledRunnerCount
216
+ if (typeof this.pooledRunnerConcurrencyOverride !== "number") this.pooledRunnerConcurrency = poolConfig.pooledRunnerConcurrency
217
+ if (typeof this.pooledRunnerMaxJobsOverride !== "number") this.pooledRunnerMaxJobs = poolConfig.pooledRunnerMaxJobs
218
+ if (typeof this.pooledRunnerMaxRssBytesOverride !== "number") this.pooledRunnerMaxRssBytes = poolConfig.pooledRunnerMaxRssBytes
219
+ if (typeof this.pooledRunnerMaxLifetimeMsOverride !== "number") this.pooledRunnerMaxLifetimeMs = poolConfig.pooledRunnerMaxLifetimeMs
174
220
 
175
221
  this.statusReporter = new BackgroundJobsStatusReporter({
176
222
  configuration: this.configuration,
@@ -211,6 +257,7 @@ export default class BackgroundJobsWorker {
211
257
  }
212
258
 
213
259
  await this._drainInflight(this.inflightInlineJobs, timeoutMs)
260
+ await this._drainInflight(this.inflightPooledJobs, timeoutMs)
214
261
  await this._drainInflight(this.inflightProcessJobs, timeoutMs)
215
262
  await this._terminateProcessChildren()
216
263
  // Give in-flight result reports (now decoupled from job slots) a bounded
@@ -311,7 +358,7 @@ export default class BackgroundJobsWorker {
311
358
  })
312
359
 
313
360
  socket.on("connect", () => {
314
- jsonSocket.send({type: "hello", role: "worker", supportsHandoffIdReporting: true, supportsHeartbeat: true, workerId: this.workerId})
361
+ jsonSocket.send({type: "hello", role: "worker", supportsHandoffIdReporting: true, supportsHeartbeat: true, supportsPooled: true, workerId: this.workerId})
315
362
  this._sendReadyIfRunning()
316
363
  this._startHeartbeat()
317
364
  })
@@ -364,6 +411,11 @@ export default class BackgroundJobsWorker {
364
411
 
365
412
  const executionMode = this._executionModeForPayload(identifiedPayload)
366
413
 
414
+ if (executionMode === "pooled") {
415
+ this._trackPooledJob(this._runPooledJob(identifiedPayload))
416
+ return
417
+ }
418
+
367
419
  if (executionMode !== "inline") {
368
420
  this._trackProcessJob(this._startProcessJob({executionMode, payload: identifiedPayload}))
369
421
  return
@@ -433,7 +485,9 @@ export default class BackgroundJobsWorker {
433
485
 
434
486
  if (executionMode) return this._normalizeExecutionMode(executionMode)
435
487
 
436
- return options.forked === false ? "inline" : "forked"
488
+ if (options.forked === false) return "inline"
489
+ if (options.forked === true) return "forked"
490
+ return "pooled"
437
491
  }
438
492
 
439
493
  /**
@@ -532,17 +586,230 @@ export default class BackgroundJobsWorker {
532
586
  _readyMessage() {
533
587
  const acceptsProcessJob = this.inflightProcessJobs.size < this.maxConcurrentForkedJobs
534
588
  const acceptsInline = this.inflightInlineJobs.size < this.maxConcurrentInlineJobs
589
+ const acceptsPooled = this._availablePooledSlots() > 0
535
590
 
536
- if (!acceptsProcessJob && !acceptsInline) return null
591
+ if (!acceptsProcessJob && !acceptsInline && !acceptsPooled) return null
537
592
 
538
593
  return {
539
594
  type: "ready",
540
595
  acceptsForked: acceptsProcessJob,
541
596
  acceptsInline,
597
+ acceptsPooled,
542
598
  acceptsSpawned: acceptsProcessJob
543
599
  }
544
600
  }
545
601
 
602
+ /**
603
+ * Tracks a pooled job and re-advertises capacity.
604
+ * @param {Promise<void>} pooledJob - Pooled job promise.
605
+ * @returns {void}
606
+ */
607
+ _trackPooledJob(pooledJob) {
608
+ /** @type {Promise<void>} */
609
+ let inflight
610
+ inflight = pooledJob.finally(() => {
611
+ this.inflightPooledJobs.delete(inflight)
612
+ if (!this.shouldStop) this._sendReadyIfRunning()
613
+ })
614
+ this.inflightPooledJobs.add(inflight)
615
+ this._sendReadyIfRunning()
616
+ }
617
+
618
+ /**
619
+ * Free pooled slots across the pool: open slots in non-retiring children plus
620
+ * the slots we could add by spawning more children up to `pooledRunnerCount`.
621
+ * Retiring children (draining before replacement) never contribute capacity.
622
+ * @returns {number} - Number of pooled jobs the worker can accept right now.
623
+ */
624
+ _availablePooledSlots() {
625
+ let openInExisting = 0
626
+ let nonRetiringChildren = 0
627
+
628
+ for (const child of this.pooledChildren) {
629
+ const state = this.pooledChildStates.get(child)
630
+ if (!state || state.retiring) continue
631
+ nonRetiringChildren += 1
632
+ openInExisting += this.pooledRunnerConcurrency - state.inflight.size
633
+ }
634
+
635
+ const spawnableChildren = Math.max(0, this.pooledRunnerCount - nonRetiringChildren)
636
+
637
+ return openInExisting + spawnableChildren * this.pooledRunnerConcurrency
638
+ }
639
+
640
+ /**
641
+ * Runs a payload on a pooled child with a free concurrency slot, spawning a
642
+ * new child when every non-retiring child is full and the pool is below
643
+ * `pooledRunnerCount`. Each child runs up to `pooledRunnerConcurrency` jobs at
644
+ * once on its own event loop.
645
+ * @param {import("./types.js").BackgroundJobPayload & {id: string}} payload - Job payload.
646
+ * @returns {Promise<void>} - Resolves after the durable report.
647
+ */
648
+ _runPooledJob(payload) {
649
+ let child
650
+ for (const candidate of this.pooledChildren) {
651
+ const state = this.pooledChildStates.get(candidate)
652
+ if (state && !state.retiring && state.inflight.size < this.pooledRunnerConcurrency) {
653
+ child = candidate
654
+ break
655
+ }
656
+ }
657
+ if (!child) child = this._createPooledChild()
658
+ const state = this.pooledChildStates.get(child)
659
+ if (!state) throw new Error("Pooled runner state missing")
660
+
661
+ return new Promise((resolve) => {
662
+ state.inflight.set(payload.id, {payload, resolve})
663
+ try {
664
+ child.send({type: "job", payload})
665
+ } catch (error) {
666
+ void this._handlePooledChildFailure({child, error})
667
+ }
668
+ })
669
+ }
670
+
671
+ /**
672
+ * Creates a reusable pooled child.
673
+ * @returns {import("node:child_process").ChildProcess} - New pooled child.
674
+ */
675
+ _createPooledChild() {
676
+ const configuration = this.configuration
677
+ if (!configuration) throw new Error("Background jobs worker configuration not initialized")
678
+ const config = configuration.getBackgroundJobsConfig()
679
+ const child = fork(POOLED_RUNNER_ENTRY_PATH, [], {
680
+ cwd: configuration.getDirectory(), execArgv: [], stdio: ["ignore", "ignore", "ignore", "ipc"],
681
+ env: Object.assign({}, process.env, {VELOCIOUS_ENV: configuration.getEnvironment(), VELOCIOUS_BACKGROUND_JOBS_HOST: config.host, VELOCIOUS_BACKGROUND_JOBS_PORT: `${config.port}`})
682
+ })
683
+ this.pooledChildren.add(child)
684
+ this.inflightProcessChildren.add(child)
685
+ this.pooledChildStates.set(child, {createdAtMs: Date.now(), jobsRun: 0, inflight: new Map(), retiring: false})
686
+ child.on("message", (message) => this._handlePooledChildMessage({child, message}))
687
+ child.once("exit", (code, signal) => this._handlePooledChildFailure({child, error: new Error(`Pooled background job runner exited: code=${code} signal=${signal || "none"}`)}))
688
+ child.once("error", (error) => this._handlePooledChildFailure({child, error}))
689
+ return child
690
+ }
691
+
692
+ /**
693
+ * Handles a pooled child's per-job durable-report acknowledgement. A child
694
+ * runs jobs concurrently and reports one `job-outcome` per job id.
695
+ * @param {object} args - Message details.
696
+ * @param {import("node:child_process").ChildProcess} args.child - Pooled child.
697
+ * @param {?} args.message - IPC message.
698
+ * @returns {void}
699
+ */
700
+ _handlePooledChildMessage({child, message}) {
701
+ if (!message || typeof message !== "object") return
702
+ const record = /** @type {{type?: ?, jobId?: ?, acknowledged?: ?, rssBytes?: ?, error?: ?}} */ (message)
703
+ const state = this.pooledChildStates.get(child)
704
+ if (record.type !== "job-outcome" || !state || state.settling || typeof record.jobId !== "string") return
705
+ const entry = state.inflight.get(record.jobId)
706
+ if (!entry) return
707
+
708
+ state.inflight.delete(record.jobId)
709
+ state.jobsRun += 1
710
+ const resolve = entry.resolve
711
+
712
+ if (record.acknowledged === true) {
713
+ if (resolve) resolve(undefined)
714
+ } else {
715
+ // The child stayed alive but could not confirm this one job's terminal
716
+ // report; reclaim just this job — its concurrent siblings are unaffected.
717
+ void this._reportJobResult({
718
+ jobId: entry.payload.id,
719
+ status: "failed",
720
+ error: new Error(typeof record.error === "string" ? record.error : "Pooled runner terminal report was not acknowledged"),
721
+ handoffId: entry.payload.handoffId,
722
+ handedOffAtMs: entry.payload.handedOffAtMs,
723
+ workerId: entry.payload.workerId || this.workerId
724
+ }).finally(() => { if (resolve) resolve(undefined) })
725
+ }
726
+
727
+ const rssBytes = typeof record.rssBytes === "number" ? record.rssBytes : Number.POSITIVE_INFINITY
728
+ const runnerAgeMs = Date.now() - state.createdAtMs
729
+ if (!state.retiring && (state.jobsRun >= this.pooledRunnerMaxJobs || rssBytes >= this.pooledRunnerMaxRssBytes || runnerAgeMs >= this.pooledRunnerMaxLifetimeMs || this.shouldStop)) {
730
+ this._beginRetirePooledChild(child)
731
+ }
732
+ this._terminateIfDrained(child)
733
+ }
734
+
735
+ /**
736
+ * Marks a pooled child for retirement and eagerly spawns a single replacement
737
+ * (1-for-1) so its capacity is restored immediately without waiting for it to
738
+ * finish draining. The retiring child stops receiving new jobs and is
739
+ * terminated only once its in-flight set drains, so a long-running job (e.g. a
740
+ * build) is never cut off.
741
+ * @param {import("node:child_process").ChildProcess} child - Child to retire.
742
+ * @returns {void}
743
+ */
744
+ _beginRetirePooledChild(child) {
745
+ const state = this.pooledChildStates.get(child)
746
+ if (!state || state.retiring) return
747
+
748
+ state.retiring = true
749
+ // Best-effort pre-warm: skip when stopping (no new work) or before the
750
+ // worker is initialized (no configuration to fork a child from).
751
+ if (!this.shouldStop && this.configuration) this._createPooledChild()
752
+ }
753
+
754
+ /**
755
+ * Terminates a retiring pooled child once it has no in-flight jobs left.
756
+ * @param {import("node:child_process").ChildProcess} child - Child to check.
757
+ * @returns {void}
758
+ */
759
+ _terminateIfDrained(child) {
760
+ const state = this.pooledChildStates.get(child)
761
+ if (!state || !state.retiring || state.inflight.size > 0) return
762
+
763
+ this._retirePooledChild(child)
764
+ }
765
+
766
+ /**
767
+ * Retires a drained pooled child (removes it from tracking, then SIGTERMs it).
768
+ * @param {import("node:child_process").ChildProcess} child - Child process to retire.
769
+ * @returns {void}
770
+ */
771
+ _retirePooledChild(child) {
772
+ this.pooledChildren.delete(child)
773
+ this.pooledChildStates.delete(child)
774
+ this.inflightProcessChildren.delete(child)
775
+ child.kill("SIGTERM")
776
+ }
777
+
778
+ /**
779
+ * Removes an exited/unhealthy pooled child and reports every job that was
780
+ * in-flight on it as failed — a process-level crash's blast radius is the
781
+ * child's whole in-flight set. Capacity is refilled lazily on the next
782
+ * dispatch (a spawnable slot is still advertised), avoiding a tight respawn
783
+ * loop when a child crashes on startup.
784
+ * @param {object} args - Failure details.
785
+ * @param {import("node:child_process").ChildProcess} args.child - Pooled child.
786
+ * @param {?} args.error - Failure.
787
+ * @returns {Promise<void>}
788
+ */
789
+ async _handlePooledChildFailure({child, error}) {
790
+ const state = this.pooledChildStates.get(child)
791
+ if (state?.settling) return
792
+ if (state) state.settling = true
793
+ this.pooledChildren.delete(child)
794
+ this.inflightProcessChildren.delete(child)
795
+
796
+ const entries = state ? [...state.inflight.values()] : []
797
+ if (state) state.inflight.clear()
798
+ this.pooledChildStates.delete(child)
799
+
800
+ await Promise.allSettled(entries.map(async (entry) => {
801
+ await this._reportJobResult({
802
+ jobId: entry.payload.id,
803
+ status: "failed",
804
+ error,
805
+ handoffId: entry.payload.handoffId,
806
+ handedOffAtMs: entry.payload.handedOffAtMs,
807
+ workerId: entry.payload.workerId || this.workerId
808
+ })
809
+ if (entry.resolve) entry.resolve(undefined)
810
+ }))
811
+ }
812
+
546
813
  /**
547
814
  * Runs run job inline.
548
815
  * @param {import("./types.js").BackgroundJobPayload} payload - Payload.
@@ -164,6 +164,11 @@
164
164
  * allowed to keep in flight. Default: `4`. This is a per-worker safety cap;
165
165
  * for workload-shaped limits use per-queue caps (`queues`) instead, which are
166
166
  * enforced cluster-wide and are immune to duplicate worker processes.
167
+ * @property {number} [pooledRunnerCount] - Number of warm, reusable child runners owned by each worker. Pooled capacity is separate from inline and forked/spawned capacity. Default: `4`.
168
+ * @property {number} [pooledRunnerConcurrency] - Number of jobs each pooled child runs concurrently on its own event loop. Total per-worker pooled capacity is `pooledRunnerCount × pooledRunnerConcurrency`. `1` (default) keeps each child serial; raise it for I/O-bound jobs so a bounded set of isolated processes handles high concurrency (like the inline lane) without one process per concurrent job. Default: `1`.
169
+ * @property {number} [pooledRunnerMaxJobs] - Number of sequential jobs a pooled child runs before it is replaced, bounding process-level resource accumulation. Default: `100`.
170
+ * @property {number} [pooledRunnerMaxRssBytes] - RSS bytes after an acknowledged job at which a pooled child is replaced. Default: `536870912` (512 MiB).
171
+ * @property {number} [pooledRunnerMaxLifetimeMs] - Age after an acknowledged job at which a pooled child is replaced. Default: `3600000` (one hour).
167
172
  * @property {Record<string, {maxConcurrent?: number, priority?: number}>} [queues] - Per-queue
168
173
  * concurrency caps and dispatch priorities, Sidekiq-style. A job declares its queue (static
169
174
  * `queue` on the job class, or the `queue` enqueue option; defaults to `"default"`), and
@@ -192,6 +192,12 @@ export default class VelociousConfiguration {
192
192
  }
193
193
 
194
194
  this._isInitialized = false
195
+ /**
196
+ * In-progress `initialize()` promise, memoized so concurrent callers await
197
+ * the same bootstrap. Reset to undefined if initialization fails.
198
+ * @type {Promise<void> | undefined}
199
+ */
200
+ this._initializePromise = undefined
195
201
  this.httpServer = httpServer || {}
196
202
  /**
197
203
  * Stores the http server instance value.
@@ -1268,12 +1274,22 @@ export default class VelociousConfiguration {
1268
1274
  const envDatabaseIdentifier = process.env.VELOCIOUS_BACKGROUND_JOBS_DATABASE_IDENTIFIER
1269
1275
  const envMaxConcurrentForkedRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_MAX_CONCURRENT_FORKED_JOBS
1270
1276
  const envMaxConcurrentRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_MAX_CONCURRENT_INLINE_JOBS
1277
+ const envPooledRunnerCountRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_COUNT
1278
+ const envPooledRunnerConcurrencyRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_CONCURRENCY
1279
+ const envPooledRunnerMaxJobsRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_JOBS
1280
+ const envPooledRunnerMaxRssBytesRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_RSS_BYTES
1281
+ const envPooledRunnerMaxLifetimeMsRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_LIFETIME_MS
1271
1282
  const envDispatchStrategy = process.env.VELOCIOUS_BACKGROUND_JOBS_DISPATCH_STRATEGY
1272
1283
  const envPollIntervalRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POLL_INTERVAL_MS
1273
1284
  const envJobTimeoutRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_JOB_TIMEOUT_MS
1274
1285
  const envPort = envPortRaw ? Number(envPortRaw) : undefined
1275
1286
  const envMaxConcurrentForked = envMaxConcurrentForkedRaw ? Number(envMaxConcurrentForkedRaw) : undefined
1276
1287
  const envMaxConcurrent = envMaxConcurrentRaw ? Number(envMaxConcurrentRaw) : undefined
1288
+ const envPooledRunnerCount = envPooledRunnerCountRaw ? Number(envPooledRunnerCountRaw) : undefined
1289
+ const envPooledRunnerConcurrency = envPooledRunnerConcurrencyRaw ? Number(envPooledRunnerConcurrencyRaw) : undefined
1290
+ const envPooledRunnerMaxJobs = envPooledRunnerMaxJobsRaw ? Number(envPooledRunnerMaxJobsRaw) : undefined
1291
+ const envPooledRunnerMaxRssBytes = envPooledRunnerMaxRssBytesRaw ? Number(envPooledRunnerMaxRssBytesRaw) : undefined
1292
+ const envPooledRunnerMaxLifetimeMs = envPooledRunnerMaxLifetimeMsRaw ? Number(envPooledRunnerMaxLifetimeMsRaw) : undefined
1277
1293
  const envPollInterval = envPollIntervalRaw ? Number(envPollIntervalRaw) : undefined
1278
1294
  const envJobTimeout = envJobTimeoutRaw ? Number(envJobTimeoutRaw) : undefined
1279
1295
  const configured = this._backgroundJobs || {}
@@ -1288,6 +1304,21 @@ export default class VelociousConfiguration {
1288
1304
  const maxConcurrentForkedJobs = typeof configured.maxConcurrentForkedJobs === "number" && configured.maxConcurrentForkedJobs >= 1
1289
1305
  ? configured.maxConcurrentForkedJobs
1290
1306
  : (typeof envMaxConcurrentForked === "number" && Number.isFinite(envMaxConcurrentForked) && envMaxConcurrentForked >= 1 ? envMaxConcurrentForked : 4)
1307
+ const pooledRunnerCount = typeof configured.pooledRunnerCount === "number" && Number.isFinite(configured.pooledRunnerCount) && Number.isInteger(configured.pooledRunnerCount) && configured.pooledRunnerCount >= 1
1308
+ ? configured.pooledRunnerCount
1309
+ : (!("pooledRunnerCount" in configured) && typeof envPooledRunnerCount === "number" && Number.isFinite(envPooledRunnerCount) && Number.isInteger(envPooledRunnerCount) && envPooledRunnerCount >= 1 ? envPooledRunnerCount : 4)
1310
+ const pooledRunnerConcurrency = typeof configured.pooledRunnerConcurrency === "number" && Number.isFinite(configured.pooledRunnerConcurrency) && Number.isInteger(configured.pooledRunnerConcurrency) && configured.pooledRunnerConcurrency >= 1
1311
+ ? configured.pooledRunnerConcurrency
1312
+ : (!("pooledRunnerConcurrency" in configured) && typeof envPooledRunnerConcurrency === "number" && Number.isFinite(envPooledRunnerConcurrency) && Number.isInteger(envPooledRunnerConcurrency) && envPooledRunnerConcurrency >= 1 ? envPooledRunnerConcurrency : 1)
1313
+ const pooledRunnerMaxJobs = typeof configured.pooledRunnerMaxJobs === "number" && Number.isFinite(configured.pooledRunnerMaxJobs) && Number.isInteger(configured.pooledRunnerMaxJobs) && configured.pooledRunnerMaxJobs >= 1
1314
+ ? configured.pooledRunnerMaxJobs
1315
+ : (!("pooledRunnerMaxJobs" in configured) && typeof envPooledRunnerMaxJobs === "number" && Number.isFinite(envPooledRunnerMaxJobs) && Number.isInteger(envPooledRunnerMaxJobs) && envPooledRunnerMaxJobs >= 1 ? envPooledRunnerMaxJobs : 100)
1316
+ const pooledRunnerMaxRssBytes = typeof configured.pooledRunnerMaxRssBytes === "number" && Number.isFinite(configured.pooledRunnerMaxRssBytes) && configured.pooledRunnerMaxRssBytes >= 1
1317
+ ? configured.pooledRunnerMaxRssBytes
1318
+ : (!("pooledRunnerMaxRssBytes" in configured) && typeof envPooledRunnerMaxRssBytes === "number" && Number.isFinite(envPooledRunnerMaxRssBytes) && envPooledRunnerMaxRssBytes >= 1 ? envPooledRunnerMaxRssBytes : 512 * 1024 * 1024)
1319
+ const pooledRunnerMaxLifetimeMs = typeof configured.pooledRunnerMaxLifetimeMs === "number" && Number.isFinite(configured.pooledRunnerMaxLifetimeMs) && configured.pooledRunnerMaxLifetimeMs >= 1
1320
+ ? configured.pooledRunnerMaxLifetimeMs
1321
+ : (!("pooledRunnerMaxLifetimeMs" in configured) && typeof envPooledRunnerMaxLifetimeMs === "number" && Number.isFinite(envPooledRunnerMaxLifetimeMs) && envPooledRunnerMaxLifetimeMs >= 1 ? envPooledRunnerMaxLifetimeMs : 60 * 60 * 1000)
1291
1322
  const dispatchStrategyRaw = configured.dispatchStrategy || envDispatchStrategy
1292
1323
  const dispatchStrategy = dispatchStrategyRaw === "polling" ? "polling" : "beacon"
1293
1324
  const pollIntervalMs = typeof configured.pollIntervalMs === "number" && configured.pollIntervalMs >= 1
@@ -1316,7 +1347,7 @@ export default class VelociousConfiguration {
1316
1347
  : 60 * 60 * 1000
1317
1348
  }
1318
1349
 
1319
- return {host, port, databaseIdentifier, maxConcurrentForkedJobs, maxConcurrentInlineJobs, dispatchStrategy, pollIntervalMs, queues, jobTimeoutMs, retention}
1350
+ return {host, port, databaseIdentifier, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerConcurrency, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, dispatchStrategy, pollIntervalMs, queues, jobTimeoutMs, retention}
1320
1351
  }
1321
1352
 
1322
1353
  /**
@@ -1914,9 +1945,16 @@ export default class VelociousConfiguration {
1914
1945
  * @returns {Promise<void>} - Resolves when complete.
1915
1946
  */
1916
1947
  async initialize({type} = {type: "undefined"}) {
1917
- if (!this.isInitialized()) {
1918
- this._isInitialized = true
1919
-
1948
+ if (this._isInitialized) return
1949
+ // Memoize the in-progress initialization so concurrent callers await the same
1950
+ // bootstrap instead of racing. `_isInitialized` was previously set to `true`
1951
+ // up front, so a second caller (e.g. a pooled runner with
1952
+ // `pooledRunnerConcurrency > 1` starting several jobs on a cold child) could
1953
+ // skip initialization and load models / perform a job while the first call
1954
+ // was still awaiting model discovery and initializers. Mirrors connectBeacon.
1955
+ if (this._initializePromise) return await this._initializePromise
1956
+
1957
+ this._initializePromise = (async () => {
1920
1958
  await this.initializeModels({type})
1921
1959
  await this.getEnvironmentHandler().autoDiscoverResources(this)
1922
1960
  this._mergeDiscoveredAbilityResources()
@@ -1937,6 +1975,17 @@ export default class VelociousConfiguration {
1937
1975
  }
1938
1976
  }
1939
1977
  }
1978
+
1979
+ this._isInitialized = true
1980
+ })()
1981
+
1982
+ try {
1983
+ await this._initializePromise
1984
+ } catch (error) {
1985
+ // Let a later call retry a failed initialization instead of every future
1986
+ // caller awaiting the same cached rejection.
1987
+ this._initializePromise = undefined
1988
+ throw error
1940
1989
  }
1941
1990
  }
1942
1991
 
@@ -1,10 +1,11 @@
1
1
  // @ts-check
2
2
  import runJobPayload from "./job-runner.js";
3
+ import setRunnerProcessTitle from "./runner-process-title.js";
3
4
  // Name the process so `ps`/`top`/`htop` can identify forked job runners at a
4
5
  // glance instead of a wall of generic "node" entries. Updated to the specific
5
6
  // job name once one arrives (see runJobMessage), so operators can see exactly
6
7
  // which jobs are running, how many of each, and which are eating resources.
7
- process.title = "velocious background-jobs-runner";
8
+ setRunnerProcessTitle();
8
9
  let finishing = false;
9
10
  /**
10
11
  * Runs is job message.
@@ -79,4 +80,4 @@ process.once("disconnect", () => {
79
80
  process.once("message", (message) => {
80
81
  void handleJobMessage(message);
81
82
  });
82
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9ya2VkLXJ1bm5lci1jaGlsZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9iYWNrZ3JvdW5kLWpvYnMvZm9ya2VkLXJ1bm5lci1jaGlsZC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZO0FBRVosT0FBTyxhQUFhLE1BQU0saUJBQWlCLENBQUE7QUFFM0MsNkVBQTZFO0FBQzdFLDhFQUE4RTtBQUM5RSw4RUFBOEU7QUFDOUUsNEVBQTRFO0FBQzVFLE9BQU8sQ0FBQyxLQUFLLEdBQUcsa0NBQWtDLENBQUE7QUFFbEQsSUFBSSxTQUFTLEdBQUcsS0FBSyxDQUFBO0FBRXJCOzs7O0dBSUc7QUFDSCxTQUFTLFlBQVksQ0FBQyxPQUFPO0lBQzNCLElBQUksQ0FBQyxPQUFPLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUTtRQUFFLE9BQU8sS0FBSyxDQUFBO0lBRXpELE1BQU0sYUFBYSxHQUFHLHNDQUFzQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUE7SUFFdEUsT0FBTyxhQUFhLENBQUMsSUFBSSxLQUFLLEtBQUssSUFBSSxNQUFNLENBQUMsTUFBTSxDQUFDLGFBQWEsRUFBRSxTQUFTLENBQUMsQ0FBQTtBQUNoRixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILFNBQVMsTUFBTSxDQUFDLFFBQVE7SUFDdEIsU0FBUyxHQUFHLElBQUksQ0FBQTtJQUNoQixPQUFPLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQTtJQUUzQixJQUFJLE9BQU8sQ0FBQyxTQUFTLElBQUksT0FBTyxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQzVDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsQ0FBQTtJQUN0QixDQUFDO0lBRUQsWUFBWSxDQUFDLEdBQUcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQTtBQUM1QyxDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsU0FBUyxpQkFBaUI7SUFDeEIsSUFBSSxPQUFPLENBQUMsSUFBSTtRQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBQyxJQUFJLEVBQUUsY0FBYyxFQUFDLENBQUMsQ0FBQTtBQUN4RCxDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILEtBQUssVUFBVSxhQUFhLENBQUMsT0FBTztJQUNsQyxJQUFJLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDM0IsTUFBTSxJQUFJLEtBQUssQ0FBQyx1REFBdUQsQ0FBQyxDQUFBO0lBQzFFLENBQUM7SUFFRCwyRUFBMkU7SUFDM0UsNkVBQTZFO0lBQzdFLDhFQUE4RTtJQUM5RSxNQUFNLGFBQWEsQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLEVBQUMsZ0JBQWdCLEVBQUUsS0FBSyxFQUFDLENBQUMsQ0FBQTtBQUNqRSxDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILEtBQUssVUFBVSxnQkFBZ0IsQ0FBQyxPQUFPO0lBQ3JDLElBQUksQ0FBQztRQUNILE1BQU0sYUFBYSxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQzVCLGlCQUFpQixFQUFFLENBQUE7UUFDbkIsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFBO0lBQ1gsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixpQkFBaUIsRUFBRSxDQUFBO1FBQ25CLE9BQU8sQ0FBQyxLQUFLLENBQUMsc0NBQXNDLEVBQUUsS0FBSyxDQUFDLENBQUE7UUFDNUQsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFBO0lBQ1gsQ0FBQztBQUNILENBQUM7QUFFRCxLQUFLLE1BQU0sTUFBTSxJQUFJLENBQUMsU0FBUyxFQUFFLFFBQVEsQ0FBQyxFQUFFLENBQUM7SUFDM0MsT0FBTyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFBO0FBQzdDLENBQUM7QUFFRCxPQUFPLENBQUMsSUFBSSxDQUFDLFlBQVksRUFBRSxHQUFHLEVBQUU7SUFDOUIsSUFBSSxDQUFDLFNBQVM7UUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFBO0FBQ2pDLENBQUMsQ0FBQyxDQUFBO0FBRUYsT0FBTyxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQyxPQUFPLEVBQUUsRUFBRTtJQUNsQyxLQUFLLGdCQUFnQixDQUFDLE9BQU8sQ0FBQyxDQUFBO0FBQ2hDLENBQUMsQ0FBQyxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQHRzLWNoZWNrXG5cbmltcG9ydCBydW5Kb2JQYXlsb2FkIGZyb20gXCIuL2pvYi1ydW5uZXIuanNcIlxuXG4vLyBOYW1lIHRoZSBwcm9jZXNzIHNvIGBwc2AvYHRvcGAvYGh0b3BgIGNhbiBpZGVudGlmeSBmb3JrZWQgam9iIHJ1bm5lcnMgYXQgYVxuLy8gZ2xhbmNlIGluc3RlYWQgb2YgYSB3YWxsIG9mIGdlbmVyaWMgXCJub2RlXCIgZW50cmllcy4gVXBkYXRlZCB0byB0aGUgc3BlY2lmaWNcbi8vIGpvYiBuYW1lIG9uY2Ugb25lIGFycml2ZXMgKHNlZSBydW5Kb2JNZXNzYWdlKSwgc28gb3BlcmF0b3JzIGNhbiBzZWUgZXhhY3RseVxuLy8gd2hpY2ggam9icyBhcmUgcnVubmluZywgaG93IG1hbnkgb2YgZWFjaCwgYW5kIHdoaWNoIGFyZSBlYXRpbmcgcmVzb3VyY2VzLlxucHJvY2Vzcy50aXRsZSA9IFwidmVsb2Npb3VzIGJhY2tncm91bmQtam9icy1ydW5uZXJcIlxuXG5sZXQgZmluaXNoaW5nID0gZmFsc2VcblxuLyoqXG4gKiBSdW5zIGlzIGpvYiBtZXNzYWdlLlxuICogQHBhcmFtIHs/fSBtZXNzYWdlIC0gSVBDIG1lc3NhZ2UuXG4gKiBAcmV0dXJucyB7bWVzc2FnZSBpcyB7dHlwZTogXCJqb2JcIiwgcGF5bG9hZDogaW1wb3J0KFwiLi90eXBlcy5qc1wiKS5CYWNrZ3JvdW5kSm9iUGF5bG9hZH19IC0gV2hldGhlciB0aGlzIGlzIGEgam9iIG1lc3NhZ2UuXG4gKi9cbmZ1bmN0aW9uIGlzSm9iTWVzc2FnZShtZXNzYWdlKSB7XG4gIGlmICghbWVzc2FnZSB8fCB0eXBlb2YgbWVzc2FnZSAhPT0gXCJvYmplY3RcIikgcmV0dXJuIGZhbHNlXG5cbiAgY29uc3QgbWVzc2FnZVJlY29yZCA9IC8qKiBAdHlwZSB7e3R5cGU/OiA/LCBwYXlsb2FkPzogP319ICovIChtZXNzYWdlKVxuXG4gIHJldHVybiBtZXNzYWdlUmVjb3JkLnR5cGUgPT09IFwiam9iXCIgJiYgT2JqZWN0Lmhhc093bihtZXNzYWdlUmVjb3JkLCBcInBheWxvYWRcIilcbn1cblxuLyoqXG4gKiBSdW5zIGZpbmlzaC5cbiAqIEBwYXJhbSB7bnVtYmVyfSBleGl0Q29kZSAtIFByb2Nlc3MgZXhpdCBjb2RlLlxuICogQHJldHVybnMge3ZvaWR9XG4gKi9cbmZ1bmN0aW9uIGZpbmlzaChleGl0Q29kZSkge1xuICBmaW5pc2hpbmcgPSB0cnVlXG4gIHByb2Nlc3MuZXhpdENvZGUgPSBleGl0Q29kZVxuXG4gIGlmIChwcm9jZXNzLmNvbm5lY3RlZCAmJiBwcm9jZXNzLmRpc2Nvbm5lY3QpIHtcbiAgICBwcm9jZXNzLmRpc2Nvbm5lY3QoKVxuICB9XG5cbiAgc2V0SW1tZWRpYXRlKCgpID0+IHByb2Nlc3MuZXhpdChleGl0Q29kZSkpXG59XG5cbi8qKlxuICogUnVucyByZXBvcnQgam9iIGZpbmlzaGVkLlxuICogQHJldHVybnMge3ZvaWR9XG4gKi9cbmZ1bmN0aW9uIHJlcG9ydEpvYkZpbmlzaGVkKCkge1xuICBpZiAocHJvY2Vzcy5zZW5kKSBwcm9jZXNzLnNlbmQoe3R5cGU6IFwiam9iLXJlcG9ydGVkXCJ9KVxufVxuXG4vKipcbiAqIFJ1bnMgcnVuIGpvYiBtZXNzYWdlLlxuICogQHBhcmFtIHs/fSBtZXNzYWdlIC0gSVBDIG1lc3NhZ2UuXG4gKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyBhZnRlciB0aGUgcGF5bG9hZCBoYXMgcnVuLlxuICovXG5hc3luYyBmdW5jdGlvbiBydW5Kb2JNZXNzYWdlKG1lc3NhZ2UpIHtcbiAgaWYgKCFpc0pvYk1lc3NhZ2UobWVzc2FnZSkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXCJGb3JrZWQgYmFja2dyb3VuZCBqb2IgcnVubmVyIHJlY2VpdmVkIGludmFsaWQgcGF5bG9hZFwiKVxuICB9XG5cbiAgLy8gVGhlIHBlci1qb2IgcHJvY2VzcyB0aXRsZSAoYW5kIGl0cyByZXN0b3JlKSBpcyBzZXQgaW5zaWRlIHJ1bkpvYlBheWxvYWQsXG4gIC8vIHdoaWNoIHJlYWRzIHRoZSBqb2IgY2xhc3MncyBgc3RhdGljIHByb2Nlc3NUaXRsZWAuIFRoaXMgcHJvY2VzcyBib290cyB3aXRoXG4gIC8vIHRoZSBiYXNlIFwidmVsb2Npb3VzIGJhY2tncm91bmQtam9icy1ydW5uZXJcIiB0aXRsZSBzZXQgYXQgbW9kdWxlIGxvYWQgYWJvdmUuXG4gIGF3YWl0IHJ1bkpvYlBheWxvYWQobWVzc2FnZS5wYXlsb2FkLCB7Y2xvc2VDb25uZWN0aW9uczogZmFsc2V9KVxufVxuXG4vKipcbiAqIFJ1bnMgaGFuZGxlIGpvYiBtZXNzYWdlLlxuICogQHBhcmFtIHs/fSBtZXNzYWdlIC0gSVBDIG1lc3NhZ2UuXG4gKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyBhZnRlciBjb21wbGV0aW9uIGlzIHJlcG9ydGVkLlxuICovXG5hc3luYyBmdW5jdGlvbiBoYW5kbGVKb2JNZXNzYWdlKG1lc3NhZ2UpIHtcbiAgdHJ5IHtcbiAgICBhd2FpdCBydW5Kb2JNZXNzYWdlKG1lc3NhZ2UpXG4gICAgcmVwb3J0Sm9iRmluaXNoZWQoKVxuICAgIGZpbmlzaCgwKVxuICB9IGNhdGNoIChlcnJvcikge1xuICAgIHJlcG9ydEpvYkZpbmlzaGVkKClcbiAgICBjb25zb2xlLmVycm9yKFwiRm9ya2VkIGJhY2tncm91bmQgam9iIHJ1bm5lciBmYWlsZWQ6XCIsIGVycm9yKVxuICAgIGZpbmlzaCgxKVxuICB9XG59XG5cbmZvciAoY29uc3Qgc2lnbmFsIG9mIFtcIlNJR1RFUk1cIiwgXCJTSUdJTlRcIl0pIHtcbiAgcHJvY2Vzcy5vbmNlKHNpZ25hbCwgKCkgPT4gcHJvY2Vzcy5leGl0KDEpKVxufVxuXG5wcm9jZXNzLm9uY2UoXCJkaXNjb25uZWN0XCIsICgpID0+IHtcbiAgaWYgKCFmaW5pc2hpbmcpIHByb2Nlc3MuZXhpdCgwKVxufSlcblxucHJvY2Vzcy5vbmNlKFwibWVzc2FnZVwiLCAobWVzc2FnZSkgPT4ge1xuICB2b2lkIGhhbmRsZUpvYk1lc3NhZ2UobWVzc2FnZSlcbn0pXG4iXX0=
83
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9ya2VkLXJ1bm5lci1jaGlsZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9iYWNrZ3JvdW5kLWpvYnMvZm9ya2VkLXJ1bm5lci1jaGlsZC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZO0FBRVosT0FBTyxhQUFhLE1BQU0saUJBQWlCLENBQUE7QUFDM0MsT0FBTyxxQkFBcUIsTUFBTSwyQkFBMkIsQ0FBQTtBQUU3RCw2RUFBNkU7QUFDN0UsOEVBQThFO0FBQzlFLDhFQUE4RTtBQUM5RSw0RUFBNEU7QUFDNUUscUJBQXFCLEVBQUUsQ0FBQTtBQUV2QixJQUFJLFNBQVMsR0FBRyxLQUFLLENBQUE7QUFFckI7Ozs7R0FJRztBQUNILFNBQVMsWUFBWSxDQUFDLE9BQU87SUFDM0IsSUFBSSxDQUFDLE9BQU8sSUFBSSxPQUFPLE9BQU8sS0FBSyxRQUFRO1FBQUUsT0FBTyxLQUFLLENBQUE7SUFFekQsTUFBTSxhQUFhLEdBQUcsc0NBQXNDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQTtJQUV0RSxPQUFPLGFBQWEsQ0FBQyxJQUFJLEtBQUssS0FBSyxJQUFJLE1BQU0sQ0FBQyxNQUFNLENBQUMsYUFBYSxFQUFFLFNBQVMsQ0FBQyxDQUFBO0FBQ2hGLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsU0FBUyxNQUFNLENBQUMsUUFBUTtJQUN0QixTQUFTLEdBQUcsSUFBSSxDQUFBO0lBQ2hCLE9BQU8sQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFBO0lBRTNCLElBQUksT0FBTyxDQUFDLFNBQVMsSUFBSSxPQUFPLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDNUMsT0FBTyxDQUFDLFVBQVUsRUFBRSxDQUFBO0lBQ3RCLENBQUM7SUFFRCxZQUFZLENBQUMsR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFBO0FBQzVDLENBQUM7QUFFRDs7O0dBR0c7QUFDSCxTQUFTLGlCQUFpQjtJQUN4QixJQUFJLE9BQU8sQ0FBQyxJQUFJO1FBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFDLElBQUksRUFBRSxjQUFjLEVBQUMsQ0FBQyxDQUFBO0FBQ3hELENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsS0FBSyxVQUFVLGFBQWEsQ0FBQyxPQUFPO0lBQ2xDLElBQUksQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUMzQixNQUFNLElBQUksS0FBSyxDQUFDLHVEQUF1RCxDQUFDLENBQUE7SUFDMUUsQ0FBQztJQUVELDJFQUEyRTtJQUMzRSw2RUFBNkU7SUFDN0UsOEVBQThFO0lBQzlFLE1BQU0sYUFBYSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsRUFBQyxnQkFBZ0IsRUFBRSxLQUFLLEVBQUMsQ0FBQyxDQUFBO0FBQ2pFLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsS0FBSyxVQUFVLGdCQUFnQixDQUFDLE9BQU87SUFDckMsSUFBSSxDQUFDO1FBQ0gsTUFBTSxhQUFhLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDNUIsaUJBQWlCLEVBQUUsQ0FBQTtRQUNuQixNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUE7SUFDWCxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLGlCQUFpQixFQUFFLENBQUE7UUFDbkIsT0FBTyxDQUFDLEtBQUssQ0FBQyxzQ0FBc0MsRUFBRSxLQUFLLENBQUMsQ0FBQTtRQUM1RCxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUE7SUFDWCxDQUFDO0FBQ0gsQ0FBQztBQUVELEtBQUssTUFBTSxNQUFNLElBQUksQ0FBQyxTQUFTLEVBQUUsUUFBUSxDQUFDLEVBQUUsQ0FBQztJQUMzQyxPQUFPLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7QUFDN0MsQ0FBQztBQUVELE9BQU8sQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFLEdBQUcsRUFBRTtJQUM5QixJQUFJLENBQUMsU0FBUztRQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUE7QUFDakMsQ0FBQyxDQUFDLENBQUE7QUFFRixPQUFPLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFO0lBQ2xDLEtBQUssZ0JBQWdCLENBQUMsT0FBTyxDQUFDLENBQUE7QUFDaEMsQ0FBQyxDQUFDLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IHJ1bkpvYlBheWxvYWQgZnJvbSBcIi4vam9iLXJ1bm5lci5qc1wiXG5pbXBvcnQgc2V0UnVubmVyUHJvY2Vzc1RpdGxlIGZyb20gXCIuL3J1bm5lci1wcm9jZXNzLXRpdGxlLmpzXCJcblxuLy8gTmFtZSB0aGUgcHJvY2VzcyBzbyBgcHNgL2B0b3BgL2BodG9wYCBjYW4gaWRlbnRpZnkgZm9ya2VkIGpvYiBydW5uZXJzIGF0IGFcbi8vIGdsYW5jZSBpbnN0ZWFkIG9mIGEgd2FsbCBvZiBnZW5lcmljIFwibm9kZVwiIGVudHJpZXMuIFVwZGF0ZWQgdG8gdGhlIHNwZWNpZmljXG4vLyBqb2IgbmFtZSBvbmNlIG9uZSBhcnJpdmVzIChzZWUgcnVuSm9iTWVzc2FnZSksIHNvIG9wZXJhdG9ycyBjYW4gc2VlIGV4YWN0bHlcbi8vIHdoaWNoIGpvYnMgYXJlIHJ1bm5pbmcsIGhvdyBtYW55IG9mIGVhY2gsIGFuZCB3aGljaCBhcmUgZWF0aW5nIHJlc291cmNlcy5cbnNldFJ1bm5lclByb2Nlc3NUaXRsZSgpXG5cbmxldCBmaW5pc2hpbmcgPSBmYWxzZVxuXG4vKipcbiAqIFJ1bnMgaXMgam9iIG1lc3NhZ2UuXG4gKiBAcGFyYW0gez99IG1lc3NhZ2UgLSBJUEMgbWVzc2FnZS5cbiAqIEByZXR1cm5zIHttZXNzYWdlIGlzIHt0eXBlOiBcImpvYlwiLCBwYXlsb2FkOiBpbXBvcnQoXCIuL3R5cGVzLmpzXCIpLkJhY2tncm91bmRKb2JQYXlsb2FkfX0gLSBXaGV0aGVyIHRoaXMgaXMgYSBqb2IgbWVzc2FnZS5cbiAqL1xuZnVuY3Rpb24gaXNKb2JNZXNzYWdlKG1lc3NhZ2UpIHtcbiAgaWYgKCFtZXNzYWdlIHx8IHR5cGVvZiBtZXNzYWdlICE9PSBcIm9iamVjdFwiKSByZXR1cm4gZmFsc2VcblxuICBjb25zdCBtZXNzYWdlUmVjb3JkID0gLyoqIEB0eXBlIHt7dHlwZT86ID8sIHBheWxvYWQ/OiA/fX0gKi8gKG1lc3NhZ2UpXG5cbiAgcmV0dXJuIG1lc3NhZ2VSZWNvcmQudHlwZSA9PT0gXCJqb2JcIiAmJiBPYmplY3QuaGFzT3duKG1lc3NhZ2VSZWNvcmQsIFwicGF5bG9hZFwiKVxufVxuXG4vKipcbiAqIFJ1bnMgZmluaXNoLlxuICogQHBhcmFtIHtudW1iZXJ9IGV4aXRDb2RlIC0gUHJvY2VzcyBleGl0IGNvZGUuXG4gKiBAcmV0dXJucyB7dm9pZH1cbiAqL1xuZnVuY3Rpb24gZmluaXNoKGV4aXRDb2RlKSB7XG4gIGZpbmlzaGluZyA9IHRydWVcbiAgcHJvY2Vzcy5leGl0Q29kZSA9IGV4aXRDb2RlXG5cbiAgaWYgKHByb2Nlc3MuY29ubmVjdGVkICYmIHByb2Nlc3MuZGlzY29ubmVjdCkge1xuICAgIHByb2Nlc3MuZGlzY29ubmVjdCgpXG4gIH1cblxuICBzZXRJbW1lZGlhdGUoKCkgPT4gcHJvY2Vzcy5leGl0KGV4aXRDb2RlKSlcbn1cblxuLyoqXG4gKiBSdW5zIHJlcG9ydCBqb2IgZmluaXNoZWQuXG4gKiBAcmV0dXJucyB7dm9pZH1cbiAqL1xuZnVuY3Rpb24gcmVwb3J0Sm9iRmluaXNoZWQoKSB7XG4gIGlmIChwcm9jZXNzLnNlbmQpIHByb2Nlc3Muc2VuZCh7dHlwZTogXCJqb2ItcmVwb3J0ZWRcIn0pXG59XG5cbi8qKlxuICogUnVucyBydW4gam9iIG1lc3NhZ2UuXG4gKiBAcGFyYW0gez99IG1lc3NhZ2UgLSBJUEMgbWVzc2FnZS5cbiAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIGFmdGVyIHRoZSBwYXlsb2FkIGhhcyBydW4uXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHJ1bkpvYk1lc3NhZ2UobWVzc2FnZSkge1xuICBpZiAoIWlzSm9iTWVzc2FnZShtZXNzYWdlKSkge1xuICAgIHRocm93IG5ldyBFcnJvcihcIkZvcmtlZCBiYWNrZ3JvdW5kIGpvYiBydW5uZXIgcmVjZWl2ZWQgaW52YWxpZCBwYXlsb2FkXCIpXG4gIH1cblxuICAvLyBUaGUgcGVyLWpvYiBwcm9jZXNzIHRpdGxlIChhbmQgaXRzIHJlc3RvcmUpIGlzIHNldCBpbnNpZGUgcnVuSm9iUGF5bG9hZCxcbiAgLy8gd2hpY2ggcmVhZHMgdGhlIGpvYiBjbGFzcydzIGBzdGF0aWMgcHJvY2Vzc1RpdGxlYC4gVGhpcyBwcm9jZXNzIGJvb3RzIHdpdGhcbiAgLy8gdGhlIGJhc2UgXCJ2ZWxvY2lvdXMgYmFja2dyb3VuZC1qb2JzLXJ1bm5lclwiIHRpdGxlIHNldCBhdCBtb2R1bGUgbG9hZCBhYm92ZS5cbiAgYXdhaXQgcnVuSm9iUGF5bG9hZChtZXNzYWdlLnBheWxvYWQsIHtjbG9zZUNvbm5lY3Rpb25zOiBmYWxzZX0pXG59XG5cbi8qKlxuICogUnVucyBoYW5kbGUgam9iIG1lc3NhZ2UuXG4gKiBAcGFyYW0gez99IG1lc3NhZ2UgLSBJUEMgbWVzc2FnZS5cbiAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIGFmdGVyIGNvbXBsZXRpb24gaXMgcmVwb3J0ZWQuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGhhbmRsZUpvYk1lc3NhZ2UobWVzc2FnZSkge1xuICB0cnkge1xuICAgIGF3YWl0IHJ1bkpvYk1lc3NhZ2UobWVzc2FnZSlcbiAgICByZXBvcnRKb2JGaW5pc2hlZCgpXG4gICAgZmluaXNoKDApXG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgcmVwb3J0Sm9iRmluaXNoZWQoKVxuICAgIGNvbnNvbGUuZXJyb3IoXCJGb3JrZWQgYmFja2dyb3VuZCBqb2IgcnVubmVyIGZhaWxlZDpcIiwgZXJyb3IpXG4gICAgZmluaXNoKDEpXG4gIH1cbn1cblxuZm9yIChjb25zdCBzaWduYWwgb2YgW1wiU0lHVEVSTVwiLCBcIlNJR0lOVFwiXSkge1xuICBwcm9jZXNzLm9uY2Uoc2lnbmFsLCAoKSA9PiBwcm9jZXNzLmV4aXQoMSkpXG59XG5cbnByb2Nlc3Mub25jZShcImRpc2Nvbm5lY3RcIiwgKCkgPT4ge1xuICBpZiAoIWZpbmlzaGluZykgcHJvY2Vzcy5leGl0KDApXG59KVxuXG5wcm9jZXNzLm9uY2UoXCJtZXNzYWdlXCIsIChtZXNzYWdlKSA9PiB7XG4gIHZvaWQgaGFuZGxlSm9iTWVzc2FnZShtZXNzYWdlKVxufSlcbiJdfQ==
@@ -3,9 +3,18 @@
3
3
  * @param {import("./types.js").BackgroundJobPayload} payload - Payload.
4
4
  * @param {object} [options] - Runner options.
5
5
  * @param {boolean} [options.closeConnections] - Whether to gracefully close framework connections after the job.
6
+ * @param {boolean} [options.manageProcessTitle] - Whether to set the per-job process title and restore it afterwards. Off for concurrent pooled runners, where interleaved snapshot/restore of the single process-wide `process.title` would corrupt it; the pooled child owns an aggregate title instead.
6
7
  * @returns {Promise<void>} - Resolves when complete.
7
8
  */
8
- export default function runJobPayload(payload: import("./types.js").BackgroundJobPayload, { closeConnections }?: {
9
+ export default function runJobPayload(payload: import("./types.js").BackgroundJobPayload, { closeConnections, manageProcessTitle }?: {
9
10
  closeConnections?: boolean | undefined;
11
+ manageProcessTitle?: boolean | undefined;
10
12
  }): Promise<void>;
13
+ export class BackgroundJobPerformedFailure extends Error {
14
+ /**
15
+ * Creates a performed-job failure after its terminal report is acknowledged.
16
+ * @param {Error} cause - A job perform error whose failed terminal report was acknowledged.
17
+ */
18
+ constructor(cause: Error);
19
+ }
11
20
  //# sourceMappingURL=job-runner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"job-runner.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/job-runner.js"],"names":[],"mappings":"AAgEA;;;;;;GAMG;AACH,+CALW,OAAO,YAAY,EAAE,oBAAoB,yBAEjD;IAA0B,gBAAgB;CAC1C,GAAU,OAAO,CAAC,IAAI,CAAC,CAkEzB"}
1
+ {"version":3,"file":"job-runner.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/job-runner.js"],"names":[],"mappings":"AA2EA;;;;;;;GAOG;AACH,+CANW,OAAO,YAAY,EAAE,oBAAoB,6CAEjD;IAA0B,gBAAgB;IAChB,kBAAkB;CAC5C,GAAU,OAAO,CAAC,IAAI,CAAC,CAoEzB;AA7ID;IACE;;;OAGG;IACH,mBAFW,KAAK,EAKf;CACF"}