velocious 1.0.698 → 1.0.700
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 +2 -2
- package/build/configuration-types.js +23 -0
- package/build/configuration.js +136 -1
- package/build/database/advisory-lock-backend-policy.js +204 -0
- package/build/database/advisory-lock-file-backend.js +348 -0
- package/build/database/advisory-lock-runner.js +239 -16
- package/build/database/drivers/base.js +318 -15
- package/build/database/drivers/mssql/index.js +1 -1
- package/build/database/drivers/mysql/index.js +4 -2
- package/build/database/drivers/pgsql/index.js +4 -2
- package/build/database/drivers/sqlite/base.js +13 -129
- package/build/database/drivers/sqlite/index.js +41 -62
- package/build/database/migrator.js +120 -50
- package/build/database/record/attachments/store.js +84 -1
- package/build/environment-handlers/base.js +13 -0
- package/build/environment-handlers/node.js +11 -0
- package/build/http-server/server-lock.js +21 -115
- package/build/src/configuration-types.d.ts +68 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +21 -1
- package/build/src/configuration.d.ts +100 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +124 -2
- package/build/src/database/advisory-lock-backend-policy.d.ts +145 -0
- package/build/src/database/advisory-lock-backend-policy.d.ts.map +1 -0
- package/build/src/database/advisory-lock-backend-policy.js +184 -0
- package/build/src/database/advisory-lock-file-backend.d.ts +194 -0
- package/build/src/database/advisory-lock-file-backend.d.ts.map +1 -0
- package/build/src/database/advisory-lock-file-backend.js +313 -0
- package/build/src/database/advisory-lock-runner.d.ts +116 -15
- package/build/src/database/advisory-lock-runner.d.ts.map +1 -1
- package/build/src/database/advisory-lock-runner.js +190 -17
- package/build/src/database/drivers/base.d.ts +125 -4
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +282 -17
- package/build/src/database/drivers/mssql/index.d.ts +1 -1
- package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/index.js +2 -2
- package/build/src/database/drivers/mysql/index.d.ts +4 -2
- package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/index.js +5 -3
- package/build/src/database/drivers/pgsql/index.d.ts +4 -2
- package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
- package/build/src/database/drivers/pgsql/index.js +5 -3
- package/build/src/database/drivers/sqlite/base.d.ts +11 -49
- package/build/src/database/drivers/sqlite/base.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/base.js +14 -114
- package/build/src/database/drivers/sqlite/index.d.ts +28 -19
- package/build/src/database/drivers/sqlite/index.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/index.js +41 -59
- package/build/src/database/migrator.d.ts.map +1 -1
- package/build/src/database/migrator.js +110 -56
- package/build/src/database/record/attachments/store.d.ts +0 -3
- package/build/src/database/record/attachments/store.d.ts.map +1 -1
- package/build/src/database/record/attachments/store.js +71 -2
- package/build/src/environment-handlers/base.d.ts +14 -0
- package/build/src/environment-handlers/base.d.ts.map +1 -1
- package/build/src/environment-handlers/base.js +13 -1
- package/build/src/environment-handlers/node.d.ts +11 -0
- package/build/src/environment-handlers/node.d.ts.map +1 -1
- package/build/src/environment-handlers/node.js +11 -1
- package/build/src/http-server/server-lock.d.ts +13 -37
- package/build/src/http-server/server-lock.d.ts.map +1 -1
- package/build/src/http-server/server-lock.js +22 -109
- package/build/src/testing/test-runner.d.ts +74 -0
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +140 -1
- package/build/src/testing/test-tenant-database-pool.d.ts +183 -0
- package/build/src/testing/test-tenant-database-pool.d.ts.map +1 -0
- package/build/src/testing/test-tenant-database-pool.js +388 -0
- package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -1
- package/build/src/testing/velocious-attempt-executor.js +43 -1
- package/build/testing/test-runner.js +152 -0
- package/build/testing/test-tenant-database-pool.js +438 -0
- package/build/testing/velocious-attempt-executor.js +44 -0
- package/package.json +1 -1
- package/src/configuration-types.js +23 -0
- package/src/configuration.js +136 -1
- package/src/database/advisory-lock-backend-policy.js +204 -0
- package/src/database/advisory-lock-file-backend.js +348 -0
- package/src/database/advisory-lock-runner.js +239 -16
- package/src/database/drivers/base.js +318 -15
- package/src/database/drivers/mssql/index.js +1 -1
- package/src/database/drivers/mysql/index.js +4 -2
- package/src/database/drivers/pgsql/index.js +4 -2
- package/src/database/drivers/sqlite/base.js +13 -129
- package/src/database/drivers/sqlite/index.js +41 -62
- package/src/database/migrator.js +120 -50
- package/src/database/record/attachments/store.js +84 -1
- package/src/environment-handlers/base.js +13 -0
- package/src/environment-handlers/node.js +11 -0
- package/src/http-server/server-lock.js +21 -115
- package/src/testing/test-runner.js +152 -0
- package/src/testing/test-tenant-database-pool.js +438 -0
- package/src/testing/velocious-attempt-executor.js +44 -0
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import timeout, {TimeoutError} from "awaitery/build/timeout.js"
|
|
4
|
+
import {DEFAULT_ADVISORY_LOCK_ACQUIRE_TIMEOUT_MS} from "./advisory-lock-bound.js"
|
|
5
|
+
import {
|
|
6
|
+
AdvisoryLockBackendUnavailableError,
|
|
7
|
+
resolveAdvisoryLockBackendPolicy,
|
|
8
|
+
refuseAdvisoryLockTierBelowMinimum
|
|
9
|
+
} from "./advisory-lock-backend-policy.js"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Provenance of one advisory-lock acquisition (STC-210 design §3): every
|
|
13
|
+
* held lock reports the tier that actually served it, so a fallback is
|
|
14
|
+
* visible to the callback, in logs, and in `getAdvisoryLockBackendStatus()`.
|
|
15
|
+
* @typedef {object} AdvisoryLockBackendProvenance
|
|
16
|
+
* @property {import("./advisory-lock-backend-policy.js").AdvisoryLockTier} backend - Tier that served the acquisition.
|
|
17
|
+
* @property {string} [driverType] - Database driver type when the driver tier served it.
|
|
18
|
+
* @property {boolean} fencingSupported - Whether this tier carries fencing tokens (design §8); the file tier reports false rather than pretending.
|
|
19
|
+
* @property {string} [holderId] - Backend holder id for the file tier.
|
|
20
|
+
* @property {string} [lockName] - Lock name this provenance belongs to.
|
|
21
|
+
*/
|
|
4
22
|
|
|
5
23
|
/**
|
|
6
24
|
* Thrown when an advisory lock could not be acquired before `timeoutMs` elapsed.
|
|
@@ -77,19 +95,41 @@ export default class AdvisoryLockRunner {
|
|
|
77
95
|
* cleanup can release the lock even if callback database work is stuck.
|
|
78
96
|
* @template T
|
|
79
97
|
* @param {string} name - Lock name.
|
|
80
|
-
* @param {(args?: {control
|
|
98
|
+
* @param {(args?: {control?: import("awaitery/build/timeout.js").TimeoutControl, lockBackend: AdvisoryLockBackendProvenance}) => Promise<T>} callback - Callback to invoke while the lock is held.
|
|
81
99
|
* @param {{timeoutMs?: number | null, holdTimeoutMs?: number | null, dedicatedConnection?: boolean}} [args] - Lock and hold timeout options.
|
|
82
100
|
* @returns {Promise<T>} - Resolves with the callback result.
|
|
83
101
|
*/
|
|
84
102
|
async withAdvisoryLock(name, callback, args = {}) {
|
|
103
|
+
const fileBackend = await this.fileBackendForName(name)
|
|
104
|
+
|
|
105
|
+
if (fileBackend) {
|
|
106
|
+
const acquired = await fileBackend.acquire(name, {timeoutMs: this.fileAcquireTimeoutMs(args)})
|
|
107
|
+
|
|
108
|
+
if (!acquired) {
|
|
109
|
+
throw new AdvisoryLockTimeoutError(`Timed out waiting for advisory lock ${JSON.stringify(name)} on the file tier`, {name})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const lockBackend = this.fileProvenance(fileBackend, name)
|
|
113
|
+
|
|
114
|
+
this.configuration.recordAdvisoryLockAcquisition({backend: lockBackend.backend, fencingSupported: lockBackend.fencingSupported, lockName: name})
|
|
115
|
+
|
|
116
|
+
return await this.runLockedCallback({callback, holdTimeoutMs: args.holdTimeoutMs, lockBackend, name, release: async () => await fileBackend.release(name)})
|
|
117
|
+
}
|
|
118
|
+
|
|
85
119
|
return await this.withLockConnection(args, async (connection) => {
|
|
120
|
+
const lockBackend = this.driverProvenance(connection)
|
|
121
|
+
|
|
122
|
+
this.assertDriverTierEligible(name, lockBackend)
|
|
123
|
+
|
|
86
124
|
const acquired = await connection.acquireAdvisoryLock(name, args)
|
|
87
125
|
|
|
88
126
|
if (!acquired) {
|
|
89
127
|
throw new AdvisoryLockTimeoutError(`Timed out waiting for advisory lock ${JSON.stringify(name)}`, {name})
|
|
90
128
|
}
|
|
91
129
|
|
|
92
|
-
|
|
130
|
+
this.configuration.recordAdvisoryLockAcquisition({backend: lockBackend.backend, fencingSupported: lockBackend.fencingSupported, lockName: name})
|
|
131
|
+
|
|
132
|
+
return await this.runLockedCallback({callback, holdTimeoutMs: args.holdTimeoutMs, lockBackend, name, release: async () => await connection.releaseAdvisoryLock(name)})
|
|
93
133
|
})
|
|
94
134
|
}
|
|
95
135
|
|
|
@@ -102,22 +142,45 @@ export default class AdvisoryLockRunner {
|
|
|
102
142
|
* cancellation.
|
|
103
143
|
* @template T
|
|
104
144
|
* @param {string} name - Lock name.
|
|
105
|
-
* @param {(args?: {control
|
|
145
|
+
* @param {(args?: {control?: import("awaitery/build/timeout.js").TimeoutControl, lockBackend: AdvisoryLockBackendProvenance}) => Promise<T>} callback - Callback to invoke while the lock is held.
|
|
106
146
|
* @param {{holdTimeoutMs?: number | null, dedicatedConnection?: boolean}} [args] - Hold timeout options.
|
|
107
147
|
* @returns {Promise<T>} - Resolves with the callback result.
|
|
108
148
|
*/
|
|
109
149
|
async withAdvisoryLockOrFail(name, callback, args = {}) {
|
|
150
|
+
const fileBackend = await this.fileBackendForName(name)
|
|
151
|
+
|
|
152
|
+
if (fileBackend) {
|
|
153
|
+
const acquired = await fileBackend.tryAcquire(name)
|
|
154
|
+
|
|
155
|
+
if (!acquired) {
|
|
156
|
+
throw new AdvisoryLockBusyError(`Advisory lock ${JSON.stringify(name)} is already held on the file tier`, {name})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const lockBackend = this.fileProvenance(fileBackend, name)
|
|
160
|
+
|
|
161
|
+
this.configuration.recordAdvisoryLockAcquisition({backend: lockBackend.backend, fencingSupported: lockBackend.fencingSupported, lockName: name})
|
|
162
|
+
|
|
163
|
+
return await this.runLockedCallback({callback, holdTimeoutMs: args.holdTimeoutMs, lockBackend, name, release: async () => await fileBackend.release(name)})
|
|
164
|
+
}
|
|
165
|
+
|
|
110
166
|
return await this.withLockConnection(args, async (connection) => {
|
|
167
|
+
const lockBackend = this.driverProvenance(connection)
|
|
168
|
+
|
|
169
|
+
this.assertDriverTierEligible(name, lockBackend)
|
|
170
|
+
|
|
111
171
|
const acquired = await connection.tryAcquireAdvisoryLock(name)
|
|
112
172
|
|
|
113
173
|
if (!acquired) {
|
|
114
174
|
throw new AdvisoryLockBusyError(`Advisory lock ${JSON.stringify(name)} is already held`, {name})
|
|
115
175
|
}
|
|
116
176
|
|
|
117
|
-
|
|
177
|
+
this.configuration.recordAdvisoryLockAcquisition({backend: lockBackend.backend, fencingSupported: lockBackend.fencingSupported, lockName: name})
|
|
178
|
+
|
|
179
|
+
return await this.runLockedCallback({callback, holdTimeoutMs: args.holdTimeoutMs, lockBackend, name, release: async () => await connection.releaseAdvisoryLock(name)})
|
|
118
180
|
})
|
|
119
181
|
}
|
|
120
182
|
|
|
183
|
+
/**
|
|
121
184
|
/**
|
|
122
185
|
* Runs a callback if the advisory lock can be acquired without waiting;
|
|
123
186
|
* otherwise returns a `{acquired: false}` defer result instead of throwing.
|
|
@@ -133,33 +196,172 @@ export default class AdvisoryLockRunner {
|
|
|
133
196
|
* file lock), so a caller never branches on the backend.
|
|
134
197
|
* @template T
|
|
135
198
|
* @param {string} name - Lock name.
|
|
136
|
-
* @param {(args?: {control
|
|
199
|
+
* @param {(args?: {control?: import("awaitery/build/timeout.js").TimeoutControl, lockBackend: AdvisoryLockBackendProvenance}) => Promise<T>} callback - Callback to invoke while the lock is held.
|
|
137
200
|
* @param {{holdTimeoutMs?: number | null, dedicatedConnection?: boolean}} [args] - Hold timeout options.
|
|
138
201
|
* @returns {Promise<{acquired: true, result: T} | {acquired: false, reason: "contended", lockName: string}>} - Either the held-callback result or the defer outcome.
|
|
139
202
|
*/
|
|
140
203
|
async withAdvisoryLockOrDefer(name, callback, args = {}) {
|
|
204
|
+
const fileBackend = await this.fileBackendForName(name)
|
|
205
|
+
|
|
206
|
+
if (fileBackend) {
|
|
207
|
+
const acquired = await fileBackend.acquire(name, {timeoutMs: 0})
|
|
208
|
+
|
|
209
|
+
if (!acquired) return {acquired: false, reason: "contended", lockName: name}
|
|
210
|
+
|
|
211
|
+
const lockBackend = this.fileProvenance(fileBackend, name)
|
|
212
|
+
|
|
213
|
+
this.configuration.recordAdvisoryLockAcquisition({backend: lockBackend.backend, fencingSupported: lockBackend.fencingSupported, lockName: name})
|
|
214
|
+
|
|
215
|
+
const result = await this.runLockedCallback({callback, holdTimeoutMs: args.holdTimeoutMs, lockBackend, name, release: async () => await fileBackend.release(name)})
|
|
216
|
+
|
|
217
|
+
return {acquired: true, result}
|
|
218
|
+
}
|
|
219
|
+
|
|
141
220
|
return await this.withLockConnection(args, async (connection) => {
|
|
142
221
|
const acquired = await connection.tryAcquireAdvisoryLock(name)
|
|
143
222
|
|
|
144
223
|
if (!acquired) return {acquired: false, reason: "contended", lockName: name}
|
|
145
224
|
|
|
146
|
-
const
|
|
225
|
+
const lockBackend = this.driverProvenance(connection)
|
|
226
|
+
|
|
227
|
+
this.assertDriverTierEligible(name, lockBackend)
|
|
228
|
+
|
|
229
|
+
this.configuration.recordAdvisoryLockAcquisition({backend: lockBackend.backend, fencingSupported: lockBackend.fencingSupported, lockName: name})
|
|
230
|
+
|
|
231
|
+
const result = await this.runLockedCallback({callback, holdTimeoutMs: args.holdTimeoutMs, lockBackend, name, release: async () => await connection.releaseAdvisoryLock(name)})
|
|
147
232
|
|
|
148
233
|
return {acquired: true, result}
|
|
149
234
|
})
|
|
150
235
|
}
|
|
151
236
|
|
|
152
237
|
/**
|
|
153
|
-
* Runs the lock holder callback and releases the lock from its owning
|
|
238
|
+
* Runs the lock holder callback and releases the lock from its owning backend.
|
|
154
239
|
* @template T
|
|
155
|
-
* @param {{callback: (args?: {control
|
|
240
|
+
* @param {{callback: (args?: {control?: import("awaitery/build/timeout.js").TimeoutControl, lockBackend: AdvisoryLockBackendProvenance}) => Promise<T>, holdTimeoutMs?: number | null, lockBackend: AdvisoryLockBackendProvenance, name: string, release: () => Promise<unknown>}} args - Locked callback args.
|
|
156
241
|
* @returns {Promise<T>} - Resolves with the callback result.
|
|
157
242
|
*/
|
|
158
|
-
async runLockedCallback({callback,
|
|
243
|
+
async runLockedCallback({callback, holdTimeoutMs, lockBackend, name, release}) {
|
|
159
244
|
try {
|
|
160
|
-
return await AdvisoryLockRunner.runWithAdvisoryLockHoldTimeout(name, callback, holdTimeoutMs)
|
|
245
|
+
return await AdvisoryLockRunner.runWithAdvisoryLockHoldTimeout(name, callback, holdTimeoutMs, lockBackend)
|
|
161
246
|
} finally {
|
|
162
|
-
await
|
|
247
|
+
await release()
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Resolves the backend policy for one lock name and returns the file
|
|
253
|
+
* backend when the name explicitly opts into the file tier
|
|
254
|
+
* (`preferredTier: "file"` plus `advisoryLocks.backends.file.enabled: true`).
|
|
255
|
+
* A name whose declared minimum tier is cross-host is refused loudly
|
|
256
|
+
* (`AdvisoryLockMinTierRefusedError`) before any contact with the file
|
|
257
|
+
* tier. A name that prefers the file tier while the backend is disabled
|
|
258
|
+
* moves to the driver path (a stronger-or-equal tier), never the other way
|
|
259
|
+
* around. Nothing here ever auto-enables the file tier.
|
|
260
|
+
* @param {string} name - Lock name.
|
|
261
|
+
* @returns {Promise<import("./advisory-lock-file-backend.js").default | null>} - Resolves with the file backend serving this name, or null for the driver path.
|
|
262
|
+
*/
|
|
263
|
+
async fileBackendForName(name) {
|
|
264
|
+
const policy = resolveAdvisoryLockBackendPolicy(name, this.configuration.getAdvisoryLockPolicy())
|
|
265
|
+
|
|
266
|
+
if (policy.preferredTier !== "file") return null
|
|
267
|
+
|
|
268
|
+
if (!advisoryLockPolicyAllowsFileTier(policy)) {
|
|
269
|
+
refuseAdvisoryLockTierBelowMinimum({
|
|
270
|
+
configuration: this.configuration,
|
|
271
|
+
lockName: name,
|
|
272
|
+
minTier: policy.minTier,
|
|
273
|
+
requestedBackend: "file"
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const backend = await this.configuration.getAdvisoryLockFileBackend()
|
|
278
|
+
|
|
279
|
+
if (!backend) {
|
|
280
|
+
// Preferred tier unavailable: fail *upward* to the driver path (a
|
|
281
|
+
// stronger-or-equal tier), never downward. If the driver cannot satisfy
|
|
282
|
+
// the minimum either, `assertDriverTierEligible` throws
|
|
283
|
+
// `AdvisoryLockBackendUnavailableError` on the driver path.
|
|
284
|
+
return null
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return backend
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Fail-closed check on the driver path (design §9): an undeclared lock name
|
|
292
|
+
* keeps today's behavior (any driver serves it — full backward
|
|
293
|
+
* compatibility), but a name with an explicit policy entry is refused with
|
|
294
|
+
* `AdvisoryLockBackendUnavailableError` when the configured driver cannot
|
|
295
|
+
* satisfy its minimum tier (e.g. `minTier: "driver-native"` on a SQLite
|
|
296
|
+
* driver, which has no native advisory-lock primitive).
|
|
297
|
+
* @param {string} name - Lock name.
|
|
298
|
+
* @param {AdvisoryLockBackendProvenance} lockBackend - Tier the driver path would serve with.
|
|
299
|
+
* @returns {void}
|
|
300
|
+
*/
|
|
301
|
+
assertDriverTierEligible(name, lockBackend) {
|
|
302
|
+
const policy = resolveAdvisoryLockBackendPolicy(name, this.configuration.getAdvisoryLockPolicy())
|
|
303
|
+
|
|
304
|
+
if (!policy.entry) return
|
|
305
|
+
|
|
306
|
+
const tier = lockBackend.backend
|
|
307
|
+
|
|
308
|
+
if (tier === "process-local" && !policy.allowedTiers.includes("process-local")) {
|
|
309
|
+
throw new AdvisoryLockBackendUnavailableError(
|
|
310
|
+
`Advisory lock ${JSON.stringify(name)} requires minimum tier ${JSON.stringify(policy.minTier)} but the configured driver (${String(lockBackend.driverType)}) only offers the process-local tier`,
|
|
311
|
+
{lockName: name, minTier: policy.minTier, reasons: [`driver tier ${tier} below minTier ${policy.minTier}`]}
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Resolves the wait bound for a file-tier acquisition: an explicit caller
|
|
318
|
+
* `timeoutMs` (including an explicit `null` opt-out) wins, otherwise the
|
|
319
|
+
* same framework default that bounds driver acquisitions applies.
|
|
320
|
+
* @param {{timeoutMs?: number | null}} args - Lock options.
|
|
321
|
+
* @returns {number | null} - Wait bound in milliseconds, or `null` for an indefinite wait.
|
|
322
|
+
*/
|
|
323
|
+
fileAcquireTimeoutMs(args) {
|
|
324
|
+
if (args.timeoutMs !== undefined) return args.timeoutMs
|
|
325
|
+
|
|
326
|
+
return DEFAULT_ADVISORY_LOCK_ACQUIRE_TIMEOUT_MS
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Builds the acquisition provenance for a file-tier hold. The file tier
|
|
331
|
+
* cannot carry fencing tokens, so the limitation is reported on every
|
|
332
|
+
* acquisition rather than silently pretended away (design §8).
|
|
333
|
+
* @param {import("./advisory-lock-file-backend.js").default} backend - Backend that served the acquisition.
|
|
334
|
+
* @param {string} name - Lock name.
|
|
335
|
+
* @returns {AdvisoryLockBackendProvenance} - Acquisition provenance.
|
|
336
|
+
*/
|
|
337
|
+
fileProvenance(backend, name) {
|
|
338
|
+
return {backend: "file", fencingSupported: false, holderId: backend.holderId, lockName: name}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Builds the acquisition provenance for a driver-served hold. SQLite has no
|
|
343
|
+
* native advisory-lock primitive, so its emulation tier is reported as
|
|
344
|
+
* `process-local` rather than pretending to be database-native.
|
|
345
|
+
* @param {import("./drivers/base.js").default} connection - Connection that served the acquisition.
|
|
346
|
+
* @returns {AdvisoryLockBackendProvenance} - Acquisition provenance.
|
|
347
|
+
*/
|
|
348
|
+
driverProvenance(connection) {
|
|
349
|
+
let driverType = null
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
driverType = connection.getType()
|
|
353
|
+
} catch {
|
|
354
|
+
// Test stub drivers may not implement getType(); the tier alone identifies the backend.
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const tier = typeof connection.advisoryLockBackendTier === "function"
|
|
358
|
+
? connection.advisoryLockBackendTier()
|
|
359
|
+
: (driverType === "sqlite" ? "process-local" : "driver-native")
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
backend: /** @type {import("./advisory-lock-backend-policy.js").AdvisoryLockTier} */ (tier),
|
|
363
|
+
driverType: driverType ?? undefined,
|
|
364
|
+
fencingSupported: tier === "driver-native"
|
|
163
365
|
}
|
|
164
366
|
}
|
|
165
367
|
|
|
@@ -218,16 +420,18 @@ export default class AdvisoryLockRunner {
|
|
|
218
420
|
*
|
|
219
421
|
* The callback receives a `TimeoutControl` from awaitery, enabling cooperative
|
|
220
422
|
* cancellation via `control.check()`, `control.signal`, `control.timedOut`,
|
|
221
|
-
* and `control.remaining()
|
|
423
|
+
* and `control.remaining()`, plus `lockBackend` provenance for the tier that
|
|
424
|
+
* served the acquisition.
|
|
222
425
|
* @template T
|
|
223
426
|
* @param {string} name - Lock name (for the error message).
|
|
224
|
-
* @param {(args?: {control
|
|
427
|
+
* @param {(args?: {control?: import("awaitery/build/timeout.js").TimeoutControl, lockBackend: AdvisoryLockBackendProvenance}) => Promise<T>} callback - Callback holding the lock.
|
|
225
428
|
* @param {number | null} [holdTimeoutMs] - Max hold time; falsy disables the timeout.
|
|
429
|
+
* @param {AdvisoryLockBackendProvenance} [lockBackend] - Provenance of the acquisition serving this hold.
|
|
226
430
|
* @returns {Promise<T>} - Resolves with the callback result.
|
|
227
431
|
*/
|
|
228
|
-
static async runWithAdvisoryLockHoldTimeout(name, callback, holdTimeoutMs) {
|
|
432
|
+
static async runWithAdvisoryLockHoldTimeout(name, callback, holdTimeoutMs, lockBackend) {
|
|
229
433
|
if (!holdTimeoutMs || holdTimeoutMs <= 0) {
|
|
230
|
-
return await callback()
|
|
434
|
+
return await callback({lockBackend: lockBackend ?? AdvisoryLockRunner.unknownBackendProvenance()})
|
|
231
435
|
}
|
|
232
436
|
|
|
233
437
|
let callbackSettled = false
|
|
@@ -235,7 +439,7 @@ export default class AdvisoryLockRunner {
|
|
|
235
439
|
try {
|
|
236
440
|
return await timeout({timeout: holdTimeoutMs}, async ({control}) => {
|
|
237
441
|
try {
|
|
238
|
-
return await callback({control})
|
|
442
|
+
return await callback({control, lockBackend: lockBackend ?? AdvisoryLockRunner.unknownBackendProvenance()})
|
|
239
443
|
} finally {
|
|
240
444
|
callbackSettled = true
|
|
241
445
|
}
|
|
@@ -248,6 +452,25 @@ export default class AdvisoryLockRunner {
|
|
|
248
452
|
throw error
|
|
249
453
|
}
|
|
250
454
|
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Neutral provenance for direct `runWithAdvisoryLockHoldTimeout` callers that
|
|
458
|
+
* hold a lock acquired outside this runner.
|
|
459
|
+
* @returns {AdvisoryLockBackendProvenance} - Provenance with an unknown tier.
|
|
460
|
+
*/
|
|
461
|
+
static unknownBackendProvenance() {
|
|
462
|
+
return {backend: "process-local", fencingSupported: false}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Runs advisory lock policy allows file tier.
|
|
468
|
+
* @param {import("./advisory-lock-backend-policy.js").ResolvedAdvisoryLockBackendPolicy} policy - Resolved policy.
|
|
469
|
+
* @returns {boolean} - Whether the file tier may serve this name.
|
|
470
|
+
*/
|
|
471
|
+
function advisoryLockPolicyAllowsFileTier(policy) {
|
|
472
|
+
return policy.allowedTiers.includes("file")
|
|
251
473
|
}
|
|
252
474
|
|
|
253
475
|
export {AdvisoryLockBusyError, AdvisoryLockHoldTimeoutError, AdvisoryLockTimeoutError}
|
|
476
|
+
export {AdvisoryLockBackendUnavailableError, AdvisoryLockMinTierRefusedError} from "./advisory-lock-backend-policy.js"
|