velocious 1.0.525 → 1.0.526
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 +1 -1
- package/build/background-jobs/main.js +48 -5
- package/build/background-jobs/store.js +28 -7
- package/build/src/background-jobs/main.d.ts +12 -0
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +48 -6
- package/build/src/background-jobs/store.d.ts +2 -2
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +30 -9
- package/package.json +1 -1
- package/src/background-jobs/main.js +48 -5
- package/src/background-jobs/store.js +28 -7
package/package.json
CHANGED
|
@@ -784,6 +784,37 @@ export default class BackgroundJobsMain {
|
|
|
784
784
|
errorEvents.emit("all-error", {...payload, errorType: "background-job-failed"})
|
|
785
785
|
}
|
|
786
786
|
|
|
787
|
+
/**
|
|
788
|
+
* Emits `background-job-orphaned` (mirrored to `all-error`) for a job the time-based orphan sweep
|
|
789
|
+
* reclaimed after its worker died mid-run. Unlike `background-job-failed`, which fires on a
|
|
790
|
+
* worker's failure report, this fires from the main process's sweep, so applications can react to
|
|
791
|
+
* a dead worker's specific job — recover the work it left behind — without polling. `willRetry`
|
|
792
|
+
* reflects whether the reclaim returned the job to the queue for another attempt.
|
|
793
|
+
* @param {{job: import("./types.js").BackgroundJobRow}} args - The orphaned job.
|
|
794
|
+
* @returns {void}
|
|
795
|
+
*/
|
|
796
|
+
_emitBackgroundJobOrphaned({job}) {
|
|
797
|
+
const normalizedError = this._normalizeFailureError(job.lastError ?? "Job orphaned after timeout")
|
|
798
|
+
const payload = {
|
|
799
|
+
context: {
|
|
800
|
+
attempts: job.attempts,
|
|
801
|
+
jobArgs: job.args,
|
|
802
|
+
jobId: job.id,
|
|
803
|
+
jobName: job.jobName,
|
|
804
|
+
maxRetries: job.maxRetries,
|
|
805
|
+
stage: "background-job-orphaned",
|
|
806
|
+
status: job.status,
|
|
807
|
+
terminal: job.status === "failed" || job.status === "orphaned",
|
|
808
|
+
willRetry: job.status === "queued"
|
|
809
|
+
},
|
|
810
|
+
error: normalizedError
|
|
811
|
+
}
|
|
812
|
+
const errorEvents = this.configuration.getErrorEvents()
|
|
813
|
+
|
|
814
|
+
errorEvents.emit("background-job-orphaned", payload)
|
|
815
|
+
errorEvents.emit("all-error", {...payload, errorType: "background-job-orphaned"})
|
|
816
|
+
}
|
|
817
|
+
|
|
787
818
|
/**
|
|
788
819
|
* Runs normalize failure error.
|
|
789
820
|
* @param {?} error - Reported failure value.
|
|
@@ -1170,14 +1201,26 @@ export default class BackgroundJobsMain {
|
|
|
1170
1201
|
|
|
1171
1202
|
async _sweepOrphans() {
|
|
1172
1203
|
try {
|
|
1173
|
-
const
|
|
1204
|
+
const orphanedJobs = await this.store.markOrphanedJobs()
|
|
1174
1205
|
|
|
1175
|
-
if (
|
|
1176
|
-
this.logger.warn(() => ["Marked orphaned background jobs",
|
|
1177
|
-
// Reclaimed orphans become `queued` again — wake the dispatcher
|
|
1178
|
-
//
|
|
1206
|
+
if (orphanedJobs.length > 0) {
|
|
1207
|
+
this.logger.warn(() => ["Marked orphaned background jobs", orphanedJobs.length])
|
|
1208
|
+
// Reclaimed orphans become `queued` again — wake the dispatcher first so
|
|
1209
|
+
// an application event handler that throws below cannot strand them
|
|
1210
|
+
// queued until the next external enqueue/reconnect.
|
|
1179
1211
|
this._notifyEnqueued()
|
|
1180
1212
|
await this._drain()
|
|
1213
|
+
// Emit an event per orphaned job so applications can react to a dead
|
|
1214
|
+
// worker's specific job (e.g. targeted recovery) instead of only polling
|
|
1215
|
+
// for its aftermath. Isolate each so one throwing handler can't suppress
|
|
1216
|
+
// the events for the rest.
|
|
1217
|
+
for (const job of orphanedJobs) {
|
|
1218
|
+
try {
|
|
1219
|
+
this._emitBackgroundJobOrphaned({job})
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
this.logger.error(() => ["A background-job-orphaned event handler threw:", error])
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1181
1224
|
}
|
|
1182
1225
|
} catch (error) {
|
|
1183
1226
|
this.logger.error(() => ["Failed to mark orphaned jobs:", error])
|
|
@@ -547,7 +547,7 @@ export default class BackgroundJobsStore {
|
|
|
547
547
|
* Runs mark orphaned jobs.
|
|
548
548
|
* @param {object} [args] - Options.
|
|
549
549
|
* @param {number} [args.orphanedAfterMs] - Mark jobs orphaned after this duration.
|
|
550
|
-
* @returns {Promise<
|
|
550
|
+
* @returns {Promise<import("./types.js").BackgroundJobRow[]>} - The jobs this sweep marked orphaned.
|
|
551
551
|
*/
|
|
552
552
|
async markOrphanedJobs({orphanedAfterMs = ORPHANED_AFTER_MS} = {}) {
|
|
553
553
|
await this.ensureReady()
|
|
@@ -562,7 +562,8 @@ export default class BackgroundJobsStore {
|
|
|
562
562
|
|
|
563
563
|
const rows = await query.results()
|
|
564
564
|
|
|
565
|
-
|
|
565
|
+
/** @type {import("./types.js").BackgroundJobRow[]} */
|
|
566
|
+
const orphanedJobs = []
|
|
566
567
|
|
|
567
568
|
for (const row of rows) {
|
|
568
569
|
const job = this._normalizeJobRow(row)
|
|
@@ -588,10 +589,10 @@ export default class BackgroundJobsStore {
|
|
|
588
589
|
conditions: {id: job.id, status: "handed_off", handed_off_at_ms: job.handedOffAtMs}
|
|
589
590
|
})
|
|
590
591
|
|
|
591
|
-
if (orphanedJob)
|
|
592
|
+
if (orphanedJob) orphanedJobs.push(orphanedJob)
|
|
592
593
|
}
|
|
593
594
|
|
|
594
|
-
return
|
|
595
|
+
return orphanedJobs
|
|
595
596
|
})
|
|
596
597
|
}
|
|
597
598
|
|
|
@@ -1075,10 +1076,30 @@ export default class BackgroundJobsStore {
|
|
|
1075
1076
|
if (affectedRows !== 1) return null
|
|
1076
1077
|
await this._releaseConcurrency(db, job.concurrencyKey)
|
|
1077
1078
|
|
|
1078
|
-
|
|
1079
|
+
// Return a snapshot of the transition this update just applied rather than re-reading the row.
|
|
1080
|
+
// We won the conditional update (affectedRows === 1), so this state is authoritative; re-reading
|
|
1081
|
+
// could instead observe a newer state if another dispatcher reclaims a requeued job between the
|
|
1082
|
+
// update and the read (overlapping mains / polling dispatch), which would misreport the
|
|
1083
|
+
// status/terminal/willRetry of this transition to failure/orphan event listeners.
|
|
1084
|
+
const status = shouldRetry ? "queued" : (markOrphaned ? "orphaned" : "failed")
|
|
1085
|
+
/** @type {import("./types.js").BackgroundJobRow} */
|
|
1086
|
+
const transitionedJob = {
|
|
1087
|
+
...job,
|
|
1088
|
+
attempts: nextAttempt,
|
|
1089
|
+
handedOffAtMs: null,
|
|
1090
|
+
lastError: failureMessage,
|
|
1091
|
+
status,
|
|
1092
|
+
workerId: null
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
if (markOrphaned) transitionedJob.orphanedAtMs = now
|
|
1096
|
+
if (shouldRetry) {
|
|
1097
|
+
transitionedJob.scheduledAtMs = scheduledAt
|
|
1098
|
+
} else if (!markOrphaned) {
|
|
1099
|
+
transitionedJob.failedAtMs = now
|
|
1100
|
+
}
|
|
1079
1101
|
|
|
1080
|
-
|
|
1081
|
-
return updatedJob
|
|
1102
|
+
return transitionedJob
|
|
1082
1103
|
}
|
|
1083
1104
|
|
|
1084
1105
|
/**
|