velocious 1.0.598 → 1.0.600
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 +12 -1
- package/build/background-jobs/adapter-client.js +45 -0
- package/build/background-jobs/adapter.js +139 -0
- package/build/background-jobs/job-registry.js +1 -1
- package/build/background-jobs/job.js +20 -129
- package/build/background-jobs/main.js +71 -28
- package/build/background-jobs/perform-job.js +23 -0
- package/build/background-jobs/platform-job.js +156 -0
- package/build/background-jobs/runtime.js +156 -0
- package/build/background-jobs/sql-adapter.js +20 -0
- package/build/background-jobs/store.js +3 -1
- package/build/background-jobs/types.js +10 -0
- package/build/background-jobs/web/controller.js +5 -2
- package/build/background-jobs/worker.js +6 -8
- package/build/configuration-types.js +5 -0
- package/build/configuration.js +217 -40
- package/build/environment-handlers/base.js +20 -0
- package/build/environment-handlers/node.js +27 -20
- package/build/jobs/prune-terminal-background-jobs.js +2 -3
- package/build/src/background-jobs/adapter-client.d.ts +41 -0
- package/build/src/background-jobs/adapter-client.d.ts.map +1 -0
- package/build/src/background-jobs/adapter-client.js +39 -0
- package/build/src/background-jobs/adapter.d.ts +165 -0
- package/build/src/background-jobs/adapter.d.ts.map +1 -0
- package/build/src/background-jobs/adapter.js +121 -0
- package/build/src/background-jobs/job-registry.d.ts +1 -1
- package/build/src/background-jobs/job-registry.d.ts.map +1 -1
- package/build/src/background-jobs/job-registry.js +2 -2
- package/build/src/background-jobs/job.d.ts +6 -69
- package/build/src/background-jobs/job.d.ts.map +1 -1
- package/build/src/background-jobs/job.js +17 -116
- package/build/src/background-jobs/main.d.ts +13 -3
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +66 -30
- package/build/src/background-jobs/perform-job.d.ts +16 -0
- package/build/src/background-jobs/perform-job.d.ts.map +1 -0
- package/build/src/background-jobs/perform-job.js +22 -0
- package/build/src/background-jobs/platform-job.d.ts +110 -0
- package/build/src/background-jobs/platform-job.d.ts.map +1 -0
- package/build/src/background-jobs/platform-job.js +138 -0
- package/build/src/background-jobs/runtime.d.ts +78 -0
- package/build/src/background-jobs/runtime.d.ts.map +1 -0
- package/build/src/background-jobs/runtime.js +132 -0
- package/build/src/background-jobs/sql-adapter.d.ts +13 -0
- package/build/src/background-jobs/sql-adapter.d.ts.map +1 -0
- package/build/src/background-jobs/sql-adapter.js +18 -0
- package/build/src/background-jobs/store.d.ts +2 -1
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +4 -2
- package/build/src/background-jobs/types.d.ts +41 -0
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +11 -1
- package/build/src/background-jobs/web/controller.d.ts.map +1 -1
- package/build/src/background-jobs/web/controller.js +5 -3
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +7 -8
- package/build/src/configuration-types.d.ts +16 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +5 -1
- package/build/src/configuration.d.ts +51 -2
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +202 -39
- package/build/src/environment-handlers/base.d.ts +17 -0
- package/build/src/environment-handlers/base.d.ts.map +1 -1
- package/build/src/environment-handlers/base.js +19 -1
- package/build/src/environment-handlers/node.d.ts +18 -0
- package/build/src/environment-handlers/node.d.ts.map +1 -1
- package/build/src/environment-handlers/node.js +25 -20
- package/build/src/jobs/prune-terminal-background-jobs.d.ts.map +1 -1
- package/build/src/jobs/prune-terminal-background-jobs.js +3 -4
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/background-jobs/adapter-client.js +45 -0
- package/src/background-jobs/adapter.js +139 -0
- package/src/background-jobs/job-registry.js +1 -1
- package/src/background-jobs/job.js +20 -129
- package/src/background-jobs/main.js +71 -28
- package/src/background-jobs/perform-job.js +23 -0
- package/src/background-jobs/platform-job.js +156 -0
- package/src/background-jobs/runtime.js +156 -0
- package/src/background-jobs/sql-adapter.js +20 -0
- package/src/background-jobs/store.js +3 -1
- package/src/background-jobs/types.js +10 -0
- package/src/background-jobs/web/controller.js +5 -2
- package/src/background-jobs/worker.js +6 -8
- package/src/configuration-types.js +5 -0
- package/src/configuration.js +217 -40
- package/src/environment-handlers/base.js +20 -0
- package/src/environment-handlers/node.js +27 -20
- package/src/jobs/prune-terminal-background-jobs.js +2 -3
|
@@ -1,101 +1,31 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import configurationResolver from "../configuration-resolver.js"
|
|
4
|
+
import PlatformVelociousJob from "./platform-job.js"
|
|
5
|
+
import {
|
|
6
|
+
cancelScheduledBackgroundJobForConfiguration,
|
|
7
|
+
enqueueBackgroundJobForConfiguration,
|
|
8
|
+
replaceScheduledBackgroundJobForConfiguration
|
|
9
|
+
} from "./runtime.js"
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* needs arguments declares them as required and typed — for example
|
|
11
|
-
* `class RunBuildJob extends VelociousJob<[string]>` with `async perform(buildId)`.
|
|
12
|
-
* The default empty tuple keeps argument-less jobs (`extends VelociousJob`,
|
|
13
|
-
* `async perform()`) working unchanged.
|
|
12
|
+
* Node background-job entry. It preserves lazy configuration discovery for
|
|
13
|
+
* fresh producer processes while the explicit platform entry stays free of
|
|
14
|
+
* Node-only configuration resolution.
|
|
14
15
|
* @template {Array<ReturnType<typeof JSON.parse>>} [TArgs=[]]
|
|
16
|
+
* @augments {PlatformVelociousJob<TArgs>}
|
|
15
17
|
*/
|
|
16
|
-
export default class VelociousJob {
|
|
17
|
-
/**
|
|
18
|
-
* Database identifiers checked out while this job performs. Set an explicit
|
|
19
|
-
* list to avoid holding unrelated configured database connections, or `[]`
|
|
20
|
-
* when the job establishes any connections it needs itself. Left undefined,
|
|
21
|
-
* jobs retain the existing behavior of checking out every active database.
|
|
22
|
-
* @type {string[] | undefined}
|
|
23
|
-
*/
|
|
24
|
-
static databaseIdentifiers = undefined
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Queue this job class runs on. Subclasses set e.g. `static queue = "builds"`
|
|
28
|
-
* to route onto a queue with its own cluster-wide concurrency cap (configured
|
|
29
|
-
* via `backgroundJobs.queues`). The `{queue}` enqueue option overrides it.
|
|
30
|
-
* Left undefined, jobs run on the `"default"` queue.
|
|
31
|
-
* @type {string | undefined}
|
|
32
|
-
*/
|
|
33
|
-
static queue = undefined
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Optional process title shown for the runner while this job executes.
|
|
37
|
-
* Velocious sets `process.title` to this for the duration of the job — so
|
|
38
|
-
* `ps`/`top`/`htop` identify what a runner is doing — and restores the
|
|
39
|
-
* runner's base title when the job finishes. Left undefined, the runner falls
|
|
40
|
-
* back to `velocious job-runner: <JobName>`. Set e.g.
|
|
41
|
-
* `static processTitle = "velocious media transcoder"` to give a job a
|
|
42
|
-
* custom, human-readable title.
|
|
43
|
-
* @type {string | undefined}
|
|
44
|
-
*/
|
|
45
|
-
static processTitle = undefined
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Stops this performance and reschedules the same logical job row. This is
|
|
49
|
-
* normal control flow: it does not count as a failure or consume a retry.
|
|
50
|
-
* @param {number} delayMs - Non-negative safe-integer delay in milliseconds.
|
|
51
|
-
* @returns {never} - This method never returns.
|
|
52
|
-
*/
|
|
53
|
-
rescheduleIn(delayMs) {
|
|
54
|
-
if (!Number.isSafeInteger(delayMs) || delayMs < 0) {
|
|
55
|
-
throw new TypeError("background job reschedule delayMs must be a non-negative safe integer")
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
throw new BackgroundJobRescheduleSignal(delayMs)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Runs job name.
|
|
63
|
-
* @returns {string} - Job name.
|
|
64
|
-
*/
|
|
65
|
-
static jobName() {
|
|
66
|
-
return this.name
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Folds this job class's static `queue` into the enqueue options unless the
|
|
71
|
-
* caller already specified one.
|
|
72
|
-
* @param {import("./types.js").BackgroundJobOptions | undefined} options - Job options.
|
|
73
|
-
* @returns {import("./types.js").BackgroundJobOptions} - Options including the resolved queue.
|
|
74
|
-
*/
|
|
75
|
-
static _withQueue(options) {
|
|
76
|
-
const merged = options ? {...options} : {}
|
|
77
|
-
|
|
78
|
-
if (merged.queue === undefined && typeof this.queue === "string" && this.queue.length > 0) {
|
|
79
|
-
merged.queue = this.queue
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return merged
|
|
83
|
-
}
|
|
84
|
-
|
|
18
|
+
export default class VelociousJob extends PlatformVelociousJob {
|
|
85
19
|
/**
|
|
86
20
|
* Runs perform later.
|
|
87
21
|
* @param {...ReturnType<typeof JSON.parse>} args - Job args.
|
|
88
22
|
* @returns {Promise<string>} - Job id.
|
|
89
23
|
*/
|
|
90
24
|
static async performLater(...args) {
|
|
25
|
+
const configuration = await configurationResolver()
|
|
91
26
|
const {jobArgs, jobOptions} = this._splitArgsAndOptions(args)
|
|
92
|
-
const client = new BackgroundJobsClient()
|
|
93
27
|
|
|
94
|
-
return await
|
|
95
|
-
jobName: this.jobName(),
|
|
96
|
-
args: jobArgs,
|
|
97
|
-
options: this._withQueue(jobOptions)
|
|
98
|
-
})
|
|
28
|
+
return await enqueueBackgroundJobForConfiguration({configuration, JobClass: this, jobArgs, jobOptions})
|
|
99
29
|
}
|
|
100
30
|
|
|
101
31
|
/**
|
|
@@ -106,13 +36,9 @@ export default class VelociousJob {
|
|
|
106
36
|
* @returns {Promise<string>} - Job id.
|
|
107
37
|
*/
|
|
108
38
|
static async performLaterWithOptions({args, options}) {
|
|
109
|
-
const
|
|
39
|
+
const configuration = await configurationResolver()
|
|
110
40
|
|
|
111
|
-
return await
|
|
112
|
-
jobName: this.jobName(),
|
|
113
|
-
args,
|
|
114
|
-
options: this._withQueue(options)
|
|
115
|
-
})
|
|
41
|
+
return await enqueueBackgroundJobForConfiguration({configuration, JobClass: this, jobArgs: args, jobOptions: options})
|
|
116
42
|
}
|
|
117
43
|
|
|
118
44
|
/**
|
|
@@ -124,14 +50,9 @@ export default class VelociousJob {
|
|
|
124
50
|
* @returns {Promise<import("./types.js").BackgroundJobReplacementResult>} - Replacement result.
|
|
125
51
|
*/
|
|
126
52
|
static async replaceScheduled({scheduleKey, args, options}) {
|
|
127
|
-
const
|
|
53
|
+
const configuration = await configurationResolver()
|
|
128
54
|
|
|
129
|
-
return await
|
|
130
|
-
scheduleKey,
|
|
131
|
-
jobName: this.jobName(),
|
|
132
|
-
args,
|
|
133
|
-
options: this._withQueue(options)
|
|
134
|
-
})
|
|
55
|
+
return await replaceScheduledBackgroundJobForConfiguration({configuration, JobClass: this, scheduleKey, jobArgs: args, jobOptions: options})
|
|
135
56
|
}
|
|
136
57
|
|
|
137
58
|
/**
|
|
@@ -140,38 +61,8 @@ export default class VelociousJob {
|
|
|
140
61
|
* @returns {Promise<import("./types.js").BackgroundJobCancellationResult>} - Cancellation result.
|
|
141
62
|
*/
|
|
142
63
|
static async cancelScheduled(scheduleKey) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
return await client.cancelScheduled({scheduleKey})
|
|
146
|
-
}
|
|
64
|
+
const configuration = await configurationResolver()
|
|
147
65
|
|
|
148
|
-
|
|
149
|
-
* Runs split args and options.
|
|
150
|
-
* @param {Array<ReturnType<typeof JSON.parse>>} args - Job args.
|
|
151
|
-
* @returns {{jobArgs: Array<ReturnType<typeof JSON.parse>>, jobOptions: import("./types.js").BackgroundJobOptions}} - Split args and options.
|
|
152
|
-
*/
|
|
153
|
-
static _splitArgsAndOptions(args) {
|
|
154
|
-
if (args.length === 0) {
|
|
155
|
-
return {jobArgs: [], jobOptions: {}}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const lastArg = args[args.length - 1]
|
|
159
|
-
const isOptionsArg = lastArg && typeof lastArg === "object" && !Array.isArray(lastArg) && "jobOptions" in lastArg
|
|
160
|
-
|
|
161
|
-
if (isOptionsArg) {
|
|
162
|
-
const {jobOptions} = /** @type {{jobOptions: import("./types.js").BackgroundJobOptions}} */ (lastArg)
|
|
163
|
-
return {jobArgs: args.slice(0, -1), jobOptions: jobOptions || {}}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return {jobArgs: args, jobOptions: {}}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Override in subclasses.
|
|
171
|
-
* @param {TArgs} _args - Job args (the tuple this job class was parameterized with).
|
|
172
|
-
* @returns {Promise<void>} - Resolves when complete.
|
|
173
|
-
*/
|
|
174
|
-
async perform(..._args) {
|
|
175
|
-
throw new Error("perform not implemented")
|
|
66
|
+
return await cancelScheduledBackgroundJobForConfiguration({configuration, scheduleKey})
|
|
176
67
|
}
|
|
177
68
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import net from "net"
|
|
4
4
|
import JsonSocket from "./json-socket.js"
|
|
5
5
|
import BackgroundJobsScheduler from "./scheduler.js"
|
|
6
|
-
import BackgroundJobsStore from "./store.js"
|
|
7
6
|
import Logger from "../logger.js"
|
|
8
7
|
import PruneTerminalBackgroundJobsJob from "../jobs/prune-terminal-background-jobs.js"
|
|
9
8
|
import VelociousError from "../velocious-error.js"
|
|
@@ -78,7 +77,8 @@ export default class BackgroundJobsMain {
|
|
|
78
77
|
// long is treated as wedged/dead: its leases are released and it is dropped.
|
|
79
78
|
this.workerStaleTimeoutMs = typeof workerStaleTimeoutMs === "number" && workerStaleTimeoutMs >= 1 ? workerStaleTimeoutMs : WORKER_STALE_TIMEOUT_MS
|
|
80
79
|
this.workerLivenessSweepMs = typeof workerLivenessSweepMs === "number" && workerLivenessSweepMs >= 1 ? workerLivenessSweepMs : WORKER_LIVENESS_SWEEP_MS
|
|
81
|
-
|
|
80
|
+
/** @type {import("./adapter.js").default | undefined} */
|
|
81
|
+
this.adapter = undefined
|
|
82
82
|
this.logger = new Logger(this)
|
|
83
83
|
/**
|
|
84
84
|
* Narrows the runtime value to the documented type.
|
|
@@ -146,6 +146,24 @@ export default class BackgroundJobsMain {
|
|
|
146
146
|
this._beaconClient = undefined
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Compatibility alias for integrations that inspect the active main store.
|
|
151
|
+
* @returns {import("./adapter.js").default} - Adapter acquired by start.
|
|
152
|
+
*/
|
|
153
|
+
get store() {
|
|
154
|
+
if (!this.adapter) throw new Error("Background jobs main has not acquired its adapter")
|
|
155
|
+
|
|
156
|
+
return this.adapter
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Preserves the historical subclass seam while keeping one adapter reference.
|
|
161
|
+
* @param {import("./adapter.js").default} adapter - Adapter to assign.
|
|
162
|
+
*/
|
|
163
|
+
set store(adapter) {
|
|
164
|
+
this.adapter = adapter
|
|
165
|
+
}
|
|
166
|
+
|
|
149
167
|
/**
|
|
150
168
|
* Runs start.
|
|
151
169
|
* @returns {Promise<void>} - Resolves when listening.
|
|
@@ -154,19 +172,24 @@ export default class BackgroundJobsMain {
|
|
|
154
172
|
this._stopped = false
|
|
155
173
|
this.stopPromise = undefined
|
|
156
174
|
this.configuration.setCurrent()
|
|
157
|
-
await this.configuration.initialize({type: "background-jobs-main"})
|
|
158
|
-
await this.configuration.connectBeacon({peerType: "background-jobs-main"})
|
|
159
|
-
await this.store.ensureReady()
|
|
160
|
-
// Queue-cap changes are reconciled against the persisted backlog here, at
|
|
161
|
-
// main-process startup — the explicit lifecycle for applying queue
|
|
162
|
-
// configuration changes. The store serializes the adoption/release UPDATEs
|
|
163
|
-
// across processes with a database advisory lock, so concurrently started
|
|
164
|
-
// mains cannot interleave them.
|
|
165
|
-
await this.store.reconcileQueueConcurrency()
|
|
166
|
-
const server = net.createServer((socket) => this._handleConnection(socket))
|
|
167
|
-
this.server = server
|
|
168
175
|
|
|
169
176
|
try {
|
|
177
|
+
await this.configuration.initialize({type: "background-jobs-main"})
|
|
178
|
+
await this.configuration.connectBeacon({peerType: "background-jobs-main"})
|
|
179
|
+
|
|
180
|
+
if (!this.adapter) {
|
|
181
|
+
this.adapter = await this.configuration.acquireReadyBackgroundJobsAdapter()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Queue-cap changes are reconciled against the persisted backlog here, at
|
|
185
|
+
// main-process startup — the explicit lifecycle for applying queue
|
|
186
|
+
// configuration changes. The store serializes the adoption/release UPDATEs
|
|
187
|
+
// across processes with a database advisory lock, so concurrently started
|
|
188
|
+
// mains cannot interleave them.
|
|
189
|
+
await this.store.reconcileQueueConcurrency()
|
|
190
|
+
const server = net.createServer((socket) => this._handleConnection(socket))
|
|
191
|
+
this.server = server
|
|
192
|
+
|
|
170
193
|
await new Promise((resolve, reject) => {
|
|
171
194
|
server.once("error", reject)
|
|
172
195
|
server.listen(this.port, this.host, () => resolve(undefined))
|
|
@@ -221,7 +244,16 @@ export default class BackgroundJobsMain {
|
|
|
221
244
|
// but this drain covers it).
|
|
222
245
|
await this._drain()
|
|
223
246
|
} catch (error) {
|
|
224
|
-
|
|
247
|
+
try {
|
|
248
|
+
await this.stop()
|
|
249
|
+
} catch (cleanupError) {
|
|
250
|
+
throw new AggregateError(
|
|
251
|
+
[error, cleanupError],
|
|
252
|
+
"Background jobs main startup and cleanup failed",
|
|
253
|
+
{cause: cleanupError}
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
225
257
|
throw error
|
|
226
258
|
}
|
|
227
259
|
}
|
|
@@ -243,20 +275,27 @@ export default class BackgroundJobsMain {
|
|
|
243
275
|
async _stop() {
|
|
244
276
|
this._stopped = true
|
|
245
277
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
278
|
+
try {
|
|
279
|
+
await shutdownLifecycle({
|
|
280
|
+
onStopped: this.onStopped,
|
|
281
|
+
shutdown: async () => {
|
|
282
|
+
this._closeWorkers()
|
|
283
|
+
this._clearTimers()
|
|
284
|
+
this._disconnectBeaconHandlers()
|
|
285
|
+
try {
|
|
286
|
+
await this.scheduler?.stop()
|
|
287
|
+
} finally {
|
|
288
|
+
try {
|
|
289
|
+
await this._drainWorkerHandoffAdoptions()
|
|
290
|
+
} finally {
|
|
291
|
+
await this._stopBeaconAndServer()
|
|
292
|
+
}
|
|
293
|
+
}
|
|
257
294
|
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
295
|
+
})
|
|
296
|
+
} finally {
|
|
297
|
+
this.adapter = undefined
|
|
298
|
+
}
|
|
260
299
|
}
|
|
261
300
|
|
|
262
301
|
/**
|
|
@@ -318,7 +357,11 @@ export default class BackgroundJobsMain {
|
|
|
318
357
|
try {
|
|
319
358
|
await this._closeServer()
|
|
320
359
|
} finally {
|
|
321
|
-
if (this.closeDatabaseConnectionsOnStop)
|
|
360
|
+
if (this.closeDatabaseConnectionsOnStop) {
|
|
361
|
+
await this.configuration.closeDatabaseConnections()
|
|
362
|
+
} else {
|
|
363
|
+
await this.configuration.closeBackgroundJobsAdapter()
|
|
364
|
+
}
|
|
322
365
|
}
|
|
323
366
|
}
|
|
324
367
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Performs a job class inside its declared database-connection scope.
|
|
5
|
+
* @param {object} args - Performance options.
|
|
6
|
+
* @param {import("../configuration.js").default} args.configuration - Active configuration.
|
|
7
|
+
* @param {typeof import("./platform-job.js").default} args.JobClass - Job class.
|
|
8
|
+
* @param {Array<ReturnType<typeof JSON.parse>>} args.jobArgs - Job arguments.
|
|
9
|
+
* @param {string} args.name - Connection-scope label.
|
|
10
|
+
* @returns {Promise<void>} - Resolves after performance.
|
|
11
|
+
*/
|
|
12
|
+
export default async function performBackgroundJob({configuration, JobClass, jobArgs, name}) {
|
|
13
|
+
const jobInstance = new JobClass()
|
|
14
|
+
/**
|
|
15
|
+
* Narrows the generic subclass's runtime method to serialized job arguments.
|
|
16
|
+
* @type {(...args: Array<ReturnType<typeof JSON.parse>>) => Promise<void>}
|
|
17
|
+
*/
|
|
18
|
+
const perform = jobInstance.perform
|
|
19
|
+
|
|
20
|
+
await configuration.withConnections({databaseIdentifiers: JobClass.databaseIdentifiers, name}, async () => {
|
|
21
|
+
await perform.apply(jobInstance, jobArgs)
|
|
22
|
+
})
|
|
23
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import BackgroundJobRescheduleSignal from "./reschedule-signal.js"
|
|
4
|
+
import {cancelScheduledBackgroundJob, enqueueBackgroundJob, replaceScheduledBackgroundJob} from "./runtime.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base class for background jobs.
|
|
8
|
+
*
|
|
9
|
+
* `TArgs` is the tuple of arguments the subclass's `perform` accepts, so a job that
|
|
10
|
+
* needs arguments declares them as required and typed — for example
|
|
11
|
+
* `class RunBuildJob extends VelociousJob<[string]>` with `async perform(buildId)`.
|
|
12
|
+
* The default empty tuple keeps argument-less jobs (`extends VelociousJob`,
|
|
13
|
+
* `async perform()`) working unchanged.
|
|
14
|
+
* @template {Array<ReturnType<typeof JSON.parse>>} [TArgs=[]]
|
|
15
|
+
*/
|
|
16
|
+
export default class VelociousJob {
|
|
17
|
+
/**
|
|
18
|
+
* Database identifiers checked out while this job performs. Set an explicit
|
|
19
|
+
* list to avoid holding unrelated configured database connections, or `[]`
|
|
20
|
+
* when the job establishes any connections it needs itself. Left undefined,
|
|
21
|
+
* jobs retain the existing behavior of checking out every active database.
|
|
22
|
+
* @type {string[] | undefined}
|
|
23
|
+
*/
|
|
24
|
+
static databaseIdentifiers = undefined
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Queue this job class runs on. Subclasses set e.g. `static queue = "builds"`
|
|
28
|
+
* to route onto a queue with its own cluster-wide concurrency cap (configured
|
|
29
|
+
* via `backgroundJobs.queues`). The `{queue}` enqueue option overrides it.
|
|
30
|
+
* Left undefined, jobs run on the `"default"` queue.
|
|
31
|
+
* @type {string | undefined}
|
|
32
|
+
*/
|
|
33
|
+
static queue = undefined
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Optional process title shown for the runner while this job executes.
|
|
37
|
+
* Velocious sets `process.title` to this for the duration of the job — so
|
|
38
|
+
* `ps`/`top`/`htop` identify what a runner is doing — and restores the
|
|
39
|
+
* runner's base title when the job finishes. Left undefined, the runner falls
|
|
40
|
+
* back to `velocious job-runner: <JobName>`. Set e.g.
|
|
41
|
+
* `static processTitle = "velocious media transcoder"` to give a job a
|
|
42
|
+
* custom, human-readable title.
|
|
43
|
+
* @type {string | undefined}
|
|
44
|
+
*/
|
|
45
|
+
static processTitle = undefined
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Stops this performance and reschedules the same logical job row. This is
|
|
49
|
+
* normal control flow: it does not count as a failure or consume a retry.
|
|
50
|
+
* @param {number} delayMs - Non-negative safe-integer delay in milliseconds.
|
|
51
|
+
* @returns {never} - This method never returns.
|
|
52
|
+
*/
|
|
53
|
+
rescheduleIn(delayMs) {
|
|
54
|
+
if (!Number.isSafeInteger(delayMs) || delayMs < 0) {
|
|
55
|
+
throw new TypeError("background job reschedule delayMs must be a non-negative safe integer")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
throw new BackgroundJobRescheduleSignal(delayMs)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Runs job name.
|
|
63
|
+
* @returns {string} - Job name.
|
|
64
|
+
*/
|
|
65
|
+
static jobName() {
|
|
66
|
+
return this.name
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Folds this job class's static `queue` into the enqueue options unless the
|
|
71
|
+
* caller already specified one.
|
|
72
|
+
* @param {import("./types.js").BackgroundJobOptions | undefined} options - Job options.
|
|
73
|
+
* @returns {import("./types.js").BackgroundJobOptions} - Options including the resolved queue.
|
|
74
|
+
*/
|
|
75
|
+
static _withQueue(options) {
|
|
76
|
+
const merged = options ? {...options} : {}
|
|
77
|
+
|
|
78
|
+
if (merged.queue === undefined && typeof this.queue === "string" && this.queue.length > 0) {
|
|
79
|
+
merged.queue = this.queue
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return merged
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Runs perform later.
|
|
87
|
+
* @param {...ReturnType<typeof JSON.parse>} args - Job args.
|
|
88
|
+
* @returns {Promise<string>} - Job id.
|
|
89
|
+
*/
|
|
90
|
+
static async performLater(...args) {
|
|
91
|
+
const {jobArgs, jobOptions} = this._splitArgsAndOptions(args)
|
|
92
|
+
return await enqueueBackgroundJob({JobClass: this, jobArgs, jobOptions})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Runs perform later with options.
|
|
97
|
+
* @param {object} args - Options.
|
|
98
|
+
* @param {Array<ReturnType<typeof JSON.parse>>} args.args - Job args.
|
|
99
|
+
* @param {import("./types.js").BackgroundJobOptions} [args.options] - Job options.
|
|
100
|
+
* @returns {Promise<string>} - Job id.
|
|
101
|
+
*/
|
|
102
|
+
static async performLaterWithOptions({args, options}) {
|
|
103
|
+
return await enqueueBackgroundJob({JobClass: this, jobArgs: args, jobOptions: options})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Atomically replaces this job class's queued owner for a stable schedule key.
|
|
108
|
+
* @param {object} args - Options.
|
|
109
|
+
* @param {string} args.scheduleKey - Stable logical schedule key.
|
|
110
|
+
* @param {Array<ReturnType<typeof JSON.parse>>} args.args - Job args.
|
|
111
|
+
* @param {import("./types.js").BackgroundJobOptions} [args.options] - Job options.
|
|
112
|
+
* @returns {Promise<import("./types.js").BackgroundJobReplacementResult>} - Replacement result.
|
|
113
|
+
*/
|
|
114
|
+
static async replaceScheduled({scheduleKey, args, options}) {
|
|
115
|
+
return await replaceScheduledBackgroundJob({JobClass: this, scheduleKey, jobArgs: args, jobOptions: options})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Cancels or detaches the current owner of a stable schedule key.
|
|
120
|
+
* @param {string} scheduleKey - Stable logical schedule key.
|
|
121
|
+
* @returns {Promise<import("./types.js").BackgroundJobCancellationResult>} - Cancellation result.
|
|
122
|
+
*/
|
|
123
|
+
static async cancelScheduled(scheduleKey) {
|
|
124
|
+
return await cancelScheduledBackgroundJob(scheduleKey)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Runs split args and options.
|
|
129
|
+
* @param {Array<ReturnType<typeof JSON.parse>>} args - Job args.
|
|
130
|
+
* @returns {{jobArgs: Array<ReturnType<typeof JSON.parse>>, jobOptions: import("./types.js").BackgroundJobOptions}} - Split args and options.
|
|
131
|
+
*/
|
|
132
|
+
static _splitArgsAndOptions(args) {
|
|
133
|
+
if (args.length === 0) {
|
|
134
|
+
return {jobArgs: [], jobOptions: {}}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const lastArg = args[args.length - 1]
|
|
138
|
+
const isOptionsArg = lastArg && typeof lastArg === "object" && !Array.isArray(lastArg) && "jobOptions" in lastArg
|
|
139
|
+
|
|
140
|
+
if (isOptionsArg) {
|
|
141
|
+
const {jobOptions} = /** @type {{jobOptions: import("./types.js").BackgroundJobOptions}} */ (lastArg)
|
|
142
|
+
return {jobArgs: args.slice(0, -1), jobOptions: jobOptions || {}}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {jobArgs: args, jobOptions: {}}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Override in subclasses.
|
|
150
|
+
* @param {TArgs} _args - Job args (the tuple this job class was parameterized with).
|
|
151
|
+
* @returns {Promise<void>} - Resolves when complete.
|
|
152
|
+
*/
|
|
153
|
+
async perform(..._args) {
|
|
154
|
+
throw new Error("perform not implemented")
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {currentConfiguration} from "../current-configuration.js"
|
|
4
|
+
import performBackgroundJob from "./perform-job.js"
|
|
5
|
+
import BackgroundJobRescheduleSignal from "./reschedule-signal.js"
|
|
6
|
+
|
|
7
|
+
let inlinePerformanceSequence = 0
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Rejects options whose semantics require durable queue state.
|
|
11
|
+
* @param {import("./types.js").BackgroundJobOptions | undefined} options - Requested options.
|
|
12
|
+
* @returns {void}
|
|
13
|
+
*/
|
|
14
|
+
function validateInlineOptions(options) {
|
|
15
|
+
const optionNames = Object.keys(options || {})
|
|
16
|
+
|
|
17
|
+
if (optionNames.length > 0) {
|
|
18
|
+
throw new Error(`Background job option ${optionNames[0]} is not supported in inline mode`)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Builds an ephemeral inline performance id.
|
|
24
|
+
* @returns {string} - Performance id.
|
|
25
|
+
*/
|
|
26
|
+
function inlineJobId() {
|
|
27
|
+
inlinePerformanceSequence++
|
|
28
|
+
return `inline-${Date.now()}-${inlinePerformanceSequence}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Enqueues durably in background mode or performs immediately in inline mode.
|
|
33
|
+
* @param {object} args - Enqueue request.
|
|
34
|
+
* @param {typeof import("./platform-job.js").default} args.JobClass - Job class.
|
|
35
|
+
* @param {Array<ReturnType<typeof JSON.parse>>} args.jobArgs - Job arguments.
|
|
36
|
+
* @param {import("./types.js").BackgroundJobOptions | undefined} args.jobOptions - Job options.
|
|
37
|
+
* @returns {Promise<string>} - Durable job id or ephemeral inline performance id.
|
|
38
|
+
*/
|
|
39
|
+
export async function enqueueBackgroundJob({JobClass, jobArgs, jobOptions}) {
|
|
40
|
+
const configuration = currentConfiguration()
|
|
41
|
+
|
|
42
|
+
return await enqueueBackgroundJobForConfiguration({configuration, JobClass, jobArgs, jobOptions})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Enqueues using an explicitly resolved configuration.
|
|
47
|
+
* @param {object} args - Enqueue request.
|
|
48
|
+
* @param {import("../configuration.js").default} args.configuration - Configuration.
|
|
49
|
+
* @param {typeof import("./platform-job.js").default} args.JobClass - Job class.
|
|
50
|
+
* @param {Array<ReturnType<typeof JSON.parse>>} args.jobArgs - Job arguments.
|
|
51
|
+
* @param {import("./types.js").BackgroundJobOptions | undefined} args.jobOptions - Job options.
|
|
52
|
+
* @returns {Promise<string>} - Durable job id or ephemeral inline performance id.
|
|
53
|
+
*/
|
|
54
|
+
export async function enqueueBackgroundJobForConfiguration({configuration, JobClass, jobArgs, jobOptions}) {
|
|
55
|
+
|
|
56
|
+
if (configuration.getBackgroundJobsConfig().mode === "inline") {
|
|
57
|
+
validateInlineOptions(jobOptions)
|
|
58
|
+
configuration.setCurrent()
|
|
59
|
+
await configuration.initialize({type: "background-jobs-inline"})
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
await performBackgroundJob({
|
|
63
|
+
configuration,
|
|
64
|
+
JobClass,
|
|
65
|
+
jobArgs,
|
|
66
|
+
name: `Background job inline mode: ${JobClass.jobName()}`
|
|
67
|
+
})
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error instanceof BackgroundJobRescheduleSignal) {
|
|
70
|
+
throw new Error("rescheduleIn is not supported in inline mode", {cause: error})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw error
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return inlineJobId()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const client = configuration.getEnvironmentHandler().backgroundJobsClient({configuration})
|
|
80
|
+
|
|
81
|
+
return await client.enqueue({
|
|
82
|
+
jobName: JobClass.jobName(),
|
|
83
|
+
args: jobArgs,
|
|
84
|
+
options: JobClass._withQueue(jobOptions)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Replaces a stable durable schedule in background mode.
|
|
90
|
+
* @param {object} args - Replacement request.
|
|
91
|
+
* @param {typeof import("./platform-job.js").default} args.JobClass - Job class.
|
|
92
|
+
* @param {string} args.scheduleKey - Stable schedule key.
|
|
93
|
+
* @param {Array<ReturnType<typeof JSON.parse>>} args.jobArgs - Job arguments.
|
|
94
|
+
* @param {import("./types.js").BackgroundJobOptions | undefined} args.jobOptions - Job options.
|
|
95
|
+
* @returns {Promise<import("./types.js").BackgroundJobReplacementResult>} - Replacement result.
|
|
96
|
+
*/
|
|
97
|
+
export async function replaceScheduledBackgroundJob({JobClass, scheduleKey, jobArgs, jobOptions}) {
|
|
98
|
+
const configuration = currentConfiguration()
|
|
99
|
+
|
|
100
|
+
return await replaceScheduledBackgroundJobForConfiguration({configuration, JobClass, scheduleKey, jobArgs, jobOptions})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Replaces a stable schedule using an explicitly resolved configuration.
|
|
105
|
+
* @param {object} args - Replacement request.
|
|
106
|
+
* @param {import("../configuration.js").default} args.configuration - Configuration.
|
|
107
|
+
* @param {typeof import("./platform-job.js").default} args.JobClass - Job class.
|
|
108
|
+
* @param {string} args.scheduleKey - Stable schedule key.
|
|
109
|
+
* @param {Array<ReturnType<typeof JSON.parse>>} args.jobArgs - Job arguments.
|
|
110
|
+
* @param {import("./types.js").BackgroundJobOptions | undefined} args.jobOptions - Job options.
|
|
111
|
+
* @returns {Promise<import("./types.js").BackgroundJobReplacementResult>} - Replacement result.
|
|
112
|
+
*/
|
|
113
|
+
export async function replaceScheduledBackgroundJobForConfiguration({configuration, JobClass, scheduleKey, jobArgs, jobOptions}) {
|
|
114
|
+
|
|
115
|
+
if (configuration.getBackgroundJobsConfig().mode === "inline") {
|
|
116
|
+
throw new Error("replaceScheduled is not supported in inline mode")
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const client = configuration.getEnvironmentHandler().backgroundJobsClient({configuration})
|
|
120
|
+
|
|
121
|
+
return await client.replaceScheduled({
|
|
122
|
+
scheduleKey,
|
|
123
|
+
jobName: JobClass.jobName(),
|
|
124
|
+
args: jobArgs,
|
|
125
|
+
options: JobClass._withQueue(jobOptions)
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Cancels a stable durable schedule in background mode.
|
|
131
|
+
* @param {string} scheduleKey - Stable schedule key.
|
|
132
|
+
* @returns {Promise<import("./types.js").BackgroundJobCancellationResult>} - Cancellation result.
|
|
133
|
+
*/
|
|
134
|
+
export async function cancelScheduledBackgroundJob(scheduleKey) {
|
|
135
|
+
const configuration = currentConfiguration()
|
|
136
|
+
|
|
137
|
+
return await cancelScheduledBackgroundJobForConfiguration({configuration, scheduleKey})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Cancels a stable schedule using an explicitly resolved configuration.
|
|
142
|
+
* @param {object} args - Cancellation request.
|
|
143
|
+
* @param {import("../configuration.js").default} args.configuration - Configuration.
|
|
144
|
+
* @param {string} args.scheduleKey - Stable logical schedule key.
|
|
145
|
+
* @returns {Promise<import("./types.js").BackgroundJobCancellationResult>} - Cancellation result.
|
|
146
|
+
*/
|
|
147
|
+
export async function cancelScheduledBackgroundJobForConfiguration({configuration, scheduleKey}) {
|
|
148
|
+
|
|
149
|
+
if (configuration.getBackgroundJobsConfig().mode === "inline") {
|
|
150
|
+
throw new Error("cancelScheduled is not supported in inline mode")
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const client = configuration.getEnvironmentHandler().backgroundJobsClient({configuration})
|
|
154
|
+
|
|
155
|
+
return await client.cancelScheduled({scheduleKey})
|
|
156
|
+
}
|