velocious 1.0.631 → 1.0.632
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 -2
- package/build/application.js +30 -10
- package/build/background-jobs/forked-runner-child.js +26 -30
- package/build/background-jobs/job-runner.js +5 -7
- package/build/background-jobs/main.js +33 -28
- package/build/background-jobs/pooled-runner-child.js +17 -9
- package/build/background-jobs/runner-graceful-shutdown.js +26 -10
- package/build/background-jobs/worker.js +34 -13
- package/build/configuration-types.js +8 -0
- package/build/configuration.js +229 -31
- package/build/database/drivers/base.js +41 -14
- package/build/environment-handlers/node/cli/commands/background-jobs-worker.js +47 -16
- package/build/environment-handlers/node/cli/commands/server.js +7 -2
- package/build/error-reporting/request-details.js +28 -31
- package/build/frontend-model-controller.js +19 -13
- package/build/http-server/client/index.js +5 -13
- package/build/http-server/client/request-runner.js +26 -9
- package/build/http-server/client/request-timing.js +19 -0
- package/build/http-server/client/websocket-session.js +54 -14
- package/build/http-server/worker-handler/worker-thread.js +13 -6
- package/build/initializer.js +19 -1
- package/build/log-redactor.js +341 -0
- package/build/routes/resolver.js +32 -4
- package/build/src/application.d.ts +7 -0
- package/build/src/application.d.ts.map +1 -1
- package/build/src/application.js +32 -12
- package/build/src/background-jobs/forked-runner-child.js +25 -31
- 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 +6 -9
- package/build/src/background-jobs/main.d.ts +0 -4
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +34 -32
- package/build/src/background-jobs/pooled-runner-child.js +18 -11
- package/build/src/background-jobs/runner-graceful-shutdown.d.ts +9 -0
- package/build/src/background-jobs/runner-graceful-shutdown.d.ts.map +1 -1
- package/build/src/background-jobs/runner-graceful-shutdown.js +27 -13
- package/build/src/background-jobs/worker.d.ts +5 -0
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +31 -18
- package/build/src/configuration-types.d.ts +21 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +8 -1
- package/build/src/configuration.d.ts +75 -0
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +204 -30
- package/build/src/database/drivers/base.d.ts +5 -1
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +42 -16
- package/build/src/environment-handlers/node/cli/commands/background-jobs-worker.d.ts +36 -0
- package/build/src/environment-handlers/node/cli/commands/background-jobs-worker.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/background-jobs-worker.js +45 -16
- package/build/src/environment-handlers/node/cli/commands/server.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/server.js +8 -3
- package/build/src/error-reporting/request-details.d.ts +8 -1
- package/build/src/error-reporting/request-details.d.ts.map +1 -1
- package/build/src/error-reporting/request-details.js +30 -32
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +20 -14
- package/build/src/http-server/client/index.d.ts.map +1 -1
- package/build/src/http-server/client/index.js +5 -12
- package/build/src/http-server/client/request-runner.d.ts.map +1 -1
- package/build/src/http-server/client/request-runner.js +23 -10
- package/build/src/http-server/client/request-timing.d.ts +13 -0
- package/build/src/http-server/client/request-timing.d.ts.map +1 -1
- package/build/src/http-server/client/request-timing.js +19 -1
- package/build/src/http-server/client/websocket-session.d.ts +9 -2
- package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
- package/build/src/http-server/client/websocket-session.js +46 -15
- package/build/src/http-server/worker-handler/worker-thread.d.ts.map +1 -1
- package/build/src/http-server/worker-handler/worker-thread.js +13 -6
- package/build/src/initializer.d.ts +14 -1
- package/build/src/initializer.d.ts.map +1 -1
- package/build/src/initializer.js +18 -2
- package/build/src/log-redactor.d.ts +97 -0
- package/build/src/log-redactor.d.ts.map +1 -0
- package/build/src/log-redactor.js +297 -0
- package/build/src/routes/resolver.d.ts +7 -0
- package/build/src/routes/resolver.d.ts.map +1 -1
- package/build/src/routes/resolver.js +26 -5
- package/build/src/utils/shutdown-lifecycle.d.ts +13 -0
- package/build/src/utils/shutdown-lifecycle.d.ts.map +1 -1
- package/build/src/utils/shutdown-lifecycle.js +35 -26
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/utils/shutdown-lifecycle.js +33 -30
- package/package.json +1 -1
- package/src/application.js +30 -10
- package/src/background-jobs/forked-runner-child.js +26 -30
- package/src/background-jobs/job-runner.js +5 -7
- package/src/background-jobs/main.js +33 -28
- package/src/background-jobs/pooled-runner-child.js +17 -9
- package/src/background-jobs/runner-graceful-shutdown.js +26 -10
- package/src/background-jobs/worker.js +34 -13
- package/src/configuration-types.js +8 -0
- package/src/configuration.js +229 -31
- package/src/database/drivers/base.js +41 -14
- package/src/environment-handlers/node/cli/commands/background-jobs-worker.js +47 -16
- package/src/environment-handlers/node/cli/commands/server.js +7 -2
- package/src/error-reporting/request-details.js +28 -31
- package/src/frontend-model-controller.js +19 -13
- package/src/http-server/client/index.js +5 -13
- package/src/http-server/client/request-runner.js +26 -9
- package/src/http-server/client/request-timing.js +19 -0
- package/src/http-server/client/websocket-session.js +54 -14
- package/src/http-server/worker-handler/worker-thread.js +13 -6
- package/src/initializer.js +19 -1
- package/src/log-redactor.js +341 -0
- package/src/routes/resolver.js +32 -4
- package/src/utils/shutdown-lifecycle.js +33 -30
package/README.md
CHANGED
|
@@ -32,11 +32,12 @@
|
|
|
32
32
|
* Per-record ability checks via `.abilities(...)` on frontend queries + `record.can(action)` (see [docs/abilities.md](docs/abilities.md))
|
|
33
33
|
* Translated model attributes with current-locale relationship sorting (see [docs/translations.md](docs/translations.md))
|
|
34
34
|
* Cross-process broadcast bus for `broadcastToChannel` via `velocious beacon`, including background job runner processes (see [docs/beacon.md](docs/beacon.md))
|
|
35
|
+
* Rails-style application process initializer teardown with immutable process identity, reverse idempotent shutdown, and explicit HTTP/background-job ownership (see [docs/application-process-lifecycle.md](docs/application-process-lifecycle.md))
|
|
35
36
|
* Configurable HTTP server worker handlers plus backpressured, descriptor-only file responses with completion callbacks (see [docs/http-server.md](docs/http-server.md))
|
|
36
37
|
* Default-on buffered HTTP response compression with Brotli/gzip content negotiation, global and per-response opt-outs, and HEAD-correct representation headers (see [docs/http-server.md](docs/http-server.md#response-compression))
|
|
37
38
|
* Background jobs with Node SQL/TCP workers plus a Browser/Expo local SQLite store and in-process dispatcher, including failure events, authorized database-scoped dashboard counts, and an opt-in release-scoped main/worker generation protocol with acknowledged activation, asynchronous retirement, and retired-main recovery. Production compliance additionally requires downstream supervisor retention/activation ordering and release pins (see [docs/background-jobs.md](docs/background-jobs.md), [docs/local-background-jobs.md](docs/local-background-jobs.md), and [docs/background-jobs-dashboard.md](docs/background-jobs-dashboard.md))
|
|
38
39
|
* Durable one-off background-job scheduling with exact epoch timestamps (see [docs/scheduled-background-job-enqueue.md](docs/scheduled-background-job-enqueue.md))
|
|
39
|
-
* Rails-style request and database query logging (see [docs/logging.md](docs/logging.md))
|
|
40
|
+
* Rails-style request and database query logging with structured credential redaction (see [docs/logging.md](docs/logging.md))
|
|
40
41
|
* EJS-backed mailers with delivery, queueing, and payload rendering support (see [docs/mailers.md](docs/mailers.md))
|
|
41
42
|
* Trusted reverse proxy handling for `request.remoteAddress()` (see [docs/trusted-proxies.md](docs/trusted-proxies.md))
|
|
42
43
|
* In-process driver schema metadata caching (see [docs/schema-metadata-cache.md](docs/schema-metadata-cache.md))
|
|
@@ -60,6 +61,12 @@ npx velocious init
|
|
|
60
61
|
|
|
61
62
|
By default, Velocious looks for your configuration in `src/config/configuration.js`. If you keep the configuration elsewhere, make sure your app imports it early and calls `configuration.setCurrent()`.
|
|
62
63
|
|
|
64
|
+
Application initializers may implement `teardown()` and inspect their frozen
|
|
65
|
+
`getProcessContext()` value. Long-lived process owners call
|
|
66
|
+
`configuration.shutdown()` before framework connection cleanup; see the
|
|
67
|
+
[application process lifecycle guide](docs/application-process-lifecycle.md) for
|
|
68
|
+
promise identity, errors, process types, and pooled/forked runner semantics.
|
|
69
|
+
|
|
63
70
|
# Node SQLite driver
|
|
64
71
|
|
|
65
72
|
Projects using `velocious/build/src/database/drivers/sqlite/index.js` must install its optional peer dependencies:
|
|
@@ -1908,7 +1915,8 @@ const configuration = new Configuration({
|
|
|
1908
1915
|
console: false, // disable console output
|
|
1909
1916
|
file: true, // enable file output
|
|
1910
1917
|
directory: "/custom/logs", // optional, defaults to "<project>/log" in Node
|
|
1911
|
-
filePath: "/tmp/app.log"
|
|
1918
|
+
filePath: "/tmp/app.log", // optional explicit path
|
|
1919
|
+
sensitiveNames: ["integrationPin"] // optional app-specific additions
|
|
1912
1920
|
}
|
|
1913
1921
|
})
|
|
1914
1922
|
```
|
|
@@ -1969,6 +1977,8 @@ Model queries use operation names such as `Task Load`, `Task Count`, `Task Pluck
|
|
|
1969
1977
|
|
|
1970
1978
|
Query logging defaults to off in the `test` environment to keep CI output quiet and is skipped when no output emits `info`. Override it with `logging: {queryLogging: true}` when a test build should write SQL timing logs, and use the normal logging output settings to send those logs to console or file.
|
|
1971
1979
|
|
|
1980
|
+
- **Credential redaction**: Request headers, parsed body/params, nested arrays, URL queries, WebSocket authentication params, rendered SQL diagnostics, and request/frontend-model errors are redacted before formatting and output fan-out. Defaults match common authorization, authentication, credential, password, secret, token, API-key, cookie/session, and base64-content name variants case-insensitively. Add application names with `logging.sensitiveNames`; entries must be non-blank strings. Exact request-scoped values are replaced in SQL/error text while safe fields, SQL shape, timing, source lines, error class/backtrace, and correlation metadata remain visible. Import `LOG_REDACTION_MARKER` from `velocious/build/src/log-redactor.js` when code needs to compare the deterministic marker. See [logging and credential redaction](docs/logging.md#credential-redaction).
|
|
1981
|
+
|
|
1972
1982
|
## Listen for framework errors
|
|
1973
1983
|
|
|
1974
1984
|
Velocious emits framework errors (including uncaught controller action errors) on the configuration error event bus:
|
package/build/application.js
CHANGED
|
@@ -9,6 +9,7 @@ import SyncPublisher from "./sync/sync-publisher.js"
|
|
|
9
9
|
import SyncWebsocketChannel from "./sync/sync-websocket-channel.js"
|
|
10
10
|
import websocketEventsHost from "./http-server/websocket-events-host.js"
|
|
11
11
|
import restArgsError from "./utils/rest-args-error.js"
|
|
12
|
+
import { runShutdownSteps } from "./utils/shutdown-lifecycle.js"
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* HttpServerConfiguration type.
|
|
@@ -40,6 +41,8 @@ export default class VelociousApplication {
|
|
|
40
41
|
* Stores the http server lock value.
|
|
41
42
|
* @type {HttpServerLock | undefined} */
|
|
42
43
|
this.httpServerLock = undefined
|
|
44
|
+
/** @type {Promise<void> | undefined} */
|
|
45
|
+
this._stopPromise = undefined
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/**
|
|
@@ -186,17 +189,34 @@ export default class VelociousApplication {
|
|
|
186
189
|
* Runs stop.
|
|
187
190
|
* @returns {Promise<void>} - Resolves when complete.
|
|
188
191
|
*/
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
stop() {
|
|
193
|
+
if (!this._stopPromise) this._stopPromise = this._stop()
|
|
191
194
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
195
|
+
return this._stopPromise
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Stops application and framework resources.
|
|
200
|
+
* @returns {Promise<void>} - Resolves after every application and framework close succeeds.
|
|
201
|
+
*/
|
|
202
|
+
async _stop() {
|
|
203
|
+
await runShutdownSteps({
|
|
204
|
+
message: "Application and framework shutdown failed",
|
|
205
|
+
steps: [
|
|
206
|
+
async () => await this.logger.debug("Stopping server"),
|
|
207
|
+
async () => {
|
|
208
|
+
try {
|
|
209
|
+
await this.httpServer?.stop()
|
|
210
|
+
} finally {
|
|
211
|
+
this.configuration._httpServerInstance = undefined
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
async () => await this.configuration.shutdown(),
|
|
215
|
+
async () => await this.configuration.disconnectBeacon(),
|
|
216
|
+
async () => await this.configuration.closeDatabaseConnections(),
|
|
217
|
+
async () => await this.releaseHttpServerLock()
|
|
218
|
+
]
|
|
219
|
+
})
|
|
200
220
|
}
|
|
201
221
|
|
|
202
222
|
/**
|
|
@@ -10,8 +10,8 @@ import setRunnerProcessTitle from "./runner-process-title.js"
|
|
|
10
10
|
// which jobs are running, how many of each, and which are eating resources.
|
|
11
11
|
setRunnerProcessTitle()
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
let
|
|
13
|
+
/** @type {Promise<void> | undefined} */
|
|
14
|
+
let shutdownPromise
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Closes the runner's connections — releasing any advisory lock a killed-mid-job
|
|
@@ -21,12 +21,15 @@ let shuttingDown = false
|
|
|
21
21
|
* @param {number} exitCode - Process exit code.
|
|
22
22
|
* @returns {Promise<void>}
|
|
23
23
|
*/
|
|
24
|
-
|
|
25
|
-
if (
|
|
26
|
-
shuttingDown = true
|
|
24
|
+
function shutdownRunner(exitCode) {
|
|
25
|
+
if (shutdownPromise) return shutdownPromise
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
shutdownPromise = (async () => {
|
|
28
|
+
await closeRunnerConnections(currentConfigurationOrNull())
|
|
29
|
+
process.exit(exitCode)
|
|
30
|
+
})()
|
|
31
|
+
|
|
32
|
+
return shutdownPromise
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/**
|
|
@@ -45,25 +48,13 @@ function isJobMessage(message) {
|
|
|
45
48
|
/**
|
|
46
49
|
* Runs finish.
|
|
47
50
|
* @param {number} exitCode - Process exit code.
|
|
48
|
-
* @returns {void}
|
|
51
|
+
* @returns {Promise<void>}
|
|
49
52
|
*/
|
|
50
|
-
function finish(exitCode) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (process.connected && process.disconnect) {
|
|
55
|
-
process.disconnect()
|
|
53
|
+
async function finish(exitCode) {
|
|
54
|
+
if (process.send) {
|
|
55
|
+
await new Promise((resolve) => process.send?.({type: "job-reported"}, () => resolve(undefined)))
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
setImmediate(() => process.exit(exitCode))
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Runs report job finished.
|
|
63
|
-
* @returns {void}
|
|
64
|
-
*/
|
|
65
|
-
function reportJobFinished() {
|
|
66
|
-
if (process.send) process.send({type: "job-reported"})
|
|
57
|
+
await shutdownRunner(exitCode)
|
|
67
58
|
}
|
|
68
59
|
|
|
69
60
|
/**
|
|
@@ -79,7 +70,10 @@ async function runJobMessage(message) {
|
|
|
79
70
|
// The per-job process title (and its restore) is set inside runJobPayload,
|
|
80
71
|
// which reads the job class's `static processTitle`. This process boots with
|
|
81
72
|
// the base "velocious background-jobs-runner" title set at module load above.
|
|
82
|
-
await runJobPayload(message.payload, {
|
|
73
|
+
await runJobPayload(message.payload, {
|
|
74
|
+
closeConnections: false,
|
|
75
|
+
processType: "background-jobs-forked-runner"
|
|
76
|
+
})
|
|
83
77
|
}
|
|
84
78
|
|
|
85
79
|
/**
|
|
@@ -88,15 +82,17 @@ async function runJobMessage(message) {
|
|
|
88
82
|
* @returns {Promise<void>} - Resolves after completion is reported.
|
|
89
83
|
*/
|
|
90
84
|
async function handleJobMessage(message) {
|
|
85
|
+
let exitCode
|
|
86
|
+
|
|
91
87
|
try {
|
|
92
88
|
await runJobMessage(message)
|
|
93
|
-
|
|
94
|
-
finish(0)
|
|
89
|
+
exitCode = 0
|
|
95
90
|
} catch (error) {
|
|
96
|
-
reportJobFinished()
|
|
97
91
|
console.error("Forked background job runner failed:", error)
|
|
98
|
-
|
|
92
|
+
exitCode = 1
|
|
99
93
|
}
|
|
94
|
+
|
|
95
|
+
await finish(exitCode)
|
|
100
96
|
}
|
|
101
97
|
|
|
102
98
|
for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
@@ -104,7 +100,7 @@ for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
|
104
100
|
}
|
|
105
101
|
|
|
106
102
|
process.once("disconnect", () => {
|
|
107
|
-
|
|
103
|
+
void shutdownRunner(0)
|
|
108
104
|
})
|
|
109
105
|
|
|
110
106
|
process.once("message", (message) => {
|
|
@@ -4,6 +4,7 @@ import configurationResolver from "../configuration-resolver.js"
|
|
|
4
4
|
import BackgroundJobRegistry from "./job-registry.js"
|
|
5
5
|
import BackgroundJobsStatusReporter from "./status-reporter.js"
|
|
6
6
|
import BackgroundJobRescheduleSignal from "./reschedule-signal.js"
|
|
7
|
+
import { closeRunnerConnections } from "./runner-graceful-shutdown.js"
|
|
7
8
|
|
|
8
9
|
const BEACON_READY_TIMEOUT_MS = 5000
|
|
9
10
|
|
|
@@ -80,12 +81,13 @@ function runnerProcessTitle(JobClass, payload) {
|
|
|
80
81
|
* @param {object} [options] - Runner options.
|
|
81
82
|
* @param {boolean} [options.closeConnections] - Whether to gracefully close framework connections after the job.
|
|
82
83
|
* @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.
|
|
84
|
+
* @param {string} [options.processType] - Generic application process type.
|
|
83
85
|
* @returns {Promise<"completed" | "rescheduled">} - Acknowledged outcome.
|
|
84
86
|
*/
|
|
85
|
-
export default async function runJobPayload(payload, {closeConnections = true, manageProcessTitle = true} = {}) {
|
|
87
|
+
export default async function runJobPayload(payload, {closeConnections = true, manageProcessTitle = true, processType = "background-jobs-runner"} = {}) {
|
|
86
88
|
const configuration = await configurationResolver()
|
|
87
89
|
configuration.setCurrent()
|
|
88
|
-
await configuration.initialize({type:
|
|
90
|
+
await configuration.initialize({type: processType})
|
|
89
91
|
await connectBeacon(configuration)
|
|
90
92
|
const reporter = new BackgroundJobsStatusReporter({configuration})
|
|
91
93
|
|
|
@@ -167,11 +169,7 @@ export default async function runJobPayload(payload, {closeConnections = true, m
|
|
|
167
169
|
// one) doesn't misreport a finished job as still running.
|
|
168
170
|
if (manageProcessTitle) process.title = previousTitle
|
|
169
171
|
if (closeConnections) {
|
|
170
|
-
|
|
171
|
-
await configuration.disconnectBeacon()
|
|
172
|
-
} finally {
|
|
173
|
-
await configuration.closeDatabaseConnections()
|
|
174
|
-
}
|
|
172
|
+
await closeRunnerConnections(configuration)
|
|
175
173
|
}
|
|
176
174
|
}
|
|
177
175
|
}
|
|
@@ -7,7 +7,7 @@ import BackgroundJobsScheduler from "./scheduler.js"
|
|
|
7
7
|
import Logger from "../logger.js"
|
|
8
8
|
import PruneTerminalBackgroundJobsJob from "../jobs/prune-terminal-background-jobs.js"
|
|
9
9
|
import VelociousError from "../velocious-error.js"
|
|
10
|
-
import shutdownLifecycle from "../utils/shutdown-lifecycle.js"
|
|
10
|
+
import shutdownLifecycle, { runShutdownSteps } from "../utils/shutdown-lifecycle.js"
|
|
11
11
|
import { validateGenerationId, workerIdBelongsToGeneration } from "./generation-identity.js"
|
|
12
12
|
import BackgroundJobsLifecycleControlServer from "./lifecycle-control-server.js"
|
|
13
13
|
|
|
@@ -330,13 +330,19 @@ export default class BackgroundJobsMain {
|
|
|
330
330
|
this._startGenerationRecoveryOwnership()
|
|
331
331
|
}
|
|
332
332
|
} catch (error) {
|
|
333
|
+
let cleanupError
|
|
334
|
+
|
|
333
335
|
try {
|
|
334
336
|
await this.stop()
|
|
335
|
-
} catch (
|
|
337
|
+
} catch (caughtCleanupError) {
|
|
338
|
+
cleanupError = caughtCleanupError
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (cleanupError) {
|
|
336
342
|
throw new AggregateError(
|
|
337
343
|
[error, cleanupError],
|
|
338
344
|
"Background jobs main startup and cleanup failed",
|
|
339
|
-
{cause:
|
|
345
|
+
{cause: error}
|
|
340
346
|
)
|
|
341
347
|
}
|
|
342
348
|
|
|
@@ -440,31 +446,30 @@ export default class BackgroundJobsMain {
|
|
|
440
446
|
* Runs stop beacon and server.
|
|
441
447
|
* @returns {Promise<void>} */
|
|
442
448
|
async _stopBeaconAndServer() {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
449
|
+
await runShutdownSteps({
|
|
450
|
+
message: "Background jobs main application and framework shutdown failed",
|
|
451
|
+
steps: [
|
|
452
|
+
async () => {
|
|
453
|
+
try {
|
|
454
|
+
await this.lifecycleControlServer?.close()
|
|
455
|
+
} finally {
|
|
456
|
+
this.lifecycleControlServer = undefined
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
...(this.closeDatabaseConnectionsOnStop
|
|
460
|
+
? [async () => await this.configuration.shutdown()]
|
|
461
|
+
: []),
|
|
462
|
+
async () => await this.configuration.disconnectBeacon(),
|
|
463
|
+
async () => await this._closeServer(),
|
|
464
|
+
async () => {
|
|
465
|
+
if (this.closeDatabaseConnectionsOnStop) {
|
|
466
|
+
await this.configuration.closeDatabaseConnections()
|
|
467
|
+
} else {
|
|
468
|
+
await this.configuration.closeBackgroundJobsAdapter()
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
]
|
|
472
|
+
})
|
|
468
473
|
}
|
|
469
474
|
|
|
470
475
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import runJobPayload, { BackgroundJobPerformedFailure } from "./job-runner.js"
|
|
4
|
-
import { closeRunnerConnections, currentConfigurationOrNull } from "./runner-graceful-shutdown.js"
|
|
4
|
+
import { closeRunnerConnections, closeRunnerFrameworkConnections, currentConfigurationOrNull } from "./runner-graceful-shutdown.js"
|
|
5
5
|
import setRunnerProcessTitle from "./runner-process-title.js"
|
|
6
6
|
import PooledRunnerBrokerIdentity from "./pooled-runner-broker-identity.js"
|
|
7
7
|
import { runWithSharedTransactionBrokerConfig } from "../testing/shared-transaction-proxy-driver.js"
|
|
@@ -10,7 +10,8 @@ const BASE_PROCESS_TITLE = "velocious background-jobs-runner"
|
|
|
10
10
|
|
|
11
11
|
setRunnerProcessTitle()
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
/** @type {Promise<void> | undefined} */
|
|
14
|
+
let shutdownPromise
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Closes the runner's connections — releasing any advisory lock a killed-mid-pass
|
|
@@ -19,12 +20,15 @@ let shuttingDown = false
|
|
|
19
20
|
* @param {number} exitCode - Process exit code.
|
|
20
21
|
* @returns {Promise<void>}
|
|
21
22
|
*/
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
shuttingDown = true
|
|
23
|
+
function shutdownRunner(exitCode) {
|
|
24
|
+
if (shutdownPromise) return shutdownPromise
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
shutdownPromise = (async () => {
|
|
27
|
+
await closeRunnerConnections(currentConfigurationOrNull())
|
|
28
|
+
process.exit(exitCode)
|
|
29
|
+
})()
|
|
30
|
+
|
|
31
|
+
return shutdownPromise
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
/**
|
|
@@ -36,7 +40,7 @@ async function shutdownRunner(exitCode) {
|
|
|
36
40
|
*/
|
|
37
41
|
const runningJobIds = new Set()
|
|
38
42
|
const brokerIdentity = new PooledRunnerBrokerIdentity({
|
|
39
|
-
closeConnections: async () => await
|
|
43
|
+
closeConnections: async () => await closeRunnerFrameworkConnections(currentConfigurationOrNull())
|
|
40
44
|
})
|
|
41
45
|
|
|
42
46
|
/**
|
|
@@ -107,7 +111,11 @@ async function runJob(payload, sharedTransactionBroker) {
|
|
|
107
111
|
try {
|
|
108
112
|
const status = await runWithSharedTransactionBrokerConfig(sharedTransactionBroker, async () => {
|
|
109
113
|
return await brokerIdentity.run(sharedTransactionBroker, async () => {
|
|
110
|
-
return await runJobPayload(payload, {
|
|
114
|
+
return await runJobPayload(payload, {
|
|
115
|
+
closeConnections: false,
|
|
116
|
+
manageProcessTitle: false,
|
|
117
|
+
processType: "background-jobs-pooled-runner"
|
|
118
|
+
})
|
|
111
119
|
})
|
|
112
120
|
})
|
|
113
121
|
await sendOutcome({jobId: payload.id, acknowledged: true, status})
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
import timeout from "awaitery/build/timeout.js"
|
|
4
4
|
|
|
5
5
|
import Configuration, {CurrentConfigurationNotSetError} from "../configuration.js"
|
|
6
|
+
import { runShutdownSteps } from "../utils/shutdown-lifecycle.js"
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* The subset of a configuration a runner closes on shutdown. Typed structurally so
|
|
9
10
|
* the shutdown path stays typechecked without a broad cast, and a future signature
|
|
10
11
|
* drift surfaces at the call sites (and in tests) instead of hiding behind `any`.
|
|
11
|
-
* @typedef {{disconnectBeacon: () => Promise<void>, closeDatabaseConnections: () => Promise<void>}} RunnerCloseableConfiguration
|
|
12
|
+
* @typedef {{disconnectBeacon: () => Promise<void>, closeDatabaseConnections: () => Promise<void>, shutdown: () => Promise<void>}} RunnerCloseableConfiguration
|
|
12
13
|
*/
|
|
13
14
|
/** Bounded grace for closing framework connections on shutdown before forcing exit. */
|
|
14
15
|
const SHUTDOWN_CLOSE_TIMEOUT_MS = 5000
|
|
@@ -39,19 +40,34 @@ const SHUTDOWN_CLOSE_TIMEOUT_MS = 5000
|
|
|
39
40
|
export async function closeRunnerConnections(configuration, closeTimeoutMs = SHUTDOWN_CLOSE_TIMEOUT_MS) {
|
|
40
41
|
if (!configuration) return
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
await runShutdownSteps({
|
|
44
|
+
message: "Failed to close background-job runner application and framework resources",
|
|
45
|
+
steps: [
|
|
46
|
+
async () => await timeout({timeout: closeTimeoutMs}, () => configuration.shutdown()),
|
|
47
|
+
async () => await closeRunnerFrameworkConnections(configuration, closeTimeoutMs)
|
|
48
|
+
]
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Closes only a pooled runner's framework connections while its application
|
|
54
|
+
* process lifecycle remains active for later jobs.
|
|
55
|
+
* @param {RunnerCloseableConfiguration | null} configuration - Configuration whose framework connections to close.
|
|
56
|
+
* @param {number} [closeTimeoutMs] - Max time for each framework close.
|
|
57
|
+
* @returns {Promise<void>} - Resolves after framework cleanup.
|
|
58
|
+
*/
|
|
59
|
+
export async function closeRunnerFrameworkConnections(configuration, closeTimeoutMs = SHUTDOWN_CLOSE_TIMEOUT_MS) {
|
|
60
|
+
if (!configuration) return
|
|
61
|
+
|
|
62
|
+
const results = await Promise.allSettled([
|
|
43
63
|
timeout({timeout: closeTimeoutMs}, () => configuration.disconnectBeacon()),
|
|
44
64
|
timeout({timeout: closeTimeoutMs}, () => configuration.closeDatabaseConnections())
|
|
45
65
|
])
|
|
66
|
+
const steps = results.map((result) => async () => {
|
|
67
|
+
if (result.status === "rejected") throw result.reason
|
|
68
|
+
})
|
|
46
69
|
|
|
47
|
-
|
|
48
|
-
const errors = []
|
|
49
|
-
|
|
50
|
-
if (beaconResult.status == "rejected") errors.push(beaconResult.reason)
|
|
51
|
-
if (databaseResult.status == "rejected") errors.push(databaseResult.reason)
|
|
52
|
-
|
|
53
|
-
if (errors.length == 1) throw errors[0]
|
|
54
|
-
if (errors.length > 1) throw new AggregateError(errors, "Failed to close background-job runner connections on shutdown")
|
|
70
|
+
await runShutdownSteps({message: "Failed to close background-job runner framework resources", steps})
|
|
55
71
|
}
|
|
56
72
|
|
|
57
73
|
/**
|
|
@@ -8,7 +8,7 @@ import configurationResolver from "../configuration-resolver.js"
|
|
|
8
8
|
import BackgroundJobsStatusReporter from "./status-reporter.js"
|
|
9
9
|
import { randomUUID } from "crypto"
|
|
10
10
|
import { fileURLToPath } from "node:url"
|
|
11
|
-
import shutdownLifecycle from "../utils/shutdown-lifecycle.js"
|
|
11
|
+
import shutdownLifecycle, { runShutdownSteps } from "../utils/shutdown-lifecycle.js"
|
|
12
12
|
import BackgroundJobRescheduleSignal from "./reschedule-signal.js"
|
|
13
13
|
import performBackgroundJob from "./perform-job.js"
|
|
14
14
|
import { createGenerationWorkerId } from "./generation-identity.js"
|
|
@@ -295,13 +295,19 @@ export default class BackgroundJobsWorker {
|
|
|
295
295
|
try {
|
|
296
296
|
await this._connect({allowReconnect: false})
|
|
297
297
|
} catch (error) {
|
|
298
|
+
let cleanupError
|
|
299
|
+
|
|
298
300
|
try {
|
|
299
301
|
await this.stop()
|
|
300
|
-
} catch (
|
|
302
|
+
} catch (caughtCleanupError) {
|
|
303
|
+
cleanupError = caughtCleanupError
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (cleanupError) {
|
|
301
307
|
throw new AggregateError(
|
|
302
308
|
[error, cleanupError],
|
|
303
309
|
"Background jobs worker startup and cleanup failed",
|
|
304
|
-
{cause:
|
|
310
|
+
{cause: error}
|
|
305
311
|
)
|
|
306
312
|
}
|
|
307
313
|
|
|
@@ -391,11 +397,7 @@ export default class BackgroundJobsWorker {
|
|
|
391
397
|
if (this.jsonSocket) this.jsonSocket.close()
|
|
392
398
|
if (!this.configuration) return
|
|
393
399
|
|
|
394
|
-
|
|
395
|
-
await this.configuration.disconnectBeacon()
|
|
396
|
-
} finally {
|
|
397
|
-
if (this.closeDatabaseConnectionsOnStop) await this.configuration.closeDatabaseConnections()
|
|
398
|
-
}
|
|
400
|
+
await this._closeConfiguration()
|
|
399
401
|
}
|
|
400
402
|
})
|
|
401
403
|
}
|
|
@@ -448,15 +450,34 @@ export default class BackgroundJobsWorker {
|
|
|
448
450
|
if (this.jsonSocket) this.jsonSocket.close()
|
|
449
451
|
if (!this.configuration) return
|
|
450
452
|
|
|
451
|
-
|
|
452
|
-
await this.configuration.disconnectBeacon()
|
|
453
|
-
} finally {
|
|
454
|
-
if (this.closeDatabaseConnectionsOnStop) await this.configuration.closeDatabaseConnections()
|
|
455
|
-
}
|
|
453
|
+
await this._closeConfiguration()
|
|
456
454
|
}
|
|
457
455
|
})
|
|
458
456
|
}
|
|
459
457
|
|
|
458
|
+
/**
|
|
459
|
+
* Closes application resources before framework resources when this worker owns them.
|
|
460
|
+
* @returns {Promise<void>} - Resolves after every owned close succeeds.
|
|
461
|
+
*/
|
|
462
|
+
async _closeConfiguration() {
|
|
463
|
+
const configuration = this.configuration
|
|
464
|
+
|
|
465
|
+
if (!configuration) return
|
|
466
|
+
|
|
467
|
+
await runShutdownSteps({
|
|
468
|
+
message: "Background jobs worker application and framework shutdown failed",
|
|
469
|
+
steps: [
|
|
470
|
+
...(this.closeDatabaseConnectionsOnStop
|
|
471
|
+
? [async () => await configuration.shutdown()]
|
|
472
|
+
: []),
|
|
473
|
+
async () => await configuration.disconnectBeacon(),
|
|
474
|
+
...(this.closeDatabaseConnectionsOnStop
|
|
475
|
+
? [async () => await configuration.closeDatabaseConnections()]
|
|
476
|
+
: [])
|
|
477
|
+
]
|
|
478
|
+
})
|
|
479
|
+
}
|
|
480
|
+
|
|
460
481
|
/**
|
|
461
482
|
* Waits for a set of in-flight job promises to settle, optionally bounded by
|
|
462
483
|
* `timeoutMs`.
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
* @module types
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Immutable identity shared by application initializers for one process lifecycle.
|
|
9
|
+
* @typedef {object} ApplicationProcessContext
|
|
10
|
+
* @property {string} type - Generic process type supplied to `configuration.initialize()`.
|
|
11
|
+
* @property {string} instanceId - Opaque identity unique to this lifecycle.
|
|
12
|
+
*/
|
|
13
|
+
|
|
7
14
|
/**
|
|
8
15
|
* @typedef {(args: {request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default, response: import("./http-server/client/response.js").default}) => Promise<void>} CorsType
|
|
9
16
|
*/
|
|
@@ -133,6 +140,7 @@
|
|
|
133
140
|
* @property {Array<"debug-low-level" | "debug" | "info" | "warn" | "error">} [levels] - Override which log levels are emitted.
|
|
134
141
|
* @property {boolean} [debugLowLevel] - Convenience flag to include very low-level debug logs.
|
|
135
142
|
* @property {boolean} [queryLogging] - Enable/disable database query logging. Defaults to true outside test and false in test.
|
|
143
|
+
* @property {string[]} [sensitiveNames] - Additional case-insensitive sensitive header/parameter names to redact from logging.
|
|
136
144
|
* @property {LoggerConfig[]} [loggers] - Logger instances (converted to outputs when configured).
|
|
137
145
|
* @property {LoggingOutputConfig[]} [outputs] - Explicit logger outputs (overrides console/file defaults when provided).
|
|
138
146
|
*/
|