velocious 1.0.529 → 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.
- package/README.md +2 -1
- package/build/background-jobs/job-runner.js +5 -3
- package/build/background-jobs/pooled-runner-child.js +53 -16
- package/build/background-jobs/worker.js +124 -36
- package/build/configuration-types.js +1 -0
- package/build/configuration.js +33 -4
- package/build/src/background-jobs/job-runner.d.ts +3 -1
- package/build/src/background-jobs/job-runner.d.ts.map +1 -1
- package/build/src/background-jobs/job-runner.js +8 -4
- package/build/src/background-jobs/pooled-runner-child.js +49 -18
- package/build/src/background-jobs/worker.d.ts +48 -10
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +122 -39
- package/build/src/configuration-types.d.ts +5 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +2 -1
- package/build/src/configuration.d.ts +6 -0
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +34 -4
- package/package.json +1 -1
- package/src/background-jobs/job-runner.js +5 -3
- package/src/background-jobs/pooled-runner-child.js +53 -16
- package/src/background-jobs/worker.js +124 -36
- package/src/configuration-types.js +1 -0
- package/src/configuration.js +33 -4
package/README.md
CHANGED
|
@@ -1977,6 +1977,7 @@ export default new Configuration({
|
|
|
1977
1977
|
maxConcurrentForkedJobs: 4,
|
|
1978
1978
|
maxConcurrentInlineJobs: 4,
|
|
1979
1979
|
pooledRunnerCount: 4,
|
|
1980
|
+
pooledRunnerConcurrency: 1,
|
|
1980
1981
|
pooledRunnerMaxJobs: 100,
|
|
1981
1982
|
pooledRunnerMaxRssBytes: 536870912,
|
|
1982
1983
|
pooledRunnerMaxLifetimeMs: 3600000,
|
|
@@ -2008,7 +2009,7 @@ VELOCIOUS_BACKGROUND_JOBS_JOB_TIMEOUT_MS=5400000
|
|
|
2008
2009
|
|
|
2009
2010
|
`maxConcurrentInlineJobs` (default: `4`) caps how many `executionMode: "inline"` jobs a single `background-jobs-worker` process runs in parallel. Concurrency is at the JS event-loop level: every job in flight shares the worker's process and DB connection pool, so the cap should fit the pool, not the CPU count. Forking remains the right tool when you need memory isolation across long-running jobs or want to use more cores. The older `forked: false` option still maps to inline mode.
|
|
2010
2011
|
|
|
2011
|
-
New jobs default to `executionMode: "pooled"`: a worker runs them
|
|
2012
|
+
New jobs default to `executionMode: "pooled"`: a worker runs them in warm, reusable Node child runners. `pooledRunnerCount` (default: `4`) bounds this independent per-worker pool, and `pooledRunnerConcurrency` (default: `1`) sets how many jobs each child runs at once on its own event loop, so total pooled capacity is `pooledRunnerCount × pooledRunnerConcurrency` — raise concurrency for I/O-bound jobs to get high throughput from a bounded, isolated set of processes. `pooledRunnerCount`, `pooledRunnerConcurrency`, and `pooledRunnerMaxJobs` must be finite positive integers; the RSS and lifetime limits must be finite positive numbers. A child is recycled after an acknowledged job when it reaches `pooledRunnerMaxJobs` (default: `100`), `pooledRunnerMaxRssBytes` (default: `536870912`, or 512 MiB), or `pooledRunnerMaxLifetimeMs` (default: `3600000`, or one hour). Pooled rows remain readable by older mains during rolling upgrades and run there with legacy one-shot fork behavior. `forked: true` remains an explicit alias for `"forked"`; `forked: false` remains inline. See [execution modes and pooled runners](docs/background-jobs.md#execution-modes-and-pooled-runners).
|
|
2012
2013
|
|
|
2013
2014
|
`maxConcurrentForkedJobs` (default: `4`) caps how many out-of-process `executionMode: "forked"` or `executionMode: "spawned"` jobs one worker may keep in flight. Forked jobs use `child_process.fork()` with an attached IPC channel. After the main process acknowledges their durable status report, forked and spawned one-shot runners exit without waiting for graceful Beacon/database teardown; the OS closes their process-owned resources. A missing or rejected status acknowledgement makes the runner exit as failed instead of reporting clean success. Spawned jobs use the legacy `background-jobs-runner` CLI process via `child_process.spawn()` and are only for callers that intentionally want that spawned behavior.
|
|
2014
2015
|
|
|
@@ -78,9 +78,10 @@ function runnerProcessTitle(JobClass, payload) {
|
|
|
78
78
|
* @param {import("./types.js").BackgroundJobPayload} payload - Payload.
|
|
79
79
|
* @param {object} [options] - Runner options.
|
|
80
80
|
* @param {boolean} [options.closeConnections] - Whether to gracefully close framework connections after the job.
|
|
81
|
+
* @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.
|
|
81
82
|
* @returns {Promise<void>} - Resolves when complete.
|
|
82
83
|
*/
|
|
83
|
-
export default async function runJobPayload(payload, {closeConnections = true} = {}) {
|
|
84
|
+
export default async function runJobPayload(payload, {closeConnections = true, manageProcessTitle = true} = {}) {
|
|
84
85
|
const configuration = await configurationResolver()
|
|
85
86
|
configuration.setCurrent()
|
|
86
87
|
await configuration.initialize({type: "background-jobs-runner"})
|
|
@@ -98,8 +99,9 @@ export default async function runJobPayload(payload, {closeConnections = true} =
|
|
|
98
99
|
|
|
99
100
|
// Name the process after the job it is running so `ps`/`top` show what each
|
|
100
101
|
// runner is doing; restored in the `finally` below when the job finishes.
|
|
102
|
+
// Skipped for concurrent pooled runners, whose child owns an aggregate title.
|
|
101
103
|
const previousTitle = process.title
|
|
102
|
-
process.title = runnerProcessTitle(JobClass, payload)
|
|
104
|
+
if (manageProcessTitle) process.title = runnerProcessTitle(JobClass, payload)
|
|
103
105
|
|
|
104
106
|
try {
|
|
105
107
|
try {
|
|
@@ -136,7 +138,7 @@ export default async function runJobPayload(payload, {closeConnections = true} =
|
|
|
136
138
|
} finally {
|
|
137
139
|
// Restore the runner's base title so a lingering/idle runner (or a reused
|
|
138
140
|
// one) doesn't misreport a finished job as still running.
|
|
139
|
-
process.title = previousTitle
|
|
141
|
+
if (manageProcessTitle) process.title = previousTitle
|
|
140
142
|
if (closeConnections) {
|
|
141
143
|
try {
|
|
142
144
|
await configuration.disconnectBeacon()
|
|
@@ -3,8 +3,32 @@
|
|
|
3
3
|
import runJobPayload, { BackgroundJobPerformedFailure } from "./job-runner.js"
|
|
4
4
|
import setRunnerProcessTitle from "./runner-process-title.js"
|
|
5
5
|
|
|
6
|
+
const BASE_PROCESS_TITLE = "velocious background-jobs-runner"
|
|
7
|
+
|
|
6
8
|
setRunnerProcessTitle()
|
|
7
|
-
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Ids of jobs currently running in this child. A pooled child runs up to
|
|
12
|
+
* `pooledRunnerConcurrency` jobs at once (the worker only dispatches within that
|
|
13
|
+
* bound); the set dedupes a redelivered job id and lets each job settle
|
|
14
|
+
* independently.
|
|
15
|
+
* @type {Set<string>}
|
|
16
|
+
*/
|
|
17
|
+
const runningJobIds = new Set()
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sets an aggregate process title from the current in-flight count. A child runs
|
|
21
|
+
* jobs concurrently, so a per-job title (which `runJobPayload` would snapshot and
|
|
22
|
+
* restore around a single job) cannot represent the process — interleaved
|
|
23
|
+
* completions would leave a stale label. Recomputing from `runningJobIds.size` is
|
|
24
|
+
* concurrency-safe and honest: `ps`/`top` show how many jobs the child is running.
|
|
25
|
+
* @returns {void}
|
|
26
|
+
*/
|
|
27
|
+
function updateProcessTitle() {
|
|
28
|
+
const count = runningJobIds.size
|
|
29
|
+
|
|
30
|
+
process.title = count > 0 ? `${BASE_PROCESS_TITLE}: ${count} ${count === 1 ? "job" : "jobs"}` : BASE_PROCESS_TITLE
|
|
31
|
+
}
|
|
8
32
|
|
|
9
33
|
/**
|
|
10
34
|
* Checks whether an IPC value is a runnable pooled job message.
|
|
@@ -46,33 +70,46 @@ function sendOutcome({jobId, acknowledged, status, error}) {
|
|
|
46
70
|
}
|
|
47
71
|
|
|
48
72
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
73
|
+
* Runs one job concurrently with any siblings and reports its own terminal
|
|
74
|
+
* outcome. A single job's unexpected failure reports that job for reclamation
|
|
75
|
+
* (`acknowledged: false`) but does NOT take down the child — its concurrent
|
|
76
|
+
* siblings keep running. Only a process-level fault (which escapes every
|
|
77
|
+
* per-job try/catch) ends the child, which the worker sees as an exit and
|
|
78
|
+
* reclaims for the whole in-flight set.
|
|
79
|
+
* @param {import("./types.js").BackgroundJobPayload & {id: string}} payload - Job payload.
|
|
51
80
|
* @returns {Promise<void>} - Resolves after reporting.
|
|
52
81
|
*/
|
|
53
|
-
async function
|
|
54
|
-
if (running || !isJobMessage(message)) return
|
|
55
|
-
|
|
56
|
-
running = true
|
|
82
|
+
async function runJob(payload) {
|
|
57
83
|
try {
|
|
58
|
-
await runJobPayload(
|
|
59
|
-
await sendOutcome({jobId:
|
|
84
|
+
await runJobPayload(payload, {closeConnections: false, manageProcessTitle: false})
|
|
85
|
+
await sendOutcome({jobId: payload.id, acknowledged: true, status: "completed"})
|
|
60
86
|
} catch (error) {
|
|
61
87
|
if (error instanceof BackgroundJobPerformedFailure) {
|
|
62
|
-
await sendOutcome({jobId:
|
|
88
|
+
await sendOutcome({jobId: payload.id, acknowledged: true, status: "failed"})
|
|
63
89
|
} else {
|
|
64
90
|
const reportError = error instanceof Error ? error : new Error(String(error))
|
|
65
91
|
console.error("Pooled background job runner failed before terminal acknowledgement:", reportError)
|
|
66
|
-
await sendOutcome({jobId:
|
|
67
|
-
process.exitCode = 1
|
|
68
|
-
if (process.disconnect) process.disconnect()
|
|
69
|
-
setImmediate(() => process.exit(1))
|
|
92
|
+
await sendOutcome({jobId: payload.id, acknowledged: false, error: reportError})
|
|
70
93
|
}
|
|
71
94
|
} finally {
|
|
72
|
-
|
|
95
|
+
runningJobIds.delete(payload.id)
|
|
96
|
+
updateProcessTitle()
|
|
73
97
|
}
|
|
74
98
|
}
|
|
75
99
|
|
|
76
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Handles a job message, starting it alongside any concurrent siblings.
|
|
102
|
+
* @param {?} message - IPC message.
|
|
103
|
+
* @returns {void}
|
|
104
|
+
*/
|
|
105
|
+
function handleMessage(message) {
|
|
106
|
+
if (!isJobMessage(message) || runningJobIds.has(message.payload.id)) return
|
|
107
|
+
|
|
108
|
+
runningJobIds.add(message.payload.id)
|
|
109
|
+
updateProcessTitle()
|
|
110
|
+
void runJob(message.payload)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
process.on("message", (message) => handleMessage(message))
|
|
77
114
|
process.once("disconnect", () => process.exit(0))
|
|
78
115
|
for (const signal of ["SIGTERM", "SIGINT"]) process.once(signal, () => process.exit(1))
|
|
@@ -66,6 +66,7 @@ export default class BackgroundJobsWorker {
|
|
|
66
66
|
* @param {number} [args.maxConcurrentForkedJobs] - Override the process runner concurrency cap from `configuration.getBackgroundJobsConfig()`.
|
|
67
67
|
* @param {number} [args.maxConcurrentInlineJobs] - Override the inline-job concurrency cap from `configuration.getBackgroundJobsConfig()`.
|
|
68
68
|
* @param {number} [args.pooledRunnerCount] - Override the pooled runner count.
|
|
69
|
+
* @param {number} [args.pooledRunnerConcurrency] - Override the per-runner concurrency.
|
|
69
70
|
* @param {number} [args.pooledRunnerMaxJobs] - Override the per-runner recycle job count.
|
|
70
71
|
* @param {number} [args.pooledRunnerMaxRssBytes] - Override the per-runner recycle RSS limit.
|
|
71
72
|
* @param {number} [args.pooledRunnerMaxLifetimeMs] - Override the per-runner recycle lifetime.
|
|
@@ -73,7 +74,7 @@ export default class BackgroundJobsWorker {
|
|
|
73
74
|
* @param {number} [args.heartbeatIntervalMs] - Override the liveness heartbeat interval (default 15000ms).
|
|
74
75
|
* @param {number} [args.jobTimeoutMs] - Override the forked-job wall-clock timeout from `configuration.getBackgroundJobsConfig()`. `0` disables it.
|
|
75
76
|
*/
|
|
76
|
-
constructor({configuration, host, port, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, forkedChildSigkillGraceMs, heartbeatIntervalMs, jobTimeoutMs} = {}) {
|
|
77
|
+
constructor({configuration, host, port, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerConcurrency, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, forkedChildSigkillGraceMs, heartbeatIntervalMs, jobTimeoutMs} = {}) {
|
|
77
78
|
/**
|
|
78
79
|
* Narrows the runtime value to the documented type.
|
|
79
80
|
* @type {Promise<import("../configuration.js").default>} */
|
|
@@ -110,10 +111,12 @@ export default class BackgroundJobsWorker {
|
|
|
110
111
|
* @type {number} */
|
|
111
112
|
this.maxConcurrentForkedJobs = this.maxConcurrentForkedJobsOverride || 4
|
|
112
113
|
this.pooledRunnerCountOverride = positiveInteger(pooledRunnerCount)
|
|
114
|
+
this.pooledRunnerConcurrencyOverride = positiveInteger(pooledRunnerConcurrency)
|
|
113
115
|
this.pooledRunnerMaxJobsOverride = positiveInteger(pooledRunnerMaxJobs)
|
|
114
116
|
this.pooledRunnerMaxRssBytesOverride = positiveNumber(pooledRunnerMaxRssBytes)
|
|
115
117
|
this.pooledRunnerMaxLifetimeMsOverride = positiveNumber(pooledRunnerMaxLifetimeMs)
|
|
116
118
|
this.pooledRunnerCount = this.pooledRunnerCountOverride || 4
|
|
119
|
+
this.pooledRunnerConcurrency = this.pooledRunnerConcurrencyOverride || 1
|
|
117
120
|
this.pooledRunnerMaxJobs = this.pooledRunnerMaxJobsOverride || 100
|
|
118
121
|
this.pooledRunnerMaxRssBytes = this.pooledRunnerMaxRssBytesOverride || 512 * 1024 * 1024
|
|
119
122
|
this.pooledRunnerMaxLifetimeMs = this.pooledRunnerMaxLifetimeMsOverride || 60 * 60 * 1000
|
|
@@ -183,7 +186,7 @@ export default class BackgroundJobsWorker {
|
|
|
183
186
|
this.inflightPooledJobs = new Set()
|
|
184
187
|
/** @type {Set<import("node:child_process").ChildProcess>} */
|
|
185
188
|
this.pooledChildren = new Set()
|
|
186
|
-
/** @type {Map<import("node:child_process").ChildProcess, {createdAtMs: number, jobsRun: number, payload
|
|
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}>} */
|
|
187
190
|
this.pooledChildStates = new Map()
|
|
188
191
|
}
|
|
189
192
|
|
|
@@ -210,6 +213,7 @@ export default class BackgroundJobsWorker {
|
|
|
210
213
|
}
|
|
211
214
|
const poolConfig = this.configuration.getBackgroundJobsConfig()
|
|
212
215
|
if (typeof this.pooledRunnerCountOverride !== "number") this.pooledRunnerCount = poolConfig.pooledRunnerCount
|
|
216
|
+
if (typeof this.pooledRunnerConcurrencyOverride !== "number") this.pooledRunnerConcurrency = poolConfig.pooledRunnerConcurrency
|
|
213
217
|
if (typeof this.pooledRunnerMaxJobsOverride !== "number") this.pooledRunnerMaxJobs = poolConfig.pooledRunnerMaxJobs
|
|
214
218
|
if (typeof this.pooledRunnerMaxRssBytesOverride !== "number") this.pooledRunnerMaxRssBytes = poolConfig.pooledRunnerMaxRssBytes
|
|
215
219
|
if (typeof this.pooledRunnerMaxLifetimeMsOverride !== "number") this.pooledRunnerMaxLifetimeMs = poolConfig.pooledRunnerMaxLifetimeMs
|
|
@@ -582,7 +586,7 @@ export default class BackgroundJobsWorker {
|
|
|
582
586
|
_readyMessage() {
|
|
583
587
|
const acceptsProcessJob = this.inflightProcessJobs.size < this.maxConcurrentForkedJobs
|
|
584
588
|
const acceptsInline = this.inflightInlineJobs.size < this.maxConcurrentInlineJobs
|
|
585
|
-
const acceptsPooled = this.
|
|
589
|
+
const acceptsPooled = this._availablePooledSlots() > 0
|
|
586
590
|
|
|
587
591
|
if (!acceptsProcessJob && !acceptsInline && !acceptsPooled) return null
|
|
588
592
|
|
|
@@ -612,23 +616,54 @@ export default class BackgroundJobsWorker {
|
|
|
612
616
|
}
|
|
613
617
|
|
|
614
618
|
/**
|
|
615
|
-
*
|
|
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.
|
|
616
645
|
* @param {import("./types.js").BackgroundJobPayload & {id: string}} payload - Job payload.
|
|
617
646
|
* @returns {Promise<void>} - Resolves after the durable report.
|
|
618
647
|
*/
|
|
619
648
|
_runPooledJob(payload) {
|
|
620
|
-
let child
|
|
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
|
+
}
|
|
621
657
|
if (!child) child = this._createPooledChild()
|
|
622
658
|
const state = this.pooledChildStates.get(child)
|
|
623
659
|
if (!state) throw new Error("Pooled runner state missing")
|
|
624
660
|
|
|
625
661
|
return new Promise((resolve) => {
|
|
626
|
-
state.payload
|
|
627
|
-
state.resolve = resolve
|
|
662
|
+
state.inflight.set(payload.id, {payload, resolve})
|
|
628
663
|
try {
|
|
629
664
|
child.send({type: "job", payload})
|
|
630
665
|
} catch (error) {
|
|
631
|
-
this._handlePooledChildFailure({child, error})
|
|
666
|
+
void this._handlePooledChildFailure({child, error})
|
|
632
667
|
}
|
|
633
668
|
})
|
|
634
669
|
}
|
|
@@ -647,7 +682,7 @@ export default class BackgroundJobsWorker {
|
|
|
647
682
|
})
|
|
648
683
|
this.pooledChildren.add(child)
|
|
649
684
|
this.inflightProcessChildren.add(child)
|
|
650
|
-
this.pooledChildStates.set(child, {createdAtMs: Date.now(), jobsRun: 0})
|
|
685
|
+
this.pooledChildStates.set(child, {createdAtMs: Date.now(), jobsRun: 0, inflight: new Map(), retiring: false})
|
|
651
686
|
child.on("message", (message) => this._handlePooledChildMessage({child, message}))
|
|
652
687
|
child.once("exit", (code, signal) => this._handlePooledChildFailure({child, error: new Error(`Pooled background job runner exited: code=${code} signal=${signal || "none"}`)}))
|
|
653
688
|
child.once("error", (error) => this._handlePooledChildFailure({child, error}))
|
|
@@ -655,7 +690,8 @@ export default class BackgroundJobsWorker {
|
|
|
655
690
|
}
|
|
656
691
|
|
|
657
692
|
/**
|
|
658
|
-
* Handles a pooled child's durable-report acknowledgement.
|
|
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.
|
|
659
695
|
* @param {object} args - Message details.
|
|
660
696
|
* @param {import("node:child_process").ChildProcess} args.child - Pooled child.
|
|
661
697
|
* @param {?} args.message - IPC message.
|
|
@@ -665,23 +701,70 @@ export default class BackgroundJobsWorker {
|
|
|
665
701
|
if (!message || typeof message !== "object") return
|
|
666
702
|
const record = /** @type {{type?: ?, jobId?: ?, acknowledged?: ?, rssBytes?: ?, error?: ?}} */ (message)
|
|
667
703
|
const state = this.pooledChildStates.get(child)
|
|
668
|
-
if (record.type !== "job-outcome" || !state
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
const resolve = state.resolve
|
|
674
|
-
state.payload = undefined
|
|
675
|
-
state.resolve = undefined
|
|
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)
|
|
676
709
|
state.jobsRun += 1
|
|
677
|
-
|
|
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
|
+
|
|
678
727
|
const rssBytes = typeof record.rssBytes === "number" ? record.rssBytes : Number.POSITIVE_INFINITY
|
|
679
728
|
const runnerAgeMs = Date.now() - state.createdAtMs
|
|
680
|
-
if (state.jobsRun >= this.pooledRunnerMaxJobs || rssBytes >= this.pooledRunnerMaxRssBytes || runnerAgeMs >= this.pooledRunnerMaxLifetimeMs || this.shouldStop)
|
|
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)
|
|
681
764
|
}
|
|
682
765
|
|
|
683
766
|
/**
|
|
684
|
-
* Retires
|
|
767
|
+
* Retires a drained pooled child (removes it from tracking, then SIGTERMs it).
|
|
685
768
|
* @param {import("node:child_process").ChildProcess} child - Child process to retire.
|
|
686
769
|
* @returns {void}
|
|
687
770
|
*/
|
|
@@ -693,7 +776,11 @@ export default class BackgroundJobsWorker {
|
|
|
693
776
|
}
|
|
694
777
|
|
|
695
778
|
/**
|
|
696
|
-
* Removes and reports
|
|
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.
|
|
697
784
|
* @param {object} args - Failure details.
|
|
698
785
|
* @param {import("node:child_process").ChildProcess} args.child - Pooled child.
|
|
699
786
|
* @param {?} args.error - Failure.
|
|
@@ -705,21 +792,22 @@ export default class BackgroundJobsWorker {
|
|
|
705
792
|
if (state) state.settling = true
|
|
706
793
|
this.pooledChildren.delete(child)
|
|
707
794
|
this.inflightProcessChildren.delete(child)
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
}
|
|
712
|
-
const payload = state.payload
|
|
713
|
-
await this._reportJobResult({
|
|
714
|
-
jobId: payload.id,
|
|
715
|
-
status: "failed",
|
|
716
|
-
error,
|
|
717
|
-
handoffId: payload.handoffId,
|
|
718
|
-
handedOffAtMs: payload.handedOffAtMs,
|
|
719
|
-
workerId: payload.workerId || this.workerId
|
|
720
|
-
})
|
|
795
|
+
|
|
796
|
+
const entries = state ? [...state.inflight.values()] : []
|
|
797
|
+
if (state) state.inflight.clear()
|
|
721
798
|
this.pooledChildStates.delete(child)
|
|
722
|
-
|
|
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
|
+
}))
|
|
723
811
|
}
|
|
724
812
|
|
|
725
813
|
/**
|
|
@@ -165,6 +165,7 @@
|
|
|
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
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`.
|
|
168
169
|
* @property {number} [pooledRunnerMaxJobs] - Number of sequential jobs a pooled child runs before it is replaced, bounding process-level resource accumulation. Default: `100`.
|
|
169
170
|
* @property {number} [pooledRunnerMaxRssBytes] - RSS bytes after an acknowledged job at which a pooled child is replaced. Default: `536870912` (512 MiB).
|
|
170
171
|
* @property {number} [pooledRunnerMaxLifetimeMs] - Age after an acknowledged job at which a pooled child is replaced. Default: `3600000` (one hour).
|
package/build/configuration.js
CHANGED
|
@@ -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.
|
|
@@ -1269,6 +1275,7 @@ export default class VelociousConfiguration {
|
|
|
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
|
|
1271
1277
|
const envPooledRunnerCountRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_COUNT
|
|
1278
|
+
const envPooledRunnerConcurrencyRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_CONCURRENCY
|
|
1272
1279
|
const envPooledRunnerMaxJobsRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_JOBS
|
|
1273
1280
|
const envPooledRunnerMaxRssBytesRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_RSS_BYTES
|
|
1274
1281
|
const envPooledRunnerMaxLifetimeMsRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_LIFETIME_MS
|
|
@@ -1279,6 +1286,7 @@ export default class VelociousConfiguration {
|
|
|
1279
1286
|
const envMaxConcurrentForked = envMaxConcurrentForkedRaw ? Number(envMaxConcurrentForkedRaw) : undefined
|
|
1280
1287
|
const envMaxConcurrent = envMaxConcurrentRaw ? Number(envMaxConcurrentRaw) : undefined
|
|
1281
1288
|
const envPooledRunnerCount = envPooledRunnerCountRaw ? Number(envPooledRunnerCountRaw) : undefined
|
|
1289
|
+
const envPooledRunnerConcurrency = envPooledRunnerConcurrencyRaw ? Number(envPooledRunnerConcurrencyRaw) : undefined
|
|
1282
1290
|
const envPooledRunnerMaxJobs = envPooledRunnerMaxJobsRaw ? Number(envPooledRunnerMaxJobsRaw) : undefined
|
|
1283
1291
|
const envPooledRunnerMaxRssBytes = envPooledRunnerMaxRssBytesRaw ? Number(envPooledRunnerMaxRssBytesRaw) : undefined
|
|
1284
1292
|
const envPooledRunnerMaxLifetimeMs = envPooledRunnerMaxLifetimeMsRaw ? Number(envPooledRunnerMaxLifetimeMsRaw) : undefined
|
|
@@ -1299,6 +1307,9 @@ export default class VelociousConfiguration {
|
|
|
1299
1307
|
const pooledRunnerCount = typeof configured.pooledRunnerCount === "number" && Number.isFinite(configured.pooledRunnerCount) && Number.isInteger(configured.pooledRunnerCount) && configured.pooledRunnerCount >= 1
|
|
1300
1308
|
? configured.pooledRunnerCount
|
|
1301
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)
|
|
1302
1313
|
const pooledRunnerMaxJobs = typeof configured.pooledRunnerMaxJobs === "number" && Number.isFinite(configured.pooledRunnerMaxJobs) && Number.isInteger(configured.pooledRunnerMaxJobs) && configured.pooledRunnerMaxJobs >= 1
|
|
1303
1314
|
? configured.pooledRunnerMaxJobs
|
|
1304
1315
|
: (!("pooledRunnerMaxJobs" in configured) && typeof envPooledRunnerMaxJobs === "number" && Number.isFinite(envPooledRunnerMaxJobs) && Number.isInteger(envPooledRunnerMaxJobs) && envPooledRunnerMaxJobs >= 1 ? envPooledRunnerMaxJobs : 100)
|
|
@@ -1336,7 +1347,7 @@ export default class VelociousConfiguration {
|
|
|
1336
1347
|
: 60 * 60 * 1000
|
|
1337
1348
|
}
|
|
1338
1349
|
|
|
1339
|
-
return {host, port, databaseIdentifier, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, dispatchStrategy, pollIntervalMs, queues, jobTimeoutMs, retention}
|
|
1350
|
+
return {host, port, databaseIdentifier, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerConcurrency, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, dispatchStrategy, pollIntervalMs, queues, jobTimeoutMs, retention}
|
|
1340
1351
|
}
|
|
1341
1352
|
|
|
1342
1353
|
/**
|
|
@@ -1934,9 +1945,16 @@ export default class VelociousConfiguration {
|
|
|
1934
1945
|
* @returns {Promise<void>} - Resolves when complete.
|
|
1935
1946
|
*/
|
|
1936
1947
|
async initialize({type} = {type: "undefined"}) {
|
|
1937
|
-
if (
|
|
1938
|
-
|
|
1939
|
-
|
|
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 () => {
|
|
1940
1958
|
await this.initializeModels({type})
|
|
1941
1959
|
await this.getEnvironmentHandler().autoDiscoverResources(this)
|
|
1942
1960
|
this._mergeDiscoveredAbilityResources()
|
|
@@ -1957,6 +1975,17 @@ export default class VelociousConfiguration {
|
|
|
1957
1975
|
}
|
|
1958
1976
|
}
|
|
1959
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
|
|
1960
1989
|
}
|
|
1961
1990
|
}
|
|
1962
1991
|
|
|
@@ -3,10 +3,12 @@
|
|
|
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>;
|
|
11
13
|
export class BackgroundJobPerformedFailure extends Error {
|
|
12
14
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job-runner.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/job-runner.js"],"names":[],"mappings":"AA2EA
|
|
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"}
|
|
@@ -68,9 +68,10 @@ function runnerProcessTitle(JobClass, payload) {
|
|
|
68
68
|
* @param {import("./types.js").BackgroundJobPayload} payload - Payload.
|
|
69
69
|
* @param {object} [options] - Runner options.
|
|
70
70
|
* @param {boolean} [options.closeConnections] - Whether to gracefully close framework connections after the job.
|
|
71
|
+
* @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.
|
|
71
72
|
* @returns {Promise<void>} - Resolves when complete.
|
|
72
73
|
*/
|
|
73
|
-
export default async function runJobPayload(payload, { closeConnections = true } = {}) {
|
|
74
|
+
export default async function runJobPayload(payload, { closeConnections = true, manageProcessTitle = true } = {}) {
|
|
74
75
|
const configuration = await configurationResolver();
|
|
75
76
|
configuration.setCurrent();
|
|
76
77
|
await configuration.initialize({ type: "background-jobs-runner" });
|
|
@@ -86,8 +87,10 @@ export default async function runJobPayload(payload, { closeConnections = true }
|
|
|
86
87
|
const perform = jobInstance.perform;
|
|
87
88
|
// Name the process after the job it is running so `ps`/`top` show what each
|
|
88
89
|
// runner is doing; restored in the `finally` below when the job finishes.
|
|
90
|
+
// Skipped for concurrent pooled runners, whose child owns an aggregate title.
|
|
89
91
|
const previousTitle = process.title;
|
|
90
|
-
|
|
92
|
+
if (manageProcessTitle)
|
|
93
|
+
process.title = runnerProcessTitle(JobClass, payload);
|
|
91
94
|
try {
|
|
92
95
|
try {
|
|
93
96
|
await configuration.withConnections({ name: `Background job runner: ${payload.jobName}` }, async () => {
|
|
@@ -123,7 +126,8 @@ export default async function runJobPayload(payload, { closeConnections = true }
|
|
|
123
126
|
finally {
|
|
124
127
|
// Restore the runner's base title so a lingering/idle runner (or a reused
|
|
125
128
|
// one) doesn't misreport a finished job as still running.
|
|
126
|
-
|
|
129
|
+
if (manageProcessTitle)
|
|
130
|
+
process.title = previousTitle;
|
|
127
131
|
if (closeConnections) {
|
|
128
132
|
try {
|
|
129
133
|
await configuration.disconnectBeacon();
|
|
@@ -134,4 +138,4 @@ export default async function runJobPayload(payload, { closeConnections = true }
|
|
|
134
138
|
}
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiam9iLXJ1bm5lci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9iYWNrZ3JvdW5kLWpvYnMvam9iLXJ1bm5lci5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZO0FBRVosT0FBTyxxQkFBcUIsTUFBTSw4QkFBOEIsQ0FBQTtBQUNoRSxPQUFPLHFCQUFxQixNQUFNLG1CQUFtQixDQUFBO0FBQ3JELE9BQU8sNEJBQTRCLE1BQU0sc0JBQXNCLENBQUE7QUFFL0QsTUFBTSx1QkFBdUIsR0FBRyxJQUFJLENBQUE7QUFFcEMsTUFBTSxPQUFPLDZCQUE4QixTQUFRLEtBQUs7SUFDdEQ7OztPQUdHO0lBQ0gsWUFBWSxLQUFLO1FBQ2YsS0FBSyxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsRUFBQyxLQUFLLEVBQUMsQ0FBQyxDQUFBO1FBQzdCLElBQUksQ0FBQyxJQUFJLEdBQUcsK0JBQStCLENBQUE7SUFDN0MsQ0FBQztDQUNGO0FBRUQ7Ozs7O0dBS0c7QUFDSCxTQUFTLHNCQUFzQixDQUFDLGFBQWEsRUFBRSxLQUFLO0lBQ2xELE1BQU0sV0FBVyxHQUFHLGFBQWEsQ0FBQyxjQUFjLEVBQUUsQ0FBQTtJQUNsRCxNQUFNLGVBQWUsR0FBRyxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFBO0lBQ2pGLE1BQU0sT0FBTyxHQUFHO1FBQ2QsT0FBTyxFQUFFLEVBQUMsUUFBUSxFQUFFLHdCQUF3QixFQUFFLEtBQUssRUFBRSxjQUFjLEVBQUM7UUFDcEUsS0FBSyxFQUFFLGVBQWU7S0FDdkIsQ0FBQTtJQUNELE1BQU0sV0FBVyxHQUFHLFdBQVcsQ0FBQyxhQUFhLENBQUMsaUJBQWlCLENBQUMsR0FBRyxDQUFDO1dBQy9ELFdBQVcsQ0FBQyxhQUFhLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFBO0lBRS9D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WUFDeEMsQ0FBQztvQkFBUyxDQUFDO2dCQUNULE1BQU0sYUFBYSxDQUFDLHdCQUF3QixFQUFFLENBQUE7WUFDaEQsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0FBQ0gsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG5pbXBvcnQgY29uZmlndXJhdGlvblJlc29sdmVyIGZyb20gXCIuLi9jb25maWd1cmF0aW9uLXJlc29sdmVyLmpzXCJcbmltcG9ydCBCYWNrZ3JvdW5kSm9iUmVnaXN0cnkgZnJvbSBcIi4vam9iLXJlZ2lzdHJ5LmpzXCJcbmltcG9ydCBCYWNrZ3JvdW5kSm9ic1N0YXR1c1JlcG9ydGVyIGZyb20gXCIuL3N0YXR1cy1yZXBvcnRlci5qc1wiXG5cbmNvbnN0IEJFQUNPTl9SRUFEWV9USU1FT1VUX01TID0gNTAwMFxuXG5leHBvcnQgY2xhc3MgQmFja2dyb3VuZEpvYlBlcmZvcm1lZEZhaWx1cmUgZXh0ZW5kcyBFcnJvciB7XG4gIC8qKlxuICAgKiBDcmVhdGVzIGEgcGVyZm9ybWVkLWpvYiBmYWlsdXJlIGFmdGVyIGl0cyB0ZXJtaW5hbCByZXBvcnQgaXMgYWNrbm93bGVkZ2VkLlxuICAgKiBAcGFyYW0ge0Vycm9yfSBjYXVzZSAtIEEgam9iIHBlcmZvcm0gZXJyb3Igd2hvc2UgZmFpbGVkIHRlcm1pbmFsIHJlcG9ydCB3YXMgYWNrbm93bGVkZ2VkLlxuICAgKi9cbiAgY29uc3RydWN0b3IoY2F1c2UpIHtcbiAgICBzdXBlcihjYXVzZS5tZXNzYWdlLCB7Y2F1c2V9KVxuICAgIHRoaXMubmFtZSA9IFwiQmFja2dyb3VuZEpvYlBlcmZvcm1lZEZhaWx1cmVcIlxuICB9XG59XG5cbi8qKlxuICogUnVucyByZXBvcnQgYmVhY29uIHJlYWR5IGVycm9yLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLmpzXCIpLmRlZmF1bHR9IGNvbmZpZ3VyYXRpb24gLSBDb25maWd1cmF0aW9uLlxuICogQHBhcmFtIHs/fSBlcnJvciAtIEJlYWNvbiByZWFkaW5lc3MgZXJyb3IuXG4gKiBAcmV0dXJucyB7dm9pZH1cbiAqL1xuZnVuY3Rpb24gcmVwb3J0QmVhY29uUmVhZHlFcnJvcihjb25maWd1cmF0aW9uLCBlcnJvcikge1xuICBjb25zdCBlcnJvckV2ZW50cyA9IGNvbmZpZ3VyYXRpb24uZ2V0RXJyb3JFdmVudHMoKVxuICBjb25zdCBub3JtYWxpemVkRXJyb3IgPSBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IgOiBuZXcgRXJyb3IoU3RyaW5nKGVycm9yKSlcbiAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICBjb250ZXh0OiB7cGVlclR5cGU6IFwiYmFja2dyb3VuZC1qb2JzLXJ1bm5lclwiLCBzdGFnZTogXCJiZWFjb24tcmVhZHlcIn0sXG4gICAgZXJyb3I6IG5vcm1hbGl6ZWRFcnJvclxuICB9XG4gIGNvbnN0IGhhc0xpc3RlbmVyID0gZXJyb3JFdmVudHMubGlzdGVuZXJDb3VudChcImZyYW1ld29yay1lcnJvclwiKSA+IDBcbiAgICB8fCBlcnJvckV2ZW50cy5saXN0ZW5lckNvdW50KFwiYWxsLWVycm9yXCIpID4gMFxuXG4gIGVycm9yRXZlbnRzLmVtaXQoXCJmcmFtZXdvcmstZXJyb3JcIiwgcGF5bG9hZClcbiAgZXJyb3JFdmVudHMuZW1pdChcImFsbC1lcnJvclwiLCB7Li4ucGF5bG9hZCwgZXJyb3JUeXBlOiBcImZyYW1ld29yay1lcnJvclwifSlcblxuICBpZiAoIWhhc0xpc3RlbmVyKSB7XG4gICAgY29uc29sZS5lcnJvcihgW3ZlbG9jaW91cyBmcmFtZXdvcmstZXJyb3Igc3RhZ2U9YmVhY29uLXJlYWR5XSAke25vcm1hbGl6ZWRFcnJvci5tZXNzYWdlfWApXG4gIH1cbn1cblxuLyoqXG4gKiBSdW5zIGNvbm5lY3QgYmVhY29uLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLmpzXCIpLmRlZmF1bHR9IGNvbmZpZ3VyYXRpb24gLSBDb25maWd1cmF0aW9uLlxuICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGNvbm5lY3RCZWFjb24oY29uZmlndXJhdGlvbikge1xuICBjb25zdCBiZWFjb25DbGllbnQgPSBhd2FpdCBjb25maWd1cmF0aW9uLmNvbm5lY3RCZWFjb24oe3BlZXJUeXBlOiBcImJhY2tncm91bmQtam9icy1ydW5uZXJcIn0pXG5cbiAgaWYgKCFiZWFjb25DbGllbnQpIHJldHVyblxuXG4gIHRyeSB7XG4gICAgYXdhaXQgYmVhY29uQ2xpZW50LndhaXRGb3JSZWFkeSh7dGltZW91dE1zOiBCRUFDT05fUkVBRFlfVElNRU9VVF9NU30pXG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgcmVwb3J0QmVhY29uUmVhZHlFcnJvcihjb25maWd1cmF0aW9uLCBlcnJvcilcbiAgfVxufVxuXG4vKipcbiAqIFJlc29sdmVzIHRoZSBwcm9jZXNzIHRpdGxlIHRvIHNob3cgd2hpbGUgYSBqb2IgcnVuczogdGhlIGpvYiBjbGFzcydzIGRlY2xhcmVkXG4gKiBgc3RhdGljIHByb2Nlc3NUaXRsZWAsIGVsc2UgYSBgdmVsb2Npb3VzIGpvYi1ydW5uZXI6IDxKb2JOYW1lPmAgZmFsbGJhY2suXG4gKiBAcGFyYW0ge3R5cGVvZiBpbXBvcnQoXCIuL2pvYi5qc1wiKS5kZWZhdWx0fSBKb2JDbGFzcyAtIFJlc29sdmVkIGpvYiBjbGFzcy5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi90eXBlcy5qc1wiKS5CYWNrZ3JvdW5kSm9iUGF5bG9hZH0gcGF5bG9hZCAtIFBheWxvYWQuXG4gKiBAcmV0dXJucyB7c3RyaW5nfSAtIFByb2Nlc3MgdGl0bGUuXG4gKi9cbmZ1bmN0aW9uIHJ1bm5lclByb2Nlc3NUaXRsZShKb2JDbGFzcywgcGF5bG9hZCkge1xuICBjb25zdCBkZWNsYXJlZCA9IEpvYkNsYXNzLnByb2Nlc3NUaXRsZVxuXG4gIGlmICh0eXBlb2YgZGVjbGFyZWQgPT09IFwic3RyaW5nXCIgJiYgZGVjbGFyZWQubGVuZ3RoID4gMCkgcmV0dXJuIGRlY2xhcmVkXG5cbiAgcmV0dXJuIGB2ZWxvY2lvdXMgam9iLXJ1bm5lcjogJHtwYXlsb2FkLmpvYk5hbWV9YFxufVxuXG4vKipcbiAqIFJ1bnMgcnVuIGpvYiBwYXlsb2FkLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL3R5cGVzLmpzXCIpLkJhY2tncm91bmRKb2JQYXlsb2FkfSBwYXlsb2FkIC0gUGF5bG9hZC5cbiAqIEBwYXJhbSB7b2JqZWN0fSBbb3B0aW9uc10gLSBSdW5uZXIgb3B0aW9ucy5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMuY2xvc2VDb25uZWN0aW9uc10gLSBXaGV0aGVyIHRvIGdyYWNlZnVsbHkgY2xvc2UgZnJhbWV3b3JrIGNvbm5lY3Rpb25zIGFmdGVyIHRoZSBqb2IuXG4gKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyB3aGVuIGNvbXBsZXRlLlxuICovXG5leHBvcnQgZGVmYXVsdCBhc3luYyBmdW5jdGlvbiBydW5Kb2JQYXlsb2FkKHBheWxvYWQsIHtjbG9zZUNvbm5lY3Rpb25zID0gdHJ1ZX0gPSB7fSkge1xuICBjb25zdCBjb25maWd1cmF0aW9uID0gYXdhaXQgY29uZmlndXJhdGlvblJlc29sdmVyKClcbiAgY29uZmlndXJhdGlvbi5zZXRDdXJyZW50KClcbiAgYXdhaXQgY29uZmlndXJhdGlvbi5pbml0aWFsaXplKHt0eXBlOiBcImJhY2tncm91bmQtam9icy1ydW5uZXJcIn0pXG4gIGF3YWl0IGNvbm5lY3RCZWFjb24oY29uZmlndXJhdGlvbilcbiAgY29uc3QgcmVwb3J0ZXIgPSBuZXcgQmFja2dyb3VuZEpvYnNTdGF0dXNSZXBvcnRlcih7Y29uZmlndXJhdGlvbn0pXG5cbiAgY29uc3QgcmVnaXN0cnkgPSBuZXcgQmFja2dyb3VuZEpvYlJlZ2lzdHJ5KHtjb25maWd1cmF0aW9ufSlcbiAgYXdhaXQgcmVnaXN0cnkubG9hZCgpXG4gIGNvbnN0IEpvYkNsYXNzID0gcmVnaXN0cnkuZ2V0Sm9iQnlOYW1lKHBheWxvYWQuam9iTmFtZSlcbiAgY29uc3Qgam9iSW5zdGFuY2UgPSBuZXcgSm9iQ2xhc3MoKVxuICAvKipcbiAgICogUGVyZm9ybS5cbiAgICogQHR5cGUgeyguLi5hcmdzOiBBcnJheTw/PikgPT4gUHJvbWlzZTx2b2lkPn0gKi9cbiAgY29uc3QgcGVyZm9ybSA9IGpvYkluc3RhbmNlLnBlcmZvcm1cblxuICAvLyBOYW1lIHRoZSBwcm9jZXNzIGFmdGVyIHRoZSBqb2IgaXQgaXMgcnVubmluZyBzbyBgcHNgL2B0b3BgIHNob3cgd2hhdCBlYWNoXG4gIC8vIHJ1bm5lciBpcyBkb2luZzsgcmVzdG9yZWQgaW4gdGhlIGBmaW5hbGx5YCBiZWxvdyB3aGVuIHRoZSBqb2IgZmluaXNoZXMuXG4gIGNvbnN0IHByZXZpb3VzVGl0bGUgPSBwcm9jZXNzLnRpdGxlXG4gIHByb2Nlc3MudGl0bGUgPSBydW5uZXJQcm9jZXNzVGl0bGUoSm9iQ2xhc3MsIHBheWxvYWQpXG5cbiAgdHJ5IHtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgY29uZmlndXJhdGlvbi53aXRoQ29ubmVjdGlvbnMoe25hbWU6IGBCYWNrZ3JvdW5kIGpvYiBydW5uZXI6ICR7cGF5bG9hZC5qb2JOYW1lfWB9LCBhc3luYyAoKSA9PiB7XG4gICAgICAgIGF3YWl0IHBlcmZvcm0uYXBwbHkoam9iSW5zdGFuY2UsIHBheWxvYWQuYXJncyB8fCBbXSlcbiAgICAgIH0pXG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGNvbnN0IHBlcmZvcm1lZEVycm9yID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogbmV3IEVycm9yKFN0cmluZyhlcnJvcikpXG4gICAgICBpZiAocGF5bG9hZC5pZCkge1xuICAgICAgICBhd2FpdCByZXBvcnRlci5yZXBvcnRXaXRoUmV0cnkoe1xuICAgICAgICAgIGpvYklkOiBwYXlsb2FkLmlkLFxuICAgICAgICAgIHN0YXR1czogXCJmYWlsZWRcIixcbiAgICAgICAgICBlcnJvcjogcGVyZm9ybWVkRXJyb3IsXG4gICAgICAgICAgaGFuZG9mZklkOiBwYXlsb2FkLmhhbmRvZmZJZCxcbiAgICAgICAgICB3b3JrZXJJZDogcGF5bG9hZC53b3JrZXJJZCxcbiAgICAgICAgICBoYW5kZWRPZmZBdE1zOiBwYXlsb2FkLmhhbmRlZE9mZkF0TXMsXG4gICAgICAgICAgbWF4RHVyYXRpb25NczogMzAwMDBcbiAgICAgICAgfSlcbiAgICAgIH1cblxuICAgICAgdGhyb3cgbmV3IEJhY2tncm91bmRKb2JQZXJmb3JtZWRGYWlsdXJlKHBlcmZvcm1lZEVycm9yKVxuICAgIH1cblxuICAgIGlmIChwYXlsb2FkLmlkKSB7XG4gICAgICBhd2FpdCByZXBvcnRlci5yZXBvcnRXaXRoUmV0cnkoe1xuICAgICAgICBqb2JJZDogcGF5bG9hZC5pZCxcbiAgICAgICAgc3RhdHVzOiBcImNvbXBsZXRlZFwiLFxuICAgICAgICBoYW5kb2ZmSWQ6IHBheWxvYWQuaGFuZG9mZklkLFxuICAgICAgICB3b3JrZXJJZDogcGF5bG9hZC53b3JrZXJJZCxcbiAgICAgICAgaGFuZGVkT2ZmQXRNczogcGF5bG9hZC5oYW5kZWRPZmZBdE1zLFxuICAgICAgICBtYXhEdXJhdGlvbk1zOiAzMDAwMFxuICAgICAgfSlcbiAgICB9XG4gIH0gZmluYWxseSB7XG4gICAgLy8gUmVzdG9yZSB0aGUgcnVubmVyJ3MgYmFzZSB0aXRsZSBzbyBhIGxpbmdlcmluZy9pZGxlIHJ1bm5lciAob3IgYSByZXVzZWRcbiAgICAvLyBvbmUpIGRvZXNuJ3QgbWlzcmVwb3J0IGEgZmluaXNoZWQgam9iIGFzIHN0aWxsIHJ1bm5pbmcuXG4gICAgcHJvY2Vzcy50aXRsZSA9IHByZXZpb3VzVGl0bGVcbiAgICBpZiAoY2xvc2VDb25uZWN0aW9ucykge1xuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgY29uZmlndXJhdGlvbi5kaXNjb25uZWN0QmVhY29uKClcbiAgICAgIH0gZmluYWxseSB7XG4gICAgICAgIGF3YWl0IGNvbmZpZ3VyYXRpb24uY2xvc2VEYXRhYmFzZUNvbm5lY3Rpb25zKClcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbiJdfQ==
|
|
141
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiam9iLXJ1bm5lci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9iYWNrZ3JvdW5kLWpvYnMvam9iLXJ1bm5lci5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZO0FBRVosT0FBTyxxQkFBcUIsTUFBTSw4QkFBOEIsQ0FBQTtBQUNoRSxPQUFPLHFCQUFxQixNQUFNLG1CQUFtQixDQUFBO0FBQ3JELE9BQU8sNEJBQTRCLE1BQU0sc0JBQXNCLENBQUE7QUFFL0QsTUFBTSx1QkFBdUIsR0FBRyxJQUFJLENBQUE7QUFFcEMsTUFBTSxPQUFPLDZCQUE4QixTQUFRLEtBQUs7SUFDdEQ7OztPQUdHO0lBQ0gsWUFBWSxLQUFLO1FBQ2YsS0FBSyxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsRUFBQyxLQUFLLEVBQUMsQ0FBQyxDQUFBO1FBQzdCLElBQUksQ0FBQyxJQUFJLEdBQUcsK0JBQStCLENBQUE7SUFDN0MsQ0FBQztDQUNGO0FBRUQ7Ozs7O0dBS0c7QUFDSCxTQUFTLHNCQUFzQixDQUFDLGFBQWEsRUFBRSxLQUFLO0lBQ2xELE1BQU0sV0FBVyxHQUFHLGFBQWEsQ0FBQyxjQUFjLEVBQUUsQ0FBQTtJQUNsRCxNQUFNLGVBQWUsR0FBRyxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFBO0lBQ2pGLE1BQU0sT0FBTyxHQUFHO1FBQ2QsT0FBTyxFQUFFLEVBQUMsUUFBUSxFQUFFLHdCQUF3QixFQUFFLEtBQUssRUFBRSxjQUFjLEVBQUM7UUFDcEUsS0FBSyxFQUFFLGVBQWU7S0FDdkIsQ0FBQTtJQUNELE1BQU0sV0FBVyxHQUFHLFdBQVcsQ0FBQyxhQUFhLENBQUMsaUJBQWlCLENBQUMsR0FBRyxDQUFDO1dBQy9ELFdBQVcsQ0FBQyxhQUFhLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFBO0lBRS9D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QkFBZ0IsRUFBRSxDQUFDO1lBQ3JCLElBQUksQ0FBQztnQkFDSCxNQUFNLGFBQWEsQ0FBQyxnQkFBZ0IsRUFBRSxDQUFBO1lBQ3hDLENBQUM7b0JBQVMsQ0FBQztnQkFDVCxNQUFNLGFBQWEsQ0FBQyx3QkFBd0IsRUFBRSxDQUFBO1lBQ2hELENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztBQUNILENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IGNvbmZpZ3VyYXRpb25SZXNvbHZlciBmcm9tIFwiLi4vY29uZmlndXJhdGlvbi1yZXNvbHZlci5qc1wiXG5pbXBvcnQgQmFja2dyb3VuZEpvYlJlZ2lzdHJ5IGZyb20gXCIuL2pvYi1yZWdpc3RyeS5qc1wiXG5pbXBvcnQgQmFja2dyb3VuZEpvYnNTdGF0dXNSZXBvcnRlciBmcm9tIFwiLi9zdGF0dXMtcmVwb3J0ZXIuanNcIlxuXG5jb25zdCBCRUFDT05fUkVBRFlfVElNRU9VVF9NUyA9IDUwMDBcblxuZXhwb3J0IGNsYXNzIEJhY2tncm91bmRKb2JQZXJmb3JtZWRGYWlsdXJlIGV4dGVuZHMgRXJyb3Ige1xuICAvKipcbiAgICogQ3JlYXRlcyBhIHBlcmZvcm1lZC1qb2IgZmFpbHVyZSBhZnRlciBpdHMgdGVybWluYWwgcmVwb3J0IGlzIGFja25vd2xlZGdlZC5cbiAgICogQHBhcmFtIHtFcnJvcn0gY2F1c2UgLSBBIGpvYiBwZXJmb3JtIGVycm9yIHdob3NlIGZhaWxlZCB0ZXJtaW5hbCByZXBvcnQgd2FzIGFja25vd2xlZGdlZC5cbiAgICovXG4gIGNvbnN0cnVjdG9yKGNhdXNlKSB7XG4gICAgc3VwZXIoY2F1c2UubWVzc2FnZSwge2NhdXNlfSlcbiAgICB0aGlzLm5hbWUgPSBcIkJhY2tncm91bmRKb2JQZXJmb3JtZWRGYWlsdXJlXCJcbiAgfVxufVxuXG4vKipcbiAqIFJ1bnMgcmVwb3J0IGJlYWNvbiByZWFkeSBlcnJvci5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vY29uZmlndXJhdGlvbi5qc1wiKS5kZWZhdWx0fSBjb25maWd1cmF0aW9uIC0gQ29uZmlndXJhdGlvbi5cbiAqIEBwYXJhbSB7P30gZXJyb3IgLSBCZWFjb24gcmVhZGluZXNzIGVycm9yLlxuICogQHJldHVybnMge3ZvaWR9XG4gKi9cbmZ1bmN0aW9uIHJlcG9ydEJlYWNvblJlYWR5RXJyb3IoY29uZmlndXJhdGlvbiwgZXJyb3IpIHtcbiAgY29uc3QgZXJyb3JFdmVudHMgPSBjb25maWd1cmF0aW9uLmdldEVycm9yRXZlbnRzKClcbiAgY29uc3Qgbm9ybWFsaXplZEVycm9yID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogbmV3IEVycm9yKFN0cmluZyhlcnJvcikpXG4gIGNvbnN0IHBheWxvYWQgPSB7XG4gICAgY29udGV4dDoge3BlZXJUeXBlOiBcImJhY2tncm91bmQtam9icy1ydW5uZXJcIiwgc3RhZ2U6IFwiYmVhY29uLXJlYWR5XCJ9LFxuICAgIGVycm9yOiBub3JtYWxpemVkRXJyb3JcbiAgfVxuICBjb25zdCBoYXNMaXN0ZW5lciA9IGVycm9yRXZlbnRzLmxpc3RlbmVyQ291bnQoXCJmcmFtZXdvcmstZXJyb3JcIikgPiAwXG4gICAgfHwgZXJyb3JFdmVudHMubGlzdGVuZXJDb3VudChcImFsbC1lcnJvclwiKSA+IDBcblxuICBlcnJvckV2ZW50cy5lbWl0KFwiZnJhbWV3b3JrLWVycm9yXCIsIHBheWxvYWQpXG4gIGVycm9yRXZlbnRzLmVtaXQoXCJhbGwtZXJyb3JcIiwgey4uLnBheWxvYWQsIGVycm9yVHlwZTogXCJmcmFtZXdvcmstZXJyb3JcIn0pXG5cbiAgaWYgKCFoYXNMaXN0ZW5lcikge1xuICAgIGNvbnNvbGUuZXJyb3IoYFt2ZWxvY2lvdXMgZnJhbWV3b3JrLWVycm9yIHN0YWdlPWJlYWNvbi1yZWFkeV0gJHtub3JtYWxpemVkRXJyb3IubWVzc2FnZX1gKVxuICB9XG59XG5cbi8qKlxuICogUnVucyBjb25uZWN0IGJlYWNvbi5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vY29uZmlndXJhdGlvbi5qc1wiKS5kZWZhdWx0fSBjb25maWd1cmF0aW9uIC0gQ29uZmlndXJhdGlvbi5cbiAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICovXG5hc3luYyBmdW5jdGlvbiBjb25uZWN0QmVhY29uKGNvbmZpZ3VyYXRpb24pIHtcbiAgY29uc3QgYmVhY29uQ2xpZW50ID0gYXdhaXQgY29uZmlndXJhdGlvbi5jb25uZWN0QmVhY29uKHtwZWVyVHlwZTogXCJiYWNrZ3JvdW5kLWpvYnMtcnVubmVyXCJ9KVxuXG4gIGlmICghYmVhY29uQ2xpZW50KSByZXR1cm5cblxuICB0cnkge1xuICAgIGF3YWl0IGJlYWNvbkNsaWVudC53YWl0Rm9yUmVhZHkoe3RpbWVvdXRNczogQkVBQ09OX1JFQURZX1RJTUVPVVRfTVN9KVxuICB9IGNhdGNoIChlcnJvcikge1xuICAgIHJlcG9ydEJlYWNvblJlYWR5RXJyb3IoY29uZmlndXJhdGlvbiwgZXJyb3IpXG4gIH1cbn1cblxuLyoqXG4gKiBSZXNvbHZlcyB0aGUgcHJvY2VzcyB0aXRsZSB0byBzaG93IHdoaWxlIGEgam9iIHJ1bnM6IHRoZSBqb2IgY2xhc3MncyBkZWNsYXJlZFxuICogYHN0YXRpYyBwcm9jZXNzVGl0bGVgLCBlbHNlIGEgYHZlbG9jaW91cyBqb2ItcnVubmVyOiA8Sm9iTmFtZT5gIGZhbGxiYWNrLlxuICogQHBhcmFtIHt0eXBlb2YgaW1wb3J0KFwiLi9qb2IuanNcIikuZGVmYXVsdH0gSm9iQ2xhc3MgLSBSZXNvbHZlZCBqb2IgY2xhc3MuXG4gKiBAcGFyYW0ge2ltcG9ydChcIi4vdHlwZXMuanNcIikuQmFja2dyb3VuZEpvYlBheWxvYWR9IHBheWxvYWQgLSBQYXlsb2FkLlxuICogQHJldHVybnMge3N0cmluZ30gLSBQcm9jZXNzIHRpdGxlLlxuICovXG5mdW5jdGlvbiBydW5uZXJQcm9jZXNzVGl0bGUoSm9iQ2xhc3MsIHBheWxvYWQpIHtcbiAgY29uc3QgZGVjbGFyZWQgPSBKb2JDbGFzcy5wcm9jZXNzVGl0bGVcblxuICBpZiAodHlwZW9mIGRlY2xhcmVkID09PSBcInN0cmluZ1wiICYmIGRlY2xhcmVkLmxlbmd0aCA+IDApIHJldHVybiBkZWNsYXJlZFxuXG4gIHJldHVybiBgdmVsb2Npb3VzIGpvYi1ydW5uZXI6ICR7cGF5bG9hZC5qb2JOYW1lfWBcbn1cblxuLyoqXG4gKiBSdW5zIHJ1biBqb2IgcGF5bG9hZC5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi90eXBlcy5qc1wiKS5CYWNrZ3JvdW5kSm9iUGF5bG9hZH0gcGF5bG9hZCAtIFBheWxvYWQuXG4gKiBAcGFyYW0ge29iamVjdH0gW29wdGlvbnNdIC0gUnVubmVyIG9wdGlvbnMuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmNsb3NlQ29ubmVjdGlvbnNdIC0gV2hldGhlciB0byBncmFjZWZ1bGx5IGNsb3NlIGZyYW1ld29yayBjb25uZWN0aW9ucyBhZnRlciB0aGUgam9iLlxuICogQHBhcmFtIHtib29sZWFufSBbb3B0aW9ucy5tYW5hZ2VQcm9jZXNzVGl0bGVdIC0gV2hldGhlciB0byBzZXQgdGhlIHBlci1qb2IgcHJvY2VzcyB0aXRsZSBhbmQgcmVzdG9yZSBpdCBhZnRlcndhcmRzLiBPZmYgZm9yIGNvbmN1cnJlbnQgcG9vbGVkIHJ1bm5lcnMsIHdoZXJlIGludGVybGVhdmVkIHNuYXBzaG90L3Jlc3RvcmUgb2YgdGhlIHNpbmdsZSBwcm9jZXNzLXdpZGUgYHByb2Nlc3MudGl0bGVgIHdvdWxkIGNvcnJ1cHQgaXQ7IHRoZSBwb29sZWQgY2hpbGQgb3ducyBhbiBhZ2dyZWdhdGUgdGl0bGUgaW5zdGVhZC5cbiAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gY29tcGxldGUuXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGFzeW5jIGZ1bmN0aW9uIHJ1bkpvYlBheWxvYWQocGF5bG9hZCwge2Nsb3NlQ29ubmVjdGlvbnMgPSB0cnVlLCBtYW5hZ2VQcm9jZXNzVGl0bGUgPSB0cnVlfSA9IHt9KSB7XG4gIGNvbnN0IGNvbmZpZ3VyYXRpb24gPSBhd2FpdCBjb25maWd1cmF0aW9uUmVzb2x2ZXIoKVxuICBjb25maWd1cmF0aW9uLnNldEN1cnJlbnQoKVxuICBhd2FpdCBjb25maWd1cmF0aW9uLmluaXRpYWxpemUoe3R5cGU6IFwiYmFja2dyb3VuZC1qb2JzLXJ1bm5lclwifSlcbiAgYXdhaXQgY29ubmVjdEJlYWNvbihjb25maWd1cmF0aW9uKVxuICBjb25zdCByZXBvcnRlciA9IG5ldyBCYWNrZ3JvdW5kSm9ic1N0YXR1c1JlcG9ydGVyKHtjb25maWd1cmF0aW9ufSlcblxuICBjb25zdCByZWdpc3RyeSA9IG5ldyBCYWNrZ3JvdW5kSm9iUmVnaXN0cnkoe2NvbmZpZ3VyYXRpb259KVxuICBhd2FpdCByZWdpc3RyeS5sb2FkKClcbiAgY29uc3QgSm9iQ2xhc3MgPSByZWdpc3RyeS5nZXRKb2JCeU5hbWUocGF5bG9hZC5qb2JOYW1lKVxuICBjb25zdCBqb2JJbnN0YW5jZSA9IG5ldyBKb2JDbGFzcygpXG4gIC8qKlxuICAgKiBQZXJmb3JtLlxuICAgKiBAdHlwZSB7KC4uLmFyZ3M6IEFycmF5PD8+KSA9PiBQcm9taXNlPHZvaWQ+fSAqL1xuICBjb25zdCBwZXJmb3JtID0gam9iSW5zdGFuY2UucGVyZm9ybVxuXG4gIC8vIE5hbWUgdGhlIHByb2Nlc3MgYWZ0ZXIgdGhlIGpvYiBpdCBpcyBydW5uaW5nIHNvIGBwc2AvYHRvcGAgc2hvdyB3aGF0IGVhY2hcbiAgLy8gcnVubmVyIGlzIGRvaW5nOyByZXN0b3JlZCBpbiB0aGUgYGZpbmFsbHlgIGJlbG93IHdoZW4gdGhlIGpvYiBmaW5pc2hlcy5cbiAgLy8gU2tpcHBlZCBmb3IgY29uY3VycmVudCBwb29sZWQgcnVubmVycywgd2hvc2UgY2hpbGQgb3ducyBhbiBhZ2dyZWdhdGUgdGl0bGUuXG4gIGNvbnN0IHByZXZpb3VzVGl0bGUgPSBwcm9jZXNzLnRpdGxlXG4gIGlmIChtYW5hZ2VQcm9jZXNzVGl0bGUpIHByb2Nlc3MudGl0bGUgPSBydW5uZXJQcm9jZXNzVGl0bGUoSm9iQ2xhc3MsIHBheWxvYWQpXG5cbiAgdHJ5IHtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgY29uZmlndXJhdGlvbi53aXRoQ29ubmVjdGlvbnMoe25hbWU6IGBCYWNrZ3JvdW5kIGpvYiBydW5uZXI6ICR7cGF5bG9hZC5qb2JOYW1lfWB9LCBhc3luYyAoKSA9PiB7XG4gICAgICAgIGF3YWl0IHBlcmZvcm0uYXBwbHkoam9iSW5zdGFuY2UsIHBheWxvYWQuYXJncyB8fCBbXSlcbiAgICAgIH0pXG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGNvbnN0IHBlcmZvcm1lZEVycm9yID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogbmV3IEVycm9yKFN0cmluZyhlcnJvcikpXG4gICAgICBpZiAocGF5bG9hZC5pZCkge1xuICAgICAgICBhd2FpdCByZXBvcnRlci5yZXBvcnRXaXRoUmV0cnkoe1xuICAgICAgICAgIGpvYklkOiBwYXlsb2FkLmlkLFxuICAgICAgICAgIHN0YXR1czogXCJmYWlsZWRcIixcbiAgICAgICAgICBlcnJvcjogcGVyZm9ybWVkRXJyb3IsXG4gICAgICAgICAgaGFuZG9mZklkOiBwYXlsb2FkLmhhbmRvZmZJZCxcbiAgICAgICAgICB3b3JrZXJJZDogcGF5bG9hZC53b3JrZXJJZCxcbiAgICAgICAgICBoYW5kZWRPZmZBdE1zOiBwYXlsb2FkLmhhbmRlZE9mZkF0TXMsXG4gICAgICAgICAgbWF4RHVyYXRpb25NczogMzAwMDBcbiAgICAgICAgfSlcbiAgICAgIH1cblxuICAgICAgdGhyb3cgbmV3IEJhY2tncm91bmRKb2JQZXJmb3JtZWRGYWlsdXJlKHBlcmZvcm1lZEVycm9yKVxuICAgIH1cblxuICAgIGlmIChwYXlsb2FkLmlkKSB7XG4gICAgICBhd2FpdCByZXBvcnRlci5yZXBvcnRXaXRoUmV0cnkoe1xuICAgICAgICBqb2JJZDogcGF5bG9hZC5pZCxcbiAgICAgICAgc3RhdHVzOiBcImNvbXBsZXRlZFwiLFxuICAgICAgICBoYW5kb2ZmSWQ6IHBheWxvYWQuaGFuZG9mZklkLFxuICAgICAgICB3b3JrZXJJZDogcGF5bG9hZC53b3JrZXJJZCxcbiAgICAgICAgaGFuZGVkT2ZmQXRNczogcGF5bG9hZC5oYW5kZWRPZmZBdE1zLFxuICAgICAgICBtYXhEdXJhdGlvbk1zOiAzMDAwMFxuICAgICAgfSlcbiAgICB9XG4gIH0gZmluYWxseSB7XG4gICAgLy8gUmVzdG9yZSB0aGUgcnVubmVyJ3MgYmFzZSB0aXRsZSBzbyBhIGxpbmdlcmluZy9pZGxlIHJ1bm5lciAob3IgYSByZXVzZWRcbiAgICAvLyBvbmUpIGRvZXNuJ3QgbWlzcmVwb3J0IGEgZmluaXNoZWQgam9iIGFzIHN0aWxsIHJ1bm5pbmcuXG4gICAgaWYgKG1hbmFnZVByb2Nlc3NUaXRsZSkgcHJvY2Vzcy50aXRsZSA9IHByZXZpb3VzVGl0bGVcbiAgICBpZiAoY2xvc2VDb25uZWN0aW9ucykge1xuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgY29uZmlndXJhdGlvbi5kaXNjb25uZWN0QmVhY29uKClcbiAgICAgIH0gZmluYWxseSB7XG4gICAgICAgIGF3YWl0IGNvbmZpZ3VyYXRpb24uY2xvc2VEYXRhYmFzZUNvbm5lY3Rpb25zKClcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbiJdfQ==
|