@actiondock/core 2.10.0 → 2.12.0
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 -2
- package/dist/catalog/action-catalog.js +3 -33
- package/dist/catalog/discovery.d.ts +0 -3
- package/dist/catalog/discovery.js +4 -4
- package/dist/catalog/graph.d.ts +0 -4
- package/dist/catalog/graph.js +8 -14
- package/dist/{profile/client-actions.d.ts → client/actions.d.ts} +1 -1
- package/dist/{profile/client-actions.js → client/actions.js} +2 -8
- package/dist/{profile/client-config.d.ts → client/config.d.ts} +1 -1
- package/dist/{profile/client-config.js → client/config.js} +1 -1
- package/dist/client/dispatcher.d.ts +4 -0
- package/dist/client/dispatcher.js +21 -0
- package/dist/client/health.d.ts +38 -0
- package/dist/{profile/client-health.js → client/health.js} +9 -6
- package/dist/client/index.d.ts +16 -0
- package/dist/client/index.js +20 -0
- package/dist/{profile/client-playbooks.d.ts → client/playbooks.d.ts} +1 -1
- package/dist/{profile/client-playbooks.js → client/playbooks.js} +1 -1
- package/dist/{profile/client-runs.d.ts → client/runs.d.ts} +4 -2
- package/dist/{profile/client-runs.js → client/runs.js} +7 -1
- package/dist/{profile/client-state.d.ts → client/state.d.ts} +1 -1
- package/dist/{profile/client-state.js → client/state.js} +1 -1
- package/dist/{profile/client-transport.d.ts → client/transport.d.ts} +29 -10
- package/dist/{profile/client-transport.js → client/transport.js} +125 -35
- package/dist/doctor/checks/index.d.ts +1 -1
- package/dist/doctor/checks/index.js +1 -1
- package/dist/doctor/checks/project.js +20 -88
- package/dist/doctor/checks/runtime.d.ts +0 -4
- package/dist/doctor/checks/runtime.js +0 -28
- package/dist/doctor/doctor.js +1 -2
- package/dist/errors.d.ts +53 -3
- package/dist/errors.js +53 -3
- package/dist/execution/service.d.ts +55 -37
- package/dist/execution/service.js +601 -212
- package/dist/execution/types.d.ts +24 -5
- package/dist/host/host.d.ts +42 -22
- package/dist/host/host.js +562 -149
- package/dist/host/routing.js +6 -6
- package/dist/host/types.d.ts +25 -13
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/input/advice.d.ts +0 -44
- package/dist/input/advice.js +7 -141
- package/dist/input/flat-materializer.js +1 -1
- package/dist/input/flat-parser.js +1 -1
- package/dist/input/index.d.ts +0 -1
- package/dist/input/index.js +0 -1
- package/dist/input/input-resolver.js +1 -1
- package/dist/input/validation-mapper.d.ts +1 -1
- package/dist/ipc/diagnostic.d.ts +8 -21
- package/dist/ipc/diagnostic.js +24 -57
- package/dist/ipc/host.js +21 -7
- package/dist/ipc/service.d.ts +14 -0
- package/dist/ipc/service.js +132 -26
- package/dist/ipc/types.d.ts +0 -4
- package/dist/package/index.d.ts +4 -5
- package/dist/package/index.js +1 -2
- package/dist/package/runtime.d.ts +94 -23
- package/dist/package/runtime.js +628 -274
- package/dist/package/static-index.d.ts +3 -6
- package/dist/package/static-index.js +11 -40
- package/dist/package/types.d.ts +19 -4
- package/dist/platform/index.d.ts +2 -1
- package/dist/platform/index.js +2 -1
- package/dist/platform/loader-hook.d.ts +32 -0
- package/dist/platform/loader-hook.js +90 -0
- package/dist/{node → platform}/module-loader.d.ts +13 -0
- package/dist/{node → platform}/module-loader.js +51 -4
- package/dist/platform/node.d.ts +19 -8
- package/dist/platform/node.js +50 -20
- package/dist/platform/types.d.ts +7 -47
- package/dist/process/context-process.d.ts +3 -18
- package/dist/process/context-process.js +3 -44
- package/dist/process/control.d.ts +21 -0
- package/dist/process/control.js +34 -0
- package/dist/process/converters.d.ts +23 -0
- package/dist/process/converters.js +92 -0
- package/dist/process/cursor.js +2 -22
- package/dist/process/{diagnostics-sink.d.ts → diagnostics.d.ts} +0 -7
- package/dist/process/{diagnostics-sink.js → diagnostics.js} +6 -8
- package/dist/process/index.d.ts +1 -3
- package/dist/process/index.js +1 -3
- package/dist/process/input-dispatcher.d.ts +4 -13
- package/dist/process/input-dispatcher.js +48 -69
- package/dist/process/managed-record.d.ts +3 -31
- package/dist/process/metadata-store.d.ts +0 -41
- package/dist/process/metadata-store.js +2 -341
- package/dist/process/process-driver.js +2 -2
- package/dist/process/process-manager.d.ts +8 -50
- package/dist/process/process-manager.js +28 -417
- package/dist/process/quotas.d.ts +14 -0
- package/dist/process/quotas.js +29 -0
- package/dist/process/run-executor.js +0 -1
- package/dist/process/terminal-output-cache.d.ts +20 -34
- package/dist/process/terminal-output-cache.js +43 -64
- package/dist/profile/index.d.ts +1 -2
- package/dist/profile/index.js +1 -2
- package/dist/profile/manager.d.ts +2 -4
- package/dist/profile/manager.js +10 -40
- package/dist/profile/types.d.ts +1 -17
- package/dist/project/digest.d.ts +5 -1
- package/dist/project/digest.js +81 -219
- package/dist/project/index.d.ts +4 -5
- package/dist/project/index.js +4 -5
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +3 -3
- package/dist/project/loader.js +72 -50
- package/dist/project/lockfile.js +6 -5
- package/dist/project/manifest.js +5 -4
- package/dist/project/transactions.d.ts +4 -42
- package/dist/project/transactions.js +83 -138
- package/dist/project/types-generator.js +2 -1
- package/dist/project/types.d.ts +2 -0
- package/dist/registry/index.d.ts +0 -1
- package/dist/registry/index.js +0 -1
- package/dist/registry/lock.d.ts +2 -11
- package/dist/registry/lock.js +14 -183
- package/dist/registry/registry.d.ts +1 -6
- package/dist/registry/registry.js +5 -17
- package/dist/runtime/action-registry.d.ts +1 -5
- package/dist/runtime/action-registry.js +0 -12
- package/dist/runtime/context.js +2 -2
- package/dist/runtime/events.d.ts +11 -50
- package/dist/runtime/events.js +88 -368
- package/dist/runtime/index.d.ts +0 -4
- package/dist/runtime/index.js +0 -4
- package/dist/runtime/run-persistence.d.ts +2 -0
- package/dist/runtime/run-persistence.js +2 -1
- package/dist/runtime/standalone-commands.d.ts +18 -0
- package/dist/runtime/standalone-commands.js +466 -0
- package/dist/runtime/standalone-help.d.ts +20 -0
- package/dist/runtime/standalone-help.js +72 -0
- package/dist/runtime/standalone.d.ts +1 -44
- package/dist/runtime/standalone.js +32 -573
- package/dist/schema/validator.d.ts +2 -2
- package/dist/schema/validator.js +2 -9
- package/dist/server/index.d.ts +0 -2
- package/dist/server/index.js +0 -2
- package/dist/server/routes/actions.js +1 -1
- package/dist/server/routes/info.js +1 -1
- package/dist/server/routes/playbooks.js +1 -1
- package/dist/server/routes/runs.js +123 -16
- package/dist/server/routes/state.js +43 -18
- package/dist/server/security.d.ts +1 -1
- package/dist/server/security.js +2 -2
- package/dist/server/server.js +3 -7
- package/dist/service/factory.js +4 -3
- package/dist/service/index.d.ts +1 -1
- package/dist/service/index.js +1 -1
- package/dist/service/remote-errors.d.ts +1 -1
- package/dist/service/remote-errors.js +6 -6
- package/dist/service/remote-polling.d.ts +3 -0
- package/dist/service/remote-polling.js +10 -5
- package/dist/service/remote.d.ts +5 -1
- package/dist/service/remote.js +108 -17
- package/dist/service/sse-stream.js +2 -2
- package/dist/service/types.d.ts +22 -19
- package/dist/service/types.js +1 -24
- package/dist/storage/clock.d.ts +1 -1
- package/dist/storage/clock.js +1 -1
- package/dist/storage/data-dir-lock.d.ts +10 -22
- package/dist/storage/data-dir-lock.js +28 -56
- package/dist/storage/file-lock.d.ts +64 -0
- package/dist/storage/file-lock.js +447 -0
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.js +5 -4
- package/dist/storage/run-liveness.d.ts +48 -0
- package/dist/storage/run-liveness.js +44 -0
- package/dist/storage/sqlite-config.d.ts +16 -0
- package/dist/storage/sqlite-config.js +59 -0
- package/dist/storage/sqlite-driver.d.ts +3 -1
- package/dist/storage/sqlite-driver.js +14 -4
- package/dist/storage/sqlite-events.d.ts +13 -0
- package/dist/storage/sqlite-events.js +48 -0
- package/dist/storage/sqlite-runs.d.ts +107 -0
- package/dist/storage/sqlite-runs.js +622 -0
- package/dist/storage/sqlite-schema.d.ts +5 -0
- package/dist/storage/sqlite-schema.js +143 -0
- package/dist/storage/sqlite-state.d.ts +33 -0
- package/dist/storage/sqlite-state.js +270 -0
- package/dist/storage/sqlite.d.ts +30 -57
- package/dist/storage/sqlite.js +84 -741
- package/dist/storage/types.d.ts +35 -2
- package/dist/utils/index.d.ts +18 -14
- package/dist/utils/index.js +37 -29
- package/dist/utils/semver.d.ts +18 -0
- package/dist/utils/semver.js +19 -0
- package/dist/utils/warning.d.ts +1 -0
- package/dist/utils/warning.js +52 -0
- package/dist/{json/value-validator.d.ts → value-validator.d.ts} +9 -0
- package/dist/value-validator.js +330 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +10 -12
- package/dist/execution/index.d.ts +0 -2
- package/dist/execution/index.js +0 -2
- package/dist/filter/index.d.ts +0 -1
- package/dist/filter/index.js +0 -1
- package/dist/graph/index.d.ts +0 -4
- package/dist/graph/index.js +0 -4
- package/dist/input/metadata.d.ts +0 -99
- package/dist/input/metadata.js +0 -99
- package/dist/invocation/index.d.ts +0 -2
- package/dist/invocation/index.js +0 -2
- package/dist/ipc/index.d.ts +0 -4
- package/dist/ipc/index.js +0 -4
- package/dist/json/index.d.ts +0 -1
- package/dist/json/index.js +0 -1
- package/dist/json/value-validator.js +0 -330
- package/dist/node/index.d.ts +0 -1
- package/dist/node/index.js +0 -1
- package/dist/platform/node-fs.d.ts +0 -37
- package/dist/platform/node-fs.js +0 -81
- package/dist/process/control-arbiter.d.ts +0 -108
- package/dist/process/control-arbiter.js +0 -250
- package/dist/process/process-executor.d.ts +0 -47
- package/dist/process/process-executor.js +0 -296
- package/dist/profile/client-health.d.ts +0 -14
- package/dist/profile/client-query.d.ts +0 -30
- package/dist/profile/client-query.js +0 -69
- package/dist/profile/client.d.ts +0 -26
- package/dist/profile/client.js +0 -31
- package/dist/project/closure.d.ts +0 -25
- package/dist/project/closure.js +0 -74
- package/dist/registry/store.d.ts +0 -42
- package/dist/registry/store.js +0 -43
- package/dist/runtime/clock.d.ts +0 -7
- package/dist/runtime/clock.js +0 -7
- package/dist/runtime/module-loader.d.ts +0 -2
- package/dist/runtime/module-loader.js +0 -1
- package/dist/runtime/process.d.ts +0 -8
- package/dist/runtime/process.js +0 -1
- package/dist/runtime/runner.d.ts +0 -234
- package/dist/runtime/runner.js +0 -609
- package/dist/schema/index.d.ts +0 -1
- package/dist/schema/index.js +0 -1
- package/dist/server/dispatcher.d.ts +0 -25
- package/dist/server/dispatcher.js +0 -54
- package/dist/service/local.d.ts +0 -27
- package/dist/service/local.js +0 -141
- package/dist/storage/driver.d.ts +0 -10
- package/dist/storage/driver.js +0 -10
- package/dist/storage/lazy.d.ts +0 -8
- package/dist/storage/lazy.js +0 -45
- package/dist/storage/lock-core.d.ts +0 -158
- package/dist/storage/lock-core.js +0 -872
- package/dist/utils/net.d.ts +0 -10
- package/dist/utils/net.js +0 -16
- package/dist/utils/process.d.ts +0 -10
- package/dist/utils/process.js +0 -21
- /package/dist/{filter → utils}/intent.d.ts +0 -0
- /package/dist/{filter → utils}/intent.js +0 -0
package/dist/service/remote.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { parseActionRef } from "../catalog/resolve-action.js";
|
|
2
2
|
import { ActionDockError, ACTION_CANCELLED, CAPABILITY_UNAVAILABLE, INVALID_ARGUMENT, TIMEOUT, } from "../errors.js";
|
|
3
|
-
import {
|
|
3
|
+
import { createAbortError } from "../ipc/service.js";
|
|
4
|
+
import { cancelRemoteRun, clearRemoteRuns, cleanExpiredRemoteRuns, clearRemoteState, deleteRemoteConfig, deleteRemoteStateKey, executeRemoteAction, fetchRemoteActionShow, fetchRemoteActions, fetchRemoteConfig, fetchRemoteInfo, fetchRemotePlaybookShow, fetchRemotePlaybooks, fetchRemoteRun, fetchRemoteRuns, fetchRemoteStateList, getRemoteStateKey, setRemoteConfig, setRemoteStateKey, } from "../client/index.js";
|
|
4
5
|
import { isRemoteStateKeyNotFound, wrapRemoteError } from "./remote-errors.js";
|
|
5
6
|
import { formatTerminalRunResult, pollRunCompletion } from "./remote-polling.js";
|
|
6
7
|
import { streamRemoteEvents } from "./sse-stream.js";
|
|
8
|
+
import { SystemClock } from "../storage/clock.js";
|
|
7
9
|
import { isTerminalRunStatus } from "../storage/types.js";
|
|
8
|
-
import { ACTIONDOCK_PROTOCOL_VERSION, PROTOCOL_UNSUPPORTED, SERVICE_CLOSED,
|
|
10
|
+
import { ACTIONDOCK_PROTOCOL_VERSION, PROTOCOL_UNSUPPORTED, SERVICE_CLOSED, } from "./types.js";
|
|
9
11
|
/**
|
|
10
12
|
* 远程 ActionDockService 端口实现。
|
|
11
13
|
* 封装与远端 ActionDock 服务的 HTTP / SSE 协议交互,
|
|
@@ -19,6 +21,7 @@ export class RemoteActionDockService {
|
|
|
19
21
|
allowInsecureHttp;
|
|
20
22
|
insecure;
|
|
21
23
|
dispatcher;
|
|
24
|
+
clock;
|
|
22
25
|
isClosed = false;
|
|
23
26
|
discovery;
|
|
24
27
|
execution;
|
|
@@ -36,6 +39,7 @@ export class RemoteActionDockService {
|
|
|
36
39
|
this.allowInsecureHttp = options.allowInsecureHttp;
|
|
37
40
|
this.insecure = options.insecure;
|
|
38
41
|
this.dispatcher = options.dispatcher;
|
|
42
|
+
this.clock = options.clock ?? new SystemClock();
|
|
39
43
|
const self = this;
|
|
40
44
|
this.discovery = {
|
|
41
45
|
async listPackages() {
|
|
@@ -56,7 +60,7 @@ export class RemoteActionDockService {
|
|
|
56
60
|
const [remoteMajor] = protocolVersion.split(".");
|
|
57
61
|
const [currentMajor] = ACTIONDOCK_PROTOCOL_VERSION.split(".");
|
|
58
62
|
if (remoteMajor !== currentMajor) {
|
|
59
|
-
throw new
|
|
63
|
+
throw new ActionDockError(PROTOCOL_UNSUPPORTED, `PROTOCOL_UNSUPPORTED: Remote server protocol version '${protocolVersion}' is incompatible with expected '${ACTIONDOCK_PROTOCOL_VERSION}'`);
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
if (Array.isArray(raw)) {
|
|
@@ -224,35 +228,121 @@ export class RemoteActionDockService {
|
|
|
224
228
|
},
|
|
225
229
|
};
|
|
226
230
|
this.runs = {
|
|
227
|
-
async list(query) {
|
|
231
|
+
async list(query, options) {
|
|
228
232
|
self.assertNotClosed();
|
|
233
|
+
const effectiveSignal = options?.signal ?? query?.signal;
|
|
234
|
+
try {
|
|
235
|
+
const SERVER_MAX_PAGE_SIZE = 500;
|
|
236
|
+
const SERVER_MAX_OFFSET = 100_000;
|
|
237
|
+
if (query?.offset !== undefined && (query.offset < 0 || query.offset > SERVER_MAX_OFFSET || !Number.isSafeInteger(query.offset))) {
|
|
238
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid offset '${query.offset}': must be a non-negative safe integer <= ${SERVER_MAX_OFFSET}`);
|
|
239
|
+
}
|
|
240
|
+
const requestedLimit = query?.limit;
|
|
241
|
+
let offset = query?.offset ?? 0;
|
|
242
|
+
const allItems = [];
|
|
243
|
+
while (true) {
|
|
244
|
+
if (effectiveSignal?.aborted) {
|
|
245
|
+
throw createAbortError(effectiveSignal.reason);
|
|
246
|
+
}
|
|
247
|
+
if (offset > SERVER_MAX_OFFSET) {
|
|
248
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid offset '${offset}': must be a non-negative safe integer <= ${SERVER_MAX_OFFSET}`);
|
|
249
|
+
}
|
|
250
|
+
let batchLimit = SERVER_MAX_PAGE_SIZE;
|
|
251
|
+
if (requestedLimit !== undefined) {
|
|
252
|
+
const remaining = requestedLimit - allItems.length;
|
|
253
|
+
if (remaining <= 0) {
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
batchLimit = Math.min(remaining, SERVER_MAX_PAGE_SIZE);
|
|
257
|
+
}
|
|
258
|
+
const res = await fetchRemoteRuns(self.serverUrl, self.token, {
|
|
259
|
+
packageId: query?.packageId,
|
|
260
|
+
actionId: query?.actionId,
|
|
261
|
+
status: query?.status,
|
|
262
|
+
intent: query?.intent,
|
|
263
|
+
limit: batchLimit,
|
|
264
|
+
offset,
|
|
265
|
+
requestIds: query?.requestIds,
|
|
266
|
+
allowInsecureHttp: self.allowInsecureHttp,
|
|
267
|
+
insecure: self.insecure,
|
|
268
|
+
dispatcher: self.dispatcher,
|
|
269
|
+
signal: effectiveSignal,
|
|
270
|
+
});
|
|
271
|
+
if (effectiveSignal?.aborted) {
|
|
272
|
+
throw createAbortError(effectiveSignal.reason);
|
|
273
|
+
}
|
|
274
|
+
const items = res.items || [];
|
|
275
|
+
if (items.length === 0) {
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
allItems.push(...items);
|
|
279
|
+
offset += items.length;
|
|
280
|
+
if (requestedLimit !== undefined && allItems.length >= requestedLimit) {
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
if (typeof res.total === "number" && offset >= res.total) {
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
if (items.length < batchLimit) {
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
if (offset > SERVER_MAX_OFFSET) {
|
|
290
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid offset '${offset}': must be a non-negative safe integer <= ${SERVER_MAX_OFFSET}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return allItems;
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
if (effectiveSignal?.aborted || err?.name === "AbortError") {
|
|
297
|
+
throw createAbortError(effectiveSignal?.reason ?? err);
|
|
298
|
+
}
|
|
299
|
+
wrapRemoteError(err);
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
async count(query, options) {
|
|
303
|
+
self.assertNotClosed();
|
|
304
|
+
const effectiveSignal = options?.signal ?? query?.signal;
|
|
229
305
|
try {
|
|
230
306
|
const res = await fetchRemoteRuns(self.serverUrl, self.token, {
|
|
231
307
|
packageId: query?.packageId,
|
|
232
308
|
actionId: query?.actionId,
|
|
233
309
|
status: query?.status,
|
|
234
310
|
intent: query?.intent,
|
|
235
|
-
limit:
|
|
311
|
+
limit: 1,
|
|
312
|
+
offset: 0,
|
|
313
|
+
requestIds: query?.requestIds,
|
|
236
314
|
allowInsecureHttp: self.allowInsecureHttp,
|
|
237
315
|
insecure: self.insecure,
|
|
238
316
|
dispatcher: self.dispatcher,
|
|
317
|
+
signal: effectiveSignal,
|
|
239
318
|
});
|
|
240
|
-
|
|
319
|
+
if (typeof res.total === "number") {
|
|
320
|
+
return res.total;
|
|
321
|
+
}
|
|
322
|
+
const all = await self.runs.list({ ...query, limit: undefined, offset: undefined }, options);
|
|
323
|
+
return all.length;
|
|
241
324
|
}
|
|
242
325
|
catch (err) {
|
|
326
|
+
if (effectiveSignal?.aborted || err?.name === "AbortError") {
|
|
327
|
+
throw createAbortError(effectiveSignal?.reason ?? err);
|
|
328
|
+
}
|
|
243
329
|
wrapRemoteError(err);
|
|
244
330
|
}
|
|
245
331
|
},
|
|
246
|
-
async get(runId) {
|
|
332
|
+
async get(runId, options) {
|
|
247
333
|
self.assertNotClosed();
|
|
248
334
|
try {
|
|
249
335
|
return await fetchRemoteRun(self.serverUrl, runId, self.token, {
|
|
250
336
|
allowInsecureHttp: self.allowInsecureHttp,
|
|
251
337
|
insecure: self.insecure,
|
|
252
338
|
dispatcher: self.dispatcher,
|
|
339
|
+
signal: options?.signal,
|
|
253
340
|
});
|
|
254
341
|
}
|
|
255
342
|
catch (err) {
|
|
343
|
+
if (options?.signal?.aborted || err?.name === "AbortError") {
|
|
344
|
+
throw createAbortError(options?.signal?.reason ?? err);
|
|
345
|
+
}
|
|
256
346
|
const code = String(err?.code || "");
|
|
257
347
|
if (code === "RUN_NOT_FOUND" || code === "NOT_FOUND" || err?.status === 404) {
|
|
258
348
|
return undefined;
|
|
@@ -426,7 +516,7 @@ export class RemoteActionDockService {
|
|
|
426
516
|
try {
|
|
427
517
|
const res = await getRemoteStateKey(self.serverUrl, key, self.token, {
|
|
428
518
|
package: packageId || undefined,
|
|
429
|
-
action: actionId
|
|
519
|
+
action: actionId ?? undefined,
|
|
430
520
|
namespace: opts?.namespace,
|
|
431
521
|
allowInsecureHttp: self.allowInsecureHttp,
|
|
432
522
|
insecure: self.insecure,
|
|
@@ -451,7 +541,7 @@ export class RemoteActionDockService {
|
|
|
451
541
|
try {
|
|
452
542
|
await setRemoteStateKey(self.serverUrl, key, value, self.token, {
|
|
453
543
|
package: packageId || undefined,
|
|
454
|
-
action: actionId
|
|
544
|
+
action: actionId ?? undefined,
|
|
455
545
|
namespace: opts?.namespace,
|
|
456
546
|
ttl: opts?.ttl,
|
|
457
547
|
allowInsecureHttp: self.allowInsecureHttp,
|
|
@@ -468,7 +558,7 @@ export class RemoteActionDockService {
|
|
|
468
558
|
try {
|
|
469
559
|
const res = await deleteRemoteStateKey(self.serverUrl, key, self.token, {
|
|
470
560
|
package: packageId || undefined,
|
|
471
|
-
action: actionId
|
|
561
|
+
action: actionId ?? undefined,
|
|
472
562
|
namespace: opts?.namespace,
|
|
473
563
|
allowInsecureHttp: self.allowInsecureHttp,
|
|
474
564
|
insecure: self.insecure,
|
|
@@ -488,7 +578,7 @@ export class RemoteActionDockService {
|
|
|
488
578
|
try {
|
|
489
579
|
const res = await fetchRemoteStateList(self.serverUrl, self.token, {
|
|
490
580
|
package: packageId || undefined,
|
|
491
|
-
action: actionId
|
|
581
|
+
action: actionId ?? undefined,
|
|
492
582
|
namespace: opts?.namespace,
|
|
493
583
|
prefix: opts?.prefix,
|
|
494
584
|
allowInsecureHttp: self.allowInsecureHttp,
|
|
@@ -506,7 +596,7 @@ export class RemoteActionDockService {
|
|
|
506
596
|
try {
|
|
507
597
|
const res = await clearRemoteState(self.serverUrl, self.token, {
|
|
508
598
|
package: packageId || undefined,
|
|
509
|
-
action: actionId
|
|
599
|
+
action: actionId ?? undefined,
|
|
510
600
|
namespace: opts?.namespace,
|
|
511
601
|
prefix: opts?.prefix,
|
|
512
602
|
all: opts?.all,
|
|
@@ -522,7 +612,7 @@ export class RemoteActionDockService {
|
|
|
522
612
|
},
|
|
523
613
|
async listEntries(_packageId, _opts) {
|
|
524
614
|
self.assertNotClosed();
|
|
525
|
-
throw new
|
|
615
|
+
throw new ActionDockError(CAPABILITY_UNAVAILABLE, "CAPABILITY_UNAVAILABLE: listStateEntries is not supported on remote service");
|
|
526
616
|
},
|
|
527
617
|
},
|
|
528
618
|
};
|
|
@@ -530,14 +620,14 @@ export class RemoteActionDockService {
|
|
|
530
620
|
}
|
|
531
621
|
assertNotClosed() {
|
|
532
622
|
if (this.isClosed) {
|
|
533
|
-
throw new
|
|
623
|
+
throw new ActionDockError(SERVICE_CLOSED, "RemoteActionDockService is closed");
|
|
534
624
|
}
|
|
535
625
|
}
|
|
536
626
|
async info() {
|
|
537
627
|
return this.discovery.listPackages();
|
|
538
628
|
}
|
|
539
629
|
async waitForRunCompletion(runId, signal, timeoutMs) {
|
|
540
|
-
const startTime =
|
|
630
|
+
const startTime = this.clock.monotonic();
|
|
541
631
|
const maxWaitMs = Math.max(this.baseTimeoutMs, timeoutMs ?? 0);
|
|
542
632
|
// 服务已关闭时的统一失败结果
|
|
543
633
|
const closedResult = () => ({
|
|
@@ -607,7 +697,7 @@ export class RemoteActionDockService {
|
|
|
607
697
|
if (signal?.aborted) {
|
|
608
698
|
return cancelledResult();
|
|
609
699
|
}
|
|
610
|
-
const remainingWaitMs = Math.max(0, maxWaitMs - (
|
|
700
|
+
const remainingWaitMs = Math.max(0, maxWaitMs - (this.clock.monotonic() - startTime));
|
|
611
701
|
if (remainingWaitMs <= 0) {
|
|
612
702
|
if (this.isClosed) {
|
|
613
703
|
return closedResult();
|
|
@@ -624,7 +714,7 @@ export class RemoteActionDockService {
|
|
|
624
714
|
}
|
|
625
715
|
throw err;
|
|
626
716
|
}
|
|
627
|
-
const waitedMs =
|
|
717
|
+
const waitedMs = Math.round(this.clock.monotonic() - startTime);
|
|
628
718
|
return {
|
|
629
719
|
ok: false,
|
|
630
720
|
runId,
|
|
@@ -638,6 +728,7 @@ export class RemoteActionDockService {
|
|
|
638
728
|
baseTimeoutMs: this.baseTimeoutMs,
|
|
639
729
|
isClosed: () => this.isClosed,
|
|
640
730
|
getRun: (id) => this.runs.get(id),
|
|
731
|
+
clock: this.clock,
|
|
641
732
|
}, runId, signal, remainingWaitMs, startTime, maxWaitMs);
|
|
642
733
|
}
|
|
643
734
|
async close(_options) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ActionDockError, EVENT_CURSOR_EXPIRED, REMOTE_STREAM_UNAVAILABLE } from "../errors.js";
|
|
2
|
-
import { assertSecureTransport } from "../
|
|
2
|
+
import { assertSecureTransport } from "../client/index.js";
|
|
3
3
|
import { normalizeServerUrl } from "../profile/manager.js";
|
|
4
|
-
import { getInsecureDispatcher } from "../
|
|
4
|
+
import { getInsecureDispatcher } from "../client/dispatcher.js";
|
|
5
5
|
import { parseSseMessages } from "./sse-parser.js";
|
|
6
6
|
/**
|
|
7
7
|
* 将 SSE 消息载荷解码为统一执行事件。
|
package/dist/service/types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { CancelResult, ExecutionTicket } from "../execution/types.js";
|
|
|
4
4
|
import type { ActionDockHostOptions } from "../host/types.js";
|
|
5
5
|
import type { RuntimePlatform } from "../platform/types.js";
|
|
6
6
|
import type { StateEntry } from "../storage/types.js";
|
|
7
|
-
import {
|
|
7
|
+
import { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED } from "../errors.js";
|
|
8
8
|
/**
|
|
9
9
|
* 公开运行控制选项(RunOptions)。
|
|
10
10
|
* 面向外部调用方的公开契约,彻底剥离内部 Lineage 字段,严禁污染内部运行时状态。
|
|
@@ -27,20 +27,6 @@ export declare const ACTIONDOCK_PROTOCOL_VERSION = "2.0";
|
|
|
27
27
|
* 服务端/通信协议错误码常量(单一事实源引用自 errors.ts)。
|
|
28
28
|
*/
|
|
29
29
|
export { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED };
|
|
30
|
-
/**
|
|
31
|
-
* 结构化服务通信异常类。
|
|
32
|
-
*/
|
|
33
|
-
export declare class ServiceError extends ActionDockError {
|
|
34
|
-
readonly details?: Record<string, unknown>;
|
|
35
|
-
constructor(code: ErrorCode, message: string, details?: Record<string, unknown>);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* 服务关闭超时异常类。
|
|
39
|
-
*/
|
|
40
|
-
export declare class CloseTimeoutError extends Error {
|
|
41
|
-
readonly runIds?: string[];
|
|
42
|
-
constructor(message?: string, runIds?: string[]);
|
|
43
|
-
}
|
|
44
30
|
/**
|
|
45
31
|
* 配置项安全视图契约。
|
|
46
32
|
* 屏蔽敏感配置原文,仅暴露是否已配置、是否为秘密及实际解析来源。
|
|
@@ -86,8 +72,23 @@ export interface ListRunsOptions {
|
|
|
86
72
|
status?: string;
|
|
87
73
|
/** 模糊意图检索模式 */
|
|
88
74
|
intent?: string;
|
|
75
|
+
/** 幂等请求标识集合(经 idempotency_keys 反查关联运行记录) */
|
|
76
|
+
requestIds?: string[];
|
|
77
|
+
/** 偏移游标条数(跳过的记录数) */
|
|
78
|
+
offset?: number;
|
|
89
79
|
/** 最大返回记录条数限制 */
|
|
90
80
|
limit?: number;
|
|
81
|
+
/** 取消信号,用于中止读取请求 */
|
|
82
|
+
signal?: AbortSignal;
|
|
83
|
+
/** 包白名单过滤集合(可选) */
|
|
84
|
+
packageAllowlist?: string[];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 运行记录读取控制选项。
|
|
88
|
+
*/
|
|
89
|
+
export interface RunReadOptions {
|
|
90
|
+
/** 取消信号,用于中止读取请求 */
|
|
91
|
+
signal?: AbortSignal;
|
|
91
92
|
}
|
|
92
93
|
/**
|
|
93
94
|
* 远程服务连接配置选项。
|
|
@@ -160,9 +161,11 @@ export interface EventsPort {
|
|
|
160
161
|
*/
|
|
161
162
|
export interface RunsPort {
|
|
162
163
|
/** 查询任务执行记录列表 */
|
|
163
|
-
list(query?: ListRunsOptions): Promise<RunRecord[]>;
|
|
164
|
+
list(query?: ListRunsOptions, options?: RunReadOptions): Promise<RunRecord[]>;
|
|
165
|
+
/** 统计符合条件的任务执行记录总数 */
|
|
166
|
+
count?(query?: ListRunsOptions, options?: RunReadOptions): Promise<number>;
|
|
164
167
|
/** 查询指定运行标识的记录详情 */
|
|
165
|
-
get(runId: string): Promise<RunRecord | undefined>;
|
|
168
|
+
get(runId: string, options?: RunReadOptions): Promise<RunRecord | undefined>;
|
|
166
169
|
/** 取消指定在运行的任务 */
|
|
167
170
|
cancel(runId: string, reason?: string): Promise<CancelResult>;
|
|
168
171
|
/** 清空历史任务运行记录 */
|
|
@@ -200,9 +203,9 @@ export interface StatePort {
|
|
|
200
203
|
/** 删除指定包指定动作的作用域状态项 */
|
|
201
204
|
delete(packageId: string, actionId: string, key: string, options?: StateScopeOptions): Promise<boolean>;
|
|
202
205
|
/** 列出指定包指定动作在存储中的状态键名列表 */
|
|
203
|
-
list(packageId: string, actionId
|
|
206
|
+
list(packageId: string, actionId?: string, options?: StateScopeOptions): Promise<string[]>;
|
|
204
207
|
/** 清空指定包指定动作在存储中的状态条目 */
|
|
205
|
-
clear(packageId: string, actionId
|
|
208
|
+
clear(packageId: string, actionId?: string, options?: StateScopeOptions): Promise<number>;
|
|
206
209
|
/** 列出指定包在存储中的状态条目(含完整元数据) */
|
|
207
210
|
listEntries?(packageId: string, options?: any): Promise<StateEntry[]>;
|
|
208
211
|
}
|
package/dist/service/types.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED, } from "../errors.js";
|
|
2
2
|
/**
|
|
3
3
|
* 统一协议版本常量。
|
|
4
4
|
*/
|
|
@@ -7,26 +7,3 @@ export const ACTIONDOCK_PROTOCOL_VERSION = "2.0";
|
|
|
7
7
|
* 服务端/通信协议错误码常量(单一事实源引用自 errors.ts)。
|
|
8
8
|
*/
|
|
9
9
|
export { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED };
|
|
10
|
-
/**
|
|
11
|
-
* 结构化服务通信异常类。
|
|
12
|
-
*/
|
|
13
|
-
export class ServiceError extends ActionDockError {
|
|
14
|
-
details;
|
|
15
|
-
constructor(code, message, details) {
|
|
16
|
-
super(code, message, details);
|
|
17
|
-
this.name = "ServiceError";
|
|
18
|
-
this.details = details;
|
|
19
|
-
Object.setPrototypeOf(this, ServiceError.prototype);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* 服务关闭超时异常类。
|
|
24
|
-
*/
|
|
25
|
-
export class CloseTimeoutError extends Error {
|
|
26
|
-
runIds;
|
|
27
|
-
constructor(message = "Service close operation timed out", runIds) {
|
|
28
|
-
super(message);
|
|
29
|
-
this.name = "CloseTimeoutError";
|
|
30
|
-
this.runIds = runIds;
|
|
31
|
-
}
|
|
32
|
-
}
|
package/dist/storage/clock.d.ts
CHANGED
package/dist/storage/clock.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isProcessAlive } from "../utils/index.js";
|
|
2
|
+
import { type FileLockHandle } from "./file-lock.js";
|
|
2
3
|
/**
|
|
3
4
|
* 数据目录排他锁元数据契约。
|
|
4
5
|
*/
|
|
@@ -9,7 +10,7 @@ export interface DataDirLockInfo {
|
|
|
9
10
|
hostname: string;
|
|
10
11
|
/** 宿主会话令牌 */
|
|
11
12
|
sessionToken: string;
|
|
12
|
-
/**
|
|
13
|
+
/** 内部排他锁令牌 */
|
|
13
14
|
lockToken?: string;
|
|
14
15
|
/** 创建时间戳(ISO 8601 格式) */
|
|
15
16
|
createdAt: string;
|
|
@@ -18,12 +19,11 @@ export interface DataDirLockInfo {
|
|
|
18
19
|
/** 宿主会话标识 */
|
|
19
20
|
hostSessionId?: string;
|
|
20
21
|
}
|
|
21
|
-
export { isProcessAlive
|
|
22
|
+
export { isProcessAlive };
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
-
* 仅当锁目录中持有当前 lockToken / sessionToken 时才删除,杜绝删除他人新锁。
|
|
24
|
+
* 安全释放主锁(兼容导出)。
|
|
25
25
|
*/
|
|
26
|
-
export declare function safeReleaseLock(lockPath: string, expectedSessionToken
|
|
26
|
+
export declare function safeReleaseLock(lockPath: string, expectedSessionToken?: string, expectedLockToken?: string): void;
|
|
27
27
|
/**
|
|
28
28
|
* 数据目录排他文件锁管理器。
|
|
29
29
|
* 负责在数据目录下维护 .actiondock.data.lock 排他文件锁,记录宿主进程与子进程运行状态,
|
|
@@ -32,8 +32,9 @@ export declare function safeReleaseLock(lockPath: string, expectedSessionToken:
|
|
|
32
32
|
export declare class DataDirLock {
|
|
33
33
|
private readonly lockDirPath;
|
|
34
34
|
private readonly info;
|
|
35
|
+
private readonly handle?;
|
|
35
36
|
private released;
|
|
36
|
-
constructor(lockDirPath: string, info: DataDirLockInfo);
|
|
37
|
+
constructor(lockDirPath: string, info: DataDirLockInfo, handle?: FileLockHandle);
|
|
37
38
|
/**
|
|
38
39
|
* 获取当前排他锁元数据信息。
|
|
39
40
|
*/
|
|
@@ -52,7 +53,6 @@ export declare class DataDirLock {
|
|
|
52
53
|
unregisterChildPid(pid: number): void;
|
|
53
54
|
/**
|
|
54
55
|
* 将当前锁元数据刷新持久化至磁盘文件。
|
|
55
|
-
* 先写入临时文件再通过 renameSync 原子替换,消除 truncate 空文件窗口。
|
|
56
56
|
*/
|
|
57
57
|
private flush;
|
|
58
58
|
/**
|
|
@@ -60,20 +60,8 @@ export declare class DataDirLock {
|
|
|
60
60
|
*/
|
|
61
61
|
release(): void;
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* 仲裁规则由目录锁内核(lock-core)统一承载:
|
|
66
|
-
* - 引入所有竞争者均遵守的原子 reclaim guard 机制(lockDirPath.reclaim),每个 guard 具备全局唯一 guardToken。
|
|
67
|
-
* - 基于原子目录创建 mkdirSync(lockDirPath, { mode: 0o700 }) 确立所有权,并在其下存放 metadata.json。
|
|
68
|
-
* - 兼容已有常规文件锁(如老版本或测试 mock 场景)。
|
|
69
|
-
* - 若检测到 reclaim guard 存在且处于宽限期内或持有者存活,必须等待,禁止抢先创建主锁。
|
|
70
|
-
* - 若锁目录已存在,通过宽限期机制防止将并发写入中的元数据误判为锁死亡。
|
|
71
|
-
* - 若主进程仍处于存活状态,抛出 DATA_DIR_IN_USE 错误拒绝并发启动。
|
|
72
|
-
* - 若主进程已退出但仍有子进程存活,抛出 DATA_DIR_RECOVERY_REQUIRED 错误。
|
|
73
|
-
* - 当识别到主锁为陈旧锁时,竞争者必须先原子竞争获取 reclaim guard。
|
|
74
|
-
* - 仅成功获取 reclaim guard 的唯一胜利者获准执行:复核主锁陈旧性 -> 验证并清理陈旧主锁 -> 原子创建新主锁并写入自身元数据 -> 清理 reclaim guard。
|
|
75
|
-
* - 竞争失败者等待并 continue 重试;若 reclaim guard 持有者意外崩溃,其他竞争者在超过宽限期且 PID 已死后通过 safeRemoveStaleReclaimGuard 核对 guardToken 并清理。
|
|
76
|
-
* - 严禁在获取新主锁遇到 EEXIST 时盲目 rmSync;后检发现他人活跃 guard 时,仅通过 safeRollbackLock 核对自身 sessionToken 回滚自身锁。
|
|
63
|
+
* 获取指定数据目录的排他锁。
|
|
64
|
+
* 基于统一轻量文件锁原语实现,非阻塞且支持原子抢占与崩溃恢复。
|
|
77
65
|
*
|
|
78
66
|
* @param dataDir 目标数据存储目录物理绝对路径
|
|
79
67
|
* @param options 锁配置参数
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
|
-
import {
|
|
3
|
+
import { join } from "node:path";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { DATA_DIR_IN_USE, DATA_DIR_RECOVERY_REQUIRED } from "../errors.js";
|
|
6
|
-
import {
|
|
6
|
+
import { isProcessAlive } from "../utils/index.js";
|
|
7
|
+
import { acquireFileLockSync, safeReleaseFileLockSync, } from "./file-lock.js";
|
|
7
8
|
const LOCK_DIR_NAME = ".actiondock.data.lock";
|
|
8
|
-
export { isProcessAlive
|
|
9
|
+
export { isProcessAlive };
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
-
* 仅当锁目录中持有当前 lockToken / sessionToken 时才删除,杜绝删除他人新锁。
|
|
11
|
+
* 安全释放主锁(兼容导出)。
|
|
12
12
|
*/
|
|
13
13
|
export function safeReleaseLock(lockPath, expectedSessionToken, expectedLockToken) {
|
|
14
|
-
|
|
14
|
+
safeReleaseFileLockSync(lockPath, expectedLockToken, expectedSessionToken);
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* 数据目录排他文件锁管理器。
|
|
@@ -21,10 +21,12 @@ export function safeReleaseLock(lockPath, expectedSessionToken, expectedLockToke
|
|
|
21
21
|
export class DataDirLock {
|
|
22
22
|
lockDirPath;
|
|
23
23
|
info;
|
|
24
|
+
handle;
|
|
24
25
|
released = false;
|
|
25
|
-
constructor(lockDirPath, info) {
|
|
26
|
+
constructor(lockDirPath, info, handle) {
|
|
26
27
|
this.lockDirPath = lockDirPath;
|
|
27
28
|
this.info = info;
|
|
29
|
+
this.handle = handle;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* 获取当前排他锁元数据信息。
|
|
@@ -65,7 +67,6 @@ export class DataDirLock {
|
|
|
65
67
|
}
|
|
66
68
|
/**
|
|
67
69
|
* 将当前锁元数据刷新持久化至磁盘文件。
|
|
68
|
-
* 先写入临时文件再通过 renameSync 原子替换,消除 truncate 空文件窗口。
|
|
69
70
|
*/
|
|
70
71
|
flush() {
|
|
71
72
|
try {
|
|
@@ -91,9 +92,6 @@ export class DataDirLock {
|
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
catch (err) {
|
|
94
|
-
// 元数据刷新失败不可中断宿主启动,但绝不能静默吞没:childPids 是
|
|
95
|
-
// DATA_DIR_RECOVERY_REQUIRED 判定的唯一依据,丢失会使恢复决策退化为直接接管。
|
|
96
|
-
// 输出单行告警携带原因,保证降级事件可观测。
|
|
97
95
|
const reason = err instanceof Error ? err.message : String(err);
|
|
98
96
|
console.warn(`[actiondock] data dir lock metadata flush failed (lock='${this.lockDirPath}'): ${reason}`);
|
|
99
97
|
}
|
|
@@ -105,30 +103,21 @@ export class DataDirLock {
|
|
|
105
103
|
if (this.released)
|
|
106
104
|
return;
|
|
107
105
|
this.released = true;
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
if (this.handle) {
|
|
107
|
+
this.handle.release();
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
safeReleaseFileLockSync(this.lockDirPath, this.info.lockToken, this.info.sessionToken);
|
|
111
|
+
}
|
|
110
112
|
}
|
|
111
113
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* 仲裁规则由目录锁内核(lock-core)统一承载:
|
|
115
|
-
* - 引入所有竞争者均遵守的原子 reclaim guard 机制(lockDirPath.reclaim),每个 guard 具备全局唯一 guardToken。
|
|
116
|
-
* - 基于原子目录创建 mkdirSync(lockDirPath, { mode: 0o700 }) 确立所有权,并在其下存放 metadata.json。
|
|
117
|
-
* - 兼容已有常规文件锁(如老版本或测试 mock 场景)。
|
|
118
|
-
* - 若检测到 reclaim guard 存在且处于宽限期内或持有者存活,必须等待,禁止抢先创建主锁。
|
|
119
|
-
* - 若锁目录已存在,通过宽限期机制防止将并发写入中的元数据误判为锁死亡。
|
|
120
|
-
* - 若主进程仍处于存活状态,抛出 DATA_DIR_IN_USE 错误拒绝并发启动。
|
|
121
|
-
* - 若主进程已退出但仍有子进程存活,抛出 DATA_DIR_RECOVERY_REQUIRED 错误。
|
|
122
|
-
* - 当识别到主锁为陈旧锁时,竞争者必须先原子竞争获取 reclaim guard。
|
|
123
|
-
* - 仅成功获取 reclaim guard 的唯一胜利者获准执行:复核主锁陈旧性 -> 验证并清理陈旧主锁 -> 原子创建新主锁并写入自身元数据 -> 清理 reclaim guard。
|
|
124
|
-
* - 竞争失败者等待并 continue 重试;若 reclaim guard 持有者意外崩溃,其他竞争者在超过宽限期且 PID 已死后通过 safeRemoveStaleReclaimGuard 核对 guardToken 并清理。
|
|
125
|
-
* - 严禁在获取新主锁遇到 EEXIST 时盲目 rmSync;后检发现他人活跃 guard 时,仅通过 safeRollbackLock 核对自身 sessionToken 回滚自身锁。
|
|
114
|
+
* 获取指定数据目录的排他锁。
|
|
115
|
+
* 基于统一轻量文件锁原语实现,非阻塞且支持原子抢占与崩溃恢复。
|
|
126
116
|
*
|
|
127
117
|
* @param dataDir 目标数据存储目录物理绝对路径
|
|
128
118
|
* @param options 锁配置参数
|
|
129
119
|
*/
|
|
130
120
|
static acquire(dataDir, options = {}) {
|
|
131
|
-
const timeoutMs = options.acquireTimeoutMs ?? 5000;
|
|
132
121
|
if (!existsSync(dataDir)) {
|
|
133
122
|
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
134
123
|
}
|
|
@@ -144,45 +133,28 @@ export class DataDirLock {
|
|
|
144
133
|
childPids: [],
|
|
145
134
|
hostSessionId: options.hostSessionId,
|
|
146
135
|
};
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
sessionToken: token,
|
|
154
|
-
lockToken,
|
|
155
|
-
acquireTimeoutMs: options.acquireTimeoutMs,
|
|
156
|
-
createLockError(message) {
|
|
157
|
-
const timeoutErr = new Error(message);
|
|
158
|
-
timeoutErr.code = DATA_DIR_IN_USE;
|
|
159
|
-
return timeoutErr;
|
|
160
|
-
},
|
|
161
|
-
messages: {
|
|
162
|
-
timeoutWaitingReclaimGuard: (holderPid) => `DATA_DIR_IN_USE: Timeout waiting for active reclaim guard (PID ${holderPid ?? "unknown"}) on data directory '${dataDir}' after ${timeoutMs}ms`,
|
|
163
|
-
timeoutAcquireBlockedByGuard: (holderPid) => `DATA_DIR_IN_USE: Timeout acquiring lock on data directory '${dataDir}' due to active reclaim guard (PID ${holderPid}) after ${timeoutMs}ms`,
|
|
164
|
-
timeoutAcquire: () => `DATA_DIR_IN_USE: Timeout acquiring lock on data directory '${dataDir}' after ${timeoutMs}ms`,
|
|
165
|
-
timeoutGracePeriod: () => `DATA_DIR_IN_USE: Timeout waiting for lock grace period on data directory '${dataDir}' after ${timeoutMs}ms`,
|
|
166
|
-
timeoutGuardContention: () => `DATA_DIR_IN_USE: Timeout contending for reclaim guard on data directory '${dataDir}' after ${timeoutMs}ms`,
|
|
167
|
-
timeoutReclaimStale: () => `DATA_DIR_IN_USE: Timeout reclaiming stale lock on data directory '${dataDir}' after ${timeoutMs}ms`,
|
|
168
|
-
timeoutCreate: () => `DATA_DIR_IN_USE: Timeout creating primary lock on data directory '${dataDir}' after ${timeoutMs}ms`,
|
|
136
|
+
const handle = acquireFileLockSync(lockDirPath, {
|
|
137
|
+
metadata: newLockInfo,
|
|
138
|
+
createLockError() {
|
|
139
|
+
const inUseErr = new Error(`DATA_DIR_IN_USE: Data directory '${dataDir}' is in use by another active Host process`);
|
|
140
|
+
inUseErr.code = DATA_DIR_IN_USE;
|
|
141
|
+
return inUseErr;
|
|
169
142
|
},
|
|
170
|
-
|
|
143
|
+
assertHolderReclaimable(info) {
|
|
171
144
|
const holderPid = info.pid;
|
|
172
|
-
if (isProcessAlive(holderPid)) {
|
|
145
|
+
if (holderPid && isProcessAlive(holderPid)) {
|
|
173
146
|
const inUseErr = new Error(`DATA_DIR_IN_USE: Data directory '${dataDir}' is in use by another active Host process (PID ${holderPid})`);
|
|
174
147
|
inUseErr.code = DATA_DIR_IN_USE;
|
|
175
148
|
throw inUseErr;
|
|
176
149
|
}
|
|
177
|
-
|
|
178
|
-
const activeChildren = (info.childPids || []).filter((childPid) => isProcessAlive(childPid));
|
|
150
|
+
const activeChildren = info.childPids?.filter((childPid) => isProcessAlive(childPid)) ?? [];
|
|
179
151
|
if (activeChildren.length > 0) {
|
|
180
|
-
const recoveryErr = new Error(`DATA_DIR_RECOVERY_REQUIRED: Data directory recovery required for '${dataDir}': previous host (PID ${holderPid}) exited but child processes (${activeChildren.join(", ")}) are still running`);
|
|
152
|
+
const recoveryErr = new Error(`DATA_DIR_RECOVERY_REQUIRED: Data directory recovery required for '${dataDir}': previous host (PID ${holderPid ?? "unknown"}) exited but child processes (${activeChildren.join(", ")}) are still running`);
|
|
181
153
|
recoveryErr.code = DATA_DIR_RECOVERY_REQUIRED;
|
|
182
154
|
throw recoveryErr;
|
|
183
155
|
}
|
|
184
156
|
},
|
|
185
|
-
onAcquired: () => new DataDirLock(lockDirPath, newLockInfo),
|
|
186
157
|
});
|
|
158
|
+
return new DataDirLock(lockDirPath, newLockInfo, handle);
|
|
187
159
|
}
|
|
188
160
|
}
|