velocious 1.0.695 → 1.0.697
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/adapter.js +1 -1
- package/build/background-jobs/client.js +1 -1
- package/build/background-jobs/job-record.js +23 -0
- package/build/background-jobs/local-adapter.js +1 -1
- package/build/background-jobs/local-store.js +10 -5
- package/build/background-jobs/main.js +6 -1
- package/build/background-jobs/owner-liveness.js +92 -0
- package/build/background-jobs/pooled-runner-child.js +5 -1
- package/build/background-jobs/status-reporter.js +7 -4
- package/build/background-jobs/store.js +13 -5
- package/build/background-jobs/types.js +2 -1
- package/build/background-jobs/worker.js +4 -2
- package/build/database/migrations-ledger.js +96 -8
- package/build/src/background-jobs/adapter.d.ts +2 -1
- package/build/src/background-jobs/adapter.d.ts.map +1 -1
- package/build/src/background-jobs/adapter.js +2 -2
- package/build/src/background-jobs/client.js +2 -2
- package/build/src/background-jobs/job-record.d.ts +16 -0
- package/build/src/background-jobs/job-record.d.ts.map +1 -1
- package/build/src/background-jobs/job-record.js +23 -1
- package/build/src/background-jobs/local-adapter.d.ts +2 -1
- package/build/src/background-jobs/local-adapter.d.ts.map +1 -1
- package/build/src/background-jobs/local-adapter.js +2 -2
- package/build/src/background-jobs/local-store.d.ts +5 -3
- package/build/src/background-jobs/local-store.d.ts.map +1 -1
- package/build/src/background-jobs/local-store.js +12 -6
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +7 -2
- package/build/src/background-jobs/owner-liveness.d.ts +71 -0
- package/build/src/background-jobs/owner-liveness.d.ts.map +1 -0
- package/build/src/background-jobs/owner-liveness.js +78 -0
- package/build/src/background-jobs/pooled-runner-child.js +6 -2
- 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 +8 -5
- package/build/src/background-jobs/store.d.ts +5 -3
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +15 -6
- package/build/src/background-jobs/types.d.ts +7 -1
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +3 -2
- package/build/src/background-jobs/worker.d.ts +2 -1
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +6 -4
- package/build/src/database/migrations-ledger.d.ts +34 -1
- package/build/src/database/migrations-ledger.d.ts.map +1 -1
- package/build/src/database/migrations-ledger.js +82 -9
- package/package.json +1 -1
- package/src/background-jobs/adapter.js +1 -1
- package/src/background-jobs/client.js +1 -1
- package/src/background-jobs/job-record.js +23 -0
- package/src/background-jobs/local-adapter.js +1 -1
- package/src/background-jobs/local-store.js +10 -5
- package/src/background-jobs/main.js +6 -1
- package/src/background-jobs/owner-liveness.js +92 -0
- package/src/background-jobs/pooled-runner-child.js +5 -1
- package/src/background-jobs/status-reporter.js +7 -4
- package/src/background-jobs/store.js +13 -5
- package/src/background-jobs/types.js +2 -1
- package/src/background-jobs/worker.js +4 -2
- package/src/database/migrations-ledger.js +96 -8
|
@@ -223,6 +223,7 @@
|
|
|
223
223
|
* @property {number | null} childStartedAtMs - Epoch ms when the job's perform started in the pooled child, or null when it never started.
|
|
224
224
|
* @property {string | null} childInstanceId - Stable identity of the pooled child process that accepted the job, or null.
|
|
225
225
|
* @property {number | null} childPid - OS pid of the pooled child process that accepted the job, or null.
|
|
226
|
+
* @property {string | null} runnerHostname - Hostname reported by the pooled child process that accepted the job, or null.
|
|
226
227
|
*/
|
|
227
228
|
/**
|
|
228
229
|
* @typedef {"queued" | "handed_off" | null} BackgroundJobReplacementPreviousStatus
|
|
@@ -292,7 +293,7 @@
|
|
|
292
293
|
* @typedef {{type: "schedule-woken", jobId: string | null, outcome: BackgroundJobWakeOutcome}} BackgroundJobScheduleWokenMessage
|
|
293
294
|
* @typedef {{type: "wake-scheduled-error", error?: string}} BackgroundJobWakeScheduledErrorMessage
|
|
294
295
|
* @typedef {{type: "job", payload: BackgroundJobPayload}} BackgroundJobJobMessage
|
|
295
|
-
* @typedef {{type: "job-accepted", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number, receivedAtMs?: number, startedAtMs?: number, childInstanceId?: string, childPid?: number}} BackgroundJobAcceptedMessage
|
|
296
|
+
* @typedef {{type: "job-accepted", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number, receivedAtMs?: number, startedAtMs?: number, childInstanceId?: string, childPid?: number, runnerHostname?: string}} BackgroundJobAcceptedMessage
|
|
296
297
|
* @typedef {{type: "job-complete", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number}} BackgroundJobCompleteMessage
|
|
297
298
|
* @typedef {{type: "job-failed", jobId: string, error?: ReturnType<typeof JSON.parse>, handoffId?: string, workerId?: string, handedOffAtMs?: number, runnerFailure?: PooledRunnerFailure}} BackgroundJobFailedMessage
|
|
298
299
|
* @typedef {{type: "job-reschedule", jobId: string, delayMs: number, handoffId?: string, workerId?: string, handedOffAtMs?: number}} BackgroundJobRescheduleMessage
|
|
@@ -93,7 +93,7 @@ function positiveInteger(value) {
|
|
|
93
93
|
* one job. The child carries its exact handoff lease so the worker can forward
|
|
94
94
|
* the report without depending on its in-flight entry still existing.
|
|
95
95
|
* @param {ReturnType<typeof JSON.parse>} message - IPC message.
|
|
96
|
-
* @returns {message is {type: "job-received" | "job-started", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number, receivedAtMs?: number, startedAtMs?: number, childInstanceId?: string, childPid?: number}} - Whether this is a valid acceptance message.
|
|
96
|
+
* @returns {message is {type: "job-received" | "job-started", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number, receivedAtMs?: number, startedAtMs?: number, childInstanceId?: string, childPid?: number, runnerHostname?: string}} - Whether this is a valid acceptance message.
|
|
97
97
|
*/
|
|
98
98
|
function isChildAcceptanceMessage(message) {
|
|
99
99
|
if (!message || typeof message !== "object") return false
|
|
@@ -108,6 +108,7 @@ function isChildAcceptanceMessage(message) {
|
|
|
108
108
|
&& (record.startedAtMs === undefined || typeof record.startedAtMs === "number")
|
|
109
109
|
&& (record.childInstanceId === undefined || typeof record.childInstanceId === "string")
|
|
110
110
|
&& (record.childPid === undefined || Number.isInteger(record.childPid))
|
|
111
|
+
&& (record.runnerHostname === undefined || typeof record.runnerHostname === "string")
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
/**
|
|
@@ -1510,7 +1511,7 @@ export default class BackgroundJobsWorker {
|
|
|
1510
1511
|
* or outcome that already settled the worker's in-flight entry cannot lose
|
|
1511
1512
|
* the fencing. A report that never lands degrades phase diagnostics for that
|
|
1512
1513
|
* job only — it must never block or fail the job itself.
|
|
1513
|
-
* @param {{type: "job-received" | "job-started", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number, receivedAtMs?: number, startedAtMs?: number, childInstanceId?: string, childPid?: number}} message - Validated child acceptance message.
|
|
1514
|
+
* @param {{type: "job-received" | "job-started", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number, receivedAtMs?: number, startedAtMs?: number, childInstanceId?: string, childPid?: number, runnerHostname?: string}} message - Validated child acceptance message.
|
|
1514
1515
|
* @returns {void}
|
|
1515
1516
|
*/
|
|
1516
1517
|
_reportChildAccepted(message) {
|
|
@@ -1525,6 +1526,7 @@ export default class BackgroundJobsWorker {
|
|
|
1525
1526
|
startedAtMs: message.startedAtMs,
|
|
1526
1527
|
childInstanceId: message.childInstanceId,
|
|
1527
1528
|
childPid: message.childPid,
|
|
1529
|
+
runnerHostname: message.runnerHostname,
|
|
1528
1530
|
maxDurationMs: CHILD_ACCEPTANCE_REPORT_MAX_DURATION_MS
|
|
1529
1531
|
}).catch((error) => {
|
|
1530
1532
|
console.error("Background job child-acceptance reporting failed:", error)
|
|
@@ -4,6 +4,11 @@ import {digg} from "diggerize"
|
|
|
4
4
|
import TableData from "./table-data/index.js"
|
|
5
5
|
|
|
6
6
|
const TABLE_NAME = "schema_migrations"
|
|
7
|
+
/** SQL Server "There is already an object named ..." — raised when two sessions race to create the same table. */
|
|
8
|
+
const MSSQL_OBJECT_ALREADY_EXISTS_ERROR_NUMBER = 2714
|
|
9
|
+
/** SQL Server deadlock victim — raised when concurrent sessions deadlock on the same catalog reads. */
|
|
10
|
+
const MSSQL_DEADLOCK_VICTIM_ERROR_NUMBER = 1205
|
|
11
|
+
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* Single owner of the `schema_migrations` ledger shape and the only place that reads
|
|
@@ -35,24 +40,107 @@ export default class MigrationsLedger {
|
|
|
35
40
|
return Boolean(table)
|
|
36
41
|
}
|
|
37
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Whether the error means a concurrent session already created the ledger table.
|
|
45
|
+
*
|
|
46
|
+
* Several processes bootstrap the same database at the same time (for example every
|
|
47
|
+
* CI test shard), and the MSSQL `IF NOT EXISTS (...) BEGIN CREATE TABLE ... END` guard
|
|
48
|
+
* is NOT atomic: two sessions can both evaluate the check as false and then both
|
|
49
|
+
* create, so the loser is rejected. Walks the wrapped-error cause chain because
|
|
50
|
+
* `db.query` re-wraps the driver's `RequestError` in a plain `Error`, and matches the
|
|
51
|
+
* stable numeric error number instead of the localized message text.
|
|
52
|
+
* @param {unknown} error - Error thrown while creating the ledger table.
|
|
53
|
+
* @returns {boolean} - True if the table was created by a concurrent session.
|
|
54
|
+
*/
|
|
55
|
+
static isConcurrentCreationError(error) {
|
|
56
|
+
let current = error
|
|
57
|
+
|
|
58
|
+
while (current instanceof Error) {
|
|
59
|
+
const errorNumber = /** @type {{number?: unknown}} */ (current).number
|
|
60
|
+
|
|
61
|
+
if (typeof errorNumber === "number" && errorNumber === MSSQL_OBJECT_ALREADY_EXISTS_ERROR_NUMBER) return true
|
|
62
|
+
|
|
63
|
+
current = current.cause
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether the error is a transient deadlock that should be retried.
|
|
71
|
+
*
|
|
72
|
+
* Concurrent boots also deadlock on the `INFORMATION_SCHEMA` metadata scan that
|
|
73
|
+
* `getTableByName` performs on SQL Server; the victim transaction rolled back cleanly,
|
|
74
|
+
* so retrying it is safe. Matched by numeric error number, never by message text.
|
|
75
|
+
* @param {unknown} error - Error thrown while inspecting or creating the ledger table.
|
|
76
|
+
* @returns {boolean} - True if the error is a retryable deadlock.
|
|
77
|
+
*/
|
|
78
|
+
static isDeadlockError(error) {
|
|
79
|
+
let current = error
|
|
80
|
+
|
|
81
|
+
while (current instanceof Error) {
|
|
82
|
+
const errorNumber = /** @type {{number?: unknown}} */ (current).number
|
|
83
|
+
|
|
84
|
+
if (typeof errorNumber === "number" && errorNumber === MSSQL_DEADLOCK_VICTIM_ERROR_NUMBER) return true
|
|
85
|
+
|
|
86
|
+
current = current.cause
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
|
|
38
92
|
/**
|
|
39
93
|
* Creates the ledger table if it does not exist. This is the single definition of
|
|
40
94
|
* the `schema_migrations` table shape.
|
|
95
|
+
*
|
|
96
|
+
* Safe under concurrency: a session that loses the create race (or deadlocks on the
|
|
97
|
+
* catalog read) converges on the existing table instead of failing its caller. That
|
|
98
|
+
* matters because this runs on every boot of a database configured for migrations
|
|
99
|
+
* (see `Migrator#createMigrationsTableForDatabase` and
|
|
100
|
+
* `migrateRequireContextForDatabase`), so a crash here takes down a whole test shard
|
|
101
|
+
* or deployment over a table that provably already exists.
|
|
41
102
|
* @param {import("./drivers/base.js").default} db - Database that should contain the ledger table.
|
|
103
|
+
* @param {{maxAttempts?: number}} [args] - Retry budget for transient catalog deadlocks.
|
|
42
104
|
* @returns {Promise<void>}
|
|
43
105
|
*/
|
|
44
|
-
static async ensureTable(db) {
|
|
45
|
-
|
|
106
|
+
static async ensureTable(db, {maxAttempts = 3} = {}) {
|
|
107
|
+
let attempt = 0
|
|
46
108
|
|
|
47
|
-
|
|
109
|
+
while (true) {
|
|
110
|
+
attempt++
|
|
48
111
|
|
|
49
|
-
|
|
112
|
+
try {
|
|
113
|
+
if (await MigrationsLedger.tableExists(db)) return
|
|
50
114
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
115
|
+
const tableData = new TableData(TABLE_NAME, {ifNotExists: true})
|
|
116
|
+
|
|
117
|
+
tableData.string("version", {null: false, primaryKey: true})
|
|
54
118
|
|
|
55
|
-
|
|
119
|
+
for (const sql of await db.createTableSql(tableData)) {
|
|
120
|
+
await db.query(sql)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
db.clearSchemaCache()
|
|
124
|
+
|
|
125
|
+
return
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (MigrationsLedger.isConcurrentCreationError(error)) {
|
|
128
|
+
// Another session created the ledger between our check and our create. The
|
|
129
|
+
// table exists with exactly this shape, so the goal state is already reached.
|
|
130
|
+
db.clearSchemaCache()
|
|
131
|
+
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (MigrationsLedger.isDeadlockError(error) && attempt < maxAttempts) {
|
|
136
|
+
db.clearSchemaCache()
|
|
137
|
+
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
throw error
|
|
142
|
+
}
|
|
143
|
+
}
|
|
56
144
|
}
|
|
57
145
|
|
|
58
146
|
/**
|