velocious 1.0.515 → 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 +3 -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 +31 -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/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-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/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 +31 -29
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +32 -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/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-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/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/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/sync/device-identity.js +3 -3
- package/build/sync/local-mutation-log.js +0 -1
- package/build/tenants/tenant-iterator.js +8 -8
- package/build/tenants/tenant.js +6 -6
- 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 +31 -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/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-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/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/tenants/tenant-iterator.js +8 -8
- package/src/tenants/tenant.js +6 -6
|
@@ -306,44 +306,51 @@ export default class BackgroundJobsStore {
|
|
|
306
306
|
* @param {object} args - Options.
|
|
307
307
|
* @param {string} args.jobId - Job id.
|
|
308
308
|
* @param {string} [args.workerId] - Worker id.
|
|
309
|
-
* @returns {Promise<
|
|
309
|
+
* @returns {Promise<import("./types.js").BackgroundJobHandoff | null>} - Claimed handoff lease, or null when no longer queued.
|
|
310
310
|
*/
|
|
311
311
|
async markHandedOff({jobId, workerId}) {
|
|
312
312
|
await this.ensureReady()
|
|
313
313
|
|
|
314
314
|
const handedOffAtMs = Date.now()
|
|
315
|
+
const handoffId = randomUUID()
|
|
315
316
|
|
|
316
|
-
await this._withDb(async (db) => {
|
|
317
|
+
return await this._withDb(async (db) => {
|
|
317
318
|
await db.update({
|
|
318
319
|
tableName: JOBS_TABLE,
|
|
319
320
|
data: {
|
|
320
321
|
status: "handed_off",
|
|
321
322
|
handed_off_at_ms: handedOffAtMs,
|
|
323
|
+
handoff_id: handoffId,
|
|
322
324
|
worker_id: workerId || null
|
|
323
325
|
},
|
|
324
|
-
conditions: {id: jobId}
|
|
326
|
+
conditions: {id: jobId, status: "queued"}
|
|
325
327
|
})
|
|
326
|
-
})
|
|
327
328
|
|
|
328
|
-
|
|
329
|
+
const job = await this._getJobRowById(db, jobId)
|
|
330
|
+
|
|
331
|
+
if (job?.status !== "handed_off" || job.handoffId !== handoffId) return null
|
|
332
|
+
|
|
333
|
+
return {handedOffAtMs, handoffId}
|
|
334
|
+
})
|
|
329
335
|
}
|
|
330
336
|
|
|
331
337
|
/**
|
|
332
338
|
* Runs mark completed.
|
|
333
339
|
* @param {object} args - Options.
|
|
334
340
|
* @param {string} args.jobId - Job id.
|
|
341
|
+
* @param {string} [args.handoffId] - Handoff lease id.
|
|
335
342
|
* @param {string} [args.workerId] - Worker id.
|
|
336
343
|
* @param {number} [args.handedOffAtMs] - Handed off timestamp.
|
|
337
|
-
* @returns {Promise<
|
|
344
|
+
* @returns {Promise<boolean>} - Whether the fenced report was accepted.
|
|
338
345
|
*/
|
|
339
|
-
async markCompleted({jobId, workerId, handedOffAtMs}) {
|
|
346
|
+
async markCompleted({jobId, handoffId, workerId, handedOffAtMs}) {
|
|
340
347
|
await this.ensureReady()
|
|
341
348
|
|
|
342
|
-
await this._withDb(async (db) => {
|
|
349
|
+
return await this._withDb(async (db) => await this._withHandoffLock({db, jobId}, async () => {
|
|
343
350
|
const job = await this._getJobRowById(db, jobId)
|
|
344
351
|
|
|
345
|
-
if (!job) return
|
|
346
|
-
if (!this._shouldAcceptReport({job, workerId, handedOffAtMs})) return
|
|
352
|
+
if (!job) return false
|
|
353
|
+
if (!this._shouldAcceptReport({job, handoffId, workerId, handedOffAtMs})) return false
|
|
347
354
|
|
|
348
355
|
await db.update({
|
|
349
356
|
tableName: JOBS_TABLE,
|
|
@@ -351,18 +358,23 @@ export default class BackgroundJobsStore {
|
|
|
351
358
|
status: "completed",
|
|
352
359
|
completed_at_ms: Date.now()
|
|
353
360
|
},
|
|
354
|
-
conditions:
|
|
361
|
+
conditions: this._activeHandoffConditions(job)
|
|
355
362
|
})
|
|
356
|
-
|
|
363
|
+
|
|
364
|
+
const updatedJob = await this._getJobRowById(db, jobId)
|
|
365
|
+
|
|
366
|
+
return updatedJob?.status === "completed" && updatedJob.handoffId === job.handoffId
|
|
367
|
+
}))
|
|
357
368
|
}
|
|
358
369
|
|
|
359
370
|
/**
|
|
360
371
|
* Runs mark returned to queue.
|
|
361
372
|
* @param {object} args - Options.
|
|
362
373
|
* @param {string} args.jobId - Job id.
|
|
374
|
+
* @param {string} args.handoffId - Handoff lease id.
|
|
363
375
|
* @returns {Promise<void>} - Resolves when updated.
|
|
364
376
|
*/
|
|
365
|
-
async markReturnedToQueue({jobId}) {
|
|
377
|
+
async markReturnedToQueue({jobId, handoffId}) {
|
|
366
378
|
await this.ensureReady()
|
|
367
379
|
|
|
368
380
|
await this._withDb(async (db) => {
|
|
@@ -372,9 +384,10 @@ export default class BackgroundJobsStore {
|
|
|
372
384
|
status: "queued",
|
|
373
385
|
scheduled_at_ms: Date.now(),
|
|
374
386
|
handed_off_at_ms: null,
|
|
387
|
+
handoff_id: null,
|
|
375
388
|
worker_id: null
|
|
376
389
|
},
|
|
377
|
-
conditions: {id: jobId}
|
|
390
|
+
conditions: {handoff_id: handoffId, id: jobId, status: "handed_off"}
|
|
378
391
|
})
|
|
379
392
|
})
|
|
380
393
|
}
|
|
@@ -384,21 +397,22 @@ export default class BackgroundJobsStore {
|
|
|
384
397
|
* @param {object} args - Options.
|
|
385
398
|
* @param {string} args.jobId - Job id.
|
|
386
399
|
* @param {?} args.error - Error.
|
|
400
|
+
* @param {string} [args.handoffId] - Handoff lease id.
|
|
387
401
|
* @param {string} [args.workerId] - Worker id.
|
|
388
402
|
* @param {number} [args.handedOffAtMs] - Handed off timestamp.
|
|
389
403
|
* @returns {Promise<import("./types.js").BackgroundJobRow | null>} - Updated job row when the report was accepted.
|
|
390
404
|
*/
|
|
391
|
-
async markFailed({jobId, error, workerId, handedOffAtMs}) {
|
|
405
|
+
async markFailed({jobId, error, handoffId, workerId, handedOffAtMs}) {
|
|
392
406
|
await this.ensureReady()
|
|
393
407
|
|
|
394
|
-
return await this._withDb(async (db) => {
|
|
408
|
+
return await this._withDb(async (db) => await this._withHandoffLock({db, jobId}, async () => {
|
|
395
409
|
const job = await this._getJobRowById(db, jobId)
|
|
396
410
|
|
|
397
411
|
if (!job) return null
|
|
398
|
-
if (!this._shouldAcceptReport({job, workerId, handedOffAtMs})) return null
|
|
412
|
+
if (!this._shouldAcceptReport({job, handoffId, workerId, handedOffAtMs})) return null
|
|
399
413
|
|
|
400
414
|
return await this._applyFailure({db, job, error, markOrphaned: false})
|
|
401
|
-
})
|
|
415
|
+
}))
|
|
402
416
|
}
|
|
403
417
|
|
|
404
418
|
/**
|
|
@@ -420,18 +434,22 @@ export default class BackgroundJobsStore {
|
|
|
420
434
|
|
|
421
435
|
const rows = await query.results()
|
|
422
436
|
|
|
437
|
+
let orphanedCount = 0
|
|
438
|
+
|
|
423
439
|
for (const row of rows) {
|
|
424
440
|
const job = this._normalizeJobRow(row)
|
|
425
441
|
|
|
426
|
-
await this._applyFailure({
|
|
442
|
+
const orphanedJob = await this._applyFailure({
|
|
427
443
|
db,
|
|
428
444
|
job,
|
|
429
445
|
error: "Job orphaned after timeout",
|
|
430
446
|
markOrphaned: true
|
|
431
447
|
})
|
|
448
|
+
|
|
449
|
+
if (orphanedJob) orphanedCount += 1
|
|
432
450
|
}
|
|
433
451
|
|
|
434
|
-
return
|
|
452
|
+
return orphanedCount
|
|
435
453
|
})
|
|
436
454
|
}
|
|
437
455
|
|
|
@@ -562,6 +580,7 @@ export default class BackgroundJobsStore {
|
|
|
562
580
|
table.bigint("scheduled_at_ms", {null: false, index: true})
|
|
563
581
|
table.bigint("created_at_ms", {null: false, index: true})
|
|
564
582
|
table.bigint("handed_off_at_ms", {null: true, index: true})
|
|
583
|
+
table.string("handoff_id", {null: true})
|
|
565
584
|
table.bigint("completed_at_ms", {null: true})
|
|
566
585
|
table.bigint("failed_at_ms", {null: true})
|
|
567
586
|
table.bigint("orphaned_at_ms", {null: true, index: true})
|
|
@@ -594,6 +613,35 @@ export default class BackgroundJobsStore {
|
|
|
594
613
|
db.clearSchemaCache()
|
|
595
614
|
}
|
|
596
615
|
|
|
616
|
+
const refreshedTable = await db.getTableByNameOrFail(JOBS_TABLE)
|
|
617
|
+
const handoffIdColumn = await refreshedTable.getColumnByName("handoff_id")
|
|
618
|
+
|
|
619
|
+
if (!handoffIdColumn) {
|
|
620
|
+
const lockName = `${MIGRATION_SCOPE}:handoff_id_column`
|
|
621
|
+
const acquired = await db.acquireAdvisoryLock(lockName)
|
|
622
|
+
|
|
623
|
+
if (!acquired) throw new Error("Failed to acquire background jobs handoff schema lock")
|
|
624
|
+
|
|
625
|
+
try {
|
|
626
|
+
db.clearSchemaCache()
|
|
627
|
+
const lockedTable = await db.getTableByNameOrFail(JOBS_TABLE)
|
|
628
|
+
|
|
629
|
+
if (!(await lockedTable.getColumnByName("handoff_id"))) {
|
|
630
|
+
const tableData = new TableData(JOBS_TABLE)
|
|
631
|
+
tableData.string("handoff_id", {null: true})
|
|
632
|
+
const sqls = await db.alterTableSQLs(tableData)
|
|
633
|
+
|
|
634
|
+
for (const sql of sqls) {
|
|
635
|
+
await db.query(sql)
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
db.clearSchemaCache()
|
|
639
|
+
}
|
|
640
|
+
} finally {
|
|
641
|
+
await db.releaseAdvisoryLock(lockName)
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
597
645
|
await this._backfillExecutionModesOnce(db)
|
|
598
646
|
}
|
|
599
647
|
|
|
@@ -639,14 +687,16 @@ export default class BackgroundJobsStore {
|
|
|
639
687
|
* @returns {Promise<void>} - Resolves when complete.
|
|
640
688
|
*/
|
|
641
689
|
async _recordMigration(db, version) {
|
|
642
|
-
await db.
|
|
690
|
+
await db.upsert({
|
|
643
691
|
tableName: MIGRATIONS_TABLE,
|
|
644
692
|
data: {
|
|
645
693
|
key: this._migrationKey(version),
|
|
646
694
|
scope: MIGRATION_SCOPE,
|
|
647
695
|
version,
|
|
648
696
|
applied_at_ms: Date.now()
|
|
649
|
-
}
|
|
697
|
+
},
|
|
698
|
+
conflictColumns: ["key"],
|
|
699
|
+
updateColumns: ["scope", "version", "applied_at_ms"]
|
|
650
700
|
})
|
|
651
701
|
}
|
|
652
702
|
|
|
@@ -689,7 +739,7 @@ export default class BackgroundJobsStore {
|
|
|
689
739
|
* @param {import("./types.js").BackgroundJobRow} args.job - Job row.
|
|
690
740
|
* @param {?} args.error - Error.
|
|
691
741
|
* @param {boolean} args.markOrphaned - Whether marking orphaned.
|
|
692
|
-
* @returns {Promise<import("./types.js").BackgroundJobRow>} - Updated job row.
|
|
742
|
+
* @returns {Promise<import("./types.js").BackgroundJobRow | null>} - Updated job row when the lease transition won.
|
|
693
743
|
*/
|
|
694
744
|
async _applyFailure({db, job, error, markOrphaned}) {
|
|
695
745
|
const now = Date.now()
|
|
@@ -710,10 +760,17 @@ export default class BackgroundJobsStore {
|
|
|
710
760
|
await db.update({
|
|
711
761
|
tableName: JOBS_TABLE,
|
|
712
762
|
data: update,
|
|
713
|
-
conditions:
|
|
763
|
+
conditions: this._activeHandoffConditions(job)
|
|
714
764
|
})
|
|
715
765
|
|
|
716
|
-
|
|
766
|
+
const updatedJob = await this._getJobRowById(db, job.id)
|
|
767
|
+
|
|
768
|
+
if (!updatedJob) return null
|
|
769
|
+
if (updatedJob.handoffId !== job.handoffId) return null
|
|
770
|
+
if (updatedJob.attempts !== nextAttempt) return null
|
|
771
|
+
if (updatedJob.status !== update.status) return null
|
|
772
|
+
|
|
773
|
+
return updatedJob
|
|
717
774
|
}
|
|
718
775
|
|
|
719
776
|
/**
|
|
@@ -782,29 +839,6 @@ export default class BackgroundJobsStore {
|
|
|
782
839
|
update.failed_at_ms = now
|
|
783
840
|
}
|
|
784
841
|
|
|
785
|
-
/**
|
|
786
|
-
* Runs job with failure update.
|
|
787
|
-
* @param {object} args - Options.
|
|
788
|
-
* @param {string} args.failureMessage - Last failure message.
|
|
789
|
-
* @param {import("./types.js").BackgroundJobRow} args.job - Job row.
|
|
790
|
-
* @param {number} args.nextAttempt - Next attempt count.
|
|
791
|
-
* @param {Record<string, ?>} args.update - Database update data.
|
|
792
|
-
* @returns {import("./types.js").BackgroundJobRow} - Updated job row.
|
|
793
|
-
*/
|
|
794
|
-
_jobWithFailureUpdate({failureMessage, job, nextAttempt, update}) {
|
|
795
|
-
return {
|
|
796
|
-
...job,
|
|
797
|
-
attempts: nextAttempt,
|
|
798
|
-
failedAtMs: update.failed_at_ms ?? job.failedAtMs,
|
|
799
|
-
handedOffAtMs: null,
|
|
800
|
-
lastError: failureMessage,
|
|
801
|
-
orphanedAtMs: update.orphaned_at_ms ?? job.orphanedAtMs,
|
|
802
|
-
scheduledAtMs: update.scheduled_at_ms ?? job.scheduledAtMs,
|
|
803
|
-
status: update.status,
|
|
804
|
-
workerId: null
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
|
|
808
842
|
/**
|
|
809
843
|
* Runs normalize job row.
|
|
810
844
|
* @param {Record<string, ?>} row - Raw database row.
|
|
@@ -827,6 +861,7 @@ export default class BackgroundJobsStore {
|
|
|
827
861
|
scheduledAtMs: this._normalizeNumber(row.scheduled_at_ms),
|
|
828
862
|
createdAtMs: this._normalizeNumber(row.created_at_ms),
|
|
829
863
|
handedOffAtMs: this._normalizeNumber(row.handed_off_at_ms),
|
|
864
|
+
handoffId: row.handoff_id ? String(row.handoff_id) : null,
|
|
830
865
|
completedAtMs: this._normalizeNumber(row.completed_at_ms),
|
|
831
866
|
failedAtMs: this._normalizeNumber(row.failed_at_ms),
|
|
832
867
|
orphanedAtMs: this._normalizeNumber(row.orphaned_at_ms),
|
|
@@ -937,18 +972,65 @@ export default class BackgroundJobsStore {
|
|
|
937
972
|
return /** @type {T} */ (result)
|
|
938
973
|
}
|
|
939
974
|
|
|
975
|
+
/**
|
|
976
|
+
* Serializes reports for one job so duplicate reports cannot both appear accepted.
|
|
977
|
+
* @template T
|
|
978
|
+
* @param {object} args - Options.
|
|
979
|
+
* @param {import("../database/drivers/base.js").default} args.db - Database connection.
|
|
980
|
+
* @param {string} args.jobId - Job id.
|
|
981
|
+
* @param {() => Promise<T>} callback - Locked callback.
|
|
982
|
+
* @returns {Promise<T>} - Callback result.
|
|
983
|
+
*/
|
|
984
|
+
async _withHandoffLock({db, jobId}, callback) {
|
|
985
|
+
const lockName = `background-job:${jobId}`
|
|
986
|
+
const acquired = await db.acquireAdvisoryLock(lockName)
|
|
987
|
+
|
|
988
|
+
if (!acquired) throw new Error(`Failed to acquire background job handoff lock: ${jobId}`)
|
|
989
|
+
|
|
990
|
+
try {
|
|
991
|
+
return await callback()
|
|
992
|
+
} finally {
|
|
993
|
+
await db.releaseAdvisoryLock(lockName)
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
940
997
|
/**
|
|
941
998
|
* Runs should accept report.
|
|
942
999
|
* @param {object} args - Options.
|
|
943
1000
|
* @param {import("./types.js").BackgroundJobRow} args.job - Job row.
|
|
1001
|
+
* @param {string | null | undefined} args.handoffId - Handoff lease id from report.
|
|
944
1002
|
* @param {string | null | undefined} args.workerId - Worker id from report.
|
|
945
1003
|
* @param {number | null | undefined} args.handedOffAtMs - Handed off timestamp from report.
|
|
946
1004
|
* @returns {boolean} - Whether to accept the report.
|
|
947
1005
|
*/
|
|
948
|
-
_shouldAcceptReport({job, workerId, handedOffAtMs}) {
|
|
1006
|
+
_shouldAcceptReport({job, handoffId, workerId, handedOffAtMs}) {
|
|
949
1007
|
if (job.status !== "handed_off") return false
|
|
950
1008
|
|
|
951
|
-
return this.
|
|
1009
|
+
return this._handoffIdReportMatches({handoffId, job})
|
|
1010
|
+
&& this._workerReportMatches({job, workerId})
|
|
1011
|
+
&& this._handoffReportMatches({handedOffAtMs, job})
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* Runs active handoff conditions.
|
|
1016
|
+
* @param {import("./types.js").BackgroundJobRow} job - Job row.
|
|
1017
|
+
* @returns {Record<string, string | null>} - Conditional transition fence.
|
|
1018
|
+
*/
|
|
1019
|
+
_activeHandoffConditions(job) {
|
|
1020
|
+
return {handoff_id: job.handoffId, id: job.id, status: "handed_off"}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Runs handoff id report matches.
|
|
1025
|
+
* @param {object} args - Options.
|
|
1026
|
+
* @param {string | null | undefined} args.handoffId - Handoff lease id from report.
|
|
1027
|
+
* @param {import("./types.js").BackgroundJobRow} args.job - Job row.
|
|
1028
|
+
* @returns {boolean} - Whether the handoff lease matches.
|
|
1029
|
+
*/
|
|
1030
|
+
_handoffIdReportMatches({handoffId, job}) {
|
|
1031
|
+
if (!job.handoffId) return true
|
|
1032
|
+
|
|
1033
|
+
return handoffId === job.handoffId
|
|
952
1034
|
}
|
|
953
1035
|
|
|
954
1036
|
/**
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {"inline" | "forked" | "spawned"} BackgroundJobExecutionMode
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {object} BackgroundJobHandoff
|
|
8
|
+
* @property {string} handoffId - Unique handoff lease id.
|
|
9
|
+
* @property {number} handedOffAtMs - Time handed to a worker in ms.
|
|
10
|
+
*/
|
|
6
11
|
/**
|
|
7
12
|
* @typedef {object} BackgroundJobOptions
|
|
8
13
|
* @property {BackgroundJobExecutionMode} [executionMode] - How the job should run. Defaults to `"forked"`.
|
|
@@ -14,6 +19,7 @@
|
|
|
14
19
|
* @property {string} [id] - Job id.
|
|
15
20
|
* @property {string} jobName - Job class name.
|
|
16
21
|
* @property {Array<?>} [args] - Serialized job arguments.
|
|
22
|
+
* @property {string} [handoffId] - Unique handoff lease id.
|
|
17
23
|
* @property {string} [workerId] - Worker id handling the job.
|
|
18
24
|
* @property {number} [handedOffAtMs] - Time handed to a worker in ms.
|
|
19
25
|
* @property {BackgroundJobOptions} [options] - Runtime options.
|
|
@@ -31,6 +37,7 @@
|
|
|
31
37
|
* @property {number | null} scheduledAtMs - Next scheduled time in ms.
|
|
32
38
|
* @property {number | null} createdAtMs - Creation time in ms.
|
|
33
39
|
* @property {number | null} handedOffAtMs - Time handed to worker in ms.
|
|
40
|
+
* @property {string | null} handoffId - Unique latest handoff lease id.
|
|
34
41
|
* @property {number | null} completedAtMs - Completion time in ms.
|
|
35
42
|
* @property {number | null} failedAtMs - Failure time in ms.
|
|
36
43
|
* @property {number | null} orphanedAtMs - Orphaned time in ms.
|
|
@@ -44,6 +51,7 @@
|
|
|
44
51
|
* @property {number | null} attempts - Updated failure attempts count.
|
|
45
52
|
* @property {boolean} terminal - Whether this failure ended the job.
|
|
46
53
|
* @property {boolean} willRetry - Whether the job was returned to the queue.
|
|
54
|
+
* @property {string | undefined} handoffId - Handoff lease id from the worker report.
|
|
47
55
|
* @property {number | undefined} handedOffAtMs - Handoff timestamp from the worker report.
|
|
48
56
|
* @property {string | undefined} workerId - Worker id from the worker report.
|
|
49
57
|
*/
|
|
@@ -51,15 +59,15 @@
|
|
|
51
59
|
* @typedef {"worker" | "client" | "reporter"} BackgroundJobSocketRole
|
|
52
60
|
*/
|
|
53
61
|
/**
|
|
54
|
-
* @typedef {{type: "hello", role: BackgroundJobSocketRole, workerId?: string}} BackgroundJobHelloMessage
|
|
62
|
+
* @typedef {{type: "hello", role: BackgroundJobSocketRole, supportsHandoffIdReporting?: boolean, workerId?: string}} BackgroundJobHelloMessage
|
|
55
63
|
* @typedef {{type: "ready", acceptsForked?: boolean, acceptsInline?: boolean, acceptsSpawned?: boolean}} BackgroundJobReadyMessage
|
|
56
64
|
* @typedef {{type: "draining"}} BackgroundJobDrainingMessage
|
|
57
65
|
* @typedef {{type: "enqueue", jobName: string, args?: Array<?>, options?: BackgroundJobOptions}} BackgroundJobEnqueueMessage
|
|
58
66
|
* @typedef {{type: "enqueued", jobId: string}} BackgroundJobEnqueuedMessage
|
|
59
67
|
* @typedef {{type: "enqueue-error", error?: string}} BackgroundJobEnqueueErrorMessage
|
|
60
68
|
* @typedef {{type: "job", payload: BackgroundJobPayload}} BackgroundJobJobMessage
|
|
61
|
-
* @typedef {{type: "job-complete", jobId: string, workerId?: string, handedOffAtMs?: number}} BackgroundJobCompleteMessage
|
|
62
|
-
* @typedef {{type: "job-failed", jobId: string, error?: ?, workerId?: string, handedOffAtMs?: number}} BackgroundJobFailedMessage
|
|
69
|
+
* @typedef {{type: "job-complete", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number}} BackgroundJobCompleteMessage
|
|
70
|
+
* @typedef {{type: "job-failed", jobId: string, error?: ?, handoffId?: string, workerId?: string, handedOffAtMs?: number}} BackgroundJobFailedMessage
|
|
63
71
|
* @typedef {{type: "job-updated", jobId: string}} BackgroundJobUpdatedMessage
|
|
64
72
|
* @typedef {{type: "job-update-error", jobId: string, error?: string}} BackgroundJobUpdateErrorMessage
|
|
65
73
|
*/
|
|
@@ -261,7 +261,7 @@ export default class BackgroundJobsWorker {
|
|
|
261
261
|
})
|
|
262
262
|
|
|
263
263
|
socket.on("connect", () => {
|
|
264
|
-
jsonSocket.send({type: "hello", role: "worker", workerId: this.workerId})
|
|
264
|
+
jsonSocket.send({type: "hello", role: "worker", supportsHandoffIdReporting: true, workerId: this.workerId})
|
|
265
265
|
this._sendReadyIfRunning()
|
|
266
266
|
})
|
|
267
267
|
}
|
|
@@ -399,6 +399,7 @@ export default class BackgroundJobsWorker {
|
|
|
399
399
|
await this._reportJobResult({
|
|
400
400
|
jobId: payload.id,
|
|
401
401
|
status: "completed",
|
|
402
|
+
handoffId: payload.handoffId,
|
|
402
403
|
handedOffAtMs: payload.handedOffAtMs,
|
|
403
404
|
workerId: payload.workerId || this.workerId
|
|
404
405
|
})
|
|
@@ -407,6 +408,7 @@ export default class BackgroundJobsWorker {
|
|
|
407
408
|
jobId: payload.id,
|
|
408
409
|
status: "failed",
|
|
409
410
|
error,
|
|
411
|
+
handoffId: payload.handoffId,
|
|
410
412
|
handedOffAtMs: payload.handedOffAtMs,
|
|
411
413
|
workerId: payload.workerId || this.workerId
|
|
412
414
|
})
|
|
@@ -533,7 +535,7 @@ export default class BackgroundJobsWorker {
|
|
|
533
535
|
* @param {object} args - Options.
|
|
534
536
|
* @param {import("node:child_process").ChildProcess} args.child - Forked child process.
|
|
535
537
|
* @param {number | null} args.code - Exit code.
|
|
536
|
-
* @param {
|
|
538
|
+
* @param {keyof typeof import("node:os").constants.signals | null} args.signal - Exit signal.
|
|
537
539
|
* @param {import("./types.js").BackgroundJobPayload & {id: string}} args.payload - Payload.
|
|
538
540
|
* @param {(value: void) => void} args.resolve - Promise resolver.
|
|
539
541
|
* @returns {Promise<void>} - Resolves after failure is reported.
|
|
@@ -558,7 +560,7 @@ export default class BackgroundJobsWorker {
|
|
|
558
560
|
* Runs forked child exited cleanly.
|
|
559
561
|
* @param {object} args - Options.
|
|
560
562
|
* @param {number | null} args.code - Exit code.
|
|
561
|
-
* @param {
|
|
563
|
+
* @param {keyof typeof import("node:os").constants.signals | null} args.signal - Exit signal.
|
|
562
564
|
* @returns {boolean} - Whether the child exited cleanly.
|
|
563
565
|
*/
|
|
564
566
|
_forkedChildExitedCleanly({code, signal}) {
|
|
@@ -609,6 +611,7 @@ export default class BackgroundJobsWorker {
|
|
|
609
611
|
jobId: payload.id,
|
|
610
612
|
status: "failed",
|
|
611
613
|
error,
|
|
614
|
+
handoffId: payload.handoffId,
|
|
612
615
|
handedOffAtMs: payload.handedOffAtMs,
|
|
613
616
|
workerId: payload.workerId || this.workerId
|
|
614
617
|
})
|
|
@@ -665,15 +668,16 @@ export default class BackgroundJobsWorker {
|
|
|
665
668
|
* @param {string} args.jobId - Job id.
|
|
666
669
|
* @param {"completed" | "failed"} args.status - Status.
|
|
667
670
|
* @param {?} [args.error] - Error.
|
|
671
|
+
* @param {string} [args.handoffId] - Handoff lease id.
|
|
668
672
|
* @param {number} [args.handedOffAtMs] - Handed off timestamp.
|
|
669
673
|
* @param {string} [args.workerId] - Worker id.
|
|
670
674
|
* @returns {Promise<void>} - Resolves when reported.
|
|
671
675
|
*/
|
|
672
|
-
async _reportJobResult({jobId, status, error, handedOffAtMs, workerId}) {
|
|
676
|
+
async _reportJobResult({jobId, status, error, handoffId, handedOffAtMs, workerId}) {
|
|
673
677
|
if (!this.statusReporter) return
|
|
674
678
|
|
|
675
679
|
try {
|
|
676
|
-
await this.statusReporter.reportWithRetry({jobId, status, error, handedOffAtMs, workerId})
|
|
680
|
+
await this.statusReporter.reportWithRetry({jobId, status, error, handoffId, handedOffAtMs, workerId})
|
|
677
681
|
} catch (reportError) {
|
|
678
682
|
console.error("Background job status reporting failed:", reportError)
|
|
679
683
|
}
|
package/build/beacon/client.js
CHANGED
|
@@ -9,6 +9,7 @@ import EventEmitter from "../utils/event-emitter.js"
|
|
|
9
9
|
|
|
10
10
|
const DEFAULT_RECONNECT_DELAY_MS = 1000
|
|
11
11
|
const MAX_RECONNECT_DELAY_MS = 30_000
|
|
12
|
+
const DEFAULT_CLOSE_TIMEOUT_MS = 1000
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* BeaconBroadcastHandler type.
|
|
@@ -43,8 +44,9 @@ export default class BeaconClient extends EventEmitter {
|
|
|
43
44
|
* @param {string} [args.peerId] - Optional explicit peer id (defaults to a random UUID).
|
|
44
45
|
* @param {number} [args.reconnectDelayMs] - Starting reconnect delay in ms.
|
|
45
46
|
* @param {number} [args.maxReconnectDelayMs] - Maximum reconnect delay in ms.
|
|
47
|
+
* @param {number} [args.closeTimeoutMs] - Maximum graceful socket close wait in ms.
|
|
46
48
|
*/
|
|
47
|
-
constructor({host, port, peerType, peerId, reconnectDelayMs, maxReconnectDelayMs}) {
|
|
49
|
+
constructor({host, port, peerType, peerId, reconnectDelayMs, maxReconnectDelayMs, closeTimeoutMs}) {
|
|
48
50
|
super()
|
|
49
51
|
this.host = host
|
|
50
52
|
this.port = port
|
|
@@ -53,6 +55,7 @@ export default class BeaconClient extends EventEmitter {
|
|
|
53
55
|
this._initialReconnectDelayMs = reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS
|
|
54
56
|
this._maxReconnectDelayMs = maxReconnectDelayMs ?? MAX_RECONNECT_DELAY_MS
|
|
55
57
|
this._reconnectDelayMs = this._initialReconnectDelayMs
|
|
58
|
+
this._closeTimeoutMs = closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS
|
|
56
59
|
/**
|
|
57
60
|
* Narrows the runtime value to the documented type.
|
|
58
61
|
* @type {JsonSocket | undefined} */
|
|
@@ -66,7 +69,7 @@ export default class BeaconClient extends EventEmitter {
|
|
|
66
69
|
this._closed = false
|
|
67
70
|
/**
|
|
68
71
|
* Narrows the runtime value to the documented type.
|
|
69
|
-
* @type {
|
|
72
|
+
* @type {ReturnType<typeof setTimeout> | undefined} */
|
|
70
73
|
this._reconnectTimer = undefined
|
|
71
74
|
/**
|
|
72
75
|
* Narrows the runtime value to the documented type.
|
|
@@ -235,10 +238,14 @@ export default class BeaconClient extends EventEmitter {
|
|
|
235
238
|
|
|
236
239
|
if (socket.destroyed) return
|
|
237
240
|
|
|
238
|
-
await
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
241
|
+
await timeout({timeout: this._closeTimeoutMs}, async () => {
|
|
242
|
+
await new Promise((resolve) => {
|
|
243
|
+
socket.once("close", () => resolve(undefined))
|
|
244
|
+
socket.end()
|
|
245
|
+
socket.destroySoon()
|
|
246
|
+
})
|
|
247
|
+
}).catch(() => {
|
|
248
|
+
socket.destroy()
|
|
242
249
|
})
|
|
243
250
|
}
|
|
244
251
|
|