@actiondock/core 2.10.0 → 2.11.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
|
@@ -1,121 +1,22 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { decodeBytes, encodeBytes, } from "@actiondock/sdk";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { NOT_FOUND, PROCESS_CANCELLED, PROCESS_LOST, PROCESS_SPAWN_ERROR, REQUEST_CONFLICT, SERVER_ERROR, UNSUPPORTED_CAPABILITY, OUTPUT_UNAVAILABLE, ProcessError, } from "../errors.js";
|
|
3
|
+
import { formatProcessScope, checkOwnerAuthorized, hashRequestPayload, toSdkProcessInfo, toStoredProcessRecord } from "./converters.js";
|
|
4
|
+
import { DiagnosticsSink } from "./diagnostics.js";
|
|
5
|
+
import { ProcessQuotaTracker } from "./quotas.js";
|
|
6
|
+
import { ProcessControlManager } from "./control.js";
|
|
5
7
|
import { ContextProcessAPI } from "./context-process.js";
|
|
6
|
-
import { ControlArbiter } from "./control-arbiter.js";
|
|
7
8
|
import { InputDispatcher } from "./input-dispatcher.js";
|
|
8
|
-
import { DiagnosticsSink } from "./diagnostics-sink.js";
|
|
9
9
|
import { MemoryProcessMetadataStore, } from "./metadata-store.js";
|
|
10
10
|
import { ProcessOutputLog } from "./output-log.js";
|
|
11
11
|
import { ReservationTable, } from "./reservation-table.js";
|
|
12
12
|
import { RunExecutor } from "./run-executor.js";
|
|
13
13
|
import { TerminalOutputCache } from "./terminal-output-cache.js";
|
|
14
|
-
|
|
15
|
-
* 格式化生成作用域唯一隔离字符串。
|
|
16
|
-
*/
|
|
17
|
-
export function formatProcessScope(owner) {
|
|
18
|
-
return `${owner.tenantId}:${owner.principalId}:${owner.packageInstanceId}:${owner.generationId}`;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* 校验调用方所有者凭据与目标记录是否完全匹配。
|
|
22
|
-
*/
|
|
23
|
-
function checkOwnerAuthorized(owner, target) {
|
|
24
|
-
if (!owner ||
|
|
25
|
-
!owner.tenantId ||
|
|
26
|
-
!owner.principalId ||
|
|
27
|
-
!owner.packageInstanceId ||
|
|
28
|
-
!owner.generationId) {
|
|
29
|
-
throw new ProcessError(ACCESS_DENIED, "Missing required owner identity fields");
|
|
30
|
-
}
|
|
31
|
-
if (owner.tenantId !== target.tenantId ||
|
|
32
|
-
owner.principalId !== target.principalId ||
|
|
33
|
-
owner.packageInstanceId !== target.packageInstanceId ||
|
|
34
|
-
owner.generationId !== target.generationId) {
|
|
35
|
-
throw new ProcessError(ACCESS_DENIED, "Access denied: owner identity mismatch");
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* 计算任意 JSON 负载对象的 SHA-256 哈希值。
|
|
40
|
-
*/
|
|
41
|
-
function hashRequestPayload(payload) {
|
|
42
|
-
return createHash("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* 将持久化记录转换为 SDK 标准 ProcessInfo 快照。
|
|
46
|
-
*/
|
|
47
|
-
function toSdkProcessInfo(record) {
|
|
48
|
-
const ctrl = (record.control ?? record.controlState ?? "free");
|
|
49
|
-
const exitDetails = record.exitCode !== undefined || record.exitSignal !== undefined
|
|
50
|
-
? { code: record.exitCode ?? null, signal: record.exitSignal ?? null }
|
|
51
|
-
: undefined;
|
|
52
|
-
return {
|
|
53
|
-
id: record.processId,
|
|
54
|
-
hostEpoch: record.hostEpoch,
|
|
55
|
-
state: record.state,
|
|
56
|
-
control: ctrl,
|
|
57
|
-
io: (record.ioConfig ?? { mode: "pipe" }),
|
|
58
|
-
capabilities: (record.capabilities ?? {
|
|
59
|
-
pty: false,
|
|
60
|
-
resize: false,
|
|
61
|
-
inputEOF: false,
|
|
62
|
-
interruptForeground: false,
|
|
63
|
-
terminationScope: "process",
|
|
64
|
-
}),
|
|
65
|
-
createdAt: record.createdAt ?? new Date().toISOString(),
|
|
66
|
-
exit: exitDetails,
|
|
67
|
-
endReason: record.endReason,
|
|
68
|
-
outputClosed: Boolean(record.outputClosed),
|
|
69
|
-
outputEndReason: record.outputEndReason,
|
|
70
|
-
effectiveLimits: (record.effectiveLimits ?? {
|
|
71
|
-
idleMs: 60000,
|
|
72
|
-
lifetimeMs: 3600000,
|
|
73
|
-
outputBufferBytes: 4 * 1024 * 1024,
|
|
74
|
-
}),
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* 将内部模型转换为持久化记录。
|
|
79
|
-
*/
|
|
80
|
-
function toStoredProcessRecord(owner, info, startRequestId) {
|
|
81
|
-
return {
|
|
82
|
-
processId: info.id,
|
|
83
|
-
tenantId: owner.tenantId,
|
|
84
|
-
principalId: owner.principalId,
|
|
85
|
-
packageInstanceId: owner.packageInstanceId,
|
|
86
|
-
generationId: owner.generationId,
|
|
87
|
-
hostEpoch: info.hostEpoch,
|
|
88
|
-
state: info.state,
|
|
89
|
-
controlState: info.control,
|
|
90
|
-
control: info.control,
|
|
91
|
-
ioConfig: info.io,
|
|
92
|
-
capabilities: info.capabilities,
|
|
93
|
-
createdAt: info.createdAt,
|
|
94
|
-
exitCode: info.exit ? info.exit.code : null,
|
|
95
|
-
exitSignal: info.exit ? info.exit.signal : null,
|
|
96
|
-
endReason: info.endReason ?? null,
|
|
97
|
-
outputClosed: info.outputClosed,
|
|
98
|
-
outputEndReason: info.outputEndReason ?? null,
|
|
99
|
-
inputClosed: false,
|
|
100
|
-
effectiveLimits: info.effectiveLimits,
|
|
101
|
-
startRequestId,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* 校验正整数毫秒时长参数,非法时抛出参数错误。
|
|
106
|
-
*/
|
|
107
|
-
function checkPositiveDurationMs(value, field) {
|
|
108
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
109
|
-
throw new ProcessError(INPUT_VALIDATION_FAILED, `Invalid ${field}: must be a positive integer (milliseconds)`, {
|
|
110
|
-
[field]: value,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
14
|
+
export { formatProcessScope } from "./converters.js";
|
|
114
15
|
/**
|
|
115
16
|
* 受管进程核心管理器。
|
|
116
17
|
*
|
|
117
18
|
* 职责范畴:
|
|
118
|
-
* -
|
|
19
|
+
* - 宿主身份与配额检查(每宿主进程、单进程输出缓冲上限)。
|
|
119
20
|
* - 严格归属所有者鉴权校验(基于 tenantId, principalId, packageInstanceId, generationId)。
|
|
120
21
|
* - 控制权状态机推进(free -> held -> quarantined -> closed)。
|
|
121
22
|
* - 输入队列去重与串行调度执行。
|
|
@@ -139,9 +40,10 @@ export class ProcessManager {
|
|
|
139
40
|
/** 一次性 run 执行器:仅依赖驱动、错误映射与输入校验,不触碰进程注册表 */
|
|
140
41
|
runExecutor;
|
|
141
42
|
/** 控制权仲裁器:独占控制权状态机全部推进路径的唯一持有者 */
|
|
142
|
-
controlArbiter;
|
|
143
43
|
/** 输入调度器:输入队列入队、串行调度与接管原语的唯一持有者 */
|
|
144
44
|
inputDispatcher;
|
|
45
|
+
quotaTracker;
|
|
46
|
+
controlManager;
|
|
145
47
|
initPromise;
|
|
146
48
|
isShutdown = false;
|
|
147
49
|
constructor(options) {
|
|
@@ -149,14 +51,8 @@ export class ProcessManager {
|
|
|
149
51
|
this.driver = options.driver;
|
|
150
52
|
this.metadataStore = options.metadataStore ?? new MemoryProcessMetadataStore();
|
|
151
53
|
this.quotas = {
|
|
152
|
-
maxActiveProcessesPerScope: options.quotas?.maxActiveProcessesPerScope ?? 8,
|
|
153
54
|
maxActiveProcessesPerHost: options.quotas?.maxActiveProcessesPerHost ?? 64,
|
|
154
55
|
maxOutputBufferBytesPerProcess: options.quotas?.maxOutputBufferBytesPerProcess ?? 4 * 1024 * 1024,
|
|
155
|
-
maxOutputBufferBytesPerHost: options.quotas?.maxOutputBufferBytesPerHost ?? 128 * 1024 * 1024,
|
|
156
|
-
maxPendingQueueBytesPerProcess: options.quotas?.maxPendingQueueBytesPerProcess ?? 1 * 1024 * 1024,
|
|
157
|
-
maxPendingQueueBytesPerHost: options.quotas?.maxPendingQueueBytesPerHost ?? 16 * 1024 * 1024,
|
|
158
|
-
maxWaitersPerProcess: options.quotas?.maxWaitersPerProcess ?? 8,
|
|
159
|
-
maxWaitersPerHost: options.quotas?.maxWaitersPerHost ?? 256,
|
|
160
56
|
};
|
|
161
57
|
this.defaultLimits = {
|
|
162
58
|
idleMs: options.defaultLimits?.idleMs ?? 60000,
|
|
@@ -169,33 +65,24 @@ export class ProcessManager {
|
|
|
169
65
|
this.reservationTable = new ReservationTable();
|
|
170
66
|
this.terminalOutputCache = new TerminalOutputCache({
|
|
171
67
|
retentionMs: this.terminalLogRetentionMs,
|
|
172
|
-
maxHostBufferBytes: this.quotas.maxOutputBufferBytesPerHost,
|
|
173
68
|
});
|
|
174
69
|
this.runExecutor = new RunExecutor(this.driver);
|
|
175
|
-
this.controlArbiter = new ControlArbiter({
|
|
176
|
-
persistState: (processId, patch) => this.persistState(processId, patch),
|
|
177
|
-
persistGrantReceipt: (grant, wait, proc) => this.persistReceipt({
|
|
178
|
-
hostEpoch: this.hostEpoch,
|
|
179
|
-
scope: proc.scope,
|
|
180
|
-
processId: proc.info.id,
|
|
181
|
-
requestId: wait.requestId,
|
|
182
|
-
}, grant, hashRequestPayload({ waitMs: wait.waitMs, ttlMs: wait.ttlMs })),
|
|
183
|
-
refreshIdleTimer: (proc) => this.refreshIdleTimer(proc),
|
|
184
|
-
quarantineProcess: (owner, processId, token, reason) => this.quarantineProcess(owner, processId, token, reason),
|
|
185
|
-
countHostAcquireWaiters: () => this.countHostAcquireWaiters(),
|
|
186
|
-
maxWaitersPerProcess: this.quotas.maxWaitersPerProcess,
|
|
187
|
-
maxWaitersPerHost: this.quotas.maxWaitersPerHost,
|
|
188
|
-
});
|
|
189
70
|
this.inputDispatcher = new InputDispatcher({
|
|
190
71
|
persistReceipt: (key, receipt, payloadHash) => this.persistReceipt(key, receipt, payloadHash),
|
|
191
72
|
persistState: (processId, patch) => this.persistState(processId, patch),
|
|
192
|
-
quarantineProcess: (owner, processId,
|
|
73
|
+
quarantineProcess: (owner, processId, reason) => this.quarantineProcess(owner, processId, reason),
|
|
193
74
|
refreshIdleTimer: (proc) => this.refreshIdleTimer(proc),
|
|
194
75
|
recordDiagnostic: (message, err) => this.recordDiagnostic(message, err),
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
76
|
+
});
|
|
77
|
+
this.quotaTracker = new ProcessQuotaTracker({
|
|
78
|
+
processes: this.processes,
|
|
79
|
+
quotas: this.quotas,
|
|
80
|
+
defaultLimits: this.defaultLimits,
|
|
81
|
+
});
|
|
82
|
+
this.controlManager = new ProcessControlManager({
|
|
83
|
+
persistState: (processId, patch) => this.persistState(processId, patch),
|
|
84
|
+
takeoverQueue: (proc, reason) => this.inputDispatcher.takeoverQueue(proc, reason)
|
|
85
|
+
});
|
|
199
86
|
}
|
|
200
87
|
/**
|
|
201
88
|
* 初始化宿主环境,原子性收敛旧宿主遗留的非终态进程。
|
|
@@ -293,10 +180,6 @@ export class ProcessManager {
|
|
|
293
180
|
}
|
|
294
181
|
// 防御性清理残余定时器(stop 内部已清理,此处兜底)
|
|
295
182
|
for (const proc of this.processes.values()) {
|
|
296
|
-
if (proc.ttlTimer) {
|
|
297
|
-
clearTimeout(proc.ttlTimer);
|
|
298
|
-
proc.ttlTimer = undefined;
|
|
299
|
-
}
|
|
300
183
|
if (proc.idleTimer) {
|
|
301
184
|
clearTimeout(proc.idleTimer);
|
|
302
185
|
proc.idleTimer = undefined;
|
|
@@ -356,7 +239,7 @@ export class ProcessManager {
|
|
|
356
239
|
return cached;
|
|
357
240
|
}
|
|
358
241
|
// 宿主与作用域配额检查
|
|
359
|
-
this.checkSpawnQuotas(scope, input.limits?.outputBufferBytes);
|
|
242
|
+
this.quotaTracker.checkSpawnQuotas(scope, input.limits?.outputBufferBytes);
|
|
360
243
|
// 驱动能力校验
|
|
361
244
|
const caps = this.driver.getCapabilities();
|
|
362
245
|
if (input.spec.io.mode === "pty" && !caps.pty) {
|
|
@@ -370,7 +253,6 @@ export class ProcessManager {
|
|
|
370
253
|
};
|
|
371
254
|
const outputLog = new ProcessOutputLog(this.hostEpoch, processId, {
|
|
372
255
|
maxBufferBytes: effectiveLimits.outputBufferBytes,
|
|
373
|
-
maxWaiters: this.quotas.maxWaitersPerProcess,
|
|
374
256
|
});
|
|
375
257
|
const info = {
|
|
376
258
|
id: processId,
|
|
@@ -388,11 +270,8 @@ export class ProcessManager {
|
|
|
388
270
|
owner: { ...owner },
|
|
389
271
|
scope,
|
|
390
272
|
outputLog,
|
|
391
|
-
controlEpoch: 0,
|
|
392
273
|
cancelEpoch: 0,
|
|
393
274
|
inputQueue: [],
|
|
394
|
-
pendingInputBytes: 0,
|
|
395
|
-
acquireWaiters: [],
|
|
396
275
|
inputClosed: false,
|
|
397
276
|
isDispatching: false,
|
|
398
277
|
effectiveLimits,
|
|
@@ -552,106 +431,6 @@ export class ProcessManager {
|
|
|
552
431
|
nextPageToken: res.nextPageToken,
|
|
553
432
|
};
|
|
554
433
|
}
|
|
555
|
-
/**
|
|
556
|
-
* 申请指定受管进程的独占控制令牌。
|
|
557
|
-
*/
|
|
558
|
-
async acquire(owner, id, input, call, runId) {
|
|
559
|
-
checkPositiveDurationMs(input.waitMs, "waitMs");
|
|
560
|
-
checkPositiveDurationMs(input.ttlMs, "ttlMs");
|
|
561
|
-
await this.ensureInitialized();
|
|
562
|
-
this.assertNotShutdown();
|
|
563
|
-
const proc = await this.getOrLoadProcess(owner, id);
|
|
564
|
-
if (proc.info.state === "exited" ||
|
|
565
|
-
proc.info.state === "failed" ||
|
|
566
|
-
proc.info.state === "lost") {
|
|
567
|
-
throw new ProcessError(CONTROL_REVOKED, "Process has terminated");
|
|
568
|
-
}
|
|
569
|
-
if (proc.info.control === "quarantined") {
|
|
570
|
-
throw new ProcessError(PROCESS_QUARANTINED, "Process is in quarantined state");
|
|
571
|
-
}
|
|
572
|
-
if (proc.info.control === "closed") {
|
|
573
|
-
throw new ProcessError(CONTROL_REVOKED, "Process control is closed");
|
|
574
|
-
}
|
|
575
|
-
const key = {
|
|
576
|
-
hostEpoch: this.hostEpoch,
|
|
577
|
-
scope: proc.scope,
|
|
578
|
-
processId: id,
|
|
579
|
-
requestId: input.requestId,
|
|
580
|
-
};
|
|
581
|
-
const payloadHash = hashRequestPayload({ waitMs: input.waitMs, ttlMs: input.ttlMs });
|
|
582
|
-
// 同步预占幂等请求,跨 await 窗口内拦截并发同 requestId 双重授权
|
|
583
|
-
const inFlight = this.reserveRequest(key, payloadHash, "acquire");
|
|
584
|
-
if (inFlight) {
|
|
585
|
-
return (await inFlight.promise);
|
|
586
|
-
}
|
|
587
|
-
try {
|
|
588
|
-
// 幂等去重检查
|
|
589
|
-
const existing = await this.metadataStore.getRequest(key);
|
|
590
|
-
if (existing) {
|
|
591
|
-
if (existing.payloadHash !== payloadHash) {
|
|
592
|
-
throw new ProcessError(REQUEST_CONFLICT, "Request conflict: identical requestId with different payload", {
|
|
593
|
-
requestId: input.requestId,
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
const cached = existing.receipt;
|
|
597
|
-
this.commitReservation(key, "acquire", cached);
|
|
598
|
-
return cached;
|
|
599
|
-
}
|
|
600
|
-
// 控制权仲裁:空闲且无等待者时直接授予,否则 FIFO 排队等待
|
|
601
|
-
// 排队路径凭据由唤醒方落盘;原请求 resolve 后在此处结算预约,保持两阶段时序不变
|
|
602
|
-
const outcome = this.controlArbiter.tryAcquire(proc, { requestId: input.requestId, waitMs: input.waitMs, ttlMs: input.ttlMs }, call, runId);
|
|
603
|
-
const grant = await outcome.waiter;
|
|
604
|
-
if (outcome.granted) {
|
|
605
|
-
await this.metadataStore.recordRequest(key, grant, payloadHash);
|
|
606
|
-
}
|
|
607
|
-
this.commitReservation(key, "acquire", grant);
|
|
608
|
-
return grant;
|
|
609
|
-
}
|
|
610
|
-
catch (err) {
|
|
611
|
-
// 失败路径:以原始错误结算预占,等待同 requestId 的并发调用方收到真实失败原因而非固定冲突错误
|
|
612
|
-
this.rejectReservation(key, "acquire", err);
|
|
613
|
-
throw err;
|
|
614
|
-
}
|
|
615
|
-
finally {
|
|
616
|
-
// 成功路径已在 commit 中移除预占;失败路径已在 catch 中以原始错误结算;
|
|
617
|
-
// 此处仅为既未 commit 也未 reject 的异常逃逸路径释放预占并唤醒等待方
|
|
618
|
-
this.rejectReservation(key, "acquire", new ProcessError(REQUEST_CONFLICT, "Concurrent acquire request did not produce a result", { requestId: input.requestId }));
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* 延长当前有效控制令牌的存活时间。
|
|
623
|
-
*/
|
|
624
|
-
async renew(owner, id, token, ttlMs, call) {
|
|
625
|
-
checkPositiveDurationMs(ttlMs, "ttlMs");
|
|
626
|
-
await this.ensureInitialized();
|
|
627
|
-
const proc = await this.getOrLoadProcess(owner, id);
|
|
628
|
-
// 提交前校验持有凭据有效性,随后由仲裁器清理旧定时器并设置新定时器
|
|
629
|
-
this.controlArbiter.validateHeldGrant(proc, token);
|
|
630
|
-
return this.controlArbiter.renewGrant(proc, ttlMs);
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* 显式释放控制令牌,允许后续控制者申请。
|
|
634
|
-
*/
|
|
635
|
-
async release(owner, id, token, call) {
|
|
636
|
-
await this.ensureInitialized();
|
|
637
|
-
const proc = await this.getOrLoadProcess(owner, id);
|
|
638
|
-
// 提交前校验持有凭据有效性
|
|
639
|
-
if (proc.info.control === "quarantined") {
|
|
640
|
-
throw new ProcessError(PROCESS_QUARANTINED, "Process is in quarantined state");
|
|
641
|
-
}
|
|
642
|
-
if (proc.info.control !== "held" || !proc.currentGrant) {
|
|
643
|
-
throw new ProcessError(CONTROL_REVOKED, "Process control is not currently held");
|
|
644
|
-
}
|
|
645
|
-
if (proc.currentGrant.token !== token) {
|
|
646
|
-
throw new ProcessError(ACCESS_DENIED, "Invalid control token");
|
|
647
|
-
}
|
|
648
|
-
// 当队列中存在待 dispatch 的操作时抛出 CONTROL_BUSY 拒绝 release
|
|
649
|
-
if (proc.inputQueue.length > 0) {
|
|
650
|
-
throw new ProcessError(CONTROL_BUSY, "Cannot release control while operations are pending in queue");
|
|
651
|
-
}
|
|
652
|
-
// 仲裁器完成清理定时器、凭据置空与唯一唤醒下一个等待者
|
|
653
|
-
this.controlArbiter.releaseGrant(proc);
|
|
654
|
-
}
|
|
655
434
|
/**
|
|
656
435
|
* 向受管进程输入流写入原始字节数据。
|
|
657
436
|
*/
|
|
@@ -664,7 +443,7 @@ export class ProcessManager {
|
|
|
664
443
|
processId: id,
|
|
665
444
|
requestId: input.requestId,
|
|
666
445
|
};
|
|
667
|
-
const payloadHash = hashRequestPayload({
|
|
446
|
+
const payloadHash = hashRequestPayload({ data: input.data });
|
|
668
447
|
// 同步预占幂等请求,跨 await 窗口内拦截并发同 requestId 双重入队
|
|
669
448
|
const inFlight = this.reserveRequest(key, payloadHash, "write");
|
|
670
449
|
if (inFlight) {
|
|
@@ -687,7 +466,6 @@ export class ProcessManager {
|
|
|
687
466
|
const rawBytes = decodeBytes(input.data);
|
|
688
467
|
const queued = await this.inputDispatcher.enqueueWrite(proc, {
|
|
689
468
|
requestId: input.requestId,
|
|
690
|
-
token: input.token,
|
|
691
469
|
data: rawBytes,
|
|
692
470
|
key,
|
|
693
471
|
payloadHash,
|
|
@@ -718,7 +496,7 @@ export class ProcessManager {
|
|
|
718
496
|
processId: id,
|
|
719
497
|
requestId: input.requestId,
|
|
720
498
|
};
|
|
721
|
-
const payloadHash = hashRequestPayload({
|
|
499
|
+
const payloadHash = hashRequestPayload({ action: input.action });
|
|
722
500
|
// 同步预占幂等请求,跨 await 窗口内拦截并发同 requestId 双重入队
|
|
723
501
|
const inFlight = this.reserveRequest(key, payloadHash, "control");
|
|
724
502
|
if (inFlight) {
|
|
@@ -752,7 +530,6 @@ export class ProcessManager {
|
|
|
752
530
|
// 输入调度器完成授权校验、落盘与入队
|
|
753
531
|
const queued = await this.inputDispatcher.enqueueControl(proc, {
|
|
754
532
|
requestId: input.requestId,
|
|
755
|
-
token: input.token,
|
|
756
533
|
action: input.action,
|
|
757
534
|
key,
|
|
758
535
|
payloadHash,
|
|
@@ -796,43 +573,6 @@ export class ProcessManager {
|
|
|
796
573
|
await this.ensureInitialized();
|
|
797
574
|
const proc = await this.getOrLoadProcess(owner, id);
|
|
798
575
|
if (proc.outputUnavailable) {
|
|
799
|
-
const requestedPos = parseCursor(input.cursor, proc.info.hostEpoch, id);
|
|
800
|
-
const tombstone = proc.outputTombstone ?? this.terminalOutputCache.getTombstone(id);
|
|
801
|
-
if (tombstone) {
|
|
802
|
-
const tailPos = parseCursor(tombstone.tailCursor, proc.info.hostEpoch, id);
|
|
803
|
-
const cmp = compareCursorPos(requestedPos, tailPos);
|
|
804
|
-
if (cmp > 0) {
|
|
805
|
-
throw new ProcessError(INVALID_CURSOR, "Requested cursor is beyond the end of the output log", {
|
|
806
|
-
cursor: input.cursor,
|
|
807
|
-
tailCursor: tombstone.tailCursor,
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
if (cmp === 0) {
|
|
811
|
-
return {
|
|
812
|
-
chunks: [],
|
|
813
|
-
nextCursor: tombstone.tailCursor,
|
|
814
|
-
earliestCursor: tombstone.tailCursor,
|
|
815
|
-
tailCursor: tombstone.tailCursor,
|
|
816
|
-
truncated: false,
|
|
817
|
-
eof: true,
|
|
818
|
-
process: { ...proc.info },
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
const gapMode = input.onGap ?? "error";
|
|
822
|
-
if (gapMode === "error") {
|
|
823
|
-
throw new ProcessError(OUTPUT_UNAVAILABLE, `Process output for '${id}' is unavailable because it has been evicted from memory`, { processId: id });
|
|
824
|
-
}
|
|
825
|
-
return {
|
|
826
|
-
chunks: [],
|
|
827
|
-
nextCursor: tombstone.tailCursor,
|
|
828
|
-
earliestCursor: tombstone.tailCursor,
|
|
829
|
-
tailCursor: tombstone.tailCursor,
|
|
830
|
-
truncated: true,
|
|
831
|
-
gap: { fromCursor: input.cursor, toCursor: tombstone.tailCursor },
|
|
832
|
-
eof: true,
|
|
833
|
-
process: { ...proc.info },
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
576
|
throw new ProcessError(OUTPUT_UNAVAILABLE, `Process output for '${id}' is unavailable because it has been evicted from memory`, { processId: id });
|
|
837
577
|
}
|
|
838
578
|
const logResult = await proc.outputLog.waitForData(input.cursor, input.waitMs, call?.signal, {
|
|
@@ -866,8 +606,6 @@ export class ProcessManager {
|
|
|
866
606
|
proc.info.state === "lost") {
|
|
867
607
|
return { ...proc.info };
|
|
868
608
|
}
|
|
869
|
-
// 撤销控制权与定时器
|
|
870
|
-
this.controlArbiter.disposeProcess(proc);
|
|
871
609
|
if (proc.idleTimer) {
|
|
872
610
|
clearTimeout(proc.idleTimer);
|
|
873
611
|
proc.idleTimer = undefined;
|
|
@@ -882,11 +620,6 @@ export class ProcessManager {
|
|
|
882
620
|
}
|
|
883
621
|
// 调度器同步原子接管输入队列:纪元递增、标记失败、登记落盘、清空与字节归零
|
|
884
622
|
this.inputDispatcher.takeoverQueue(proc, PROCESS_CANCELLED);
|
|
885
|
-
// 终态拒绝全部排队等待者
|
|
886
|
-
this.controlArbiter.revokeAllWaiters(proc, {
|
|
887
|
-
code: CONTROL_REVOKED,
|
|
888
|
-
message: "Process was stopped",
|
|
889
|
-
});
|
|
890
623
|
proc.info.endReason = proc.info.endReason ?? "requested";
|
|
891
624
|
proc.info.state = "stopping";
|
|
892
625
|
proc.info.control = "closed";
|
|
@@ -910,35 +643,12 @@ export class ProcessManager {
|
|
|
910
643
|
/**
|
|
911
644
|
* 将进程置入隔离状态,撤销有效控制权并取消待 dispatch 的输入队列。
|
|
912
645
|
*/
|
|
913
|
-
async quarantineProcess(owner, processId,
|
|
646
|
+
async quarantineProcess(owner, processId, reason) {
|
|
914
647
|
const proc = this.processes.get(processId);
|
|
915
648
|
if (!proc) {
|
|
916
649
|
return;
|
|
917
650
|
}
|
|
918
|
-
|
|
919
|
-
if (token && proc.currentGrant && proc.currentGrant.token !== token) {
|
|
920
|
-
return;
|
|
921
|
-
}
|
|
922
|
-
if (proc.info.control === "quarantined" || proc.info.control === "closed") {
|
|
923
|
-
return;
|
|
924
|
-
}
|
|
925
|
-
proc.info.control = "quarantined";
|
|
926
|
-
if (proc.ttlTimer) {
|
|
927
|
-
clearTimeout(proc.ttlTimer);
|
|
928
|
-
proc.ttlTimer = undefined;
|
|
929
|
-
}
|
|
930
|
-
proc.currentGrant = undefined;
|
|
931
|
-
// 调度器同步原子接管输入队列:纪元递增、标记失败、登记落盘、清空与字节归零
|
|
932
|
-
this.inputDispatcher.takeoverQueue(proc, CONTROL_REVOKED);
|
|
933
|
-
// 终态拒绝全部排队等待者
|
|
934
|
-
this.controlArbiter.revokeAllWaiters(proc, {
|
|
935
|
-
code: PROCESS_QUARANTINED,
|
|
936
|
-
message: reason || "Process entered quarantined state",
|
|
937
|
-
});
|
|
938
|
-
await this.persistState(processId, {
|
|
939
|
-
control: "quarantined",
|
|
940
|
-
controlState: "quarantined",
|
|
941
|
-
});
|
|
651
|
+
await this.controlManager.quarantine(proc, owner, reason);
|
|
942
652
|
}
|
|
943
653
|
/**
|
|
944
654
|
* 一次性运行外部命令,收集有限输出并支持协作式取消与超时回收。
|
|
@@ -1039,20 +749,12 @@ export class ProcessManager {
|
|
|
1039
749
|
clearTimeout(proc.idleTimer);
|
|
1040
750
|
if (proc.lifetimeTimer)
|
|
1041
751
|
clearTimeout(proc.lifetimeTimer);
|
|
1042
|
-
if (proc.ttlTimer)
|
|
1043
|
-
clearTimeout(proc.ttlTimer);
|
|
1044
752
|
proc.info.state = "exited";
|
|
1045
753
|
proc.info.control = "closed";
|
|
1046
754
|
proc.info.exit = { code: exit.code, signal: exit.signal };
|
|
1047
755
|
if (!proc.info.endReason) {
|
|
1048
756
|
proc.info.endReason = "natural";
|
|
1049
757
|
}
|
|
1050
|
-
proc.currentGrant = undefined;
|
|
1051
|
-
// 终态拒绝全部排队等待者(单一入口)
|
|
1052
|
-
this.controlArbiter.revokeAllWaiters(proc, {
|
|
1053
|
-
code: CONTROL_REVOKED,
|
|
1054
|
-
message: "Process has exited",
|
|
1055
|
-
});
|
|
1056
758
|
void this.persistState(proc.info.id, {
|
|
1057
759
|
state: "exited",
|
|
1058
760
|
control: "closed",
|
|
@@ -1110,18 +812,11 @@ export class ProcessManager {
|
|
|
1110
812
|
clearTimeout(proc.idleTimer);
|
|
1111
813
|
if (proc.lifetimeTimer)
|
|
1112
814
|
clearTimeout(proc.lifetimeTimer);
|
|
1113
|
-
if (proc.ttlTimer)
|
|
1114
|
-
clearTimeout(proc.ttlTimer);
|
|
1115
815
|
proc.info.state = "failed";
|
|
1116
816
|
proc.info.control = "closed";
|
|
1117
817
|
proc.info.endReason = "natural";
|
|
1118
818
|
proc.outputLog.closeOutput("natural");
|
|
1119
819
|
proc.info.outputClosed = true;
|
|
1120
|
-
// 终态拒绝全部排队等待者(单一入口)
|
|
1121
|
-
this.controlArbiter.revokeAllWaiters(proc, {
|
|
1122
|
-
code: CONTROL_REVOKED,
|
|
1123
|
-
message: `Process error: ${err.message}`,
|
|
1124
|
-
});
|
|
1125
820
|
void this.persistState(proc.info.id, {
|
|
1126
821
|
state: "failed",
|
|
1127
822
|
control: "closed",
|
|
@@ -1163,13 +858,11 @@ export class ProcessManager {
|
|
|
1163
858
|
}
|
|
1164
859
|
const info = toSdkProcessInfo(record);
|
|
1165
860
|
const retainedLog = this.terminalOutputCache.get(processId);
|
|
1166
|
-
const tombstone = this.terminalOutputCache.getTombstone(processId);
|
|
1167
861
|
const isTerminalLoaded = info.state === "exited" || info.state === "failed" || info.state === "lost";
|
|
1168
|
-
const isOutputUnavailable = !retainedLog && (
|
|
862
|
+
const isOutputUnavailable = !retainedLog && (isTerminalLoaded && Boolean(record.outputClosed));
|
|
1169
863
|
const outputLog = retainedLog ??
|
|
1170
864
|
new ProcessOutputLog(record.hostEpoch, processId, {
|
|
1171
865
|
maxBufferBytes: info.effectiveLimits.outputBufferBytes,
|
|
1172
|
-
maxWaiters: this.quotas.maxWaitersPerProcess,
|
|
1173
866
|
});
|
|
1174
867
|
if (record.outputClosed && !outputLog.outputClosed) {
|
|
1175
868
|
outputLog.closeOutput(record.outputEndReason ?? "natural");
|
|
@@ -1185,12 +878,8 @@ export class ProcessManager {
|
|
|
1185
878
|
scope: formatProcessScope(owner),
|
|
1186
879
|
outputLog,
|
|
1187
880
|
outputUnavailable: isOutputUnavailable,
|
|
1188
|
-
outputTombstone: tombstone,
|
|
1189
|
-
controlEpoch: 0,
|
|
1190
881
|
cancelEpoch: 0,
|
|
1191
882
|
inputQueue: [],
|
|
1192
|
-
pendingInputBytes: 0,
|
|
1193
|
-
acquireWaiters: [],
|
|
1194
883
|
inputClosed: Boolean(record.inputClosed),
|
|
1195
884
|
isDispatching: false,
|
|
1196
885
|
effectiveLimits: info.effectiveLimits,
|
|
@@ -1202,12 +891,6 @@ export class ProcessManager {
|
|
|
1202
891
|
this.processes.set(processId, loaded);
|
|
1203
892
|
return loaded;
|
|
1204
893
|
}
|
|
1205
|
-
/**
|
|
1206
|
-
* 统计终态保留日志当前在内存中实际占用的输出缓冲字节总数。
|
|
1207
|
-
*/
|
|
1208
|
-
countRetainedOutputBufferBytes() {
|
|
1209
|
-
return this.terminalOutputCache.retainedBytes();
|
|
1210
|
-
}
|
|
1211
894
|
/**
|
|
1212
895
|
* 终态驱逐与驱动释放:进程到达终态且输出已关闭后移除内存记录,
|
|
1213
896
|
* 并防御性释放底层驱动句柄(失败仅记录诊断不中断)。
|
|
@@ -1217,8 +900,8 @@ export class ProcessManager {
|
|
|
1217
900
|
if (!isTerminal || !proc.info.outputClosed) {
|
|
1218
901
|
return;
|
|
1219
902
|
}
|
|
1220
|
-
//
|
|
1221
|
-
if (proc.
|
|
903
|
+
// 仍存在未结算输入时暂缓驱逐
|
|
904
|
+
if (proc.inputQueue.length > 0) {
|
|
1222
905
|
return;
|
|
1223
906
|
}
|
|
1224
907
|
this.processes.delete(proc.info.id);
|
|
@@ -1238,76 +921,4 @@ export class ProcessManager {
|
|
|
1238
921
|
}
|
|
1239
922
|
}
|
|
1240
923
|
}
|
|
1241
|
-
/**
|
|
1242
|
-
* 启动受管进程时的配额容量校验。
|
|
1243
|
-
*/
|
|
1244
|
-
checkSpawnQuotas(scope, requestedBufferBytes) {
|
|
1245
|
-
let scopeActive = 0;
|
|
1246
|
-
let hostActive = 0;
|
|
1247
|
-
let totalBuffer = 0;
|
|
1248
|
-
for (const p of this.processes.values()) {
|
|
1249
|
-
if (p.info.state === "starting" ||
|
|
1250
|
-
p.info.state === "running" ||
|
|
1251
|
-
p.info.state === "stopping") {
|
|
1252
|
-
hostActive += 1;
|
|
1253
|
-
totalBuffer += p.effectiveLimits.outputBufferBytes;
|
|
1254
|
-
if (p.scope === scope) {
|
|
1255
|
-
scopeActive += 1;
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
if (scopeActive >= this.quotas.maxActiveProcessesPerScope) {
|
|
1260
|
-
throw new ProcessError(QUOTA_EXCEEDED, "Scope active process quota exceeded", {
|
|
1261
|
-
scope,
|
|
1262
|
-
limit: this.quotas.maxActiveProcessesPerScope,
|
|
1263
|
-
});
|
|
1264
|
-
}
|
|
1265
|
-
if (hostActive >= this.quotas.maxActiveProcessesPerHost) {
|
|
1266
|
-
throw new ProcessError(QUOTA_EXCEEDED, "Host active process quota exceeded", {
|
|
1267
|
-
limit: this.quotas.maxActiveProcessesPerHost,
|
|
1268
|
-
});
|
|
1269
|
-
}
|
|
1270
|
-
const perProcBuf = requestedBufferBytes ?? this.defaultLimits.outputBufferBytes;
|
|
1271
|
-
if (perProcBuf > this.quotas.maxOutputBufferBytesPerProcess) {
|
|
1272
|
-
throw new ProcessError(QUOTA_EXCEEDED, "Output buffer per process quota exceeded", {
|
|
1273
|
-
requested: perProcBuf,
|
|
1274
|
-
limit: this.quotas.maxOutputBufferBytesPerProcess,
|
|
1275
|
-
});
|
|
1276
|
-
}
|
|
1277
|
-
// 终态保留输出日志实际占用字节数纳入宿主预算
|
|
1278
|
-
const retainedBefore = this.countRetainedOutputBufferBytes();
|
|
1279
|
-
totalBuffer += retainedBefore;
|
|
1280
|
-
// 若新进程申请的缓冲使总预算超限,优先淘汰已有的终态保留日志(LRU 策略)
|
|
1281
|
-
// 淘汰门槛为宿主预算扣除活跃进程已占字节与新申请字节,淘汰释放的字节数同步回扣宿主预算
|
|
1282
|
-
if (totalBuffer + perProcBuf > this.quotas.maxOutputBufferBytesPerHost) {
|
|
1283
|
-
const activeBufferBytes = totalBuffer - retainedBefore;
|
|
1284
|
-
const evictedBytes = this.terminalOutputCache.evictLRUUntil(this.quotas.maxOutputBufferBytesPerHost - perProcBuf - activeBufferBytes);
|
|
1285
|
-
totalBuffer -= evictedBytes;
|
|
1286
|
-
}
|
|
1287
|
-
if (totalBuffer + perProcBuf > this.quotas.maxOutputBufferBytesPerHost) {
|
|
1288
|
-
throw new ProcessError(QUOTA_EXCEEDED, "Host output buffer quota exceeded", {
|
|
1289
|
-
limit: this.quotas.maxOutputBufferBytesPerHost,
|
|
1290
|
-
});
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
/**
|
|
1294
|
-
* 统计当前宿主所有活跃进程累计排队等待控制权的调用者总数。
|
|
1295
|
-
*/
|
|
1296
|
-
countHostAcquireWaiters() {
|
|
1297
|
-
let total = 0;
|
|
1298
|
-
for (const p of this.processes.values()) {
|
|
1299
|
-
total += p.acquireWaiters.length;
|
|
1300
|
-
}
|
|
1301
|
-
return total;
|
|
1302
|
-
}
|
|
1303
|
-
/**
|
|
1304
|
-
* 统计当前宿主所有活跃进程累计输入队列待写入字节总数。
|
|
1305
|
-
*/
|
|
1306
|
-
countHostPendingInputBytes() {
|
|
1307
|
-
let total = 0;
|
|
1308
|
-
for (const p of this.processes.values()) {
|
|
1309
|
-
total += p.pendingInputBytes;
|
|
1310
|
-
}
|
|
1311
|
-
return total;
|
|
1312
|
-
}
|
|
1313
924
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ManagedProcessRecord } from "./managed-record.js";
|
|
2
|
+
import type { ProcessManagerQuotas } from "./process-manager.js";
|
|
3
|
+
export interface QuotaContext {
|
|
4
|
+
processes: Map<string, ManagedProcessRecord>;
|
|
5
|
+
quotas: ProcessManagerQuotas;
|
|
6
|
+
defaultLimits: {
|
|
7
|
+
outputBufferBytes: number;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare class ProcessQuotaTracker {
|
|
11
|
+
private readonly ctx;
|
|
12
|
+
constructor(ctx: QuotaContext);
|
|
13
|
+
checkSpawnQuotas(_scope?: string, requestedBufferBytes?: number): void;
|
|
14
|
+
}
|