velocious 1.0.514 → 1.0.516
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 +4 -3
- package/build/background-jobs/cron-expression.js +9 -9
- package/build/background-jobs/forked-runner-child.js +1 -1
- package/build/background-jobs/job-runner.js +21 -15
- package/build/background-jobs/json-socket.js +4 -0
- package/build/background-jobs/main.js +180 -18
- package/build/background-jobs/status-reporter.js +12 -5
- package/build/background-jobs/store.js +132 -50
- package/build/background-jobs/types.js +11 -3
- package/build/background-jobs/worker.js +9 -5
- package/build/beacon/client.js +13 -6
- package/build/configuration.js +54 -29
- package/build/controller.js +9 -3
- package/build/database/drivers/sqlite/base.js +1 -1
- package/build/database/drivers/sqlite/index.js +15 -14
- package/build/database/migrations-ledger.js +16 -16
- package/build/database/pool/async-tracked-multi-connection.js +16 -0
- package/build/database/pool/base.js +26 -0
- package/build/database/query/where-model-class-hash.js +2 -0
- package/build/database/query/with-count.js +1 -1
- package/build/database/record/acts-as-list.js +2 -2
- package/build/database/record/auditing.js +23 -6
- package/build/database/record/index.js +7 -7
- package/build/database/record/validators/uniqueness.js +3 -3
- package/build/database/tenants/data-copier.js +23 -23
- package/build/database/tenants/schema-cloner.js +27 -27
- package/build/environment-handlers/browser.js +0 -1
- package/build/environment-handlers/node/cli/commands/background-jobs-runner.js +2 -1
- package/build/environment-handlers/node/cli/commands/lint/relationships.js +2 -2
- package/build/environment-handlers/node.js +1 -1
- package/build/frontend-model-controller.js +26 -18
- package/build/frontend-model-resource/base-resource.js +4 -4
- package/build/frontend-models/base.js +5 -5
- package/build/frontend-models/query.js +12 -12
- package/build/http-server/client/index.js +61 -15
- package/build/http-server/client/request-runner.js +7 -1
- package/build/http-server/client/response.js +17 -1
- package/build/http-server/client/websocket-session.js +13 -13
- package/build/http-server/server-client.js +96 -0
- package/build/http-server/websocket-channel.js +12 -11
- package/build/http-server/websocket-connection.js +12 -11
- package/build/http-server/websocket-events-host.js +1 -1
- package/build/http-server/worker-handler/in-process.js +48 -9
- package/build/http-server/worker-handler/index.js +67 -7
- package/build/http-server/worker-handler/worker-thread.js +68 -0
- package/build/src/background-jobs/cron-expression.d.ts +2 -2
- package/build/src/background-jobs/cron-expression.js +10 -10
- package/build/src/background-jobs/forked-runner-child.js +2 -2
- package/build/src/background-jobs/job-runner.d.ts +5 -1
- package/build/src/background-jobs/job-runner.d.ts.map +1 -1
- package/build/src/background-jobs/job-runner.js +22 -16
- package/build/src/background-jobs/json-socket.d.ts +4 -0
- package/build/src/background-jobs/json-socket.d.ts.map +1 -1
- package/build/src/background-jobs/json-socket.js +5 -1
- package/build/src/background-jobs/main.d.ts +62 -10
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +171 -19
- package/build/src/background-jobs/status-reporter.d.ts +6 -2
- package/build/src/background-jobs/status-reporter.d.ts.map +1 -1
- package/build/src/background-jobs/status-reporter.js +13 -6
- package/build/src/background-jobs/store.d.ts +48 -25
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +123 -50
- package/build/src/background-jobs/types.d.ts +36 -3
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +12 -4
- package/build/src/background-jobs/worker.d.ts +7 -5
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +10 -6
- package/build/src/beacon/client.d.ts +6 -3
- package/build/src/beacon/client.d.ts.map +1 -1
- package/build/src/beacon/client.js +14 -7
- package/build/src/configuration-types.d.ts +1 -1
- package/build/src/configuration.d.ts +42 -29
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +52 -30
- package/build/src/controller.d.ts +3 -0
- package/build/src/controller.d.ts.map +1 -1
- package/build/src/controller.js +9 -4
- package/build/src/database/drivers/sqlite/base.js +2 -2
- package/build/src/database/drivers/sqlite/index.d.ts +7 -6
- package/build/src/database/drivers/sqlite/index.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/index.js +16 -15
- package/build/src/database/migrations-ledger.d.ts +16 -16
- package/build/src/database/migrations-ledger.js +17 -17
- package/build/src/database/pool/async-tracked-multi-connection.d.ts +0 -11
- package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
- package/build/src/database/pool/async-tracked-multi-connection.js +16 -1
- package/build/src/database/pool/base.d.ts +21 -0
- package/build/src/database/pool/base.d.ts.map +1 -1
- package/build/src/database/pool/base.js +24 -1
- package/build/src/database/query/where-model-class-hash.d.ts.map +1 -1
- package/build/src/database/query/where-model-class-hash.js +3 -1
- package/build/src/database/query/with-count.js +2 -2
- package/build/src/database/record/acts-as-list.js +3 -3
- package/build/src/database/record/auditing.d.ts +1 -1
- package/build/src/database/record/auditing.d.ts.map +1 -1
- package/build/src/database/record/auditing.js +24 -7
- package/build/src/database/record/index.d.ts +7 -7
- package/build/src/database/record/index.js +8 -8
- package/build/src/database/record/validators/uniqueness.d.ts +3 -3
- package/build/src/database/record/validators/uniqueness.js +4 -4
- package/build/src/database/tenants/data-copier.d.ts +18 -18
- package/build/src/database/tenants/data-copier.js +24 -24
- package/build/src/database/tenants/schema-cloner.d.ts +27 -27
- package/build/src/database/tenants/schema-cloner.js +28 -28
- package/build/src/environment-handlers/browser.d.ts +9 -0
- package/build/src/environment-handlers/browser.d.ts.map +1 -1
- package/build/src/environment-handlers/browser.js +1 -2
- package/build/src/environment-handlers/node/cli/commands/background-jobs-runner.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/background-jobs-runner.js +3 -2
- package/build/src/environment-handlers/node/cli/commands/lint/relationships.js +3 -3
- package/build/src/environment-handlers/node.js +2 -2
- package/build/src/frontend-model-controller.d.ts +9 -8
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +26 -19
- package/build/src/frontend-model-resource/base-resource.js +5 -5
- package/build/src/frontend-models/base.d.ts +4 -4
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +6 -6
- package/build/src/frontend-models/query.d.ts +7 -7
- package/build/src/frontend-models/query.js +13 -13
- package/build/src/http-server/client/index.d.ts +23 -1
- package/build/src/http-server/client/index.d.ts.map +1 -1
- package/build/src/http-server/client/index.js +55 -17
- package/build/src/http-server/client/request-runner.d.ts.map +1 -1
- package/build/src/http-server/client/request-runner.js +8 -2
- package/build/src/http-server/client/response.d.ts +11 -1
- package/build/src/http-server/client/response.d.ts.map +1 -1
- package/build/src/http-server/client/response.js +16 -2
- package/build/src/http-server/client/websocket-session.d.ts +13 -13
- package/build/src/http-server/client/websocket-session.js +14 -14
- package/build/src/http-server/server-client.d.ts +13 -0
- package/build/src/http-server/server-client.d.ts.map +1 -1
- package/build/src/http-server/server-client.js +94 -1
- package/build/src/http-server/websocket-channel.d.ts +12 -11
- package/build/src/http-server/websocket-channel.d.ts.map +1 -1
- package/build/src/http-server/websocket-channel.js +13 -12
- package/build/src/http-server/websocket-connection.d.ts +12 -11
- package/build/src/http-server/websocket-connection.d.ts.map +1 -1
- package/build/src/http-server/websocket-connection.js +13 -12
- package/build/src/http-server/websocket-events-host.d.ts +1 -1
- package/build/src/http-server/websocket-events-host.js +2 -2
- package/build/src/http-server/worker-handler/in-process.d.ts +2 -0
- package/build/src/http-server/worker-handler/in-process.d.ts.map +1 -1
- package/build/src/http-server/worker-handler/in-process.js +42 -11
- package/build/src/http-server/worker-handler/index.d.ts +21 -0
- package/build/src/http-server/worker-handler/index.d.ts.map +1 -1
- package/build/src/http-server/worker-handler/index.js +61 -7
- package/build/src/http-server/worker-handler/worker-thread.d.ts +30 -0
- package/build/src/http-server/worker-handler/worker-thread.d.ts.map +1 -1
- package/build/src/http-server/worker-handler/worker-thread.js +61 -1
- package/build/src/sync/device-identity.d.ts +2 -2
- package/build/src/sync/device-identity.d.ts.map +1 -1
- package/build/src/sync/device-identity.js +4 -4
- package/build/src/sync/local-mutation-log.d.ts +0 -1
- package/build/src/sync/local-mutation-log.d.ts.map +1 -1
- package/build/src/sync/local-mutation-log.js +1 -2
- package/build/src/sync/query-scope.d.ts +5 -0
- package/build/src/sync/query-scope.d.ts.map +1 -1
- package/build/src/sync/query-scope.js +7 -2
- package/build/src/sync/sync-client-types.d.ts +2 -2
- package/build/src/sync/sync-client-types.d.ts.map +1 -1
- package/build/src/sync/sync-client-types.js +1 -1
- package/build/src/sync/sync-client.d.ts +17 -2
- package/build/src/sync/sync-client.d.ts.map +1 -1
- package/build/src/sync/sync-client.js +28 -14
- package/build/src/sync/sync-realtime-bridge.d.ts +6 -2
- package/build/src/sync/sync-realtime-bridge.d.ts.map +1 -1
- package/build/src/sync/sync-realtime-bridge.js +14 -3
- package/build/src/sync/sync-resource-base.d.ts +14 -3
- package/build/src/sync/sync-resource-base.d.ts.map +1 -1
- package/build/src/sync/sync-resource-base.js +20 -4
- package/build/src/sync/sync-scope-store.d.ts +3 -3
- package/build/src/sync/sync-scope-store.d.ts.map +1 -1
- package/build/src/sync/sync-scope-store.js +7 -5
- package/build/src/sync/sync-websocket-channel.d.ts +10 -0
- package/build/src/sync/sync-websocket-channel.d.ts.map +1 -1
- package/build/src/sync/sync-websocket-channel.js +20 -3
- package/build/src/tenants/tenant-iterator.d.ts +8 -8
- package/build/src/tenants/tenant-iterator.js +9 -9
- package/build/src/tenants/tenant.d.ts +6 -6
- package/build/src/tenants/tenant.js +7 -7
- package/build/src/testing/test-runner.d.ts +16 -0
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +79 -19
- package/build/sync/device-identity.js +3 -3
- package/build/sync/local-mutation-log.js +0 -1
- package/build/sync/query-scope.js +6 -1
- package/build/sync/sync-client-types.js +1 -1
- package/build/sync/sync-client.js +28 -15
- package/build/sync/sync-realtime-bridge.js +13 -2
- package/build/sync/sync-resource-base.js +20 -3
- package/build/sync/sync-scope-store.js +6 -4
- package/build/sync/sync-websocket-channel.js +18 -2
- package/build/tenants/tenant-iterator.js +8 -8
- package/build/tenants/tenant.js +6 -6
- package/build/testing/test-runner.js +86 -20
- package/package.json +2 -2
- package/src/background-jobs/cron-expression.js +9 -9
- package/src/background-jobs/forked-runner-child.js +1 -1
- package/src/background-jobs/job-runner.js +21 -15
- package/src/background-jobs/json-socket.js +4 -0
- package/src/background-jobs/main.js +180 -18
- package/src/background-jobs/status-reporter.js +12 -5
- package/src/background-jobs/store.js +132 -50
- package/src/background-jobs/types.js +11 -3
- package/src/background-jobs/worker.js +9 -5
- package/src/beacon/client.js +13 -6
- package/src/configuration.js +54 -29
- package/src/controller.js +9 -3
- package/src/database/drivers/sqlite/base.js +1 -1
- package/src/database/drivers/sqlite/index.js +15 -14
- package/src/database/migrations-ledger.js +16 -16
- package/src/database/pool/async-tracked-multi-connection.js +16 -0
- package/src/database/pool/base.js +26 -0
- package/src/database/query/where-model-class-hash.js +2 -0
- package/src/database/query/with-count.js +1 -1
- package/src/database/record/acts-as-list.js +2 -2
- package/src/database/record/auditing.js +23 -6
- package/src/database/record/index.js +7 -7
- package/src/database/record/validators/uniqueness.js +3 -3
- package/src/database/tenants/data-copier.js +23 -23
- package/src/database/tenants/schema-cloner.js +27 -27
- package/src/environment-handlers/browser.js +0 -1
- package/src/environment-handlers/node/cli/commands/background-jobs-runner.js +2 -1
- package/src/environment-handlers/node/cli/commands/lint/relationships.js +2 -2
- package/src/environment-handlers/node.js +1 -1
- package/src/frontend-model-controller.js +26 -18
- package/src/frontend-model-resource/base-resource.js +4 -4
- package/src/frontend-models/base.js +5 -5
- package/src/frontend-models/query.js +12 -12
- package/src/http-server/client/index.js +61 -15
- package/src/http-server/client/request-runner.js +7 -1
- package/src/http-server/client/response.js +17 -1
- package/src/http-server/client/websocket-session.js +13 -13
- package/src/http-server/server-client.js +96 -0
- package/src/http-server/websocket-channel.js +12 -11
- package/src/http-server/websocket-connection.js +12 -11
- package/src/http-server/websocket-events-host.js +1 -1
- package/src/http-server/worker-handler/in-process.js +48 -9
- package/src/http-server/worker-handler/index.js +67 -7
- package/src/http-server/worker-handler/worker-thread.js +68 -0
- package/src/sync/device-identity.js +3 -3
- package/src/sync/local-mutation-log.js +0 -1
- package/src/sync/query-scope.js +6 -1
- package/src/sync/sync-client-types.js +1 -1
- package/src/sync/sync-client.js +28 -15
- package/src/sync/sync-realtime-bridge.js +13 -2
- package/src/sync/sync-resource-base.js +20 -3
- package/src/sync/sync-scope-store.js +6 -4
- package/src/sync/sync-websocket-channel.js +18 -2
- package/src/tenants/tenant-iterator.js +8 -8
- package/src/tenants/tenant.js +6 -6
- package/src/testing/test-runner.js +86 -20
|
@@ -50,9 +50,11 @@ async function connectBeacon(configuration) {
|
|
|
50
50
|
/**
|
|
51
51
|
* Runs run job payload.
|
|
52
52
|
* @param {import("./types.js").BackgroundJobPayload} payload - Payload.
|
|
53
|
+
* @param {object} [options] - Runner options.
|
|
54
|
+
* @param {boolean} [options.closeConnections] - Whether to gracefully close framework connections after the job.
|
|
53
55
|
* @returns {Promise<void>} - Resolves when complete.
|
|
54
56
|
*/
|
|
55
|
-
export default async function runJobPayload(payload) {
|
|
57
|
+
export default async function runJobPayload(payload, {closeConnections = true} = {}) {
|
|
56
58
|
const configuration = await configurationResolver()
|
|
57
59
|
configuration.setCurrent()
|
|
58
60
|
await configuration.initialize({type: "background-jobs-runner"})
|
|
@@ -73,22 +75,13 @@ export default async function runJobPayload(payload) {
|
|
|
73
75
|
await configuration.withConnections({name: `Background job runner: ${payload.jobName}`}, async () => {
|
|
74
76
|
await perform.apply(jobInstance, payload.args || [])
|
|
75
77
|
})
|
|
76
|
-
|
|
77
|
-
if (payload.id) {
|
|
78
|
-
await reporter.reportWithRetry({
|
|
79
|
-
jobId: payload.id,
|
|
80
|
-
status: "completed",
|
|
81
|
-
workerId: payload.workerId,
|
|
82
|
-
handedOffAtMs: payload.handedOffAtMs,
|
|
83
|
-
maxDurationMs: 30000
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
78
|
} catch (error) {
|
|
87
79
|
if (payload.id) {
|
|
88
80
|
await reporter.reportWithRetry({
|
|
89
81
|
jobId: payload.id,
|
|
90
82
|
status: "failed",
|
|
91
83
|
error,
|
|
84
|
+
handoffId: payload.handoffId,
|
|
92
85
|
workerId: payload.workerId,
|
|
93
86
|
handedOffAtMs: payload.handedOffAtMs,
|
|
94
87
|
maxDurationMs: 30000
|
|
@@ -97,11 +90,24 @@ export default async function runJobPayload(payload) {
|
|
|
97
90
|
|
|
98
91
|
throw error
|
|
99
92
|
}
|
|
93
|
+
|
|
94
|
+
if (payload.id) {
|
|
95
|
+
await reporter.reportWithRetry({
|
|
96
|
+
jobId: payload.id,
|
|
97
|
+
status: "completed",
|
|
98
|
+
handoffId: payload.handoffId,
|
|
99
|
+
workerId: payload.workerId,
|
|
100
|
+
handedOffAtMs: payload.handedOffAtMs,
|
|
101
|
+
maxDurationMs: 30000
|
|
102
|
+
})
|
|
103
|
+
}
|
|
100
104
|
} finally {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
if (closeConnections) {
|
|
106
|
+
try {
|
|
107
|
+
await configuration.disconnectBeacon()
|
|
108
|
+
} finally {
|
|
109
|
+
await configuration.closeDatabaseConnections()
|
|
110
|
+
}
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
}
|
|
@@ -14,6 +14,10 @@ export default class JsonSocket extends EventEmitter {
|
|
|
14
14
|
* Narrows the runtime value to the documented type.
|
|
15
15
|
* @type {string | undefined} */
|
|
16
16
|
this.workerId = undefined
|
|
17
|
+
/**
|
|
18
|
+
* Narrows the runtime value to the documented type.
|
|
19
|
+
* @type {boolean} */
|
|
20
|
+
this.supportsHandoffIdReporting = false
|
|
17
21
|
/**
|
|
18
22
|
* Narrows the runtime value to the documented type.
|
|
19
23
|
* @type {boolean} */
|
|
@@ -64,25 +64,29 @@ export default class BackgroundJobsMain {
|
|
|
64
64
|
* Narrows the runtime value to the documented type.
|
|
65
65
|
* @type {Set<JsonSocket>} */
|
|
66
66
|
this.readyWorkers = new Set()
|
|
67
|
+
/**
|
|
68
|
+
* Active durable handoffs keyed by the exact worker socket that received them.
|
|
69
|
+
* @type {Map<JsonSocket, Map<string, string>>} */
|
|
70
|
+
this.workerHandoffs = new Map()
|
|
67
71
|
/**
|
|
68
72
|
* Narrows the runtime value to the documented type.
|
|
69
73
|
* @type {net.Server | undefined} */
|
|
70
74
|
this.server = undefined
|
|
71
75
|
/**
|
|
72
76
|
* Narrows the runtime value to the documented type.
|
|
73
|
-
* @type {
|
|
77
|
+
* @type {ReturnType<typeof setTimeout> | undefined} */
|
|
74
78
|
this._pollTimer = undefined
|
|
75
79
|
/**
|
|
76
80
|
* Narrows the runtime value to the documented type.
|
|
77
|
-
* @type {
|
|
81
|
+
* @type {ReturnType<typeof setTimeout> | undefined} */
|
|
78
82
|
this._scheduledTimer = undefined
|
|
79
83
|
/**
|
|
80
84
|
* Narrows the runtime value to the documented type.
|
|
81
|
-
* @type {
|
|
85
|
+
* @type {ReturnType<typeof setTimeout> | undefined} */
|
|
82
86
|
this._errorRetryTimer = undefined
|
|
83
87
|
/**
|
|
84
88
|
* Narrows the runtime value to the documented type.
|
|
85
|
-
* @type {
|
|
89
|
+
* @type {ReturnType<typeof setTimeout> | undefined} */
|
|
86
90
|
this._orphanTimer = undefined
|
|
87
91
|
/**
|
|
88
92
|
* Narrows the runtime value to the documented type.
|
|
@@ -270,7 +274,7 @@ export default class BackgroundJobsMain {
|
|
|
270
274
|
_setupDispatchTriggers() {
|
|
271
275
|
if (this.dispatchStrategy === "polling") {
|
|
272
276
|
this._pollTimer = setInterval(() => {
|
|
273
|
-
void this.
|
|
277
|
+
void this._retryAfterError()
|
|
274
278
|
}, this.pollIntervalMs)
|
|
275
279
|
return
|
|
276
280
|
}
|
|
@@ -330,11 +334,12 @@ export default class BackgroundJobsMain {
|
|
|
330
334
|
* @type {import("./types.js").BackgroundJobSocketRole | null} */
|
|
331
335
|
let role = null
|
|
332
336
|
|
|
337
|
+
let cleanedUp = false
|
|
333
338
|
const cleanup = () => {
|
|
334
|
-
if (
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
339
|
+
if (cleanedUp) return
|
|
340
|
+
cleanedUp = true
|
|
341
|
+
|
|
342
|
+
if (role === "worker") void this._handleWorkerSocketClosed(jsonSocket)
|
|
338
343
|
}
|
|
339
344
|
|
|
340
345
|
jsonSocket.on("close", cleanup)
|
|
@@ -377,7 +382,9 @@ export default class BackgroundJobsMain {
|
|
|
377
382
|
|
|
378
383
|
if (message.role === "worker") {
|
|
379
384
|
jsonSocket.workerId = message.workerId
|
|
385
|
+
jsonSocket.supportsHandoffIdReporting = message.supportsHandoffIdReporting === true
|
|
380
386
|
this.workers.add(jsonSocket)
|
|
387
|
+
this.workerHandoffs.set(jsonSocket, new Map())
|
|
381
388
|
}
|
|
382
389
|
|
|
383
390
|
return message.role
|
|
@@ -446,7 +453,11 @@ export default class BackgroundJobsMain {
|
|
|
446
453
|
jsonSocket.acceptsSpawnedJobs = message.acceptsSpawned !== false && message.acceptsForked !== false
|
|
447
454
|
jsonSocket.acceptsForkedJobs = message.acceptsForked !== false
|
|
448
455
|
jsonSocket.acceptsInlineJobs = message.acceptsInline !== false
|
|
449
|
-
|
|
456
|
+
if (jsonSocket.supportsHandoffIdReporting) {
|
|
457
|
+
this.readyWorkers.add(jsonSocket)
|
|
458
|
+
} else {
|
|
459
|
+
this.readyWorkers.delete(jsonSocket)
|
|
460
|
+
}
|
|
450
461
|
void this._drain()
|
|
451
462
|
}
|
|
452
463
|
|
|
@@ -463,6 +474,98 @@ export default class BackgroundJobsMain {
|
|
|
463
474
|
this.readyWorkers.delete(jsonSocket)
|
|
464
475
|
}
|
|
465
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Removes a lost worker socket and releases only leases dispatched through it.
|
|
479
|
+
* @param {JsonSocket} worker - Disconnected worker socket.
|
|
480
|
+
* @returns {Promise<void>} - Resolves after its active leases are released.
|
|
481
|
+
*/
|
|
482
|
+
async _handleWorkerSocketClosed(worker) {
|
|
483
|
+
this.workers.delete(worker)
|
|
484
|
+
this.readyWorkers.delete(worker)
|
|
485
|
+
|
|
486
|
+
if (this._stopped) {
|
|
487
|
+
this.workerHandoffs.delete(worker)
|
|
488
|
+
return
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
await this._releaseWorkerHandoffs(worker)
|
|
493
|
+
} catch (error) {
|
|
494
|
+
this._reportHandoffReleaseError(error)
|
|
495
|
+
this._scheduleErrorRetry()
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Releases all leases still owned by one exact worker socket.
|
|
501
|
+
* @param {JsonSocket} worker - Worker socket.
|
|
502
|
+
* @returns {Promise<void>} - Resolves after fenced releases and dispatch wake-up.
|
|
503
|
+
*/
|
|
504
|
+
async _releaseWorkerHandoffs(worker) {
|
|
505
|
+
const handoffs = this.workerHandoffs.get(worker)
|
|
506
|
+
|
|
507
|
+
if (!handoffs || handoffs.size === 0) {
|
|
508
|
+
this.workerHandoffs.delete(worker)
|
|
509
|
+
return
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
for (const [jobId, handoffId] of handoffs) {
|
|
513
|
+
await this._releaseHandoff({handoffId, jobId, worker})
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
this.workerHandoffs.delete(worker)
|
|
517
|
+
this._notifyEnqueued()
|
|
518
|
+
await this._drain()
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Runs one idempotent conditional lease release.
|
|
523
|
+
* @param {object} args - Options.
|
|
524
|
+
* @param {string} args.handoffId - Handoff lease id.
|
|
525
|
+
* @param {string} args.jobId - Job id.
|
|
526
|
+
* @param {JsonSocket} args.worker - Socket that received the lease.
|
|
527
|
+
* @returns {Promise<void>} - Resolves after the fenced transition.
|
|
528
|
+
*/
|
|
529
|
+
async _releaseHandoff({handoffId, jobId, worker}) {
|
|
530
|
+
await this.store.markReturnedToQueue({handoffId, jobId})
|
|
531
|
+
|
|
532
|
+
const handoffs = this.workerHandoffs.get(worker)
|
|
533
|
+
|
|
534
|
+
if (handoffs?.get(jobId) === handoffId) handoffs.delete(jobId)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Forgets a successfully reported lease without relying on worker ids.
|
|
539
|
+
* @param {object} args - Options.
|
|
540
|
+
* @param {string} args.handoffId - Handoff lease id.
|
|
541
|
+
* @param {string} args.jobId - Job id.
|
|
542
|
+
* @returns {void}
|
|
543
|
+
*/
|
|
544
|
+
_forgetHandoff({handoffId, jobId}) {
|
|
545
|
+
for (const [worker, handoffs] of this.workerHandoffs) {
|
|
546
|
+
if (handoffs.get(jobId) !== handoffId) continue
|
|
547
|
+
|
|
548
|
+
handoffs.delete(jobId)
|
|
549
|
+
if (handoffs.size === 0 && !this.workers.has(worker)) this.workerHandoffs.delete(worker)
|
|
550
|
+
return
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Reports an unexpected lease-release failure on framework error channels.
|
|
556
|
+
* @param {?} error - Release failure.
|
|
557
|
+
* @returns {void}
|
|
558
|
+
*/
|
|
559
|
+
_reportHandoffReleaseError(error) {
|
|
560
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error))
|
|
561
|
+
const payload = {context: {stage: "background-job-handoff-release"}, error: normalizedError}
|
|
562
|
+
const errorEvents = this.configuration.getErrorEvents()
|
|
563
|
+
|
|
564
|
+
this.logger.error(() => ["Failed to release disconnected worker handoffs:", normalizedError])
|
|
565
|
+
errorEvents.emit("framework-error", payload)
|
|
566
|
+
errorEvents.emit("all-error", {...payload, errorType: "framework-error"})
|
|
567
|
+
}
|
|
568
|
+
|
|
466
569
|
/**
|
|
467
570
|
* Runs handle enqueue.
|
|
468
571
|
* @param {object} args - Options.
|
|
@@ -496,11 +599,15 @@ export default class BackgroundJobsMain {
|
|
|
496
599
|
*/
|
|
497
600
|
async _handleJobComplete({jsonSocket, message}) {
|
|
498
601
|
try {
|
|
499
|
-
await this.store.markCompleted({
|
|
602
|
+
const accepted = await this.store.markCompleted({
|
|
500
603
|
jobId: message.jobId,
|
|
604
|
+
handoffId: message.handoffId,
|
|
501
605
|
workerId: message.workerId,
|
|
502
606
|
handedOffAtMs: message.handedOffAtMs
|
|
503
607
|
})
|
|
608
|
+
if (accepted && message.handoffId) {
|
|
609
|
+
this._forgetHandoff({handoffId: message.handoffId, jobId: message.jobId})
|
|
610
|
+
}
|
|
504
611
|
jsonSocket.send({type: "job-updated", jobId: message.jobId})
|
|
505
612
|
} catch (error) {
|
|
506
613
|
this.logger.error(() => ["Failed to update job completion:", error])
|
|
@@ -520,13 +627,18 @@ export default class BackgroundJobsMain {
|
|
|
520
627
|
const failedJob = await this.store.markFailed({
|
|
521
628
|
jobId: message.jobId,
|
|
522
629
|
error: message.error,
|
|
630
|
+
handoffId: message.handoffId,
|
|
523
631
|
workerId: message.workerId,
|
|
524
632
|
handedOffAtMs: message.handedOffAtMs
|
|
525
633
|
})
|
|
526
634
|
|
|
527
635
|
if (failedJob) {
|
|
636
|
+
if (message.handoffId) {
|
|
637
|
+
this._forgetHandoff({handoffId: message.handoffId, jobId: message.jobId})
|
|
638
|
+
}
|
|
528
639
|
this._emitBackgroundJobFailed({
|
|
529
640
|
error: message.error,
|
|
641
|
+
handoffId: message.handoffId,
|
|
530
642
|
handedOffAtMs: message.handedOffAtMs,
|
|
531
643
|
job: failedJob,
|
|
532
644
|
workerId: message.workerId
|
|
@@ -546,14 +658,15 @@ export default class BackgroundJobsMain {
|
|
|
546
658
|
|
|
547
659
|
/**
|
|
548
660
|
* Runs emit background job failed.
|
|
549
|
-
* @param {{error: ?, handedOffAtMs?: number, job: import("./types.js").BackgroundJobRow, workerId?: string}} args - Failure event data.
|
|
661
|
+
* @param {{error: ?, handoffId?: string, handedOffAtMs?: number, job: import("./types.js").BackgroundJobRow, workerId?: string}} args - Failure event data.
|
|
550
662
|
* @returns {void}
|
|
551
663
|
*/
|
|
552
|
-
_emitBackgroundJobFailed({error, handedOffAtMs, job, workerId}) {
|
|
664
|
+
_emitBackgroundJobFailed({error, handoffId, handedOffAtMs, job, workerId}) {
|
|
553
665
|
const normalizedError = this._normalizeFailureError(error)
|
|
554
666
|
const payload = {
|
|
555
667
|
context: {
|
|
556
668
|
attempts: job.attempts,
|
|
669
|
+
handoffId,
|
|
557
670
|
handedOffAtMs,
|
|
558
671
|
jobArgs: job.args,
|
|
559
672
|
jobId: job.id,
|
|
@@ -699,6 +812,10 @@ export default class BackgroundJobsMain {
|
|
|
699
812
|
* Runs clear error retry timer.
|
|
700
813
|
* @returns {void} */
|
|
701
814
|
_clearErrorRetryTimer() {
|
|
815
|
+
for (const worker of this.workerHandoffs.keys()) {
|
|
816
|
+
if (!this.workers.has(worker)) return
|
|
817
|
+
}
|
|
818
|
+
|
|
702
819
|
if (this._errorRetryTimer) {
|
|
703
820
|
clearTimeout(this._errorRetryTimer)
|
|
704
821
|
this._errorRetryTimer = undefined
|
|
@@ -771,10 +888,30 @@ export default class BackgroundJobsMain {
|
|
|
771
888
|
|
|
772
889
|
this._errorRetryTimer = setTimeout(() => {
|
|
773
890
|
this._errorRetryTimer = undefined
|
|
774
|
-
void this.
|
|
891
|
+
void this._retryAfterError()
|
|
775
892
|
}, this.pollIntervalMs)
|
|
776
893
|
}
|
|
777
894
|
|
|
895
|
+
/**
|
|
896
|
+
* Retries failed disconnected-socket releases before draining queued work.
|
|
897
|
+
* @returns {Promise<void>} - Resolves after retry work.
|
|
898
|
+
*/
|
|
899
|
+
async _retryAfterError() {
|
|
900
|
+
if (this._stopped) return
|
|
901
|
+
|
|
902
|
+
try {
|
|
903
|
+
for (const worker of this.workerHandoffs.keys()) {
|
|
904
|
+
if (!this.workers.has(worker)) await this._releaseWorkerHandoffs(worker)
|
|
905
|
+
}
|
|
906
|
+
} catch (error) {
|
|
907
|
+
this._reportHandoffReleaseError(error)
|
|
908
|
+
this._scheduleErrorRetry()
|
|
909
|
+
return
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
await this._drain()
|
|
913
|
+
}
|
|
914
|
+
|
|
778
915
|
/**
|
|
779
916
|
* Inner drain loop: pulls eligible queued jobs and hands them off to
|
|
780
917
|
* ready workers until one of them runs out.
|
|
@@ -790,7 +927,23 @@ export default class BackgroundJobsMain {
|
|
|
790
927
|
|
|
791
928
|
this.readyWorkers.delete(worker)
|
|
792
929
|
|
|
793
|
-
const
|
|
930
|
+
const handoff = await this.store.markHandedOff({jobId: job.id, workerId: worker.workerId})
|
|
931
|
+
|
|
932
|
+
if (!handoff) {
|
|
933
|
+
if (this.workers.has(worker)) this.readyWorkers.add(worker)
|
|
934
|
+
continue
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const handoffs = this.workerHandoffs.get(worker)
|
|
938
|
+
|
|
939
|
+
if (!handoffs || !this.workers.has(worker)) {
|
|
940
|
+
await this.store.markReturnedToQueue({handoffId: handoff.handoffId, jobId: job.id})
|
|
941
|
+
this._notifyEnqueued()
|
|
942
|
+
await this._drain()
|
|
943
|
+
continue
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
handoffs.set(job.id, handoff.handoffId)
|
|
794
947
|
|
|
795
948
|
try {
|
|
796
949
|
worker.send({
|
|
@@ -799,8 +952,9 @@ export default class BackgroundJobsMain {
|
|
|
799
952
|
id: job.id,
|
|
800
953
|
jobName: job.jobName,
|
|
801
954
|
args: job.args,
|
|
955
|
+
handoffId: handoff.handoffId,
|
|
802
956
|
workerId: worker.workerId,
|
|
803
|
-
handedOffAtMs,
|
|
957
|
+
handedOffAtMs: handoff.handedOffAtMs,
|
|
804
958
|
options: {
|
|
805
959
|
executionMode: job.executionMode,
|
|
806
960
|
forked: job.forked
|
|
@@ -809,8 +963,12 @@ export default class BackgroundJobsMain {
|
|
|
809
963
|
})
|
|
810
964
|
} catch (error) {
|
|
811
965
|
this.logger.warn(() => ["Failed to send job to worker, re-queueing:", error])
|
|
812
|
-
|
|
813
|
-
|
|
966
|
+
try {
|
|
967
|
+
worker.close()
|
|
968
|
+
} catch (closeError) {
|
|
969
|
+
this.logger.warn(() => ["Failed to close worker after job send failure:", closeError])
|
|
970
|
+
}
|
|
971
|
+
await this._handleWorkerSocketClosed(worker)
|
|
814
972
|
}
|
|
815
973
|
}
|
|
816
974
|
}
|
|
@@ -850,6 +1008,8 @@ export default class BackgroundJobsMain {
|
|
|
850
1008
|
* @returns {void}
|
|
851
1009
|
*/
|
|
852
1010
|
_addAcceptedExecutionModes({executionModes, worker}) {
|
|
1011
|
+
if (!worker.supportsHandoffIdReporting) return
|
|
1012
|
+
|
|
853
1013
|
for (const capability of WORKER_EXECUTION_MODE_CAPABILITIES) {
|
|
854
1014
|
if (capability.accepts(worker)) executionModes.add(capability.executionMode)
|
|
855
1015
|
}
|
|
@@ -874,6 +1034,8 @@ export default class BackgroundJobsMain {
|
|
|
874
1034
|
* @returns {boolean} - Whether the worker accepts the job mode.
|
|
875
1035
|
*/
|
|
876
1036
|
_workerAcceptsJob({job, worker}) {
|
|
1037
|
+
if (!worker.supportsHandoffIdReporting) return false
|
|
1038
|
+
|
|
877
1039
|
const capability = WORKER_EXECUTION_MODE_CAPABILITIES_BY_MODE.get(job.executionMode)
|
|
878
1040
|
|
|
879
1041
|
if (!capability) return false
|
|
@@ -6,6 +6,8 @@ import Logger from "../logger.js"
|
|
|
6
6
|
import normalizeBackgroundJobError from "./normalize-error.js"
|
|
7
7
|
import BackgroundJobsSocketRequest from "./socket-request.js"
|
|
8
8
|
|
|
9
|
+
class BackgroundJobUpdateError extends Error {}
|
|
10
|
+
|
|
9
11
|
export default class BackgroundJobsStatusReporter {
|
|
10
12
|
/**
|
|
11
13
|
* Runs constructor.
|
|
@@ -27,11 +29,12 @@ export default class BackgroundJobsStatusReporter {
|
|
|
27
29
|
* @param {string} args.jobId - Job id.
|
|
28
30
|
* @param {"completed" | "failed"} args.status - Status.
|
|
29
31
|
* @param {?} [args.error] - Error.
|
|
32
|
+
* @param {string} [args.handoffId] - Handoff lease id.
|
|
30
33
|
* @param {number} [args.handedOffAtMs] - Handed off timestamp.
|
|
31
34
|
* @param {string} [args.workerId] - Worker id.
|
|
32
35
|
* @returns {Promise<void>} - Resolves when reported.
|
|
33
36
|
*/
|
|
34
|
-
async report({jobId, status, error, handedOffAtMs, workerId}) {
|
|
37
|
+
async report({jobId, status, error, handoffId, handedOffAtMs, workerId}) {
|
|
35
38
|
const config = this.configuration.getBackgroundJobsConfig()
|
|
36
39
|
const host = this.host || config.host
|
|
37
40
|
const port = typeof this.port === "number" ? this.port : config.port
|
|
@@ -44,6 +47,7 @@ export default class BackgroundJobsStatusReporter {
|
|
|
44
47
|
jsonSocket.send({
|
|
45
48
|
type: status === "completed" ? "job-complete" : "job-failed",
|
|
46
49
|
jobId,
|
|
50
|
+
handoffId,
|
|
47
51
|
workerId,
|
|
48
52
|
handedOffAtMs,
|
|
49
53
|
error: error ? normalizeBackgroundJobError(error) : undefined
|
|
@@ -56,7 +60,7 @@ export default class BackgroundJobsStatusReporter {
|
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
if (message?.type === "job-update-error" && message.jobId === jobId) {
|
|
59
|
-
reject(new
|
|
63
|
+
reject(new BackgroundJobUpdateError(message.error || "Job update failed"))
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
})
|
|
@@ -69,20 +73,23 @@ export default class BackgroundJobsStatusReporter {
|
|
|
69
73
|
* @param {string} args.jobId - Job id.
|
|
70
74
|
* @param {"completed" | "failed"} args.status - Status.
|
|
71
75
|
* @param {?} [args.error] - Error.
|
|
76
|
+
* @param {string} [args.handoffId] - Handoff lease id.
|
|
72
77
|
* @param {number} [args.handedOffAtMs] - Handed off timestamp.
|
|
73
78
|
* @param {string} [args.workerId] - Worker id.
|
|
74
79
|
* @param {number} [args.maxDurationMs] - Max duration for retries.
|
|
75
80
|
* @returns {Promise<void>} - Resolves when reported.
|
|
76
81
|
*/
|
|
77
|
-
async reportWithRetry({jobId, status, error, handedOffAtMs, workerId, maxDurationMs}) {
|
|
82
|
+
async reportWithRetry({jobId, status, error, handoffId, handedOffAtMs, workerId, maxDurationMs}) {
|
|
78
83
|
let attempt = 0
|
|
79
84
|
const startTime = Date.now()
|
|
80
85
|
|
|
81
86
|
while (true) {
|
|
82
87
|
try {
|
|
83
|
-
await this.report({jobId, status, error, handedOffAtMs, workerId})
|
|
88
|
+
await this.report({jobId, status, error, handoffId, handedOffAtMs, workerId})
|
|
84
89
|
return
|
|
85
90
|
} catch (error) {
|
|
91
|
+
if (error instanceof BackgroundJobUpdateError) throw error
|
|
92
|
+
|
|
86
93
|
attempt += 1
|
|
87
94
|
const delaySeconds = Math.min(30, 0.5 * attempt)
|
|
88
95
|
|
|
@@ -90,7 +97,7 @@ export default class BackgroundJobsStatusReporter {
|
|
|
90
97
|
|
|
91
98
|
if (maxDurationMs && Date.now() - startTime >= maxDurationMs) {
|
|
92
99
|
this.logger.warn(() => ["Background job status report timed out, giving up", error])
|
|
93
|
-
|
|
100
|
+
throw error
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
await wait(delaySeconds)
|