@lesliechan721/agent-skill-eval 0.1.0-beta.3 → 0.1.0-beta.30
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/dist/bin.js +167 -39
- package/dist/bin.js.map +1 -1
- package/dist/codex-eval-app-server.d.ts +205 -0
- package/dist/codex-eval-app-server.js +1372 -0
- package/dist/codex-eval-app-server.js.map +1 -0
- package/dist/codex-eval-cases.d.ts +43 -0
- package/dist/codex-eval-cases.js +327 -0
- package/dist/codex-eval-cases.js.map +1 -0
- package/dist/codex-eval-isolation.d.ts +79 -0
- package/dist/codex-eval-isolation.js +420 -0
- package/dist/codex-eval-isolation.js.map +1 -0
- package/dist/codex-eval-process-tree.d.ts +41 -0
- package/dist/codex-eval-process-tree.js +858 -0
- package/dist/codex-eval-process-tree.js.map +1 -0
- package/dist/codex-eval-runtime-config.d.ts +43 -0
- package/dist/codex-eval-runtime-config.js +607 -0
- package/dist/codex-eval-runtime-config.js.map +1 -0
- package/dist/codex-trigger-eval.d.ts +82 -0
- package/dist/codex-trigger-eval.js +790 -0
- package/dist/codex-trigger-eval.js.map +1 -0
- package/dist/codex-workflow-evaluation.d.ts +76 -0
- package/dist/codex-workflow-evaluation.js +263 -0
- package/dist/codex-workflow-evaluation.js.map +1 -0
- package/dist/codex-workflow.d.ts +196 -0
- package/dist/codex-workflow.js +1936 -0
- package/dist/codex-workflow.js.map +1 -0
- package/dist/credentials.d.ts +5 -0
- package/dist/credentials.js +30 -0
- package/dist/credentials.js.map +1 -0
- package/dist/custom-process.d.ts +60 -0
- package/dist/custom-process.js +424 -0
- package/dist/custom-process.js.map +1 -0
- package/dist/eval-audit.d.ts +38 -0
- package/dist/eval-audit.js +71 -0
- package/dist/eval-audit.js.map +1 -0
- package/dist/eval-file.d.ts +9 -0
- package/dist/eval-file.js +15 -0
- package/dist/eval-file.js.map +1 -0
- package/dist/eval-process-management.d.ts +22 -0
- package/dist/eval-process-management.js +90 -0
- package/dist/eval-process-management.js.map +1 -0
- package/dist/eval-result.d.ts +79 -0
- package/dist/eval-result.js +144 -0
- package/dist/eval-result.js.map +1 -0
- package/dist/eval-run.d.ts +27 -0
- package/dist/eval-run.js +565 -0
- package/dist/eval-run.js.map +1 -0
- package/dist/eval-spec.d.ts +57 -0
- package/dist/eval-spec.js +120 -0
- package/dist/eval-spec.js.map +1 -0
- package/dist/eval-suite-binding.d.ts +4 -0
- package/dist/eval-suite-binding.js +19 -0
- package/dist/eval-suite-binding.js.map +1 -0
- package/dist/index.d.ts +38 -9
- package/dist/index.js +20 -6
- package/dist/index.js.map +1 -1
- package/dist/json-schema.js +75 -5
- package/dist/json-schema.js.map +1 -1
- package/dist/output-path.js +2 -2
- package/dist/process-tree.d.ts +21 -1
- package/dist/process-tree.js +797 -158
- package/dist/process-tree.js.map +1 -1
- package/dist/process.d.ts +2 -2
- package/dist/process.js +99 -34
- package/dist/process.js.map +1 -1
- package/dist/provider-doctor.d.ts +46 -0
- package/dist/provider-doctor.js +120 -0
- package/dist/provider-doctor.js.map +1 -0
- package/dist/providers.d.ts +85 -0
- package/dist/providers.js +636 -0
- package/dist/providers.js.map +1 -0
- package/dist/run-paths.d.ts +8 -0
- package/dist/run-paths.js +71 -0
- package/dist/run-paths.js.map +1 -0
- package/dist/runtime-skill.d.ts +6 -0
- package/dist/runtime-skill.js +87 -3
- package/dist/runtime-skill.js.map +1 -1
- package/dist/schemas/digests.json +3 -3
- package/dist/schemas/eval-audit.schema.json +64 -0
- package/dist/schemas/eval-result.schema.json +232 -0
- package/dist/schemas/eval-spec.schema.json +109 -0
- package/dist/schemas.d.ts +1 -3
- package/dist/schemas.js +22 -20
- package/dist/schemas.js.map +1 -1
- package/dist/selected-cases.d.ts +30 -0
- package/dist/selected-cases.js +388 -0
- package/dist/selected-cases.js.map +1 -0
- package/dist/types.d.ts +2 -34
- package/dist/windows-job.d.ts +26 -0
- package/dist/windows-job.js +97 -0
- package/dist/windows-job.js.map +1 -0
- package/package.json +17 -3
- package/dist/bundle.d.ts +0 -93
- package/dist/bundle.js +0 -216
- package/dist/bundle.js.map +0 -1
- package/dist/capabilities.d.ts +0 -2
- package/dist/capabilities.js +0 -38
- package/dist/capabilities.js.map +0 -1
- package/dist/plan.d.ts +0 -79
- package/dist/plan.js +0 -285
- package/dist/plan.js.map +0 -1
- package/dist/run.d.ts +0 -2
- package/dist/run.js +0 -592
- package/dist/run.js.map +0 -1
- package/dist/schemas/aw-skill-eval-execution-plan.schema.json +0 -220
- package/dist/schemas/eval-run-bundle.schema.json +0 -408
- package/dist/schemas/eval-runtime-capabilities.schema.json +0 -85
package/dist/process-tree.js
CHANGED
|
@@ -1,12 +1,38 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
1
|
+
import { execFile, spawn } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { once } from 'node:events';
|
|
2
4
|
const DEFAULT_PROCESS_MARKER_ENV = 'AW_NATIVE_CLI_PROCESS_MARKER';
|
|
3
5
|
const PROCESS_TREE_COMMAND_TIMEOUT_MS = 1_000;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
+
const POSIX_PROCESS_TREE_COMMAND_TIMEOUT_MS = 2_000;
|
|
7
|
+
const PROCESS_TREE_IDENTITY_INTERVAL_MS = 250;
|
|
8
|
+
const PROCESS_TREE_MARKER_INTERVAL_MS = 1_000;
|
|
9
|
+
const PROCESS_TREE_TERMINATION_GRACE_MS = 250;
|
|
10
|
+
const PROCESS_TREE_TERMINATION_SETTLE_MS = 50;
|
|
6
11
|
const PROCESS_TREE_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
7
12
|
const observationStates = new WeakMap();
|
|
13
|
+
const posixSamplers = new WeakMap();
|
|
8
14
|
const defaultExecFile = (command, args, options, callback) => execFile(command, args, options, (error, stdout) => callback(error, stdout));
|
|
9
|
-
function
|
|
15
|
+
function safeCommandFailureValue(value) {
|
|
16
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
17
|
+
return value;
|
|
18
|
+
if (typeof value !== 'string' || !/^[A-Za-z0-9_.-]{1,64}$/u.test(value))
|
|
19
|
+
return undefined;
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function commandFailure(reason, error) {
|
|
23
|
+
const details = error && typeof error === 'object'
|
|
24
|
+
? error
|
|
25
|
+
: {};
|
|
26
|
+
const code = safeCommandFailureValue(details.code);
|
|
27
|
+
const signal = safeCommandFailureValue(details.signal);
|
|
28
|
+
return {
|
|
29
|
+
reason,
|
|
30
|
+
...(code !== undefined ? { code } : {}),
|
|
31
|
+
...(typeof signal === 'string' ? { signal } : {}),
|
|
32
|
+
...(typeof details.killed === 'boolean' ? { killed: details.killed } : {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function runCommandWithDiagnostics(command, args, execFileImpl, timeoutMs) {
|
|
10
36
|
return new Promise((resolve) => {
|
|
11
37
|
let settled = false;
|
|
12
38
|
const finish = (result) => {
|
|
@@ -16,70 +42,239 @@ function runCommand(command, args, execFileImpl, timeoutMs) {
|
|
|
16
42
|
clearTimeout(timeout);
|
|
17
43
|
resolve(result);
|
|
18
44
|
};
|
|
19
|
-
const timeout = setTimeout(() => finish(
|
|
45
|
+
const timeout = setTimeout(() => finish({
|
|
46
|
+
output: null,
|
|
47
|
+
failure: commandFailure('deadline-exceeded'),
|
|
48
|
+
}), timeoutMs);
|
|
20
49
|
timeout.unref();
|
|
21
50
|
try {
|
|
22
51
|
execFileImpl(command, args, {
|
|
23
52
|
timeout: timeoutMs,
|
|
24
53
|
windowsHide: true,
|
|
25
54
|
maxBuffer: PROCESS_TREE_MAX_BUFFER_BYTES,
|
|
26
|
-
}, (error, stdout) => finish(error
|
|
55
|
+
}, (error, stdout) => finish(error
|
|
56
|
+
? { output: null, failure: commandFailure('callback-error', error) }
|
|
57
|
+
: { output: stdout }));
|
|
27
58
|
}
|
|
28
|
-
catch {
|
|
29
|
-
finish(null);
|
|
59
|
+
catch (error) {
|
|
60
|
+
finish({ output: null, failure: commandFailure('synchronous-error', error) });
|
|
30
61
|
}
|
|
31
62
|
});
|
|
32
63
|
}
|
|
64
|
+
function runCommand(command, args, execFileImpl, timeoutMs) {
|
|
65
|
+
return runCommandWithDiagnostics(command, args, execFileImpl, timeoutMs)
|
|
66
|
+
.then((result) => result.output);
|
|
67
|
+
}
|
|
33
68
|
function parsePosixProcessTable(stdout) {
|
|
34
69
|
const table = new Map();
|
|
35
70
|
for (const line of stdout.split('\n')) {
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
71
|
+
const groupMatch = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/u.exec(line);
|
|
72
|
+
if (groupMatch) {
|
|
73
|
+
table.set(Number(groupMatch[1]), {
|
|
74
|
+
ppid: Number(groupMatch[2]),
|
|
75
|
+
pgid: Number(groupMatch[3]),
|
|
76
|
+
state: groupMatch[4],
|
|
77
|
+
startTime: groupMatch[5],
|
|
78
|
+
});
|
|
38
79
|
continue;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
80
|
+
}
|
|
81
|
+
const legacyMatch = /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/u.exec(line);
|
|
82
|
+
if (!legacyMatch)
|
|
83
|
+
continue;
|
|
84
|
+
table.set(Number(legacyMatch[1]), {
|
|
85
|
+
ppid: Number(legacyMatch[2]),
|
|
86
|
+
state: legacyMatch[3],
|
|
87
|
+
startTime: legacyMatch[4],
|
|
43
88
|
});
|
|
44
89
|
}
|
|
45
90
|
return table;
|
|
46
91
|
}
|
|
47
|
-
function
|
|
92
|
+
function parsePosixMarkerScan(stdout) {
|
|
93
|
+
const table = new Map();
|
|
94
|
+
const commands = new Map();
|
|
95
|
+
for (const line of stdout.split('\n')) {
|
|
96
|
+
const actual = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S{3}\s+\S{3}\s+\d+\s+\S+\s+\d{4})\s+(.*)$/u.exec(line);
|
|
97
|
+
const synthetic = actual ??
|
|
98
|
+
/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(.*)$/u.exec(line);
|
|
99
|
+
if (!synthetic)
|
|
100
|
+
continue;
|
|
101
|
+
const pid = Number(synthetic[1]);
|
|
102
|
+
table.set(pid, {
|
|
103
|
+
ppid: Number(synthetic[2]),
|
|
104
|
+
pgid: Number(synthetic[3]),
|
|
105
|
+
state: synthetic[4],
|
|
106
|
+
startTime: synthetic[5].replace(/\s+/gu, ' '),
|
|
107
|
+
});
|
|
108
|
+
commands.set(pid, synthetic[6]);
|
|
109
|
+
}
|
|
110
|
+
return { table, commands };
|
|
111
|
+
}
|
|
112
|
+
function parseMarkerProcessIds(scan, markerEnvironmentVariable, marker) {
|
|
48
113
|
const processIds = new Set();
|
|
49
114
|
const assignment = `${markerEnvironmentVariable}=${marker}`;
|
|
50
|
-
for (const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
processIds.add(Number(match[1]));
|
|
115
|
+
for (const [pid, command] of scan.commands) {
|
|
116
|
+
if (command.split(/\s+/u).includes(assignment))
|
|
117
|
+
processIds.add(pid);
|
|
54
118
|
}
|
|
55
119
|
return processIds;
|
|
56
120
|
}
|
|
57
|
-
async function readPosixProcessTable(execFileImpl, markerEnvironmentVariable, marker, timeoutMs =
|
|
58
|
-
const [
|
|
59
|
-
|
|
121
|
+
async function readPosixProcessTable(execFileImpl, markerEnvironmentVariable, marker, timeoutMs = POSIX_PROCESS_TREE_COMMAND_TIMEOUT_MS, confirmUnmarkedPid) {
|
|
122
|
+
const identityArgs = ['-A', '-o', 'pid=,ppid=,pgid=,state=,lstart='];
|
|
123
|
+
const markerArgs = ['eww', '-A', '-o', 'pid=,ppid=,pgid=,state=,lstart=,command='];
|
|
124
|
+
const lookupStartedAt = Date.now();
|
|
125
|
+
const [identityResult, initialMarkerResult] = await Promise.all([
|
|
126
|
+
runCommandWithDiagnostics('ps', identityArgs, execFileImpl, timeoutMs),
|
|
60
127
|
marker === undefined
|
|
61
|
-
? Promise.resolve('')
|
|
62
|
-
:
|
|
128
|
+
? Promise.resolve({ output: '' })
|
|
129
|
+
: runCommandWithDiagnostics('ps', markerArgs, execFileImpl, timeoutMs),
|
|
63
130
|
]);
|
|
64
|
-
if (
|
|
131
|
+
if (identityResult.output === null)
|
|
65
132
|
return null;
|
|
133
|
+
let markerResult = initialMarkerResult;
|
|
134
|
+
let markerLookupAttempts = marker === undefined ? 0 : 1;
|
|
135
|
+
const retryTimeoutMs = timeoutMs - (Date.now() - lookupStartedAt);
|
|
136
|
+
if (marker !== undefined && markerResult.output === null && retryTimeoutMs > 0) {
|
|
137
|
+
markerLookupAttempts += 1;
|
|
138
|
+
markerResult = await runCommandWithDiagnostics('ps', markerArgs, execFileImpl, retryTimeoutMs);
|
|
139
|
+
}
|
|
140
|
+
const markerOutput = markerResult.output;
|
|
141
|
+
const markerScan = markerOutput === null ? undefined : parsePosixMarkerScan(markerOutput);
|
|
142
|
+
const table = markerScan?.table ?? parsePosixProcessTable(identityResult.output);
|
|
143
|
+
const markerPids = marker === undefined || !markerScan
|
|
144
|
+
? new Set()
|
|
145
|
+
: parseMarkerProcessIds(markerScan, markerEnvironmentVariable, marker);
|
|
146
|
+
const confirmedUnmarkedPids = new Set();
|
|
147
|
+
const unmarkedEntry = confirmUnmarkedPid === undefined || markerPids.has(confirmUnmarkedPid)
|
|
148
|
+
? undefined
|
|
149
|
+
: table.get(confirmUnmarkedPid);
|
|
150
|
+
if (unmarkedEntry && !unmarkedEntry.state?.startsWith('Z')) {
|
|
151
|
+
const confirmationOutput = await runCommand('ps', markerArgs, execFileImpl, timeoutMs);
|
|
152
|
+
if (confirmationOutput === null)
|
|
153
|
+
return null;
|
|
154
|
+
const confirmationScan = parsePosixMarkerScan(confirmationOutput);
|
|
155
|
+
const confirmedEntry = confirmationScan.table.get(confirmUnmarkedPid);
|
|
156
|
+
if (confirmedEntry?.startTime === unmarkedEntry.startTime &&
|
|
157
|
+
!parseMarkerProcessIds(confirmationScan, markerEnvironmentVariable, marker).has(confirmUnmarkedPid) &&
|
|
158
|
+
!confirmedEntry.state?.startsWith('Z')) {
|
|
159
|
+
confirmedUnmarkedPids.add(confirmUnmarkedPid);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
66
162
|
return {
|
|
67
|
-
table
|
|
68
|
-
markerPids
|
|
69
|
-
? new Set()
|
|
70
|
-
: parseMarkerProcessIds(markerOutput, markerEnvironmentVariable, marker),
|
|
163
|
+
table,
|
|
164
|
+
markerPids,
|
|
71
165
|
markerLookupSucceeded: marker === undefined || markerOutput !== null,
|
|
166
|
+
markerLookupAttempts,
|
|
167
|
+
...(markerResult.failure ? { markerLookupFailure: markerResult.failure } : {}),
|
|
168
|
+
confirmedUnmarkedPids,
|
|
72
169
|
};
|
|
73
170
|
}
|
|
171
|
+
function readPosixIdentityTable(execFileImpl) {
|
|
172
|
+
return runCommandWithDiagnostics('ps', ['-A', '-o', 'pid=,ppid=,pgid=,state=,lstart='], execFileImpl, POSIX_PROCESS_TREE_COMMAND_TIMEOUT_MS);
|
|
173
|
+
}
|
|
174
|
+
function readPosixMarkerOutput(execFileImpl) {
|
|
175
|
+
return runCommandWithDiagnostics('ps', ['eww', '-A', '-o', 'pid=,ppid=,pgid=,state=,lstart=,command='], execFileImpl, POSIX_PROCESS_TREE_COMMAND_TIMEOUT_MS);
|
|
176
|
+
}
|
|
177
|
+
function posixMarkerLookupFailure(snapshot, stage) {
|
|
178
|
+
const fields = [
|
|
179
|
+
`stage=${stage}`,
|
|
180
|
+
`attempts=${snapshot.markerLookupAttempts}`,
|
|
181
|
+
`reason=${snapshot.markerLookupFailure?.reason ?? 'unknown'}`,
|
|
182
|
+
];
|
|
183
|
+
if (snapshot.markerLookupFailure?.code !== undefined) {
|
|
184
|
+
fields.push(`code=${snapshot.markerLookupFailure.code}`);
|
|
185
|
+
}
|
|
186
|
+
if (snapshot.markerLookupFailure?.signal !== undefined) {
|
|
187
|
+
fields.push(`signal=${snapshot.markerLookupFailure.signal}`);
|
|
188
|
+
}
|
|
189
|
+
if (snapshot.markerLookupFailure?.killed !== undefined) {
|
|
190
|
+
fields.push(`killed=${snapshot.markerLookupFailure.killed}`);
|
|
191
|
+
}
|
|
192
|
+
return cleanupUnconfirmed(`POSIX process marker lookup failed (${fields.join(', ')}).`);
|
|
193
|
+
}
|
|
194
|
+
async function defaultCreateSentinel(markerEnvironmentVariable, marker) {
|
|
195
|
+
const child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 60_000)'], {
|
|
196
|
+
env: { [markerEnvironmentVariable]: marker },
|
|
197
|
+
stdio: 'ignore',
|
|
198
|
+
windowsHide: true,
|
|
199
|
+
});
|
|
200
|
+
await once(child, 'spawn');
|
|
201
|
+
if (child.pid === undefined)
|
|
202
|
+
throw cleanupUnconfirmed('inspection sentinel did not start.');
|
|
203
|
+
return {
|
|
204
|
+
pid: child.pid,
|
|
205
|
+
stop: async () => {
|
|
206
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
207
|
+
return;
|
|
208
|
+
const closed = once(child, 'close').then(() => undefined);
|
|
209
|
+
try {
|
|
210
|
+
child.kill('SIGKILL');
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// The controlled sentinel can exit before release.
|
|
214
|
+
}
|
|
215
|
+
await closed;
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function validateLeaseSnapshot(snapshot, lease, stage) {
|
|
220
|
+
if (!snapshot.markerLookupSucceeded) {
|
|
221
|
+
return posixMarkerLookupFailure(snapshot, stage);
|
|
222
|
+
}
|
|
223
|
+
const sentinelEntry = snapshot.table.get(lease.sentinel.pid);
|
|
224
|
+
if (sentinelEntry?.startTime !== lease.sentinel.startTime ||
|
|
225
|
+
!snapshot.markerPids.has(lease.sentinel.pid)) {
|
|
226
|
+
return cleanupUnconfirmed('POSIX inspection sentinel could not be verified.');
|
|
227
|
+
}
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
export async function acquireProcessTreeInspectionLease(dependencies = {}) {
|
|
231
|
+
const platform = dependencies.platform ?? process.platform;
|
|
232
|
+
if (platform !== 'darwin' && platform !== 'linux') {
|
|
233
|
+
throw cleanupUnconfirmed(`process inspection is unsupported on ${platform}.`);
|
|
234
|
+
}
|
|
235
|
+
const markerEnvironmentVariable = dependencies.markerEnvironmentVariable ?? DEFAULT_PROCESS_MARKER_ENV;
|
|
236
|
+
const marker = randomUUID();
|
|
237
|
+
const sentinel = await (dependencies.createSentinel ?? defaultCreateSentinel)(markerEnvironmentVariable, marker);
|
|
238
|
+
try {
|
|
239
|
+
const snapshot = await readPosixProcessTable(dependencies.execFile ?? defaultExecFile, markerEnvironmentVariable, marker);
|
|
240
|
+
if (!snapshot)
|
|
241
|
+
throw cleanupUnconfirmed('POSIX process identity lookup failed.');
|
|
242
|
+
if (!snapshot.markerLookupSucceeded) {
|
|
243
|
+
throw posixMarkerLookupFailure(snapshot, 'inspection-preflight');
|
|
244
|
+
}
|
|
245
|
+
const entry = snapshot.table.get(sentinel.pid);
|
|
246
|
+
if (!entry || !snapshot.markerPids.has(sentinel.pid)) {
|
|
247
|
+
throw cleanupUnconfirmed('POSIX inspection sentinel could not be verified.');
|
|
248
|
+
}
|
|
249
|
+
let released = false;
|
|
250
|
+
return {
|
|
251
|
+
marker,
|
|
252
|
+
sentinel: { pid: sentinel.pid, startTime: entry.startTime },
|
|
253
|
+
release: async () => {
|
|
254
|
+
if (released)
|
|
255
|
+
return;
|
|
256
|
+
released = true;
|
|
257
|
+
await sentinel.stop();
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
await sentinel.stop();
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
74
266
|
function parseWindowsProcessTable(stdout) {
|
|
75
267
|
const table = new Map();
|
|
76
268
|
for (const line of stdout.split('\n')) {
|
|
77
269
|
const match = /^\s*(\d+)\|(\d+)\|(.+?)\s*$/u.exec(line);
|
|
78
270
|
if (!match)
|
|
79
271
|
continue;
|
|
272
|
+
const startTime = match[3];
|
|
273
|
+
const creationTime = /^\d+$/u.test(startTime) ? BigInt(startTime) : undefined;
|
|
80
274
|
table.set(Number(match[1]), {
|
|
81
275
|
ppid: Number(match[2]),
|
|
82
|
-
startTime
|
|
276
|
+
startTime,
|
|
277
|
+
...(creationTime !== undefined ? { creationTime } : {}),
|
|
83
278
|
});
|
|
84
279
|
}
|
|
85
280
|
return table;
|
|
@@ -92,7 +287,62 @@ function readWindowsProcessTable(execFileImpl) {
|
|
|
92
287
|
].join(' ');
|
|
93
288
|
return runCommand('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], execFileImpl, PROCESS_TREE_COMMAND_TIMEOUT_MS).then((stdout) => stdout === null ? null : parseWindowsProcessTable(stdout));
|
|
94
289
|
}
|
|
95
|
-
function
|
|
290
|
+
function collectDescendantProcessIds(rootPid, table) {
|
|
291
|
+
const childrenByParent = new Map();
|
|
292
|
+
for (const [processId, entry] of table) {
|
|
293
|
+
const children = childrenByParent.get(entry.ppid) ?? [];
|
|
294
|
+
children.push(processId);
|
|
295
|
+
childrenByParent.set(entry.ppid, children);
|
|
296
|
+
}
|
|
297
|
+
const descendants = new Set();
|
|
298
|
+
const visited = new Set([rootPid]);
|
|
299
|
+
const pending = [rootPid];
|
|
300
|
+
while (pending.length > 0) {
|
|
301
|
+
const parent = pending.shift();
|
|
302
|
+
for (const childPid of childrenByParent.get(parent) ?? []) {
|
|
303
|
+
if (visited.has(childPid))
|
|
304
|
+
continue;
|
|
305
|
+
visited.add(childPid);
|
|
306
|
+
descendants.add(childPid);
|
|
307
|
+
pending.push(childPid);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return descendants;
|
|
311
|
+
}
|
|
312
|
+
function collectComparableWindowsDescendantProcessIds(root, table) {
|
|
313
|
+
if (root?.creationTime === undefined)
|
|
314
|
+
return new Set();
|
|
315
|
+
const childrenByParent = new Map();
|
|
316
|
+
for (const [processId, entry] of table) {
|
|
317
|
+
const children = childrenByParent.get(entry.ppid) ?? [];
|
|
318
|
+
children.push(processId);
|
|
319
|
+
childrenByParent.set(entry.ppid, children);
|
|
320
|
+
}
|
|
321
|
+
const descendants = new Set();
|
|
322
|
+
const visited = new Set([root.pid]);
|
|
323
|
+
const pending = [root];
|
|
324
|
+
while (pending.length > 0) {
|
|
325
|
+
const parent = pending.shift();
|
|
326
|
+
for (const childPid of childrenByParent.get(parent.pid) ?? []) {
|
|
327
|
+
if (visited.has(childPid))
|
|
328
|
+
continue;
|
|
329
|
+
visited.add(childPid);
|
|
330
|
+
const entry = table.get(childPid);
|
|
331
|
+
if (entry.creationTime === undefined ||
|
|
332
|
+
parent.creationTime === undefined ||
|
|
333
|
+
entry.creationTime < parent.creationTime)
|
|
334
|
+
continue;
|
|
335
|
+
descendants.add(childPid);
|
|
336
|
+
pending.push({
|
|
337
|
+
pid: childPid,
|
|
338
|
+
startTime: entry.startTime,
|
|
339
|
+
creationTime: entry.creationTime,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return descendants;
|
|
344
|
+
}
|
|
345
|
+
function refreshDescendants(root, knownDescendants, table, requireComparableCreationOrder = false) {
|
|
96
346
|
const childrenByParent = new Map();
|
|
97
347
|
for (const [processId, entry] of table) {
|
|
98
348
|
const children = childrenByParent.get(entry.ppid) ?? [];
|
|
@@ -100,20 +350,30 @@ function refreshDescendants(root, knownDescendants, table) {
|
|
|
100
350
|
childrenByParent.set(entry.ppid, children);
|
|
101
351
|
}
|
|
102
352
|
const roots = [root, ...knownDescendants.values()].filter((identity) => identity !== undefined && table.get(identity.pid)?.startTime === identity.startTime);
|
|
103
|
-
const pending = roots
|
|
104
|
-
const visited = new Set(pending);
|
|
353
|
+
const pending = [...roots];
|
|
354
|
+
const visited = new Set(pending.map((identity) => identity.pid));
|
|
105
355
|
while (pending.length > 0) {
|
|
106
356
|
const parent = pending.shift();
|
|
107
|
-
for (const descendantPid of childrenByParent.get(parent) ?? []) {
|
|
357
|
+
for (const descendantPid of childrenByParent.get(parent.pid) ?? []) {
|
|
108
358
|
if (visited.has(descendantPid))
|
|
109
359
|
continue;
|
|
110
360
|
visited.add(descendantPid);
|
|
111
361
|
const entry = table.get(descendantPid);
|
|
362
|
+
if (requireComparableCreationOrder &&
|
|
363
|
+
(parent.creationTime === undefined ||
|
|
364
|
+
entry.creationTime === undefined ||
|
|
365
|
+
entry.creationTime < parent.creationTime))
|
|
366
|
+
continue;
|
|
112
367
|
const knownIdentity = knownDescendants.get(descendantPid);
|
|
113
368
|
if (knownIdentity && knownIdentity.startTime !== entry.startTime)
|
|
114
369
|
continue;
|
|
115
|
-
|
|
116
|
-
|
|
370
|
+
const identity = {
|
|
371
|
+
pid: descendantPid,
|
|
372
|
+
startTime: entry.startTime,
|
|
373
|
+
...(entry.creationTime !== undefined ? { creationTime: entry.creationTime } : {}),
|
|
374
|
+
};
|
|
375
|
+
knownDescendants.set(descendantPid, identity);
|
|
376
|
+
pending.push(identity);
|
|
117
377
|
}
|
|
118
378
|
}
|
|
119
379
|
}
|
|
@@ -123,32 +383,347 @@ function refreshObservedProcesses(state, table, markerPids = new Set(), allowRoo
|
|
|
123
383
|
allowRootWithoutMarker ||
|
|
124
384
|
(state.rootDiscoveryPending && (state.platform === 'win32' || state.marker === undefined)))) {
|
|
125
385
|
const entry = table.get(state.pid);
|
|
126
|
-
if (entry)
|
|
127
|
-
state.root = {
|
|
386
|
+
if (entry && (state.platform !== 'win32' || entry.creationTime !== undefined)) {
|
|
387
|
+
state.root = {
|
|
388
|
+
pid: state.pid,
|
|
389
|
+
startTime: entry.startTime,
|
|
390
|
+
...(entry.creationTime !== undefined ? { creationTime: entry.creationTime } : {}),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
128
393
|
}
|
|
129
394
|
state.rootDiscoveryPending = false;
|
|
130
395
|
for (const processId of markerPids) {
|
|
396
|
+
if (processId === state.lease?.sentinel.pid)
|
|
397
|
+
continue;
|
|
131
398
|
if (processId === state.root?.pid)
|
|
132
399
|
continue;
|
|
133
400
|
const entry = table.get(processId);
|
|
134
401
|
if (!entry)
|
|
135
402
|
continue;
|
|
403
|
+
if (state.platform === 'win32' && entry.creationTime === undefined)
|
|
404
|
+
continue;
|
|
136
405
|
const known = state.descendants.get(processId);
|
|
137
406
|
if (!known || known.startTime === entry.startTime) {
|
|
138
|
-
state.descendants.set(processId, {
|
|
407
|
+
state.descendants.set(processId, {
|
|
408
|
+
pid: processId,
|
|
409
|
+
startTime: entry.startTime,
|
|
410
|
+
...(entry.creationTime !== undefined ? { creationTime: entry.creationTime } : {}),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (state.platform === 'win32' || state.marker === undefined) {
|
|
415
|
+
refreshDescendants(state.root, state.descendants, table, state.platform === 'win32');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function latchObservationFailure(state, error) {
|
|
419
|
+
if (state.failure)
|
|
420
|
+
return;
|
|
421
|
+
state.failure = error;
|
|
422
|
+
for (const listener of state.failureListeners)
|
|
423
|
+
listener(error);
|
|
424
|
+
}
|
|
425
|
+
function acceptPosixSnapshot(state, snapshot, stage) {
|
|
426
|
+
if (!snapshot.markerLookupSucceeded && state.marker !== undefined) {
|
|
427
|
+
latchObservationFailure(state, posixMarkerLookupFailure(snapshot, stage));
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
if (state.lease) {
|
|
431
|
+
const leaseFailure = validateLeaseSnapshot(snapshot, state.lease, stage);
|
|
432
|
+
if (leaseFailure) {
|
|
433
|
+
latchObservationFailure(state, leaseFailure);
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const rootEntry = snapshot.table.get(state.pid);
|
|
438
|
+
let confirmedUnmarkedRoot = snapshot.confirmedUnmarkedPids.has(state.pid);
|
|
439
|
+
if (state.marker !== undefined &&
|
|
440
|
+
rootEntry !== undefined &&
|
|
441
|
+
!rootEntry.state?.startsWith('Z') &&
|
|
442
|
+
(state.root === undefined || state.root.startTime === rootEntry.startTime) &&
|
|
443
|
+
!snapshot.markerPids.has(state.pid)) {
|
|
444
|
+
confirmedUnmarkedRoot ||=
|
|
445
|
+
state.unmarkedRootCandidate?.startTime === rootEntry.startTime;
|
|
446
|
+
state.unmarkedRootCandidate = { pid: state.pid, startTime: rootEntry.startTime };
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
state.unmarkedRootCandidate = undefined;
|
|
450
|
+
}
|
|
451
|
+
if (state.marker !== undefined &&
|
|
452
|
+
state.dependencies.enforceCooperativeProcessGroup === true &&
|
|
453
|
+
rootEntry !== undefined &&
|
|
454
|
+
!rootEntry.state?.startsWith('Z') &&
|
|
455
|
+
confirmedUnmarkedRoot) {
|
|
456
|
+
latchObservationFailure(state, cleanupUnconfirmed('custom process contract violation: a live provider process lost its process marker.'));
|
|
457
|
+
}
|
|
458
|
+
refreshObservedProcesses(state, snapshot.table, snapshot.markerPids);
|
|
459
|
+
if (state.marker !== undefined) {
|
|
460
|
+
const providerMarkerPids = [...snapshot.markerPids].filter((pid) => pid !== state.lease?.sentinel.pid);
|
|
461
|
+
if (state.rootPgidMode === undefined) {
|
|
462
|
+
if (state.root?.pid === state.pid &&
|
|
463
|
+
rootEntry?.startTime === state.root.startTime &&
|
|
464
|
+
rootEntry.pgid !== undefined &&
|
|
465
|
+
snapshot.markerPids.has(state.pid)) {
|
|
466
|
+
if (rootEntry.pgid === state.pid) {
|
|
467
|
+
state.rootPgid = rootEntry.pgid;
|
|
468
|
+
state.rootPgidMode = 'owned';
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
state.rootPgidMode = 'shared';
|
|
472
|
+
if (state.dependencies.enforceCooperativeProcessGroup === true) {
|
|
473
|
+
latchObservationFailure(state, cleanupUnconfirmed('custom process contract violation: the provider root left its initial process group.'));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else if (rootEntry === undefined &&
|
|
478
|
+
(providerMarkerPids.some((pid) => snapshot.table.get(pid)?.pgid !== undefined) ||
|
|
479
|
+
[...snapshot.table].some(([pid, entry]) => pid !== state.lease?.sentinel.pid &&
|
|
480
|
+
entry.pgid === state.pid &&
|
|
481
|
+
!entry.state?.startsWith('Z')))) {
|
|
482
|
+
state.rootPgid = state.pid;
|
|
483
|
+
state.rootPgidMode = 'missing';
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const expectedPgid = state.rootPgidMode === 'owned'
|
|
487
|
+
? state.rootPgid
|
|
488
|
+
: state.rootPgidMode === 'missing'
|
|
489
|
+
? state.pid
|
|
490
|
+
: undefined;
|
|
491
|
+
if (state.dependencies.enforceCooperativeProcessGroup === true &&
|
|
492
|
+
expectedPgid !== undefined &&
|
|
493
|
+
providerMarkerPids.some((pid) => {
|
|
494
|
+
const entry = snapshot.table.get(pid);
|
|
495
|
+
return entry?.pgid !== undefined && entry.pgid !== expectedPgid;
|
|
496
|
+
})) {
|
|
497
|
+
latchObservationFailure(state, cleanupUnconfirmed('custom process contract violation: a marked process left its initial process group.'));
|
|
498
|
+
}
|
|
499
|
+
const liveKnownIdentities = [state.root, ...state.descendants.values()].filter((identity) => identityMatches(identity, snapshot.table) &&
|
|
500
|
+
!snapshot.table.get(identity.pid)?.state?.startsWith('Z'));
|
|
501
|
+
const markerlessKnownIdentity = liveKnownIdentities.some((identity) => !snapshot.markerPids.has(identity.pid));
|
|
502
|
+
let markerlessInitialGroupMember = false;
|
|
503
|
+
const groupLeader = state.rootPgid === undefined
|
|
504
|
+
? undefined
|
|
505
|
+
: snapshot.table.get(state.rootPgid);
|
|
506
|
+
const groupLeaderWasReused = state.rootPgidMode === 'owned' &&
|
|
507
|
+
groupLeader !== undefined &&
|
|
508
|
+
groupLeader.startTime !== state.root?.startTime;
|
|
509
|
+
if (state.rootPgid !== undefined && !groupLeaderWasReused) {
|
|
510
|
+
for (const [pid, entry] of snapshot.table) {
|
|
511
|
+
if (entry.pgid !== state.rootPgid || entry.state?.startsWith('Z'))
|
|
512
|
+
continue;
|
|
513
|
+
if (pid !== state.pid && !snapshot.markerPids.has(pid)) {
|
|
514
|
+
markerlessInitialGroupMember = true;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (state.dependencies.enforceCooperativeProcessGroup === true &&
|
|
519
|
+
(markerlessKnownIdentity || markerlessInitialGroupMember)) {
|
|
520
|
+
latchObservationFailure(state, cleanupUnconfirmed('custom process contract violation: a live provider process lost its process marker.'));
|
|
139
521
|
}
|
|
140
522
|
}
|
|
141
|
-
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
function getPosixProcessSampler(execFileImpl) {
|
|
526
|
+
const existing = posixSamplers.get(execFileImpl);
|
|
527
|
+
if (existing)
|
|
528
|
+
return existing;
|
|
529
|
+
const sampler = {
|
|
530
|
+
execFile: execFileImpl,
|
|
531
|
+
states: new Set(),
|
|
532
|
+
identityScannedAt: Number.NEGATIVE_INFINITY,
|
|
533
|
+
markerScannedAt: Number.NEGATIVE_INFINITY,
|
|
534
|
+
};
|
|
535
|
+
posixSamplers.set(execFileImpl, sampler);
|
|
536
|
+
return sampler;
|
|
142
537
|
}
|
|
143
|
-
function
|
|
538
|
+
function snapshotFromSampler(state, sampler) {
|
|
539
|
+
const markerLookupSucceeded = state.marker === undefined || sampler.markerFailure === undefined;
|
|
540
|
+
const table = state.marker === undefined || !markerLookupSucceeded
|
|
541
|
+
? sampler.identityTable
|
|
542
|
+
: sampler.markerScan?.table;
|
|
543
|
+
if (!table)
|
|
544
|
+
return undefined;
|
|
545
|
+
const markerPids = state.marker === undefined || !markerLookupSucceeded || sampler.markerScan === undefined
|
|
546
|
+
? new Set()
|
|
547
|
+
: parseMarkerProcessIds(sampler.markerScan, state.markerEnvironmentVariable, state.marker);
|
|
548
|
+
return {
|
|
549
|
+
table,
|
|
550
|
+
markerPids,
|
|
551
|
+
markerLookupSucceeded,
|
|
552
|
+
markerLookupAttempts: state.marker === undefined ? 0 : 1,
|
|
553
|
+
...(sampler.markerFailure ? { markerLookupFailure: sampler.markerFailure } : {}),
|
|
554
|
+
confirmedUnmarkedPids: new Set(),
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function deliverSamplerIdentity(sampler) {
|
|
558
|
+
for (const state of sampler.states) {
|
|
559
|
+
if (state.disposed)
|
|
560
|
+
continue;
|
|
561
|
+
if (sampler.identityFailure) {
|
|
562
|
+
latchObservationFailure(state, cleanupUnconfirmed('POSIX process identity lookup failed.'));
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (state.marker !== undefined) {
|
|
566
|
+
if (sampler.identityTable) {
|
|
567
|
+
refreshDescendants(state.root, state.descendants, sampler.identityTable);
|
|
568
|
+
}
|
|
569
|
+
const expectedPgid = state.rootPgidMode === 'owned'
|
|
570
|
+
? state.rootPgid
|
|
571
|
+
: state.rootPgidMode === 'missing'
|
|
572
|
+
? state.pid
|
|
573
|
+
: undefined;
|
|
574
|
+
if (state.dependencies.enforceCooperativeProcessGroup === true &&
|
|
575
|
+
expectedPgid !== undefined &&
|
|
576
|
+
[state.root, ...state.descendants.values()].some((identity) => {
|
|
577
|
+
if (!identity || !sampler.identityTable)
|
|
578
|
+
return false;
|
|
579
|
+
const entry = sampler.identityTable.get(identity.pid);
|
|
580
|
+
return entry?.startTime === identity.startTime &&
|
|
581
|
+
entry.pgid !== undefined && entry.pgid !== expectedPgid;
|
|
582
|
+
})) {
|
|
583
|
+
latchObservationFailure(state, cleanupUnconfirmed('custom process contract violation: a marked process left its initial process group.'));
|
|
584
|
+
}
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const snapshot = snapshotFromSampler(state, sampler);
|
|
588
|
+
if (snapshot)
|
|
589
|
+
acceptPosixSnapshot(state, snapshot, 'observation');
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function deliverSamplerMarkers(sampler, stage) {
|
|
593
|
+
for (const state of sampler.states) {
|
|
594
|
+
if (state.disposed || state.marker === undefined)
|
|
595
|
+
continue;
|
|
596
|
+
const snapshot = snapshotFromSampler(state, sampler);
|
|
597
|
+
if (snapshot)
|
|
598
|
+
acceptPosixSnapshot(state, snapshot, stage);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function scanSamplerIdentity(sampler, force) {
|
|
602
|
+
if (sampler.identityInFlight)
|
|
603
|
+
return sampler.identityInFlight;
|
|
604
|
+
const now = Date.now();
|
|
605
|
+
if (!force &&
|
|
606
|
+
Number.isFinite(sampler.identityScannedAt) &&
|
|
607
|
+
now - sampler.identityScannedAt < PROCESS_TREE_IDENTITY_INTERVAL_MS)
|
|
608
|
+
return Promise.resolve();
|
|
609
|
+
sampler.identityScannedAt = now;
|
|
610
|
+
sampler.identityInFlight = readPosixIdentityTable(sampler.execFile)
|
|
611
|
+
.then((result) => {
|
|
612
|
+
sampler.identityFailure = result.failure;
|
|
613
|
+
sampler.identityTable = result.output === null
|
|
614
|
+
? undefined
|
|
615
|
+
: parsePosixProcessTable(result.output);
|
|
616
|
+
deliverSamplerIdentity(sampler);
|
|
617
|
+
})
|
|
618
|
+
.finally(() => {
|
|
619
|
+
sampler.identityInFlight = undefined;
|
|
620
|
+
});
|
|
621
|
+
return sampler.identityInFlight;
|
|
622
|
+
}
|
|
623
|
+
function runMarkerScan(sampler) {
|
|
624
|
+
sampler.markerScannedAt = Date.now();
|
|
625
|
+
sampler.markerInFlight = readPosixMarkerOutput(sampler.execFile)
|
|
626
|
+
.then((result) => {
|
|
627
|
+
sampler.markerFailure = result.failure;
|
|
628
|
+
sampler.markerScan = result.output === null
|
|
629
|
+
? undefined
|
|
630
|
+
: parsePosixMarkerScan(result.output);
|
|
631
|
+
deliverSamplerMarkers(sampler, 'observation');
|
|
632
|
+
})
|
|
633
|
+
.finally(() => {
|
|
634
|
+
sampler.markerInFlight = undefined;
|
|
635
|
+
});
|
|
636
|
+
return sampler.markerInFlight;
|
|
637
|
+
}
|
|
638
|
+
function scanSamplerMarkers(sampler, force) {
|
|
639
|
+
if (sampler.markerInFlight)
|
|
640
|
+
return sampler.markerInFlight;
|
|
641
|
+
const now = Date.now();
|
|
642
|
+
if (!force &&
|
|
643
|
+
Number.isFinite(sampler.markerScannedAt) &&
|
|
644
|
+
now - sampler.markerScannedAt < PROCESS_TREE_MARKER_INTERVAL_MS)
|
|
645
|
+
return Promise.resolve();
|
|
646
|
+
return runMarkerScan(sampler);
|
|
647
|
+
}
|
|
648
|
+
function schedulePosixSampler(sampler) {
|
|
649
|
+
if (sampler.timer)
|
|
650
|
+
clearTimeout(sampler.timer);
|
|
651
|
+
sampler.timer = undefined;
|
|
652
|
+
if (sampler.states.size === 0)
|
|
653
|
+
return;
|
|
654
|
+
const now = Date.now();
|
|
655
|
+
const identityDelay = Math.max(0, PROCESS_TREE_IDENTITY_INTERVAL_MS - (now - sampler.identityScannedAt));
|
|
656
|
+
const markerDelay = [...sampler.states].some((state) => state.marker !== undefined)
|
|
657
|
+
? Math.max(0, PROCESS_TREE_MARKER_INTERVAL_MS - (now - sampler.markerScannedAt))
|
|
658
|
+
: Number.POSITIVE_INFINITY;
|
|
659
|
+
const delayMs = Math.min(identityDelay, markerDelay);
|
|
660
|
+
sampler.timer = setTimeout(() => {
|
|
661
|
+
sampler.timer = undefined;
|
|
662
|
+
const current = Date.now();
|
|
663
|
+
const scans = [];
|
|
664
|
+
if (current - sampler.identityScannedAt >= PROCESS_TREE_IDENTITY_INTERVAL_MS) {
|
|
665
|
+
scans.push(scanSamplerIdentity(sampler, false));
|
|
666
|
+
}
|
|
667
|
+
if (markerDelay !== Number.POSITIVE_INFINITY &&
|
|
668
|
+
current - sampler.markerScannedAt >= PROCESS_TREE_MARKER_INTERVAL_MS) {
|
|
669
|
+
scans.push(scanSamplerMarkers(sampler, false));
|
|
670
|
+
}
|
|
671
|
+
void Promise.all(scans).finally(() => schedulePosixSampler(sampler));
|
|
672
|
+
}, delayMs);
|
|
673
|
+
sampler.timer.unref();
|
|
674
|
+
}
|
|
675
|
+
function stopObservingPosixState(state) {
|
|
676
|
+
const sampler = state.sampler;
|
|
677
|
+
if (!sampler)
|
|
678
|
+
return;
|
|
679
|
+
state.sampler = undefined;
|
|
680
|
+
sampler.states.delete(state);
|
|
681
|
+
schedulePosixSampler(sampler);
|
|
682
|
+
}
|
|
683
|
+
async function readImmediatePosixSnapshot(state, stage) {
|
|
684
|
+
const sampler = state.sampler ?? getPosixProcessSampler(state.dependencies.execFile ?? defaultExecFile);
|
|
685
|
+
if (state.marker === undefined)
|
|
686
|
+
await scanSamplerIdentity(sampler, true);
|
|
687
|
+
else
|
|
688
|
+
await scanSamplerMarkers(sampler, true);
|
|
689
|
+
if (state.marker === undefined && sampler.identityFailure) {
|
|
690
|
+
latchObservationFailure(state, cleanupUnconfirmed('POSIX process identity lookup failed.'));
|
|
691
|
+
return undefined;
|
|
692
|
+
}
|
|
693
|
+
if (state.marker !== undefined && sampler.markerFailure) {
|
|
694
|
+
latchObservationFailure(state, posixMarkerLookupFailure({
|
|
695
|
+
table: new Map(),
|
|
696
|
+
markerPids: new Set(),
|
|
697
|
+
markerLookupSucceeded: false,
|
|
698
|
+
markerLookupAttempts: 1,
|
|
699
|
+
markerLookupFailure: sampler.markerFailure,
|
|
700
|
+
confirmedUnmarkedPids: new Set(),
|
|
701
|
+
}, stage));
|
|
702
|
+
return undefined;
|
|
703
|
+
}
|
|
704
|
+
const snapshot = snapshotFromSampler(state, sampler);
|
|
705
|
+
if (!snapshot)
|
|
706
|
+
return undefined;
|
|
707
|
+
acceptPosixSnapshot(state, snapshot, stage);
|
|
708
|
+
return snapshot;
|
|
709
|
+
}
|
|
710
|
+
function createProcessTreeObservation(pid, marker, dependencies, startLookup, lease) {
|
|
144
711
|
const observation = {
|
|
145
712
|
pid,
|
|
713
|
+
onFailure: (listener) => {
|
|
714
|
+
const state = observationStates.get(observation);
|
|
715
|
+
if (!state)
|
|
716
|
+
return () => undefined;
|
|
717
|
+
state.failureListeners.add(listener);
|
|
718
|
+
if (state.failure)
|
|
719
|
+
listener(state.failure);
|
|
720
|
+
return () => state.failureListeners.delete(listener);
|
|
721
|
+
},
|
|
146
722
|
dispose: () => {
|
|
147
723
|
const state = observationStates.get(observation);
|
|
148
724
|
if (state) {
|
|
149
725
|
state.disposed = true;
|
|
150
|
-
|
|
151
|
-
clearTimeout(state.pollTimer);
|
|
726
|
+
stopObservingPosixState(state);
|
|
152
727
|
}
|
|
153
728
|
},
|
|
154
729
|
};
|
|
@@ -157,9 +732,12 @@ function createProcessTreeObservation(pid, marker, dependencies, startLookup) {
|
|
|
157
732
|
platform: dependencies.platform ?? process.platform,
|
|
158
733
|
marker,
|
|
159
734
|
markerEnvironmentVariable: dependencies.markerEnvironmentVariable ?? DEFAULT_PROCESS_MARKER_ENV,
|
|
735
|
+
lease,
|
|
160
736
|
dependencies,
|
|
161
737
|
rootDiscoveryPending: true,
|
|
738
|
+
initialWindowsLookupSucceeded: false,
|
|
162
739
|
descendants: new Map(),
|
|
740
|
+
failureListeners: new Set(),
|
|
163
741
|
disposed: false,
|
|
164
742
|
initialLookup: Promise.resolve(),
|
|
165
743
|
};
|
|
@@ -167,48 +745,45 @@ function createProcessTreeObservation(pid, marker, dependencies, startLookup) {
|
|
|
167
745
|
if (!startLookup)
|
|
168
746
|
return observation;
|
|
169
747
|
const execFileImpl = dependencies.execFile ?? defaultExecFile;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (!state.disposed && table)
|
|
748
|
+
if (state.platform === 'win32') {
|
|
749
|
+
state.initialLookup = readWindowsProcessTable(execFileImpl).then((table) => {
|
|
750
|
+
if (!state.disposed && table) {
|
|
751
|
+
state.initialWindowsLookupSucceeded = true;
|
|
173
752
|
refreshObservedProcesses(state, table);
|
|
174
|
-
})
|
|
175
|
-
: readPosixProcessTable(execFileImpl, state.markerEnvironmentVariable, marker).then((snapshot) => {
|
|
176
|
-
if (!state.disposed && snapshot) {
|
|
177
|
-
refreshObservedProcesses(state, snapshot.table, snapshot.markerPids, !snapshot.markerLookupSucceeded);
|
|
178
753
|
}
|
|
179
754
|
});
|
|
180
|
-
|
|
755
|
+
return observation;
|
|
756
|
+
}
|
|
757
|
+
const sampler = getPosixProcessSampler(execFileImpl);
|
|
758
|
+
const periodic = dependencies.enforceCooperativeProcessGroup === true;
|
|
759
|
+
if (periodic) {
|
|
760
|
+
state.sampler = sampler;
|
|
761
|
+
sampler.states.add(state);
|
|
762
|
+
}
|
|
763
|
+
state.initialLookup = (marker === undefined
|
|
764
|
+
? scanSamplerIdentity(sampler, true)
|
|
765
|
+
: Promise.all([
|
|
766
|
+
...(periodic ? [scanSamplerIdentity(sampler, true)] : []),
|
|
767
|
+
scanSamplerMarkers(sampler, true),
|
|
768
|
+
])).then(() => {
|
|
181
769
|
if (state.disposed)
|
|
182
770
|
return;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
void state.initialLookup.finally(scheduleRefresh);
|
|
771
|
+
if (periodic)
|
|
772
|
+
return;
|
|
773
|
+
const snapshot = snapshotFromSampler(state, sampler);
|
|
774
|
+
if (snapshot)
|
|
775
|
+
acceptPosixSnapshot(state, snapshot, 'observation');
|
|
776
|
+
});
|
|
777
|
+
if (periodic)
|
|
778
|
+
void state.initialLookup.finally(() => schedulePosixSampler(sampler));
|
|
192
779
|
return observation;
|
|
193
780
|
}
|
|
194
|
-
export function observeProcessTree(pid, marker, dependencies = {}) {
|
|
195
|
-
return createProcessTreeObservation(pid, marker, dependencies, true);
|
|
781
|
+
export function observeProcessTree(pid, marker, dependencies = {}, lease) {
|
|
782
|
+
return createProcessTreeObservation(pid, marker, dependencies, true, lease);
|
|
196
783
|
}
|
|
197
784
|
export async function assertProcessTreeInspectionAvailable(dependencies = {}) {
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
if (platform === 'win32') {
|
|
201
|
-
if (!(await readWindowsProcessTable(execFileImpl))) {
|
|
202
|
-
throw cleanupUnconfirmed('Windows process identity lookup failed.');
|
|
203
|
-
}
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const snapshot = await readPosixProcessTable(execFileImpl, dependencies.markerEnvironmentVariable ?? DEFAULT_PROCESS_MARKER_ENV, 'preflight-marker');
|
|
207
|
-
if (!snapshot)
|
|
208
|
-
throw cleanupUnconfirmed('POSIX process identity lookup failed.');
|
|
209
|
-
if (!snapshot.markerLookupSucceeded) {
|
|
210
|
-
throw cleanupUnconfirmed('POSIX process marker lookup failed.');
|
|
211
|
-
}
|
|
785
|
+
const lease = await acquireProcessTreeInspectionLease(dependencies);
|
|
786
|
+
await lease.release();
|
|
212
787
|
}
|
|
213
788
|
function cleanupUnconfirmed(detail) {
|
|
214
789
|
return new Error(`Process tree cleanup unconfirmed: ${detail}`);
|
|
@@ -229,10 +804,52 @@ function signalIdentity(identity, snapshot, signal, kill) {
|
|
|
229
804
|
// The process can exit between identity verification and the signal.
|
|
230
805
|
}
|
|
231
806
|
}
|
|
807
|
+
function safelyOwnedProcessGroup(state, snapshot) {
|
|
808
|
+
if (state.marker === undefined ||
|
|
809
|
+
state.rootPgid === undefined ||
|
|
810
|
+
state.rootPgid !== state.pid)
|
|
811
|
+
return undefined;
|
|
812
|
+
const pgid = state.rootPgid;
|
|
813
|
+
const members = [...snapshot.table].filter(([, entry]) => entry.pgid === pgid && !entry.state?.startsWith('Z'));
|
|
814
|
+
if (members.length === 0)
|
|
815
|
+
return undefined;
|
|
816
|
+
if (state.rootPgidMode === 'owned') {
|
|
817
|
+
const currentLeader = snapshot.table.get(pgid);
|
|
818
|
+
if (currentLeader !== undefined &&
|
|
819
|
+
currentLeader.startTime !== state.root?.startTime)
|
|
820
|
+
return undefined;
|
|
821
|
+
return pgid;
|
|
822
|
+
}
|
|
823
|
+
const known = [state.root, ...state.descendants.values()];
|
|
824
|
+
if (members.some(([pid, entry]) => !known.some((identity) => identity?.pid === pid && identity.startTime === entry.startTime)))
|
|
825
|
+
return undefined;
|
|
826
|
+
return pgid;
|
|
827
|
+
}
|
|
828
|
+
function signalProcessGroup(pgid, signal, kill) {
|
|
829
|
+
if (pgid === undefined)
|
|
830
|
+
return;
|
|
831
|
+
try {
|
|
832
|
+
kill(-pgid, signal);
|
|
833
|
+
}
|
|
834
|
+
catch {
|
|
835
|
+
// The verified process group can exit before the signal.
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
function latchLiveDescendantCompletionFailure(state, snapshot) {
|
|
839
|
+
if (state.dependencies.enforceCooperativeProcessGroup !== true)
|
|
840
|
+
return;
|
|
841
|
+
const liveDescendant = [...state.descendants.values()].some((identity) => identity.pid !== state.pid &&
|
|
842
|
+
identity.pid !== state.lease?.sentinel.pid &&
|
|
843
|
+
posixIdentityMatches(identity, snapshot) &&
|
|
844
|
+
!snapshot.table.get(identity.pid)?.state?.startsWith('Z'));
|
|
845
|
+
if (!liveDescendant)
|
|
846
|
+
return;
|
|
847
|
+
latchObservationFailure(state, cleanupUnconfirmed('custom process contract violation: all provider descendants must exit before completion.'));
|
|
848
|
+
}
|
|
232
849
|
async function runTaskkill(pid, execFileImpl) {
|
|
233
850
|
return (await runCommand('taskkill', ['/PID', String(pid), '/T', '/F'], execFileImpl, PROCESS_TREE_COMMAND_TIMEOUT_MS)) !== null;
|
|
234
851
|
}
|
|
235
|
-
async function terminateWindowsProcessTree(state, requireConfirmedCleanup) {
|
|
852
|
+
async function terminateWindowsProcessTree(state, requireConfirmedCleanup, allowUnobservedProcessOnNormalCompletion) {
|
|
236
853
|
const execFileImpl = state.dependencies.execFile ?? defaultExecFile;
|
|
237
854
|
if (!requireConfirmedCleanup) {
|
|
238
855
|
if (!(await runTaskkill(state.pid, execFileImpl))) {
|
|
@@ -240,122 +857,144 @@ async function terminateWindowsProcessTree(state, requireConfirmedCleanup) {
|
|
|
240
857
|
}
|
|
241
858
|
return;
|
|
242
859
|
}
|
|
860
|
+
const identitiesBeforeNormalCompletion = allowUnobservedProcessOnNormalCompletion
|
|
861
|
+
? {
|
|
862
|
+
root: state.root,
|
|
863
|
+
descendants: new Map(state.descendants),
|
|
864
|
+
}
|
|
865
|
+
: undefined;
|
|
243
866
|
await state.initialLookup;
|
|
244
867
|
const table = await readWindowsProcessTable(execFileImpl);
|
|
245
868
|
if (!table) {
|
|
246
869
|
throw cleanupUnconfirmed('Windows process identity lookup failed.');
|
|
247
870
|
}
|
|
248
871
|
refreshObservedProcesses(state, table);
|
|
249
|
-
const
|
|
250
|
-
|
|
872
|
+
const root = identitiesBeforeNormalCompletion
|
|
873
|
+
? identitiesBeforeNormalCompletion.root
|
|
874
|
+
: state.root;
|
|
875
|
+
const descendants = identitiesBeforeNormalCompletion?.descendants ?? state.descendants;
|
|
876
|
+
const observedIdentities = [root, ...descendants.values()].filter((identity) => identity !== undefined);
|
|
877
|
+
const candidatePids = collectDescendantProcessIds(state.pid, table);
|
|
878
|
+
const comparableCandidatePids = collectComparableWindowsDescendantProcessIds(root, table);
|
|
879
|
+
const hasInvalidCandidateEdge = [...candidatePids].some((pid) => !comparableCandidatePids.has(pid));
|
|
880
|
+
const hasUnobservedCandidate = [...candidatePids].some((pid) => !identityMatches(descendants.get(pid), table));
|
|
881
|
+
const observedPidWasReused = (candidateTable) => observedIdentities.some((identity) => {
|
|
882
|
+
const entry = candidateTable.get(identity.pid);
|
|
883
|
+
return entry !== undefined && entry.startTime !== identity.startTime;
|
|
884
|
+
});
|
|
885
|
+
const rootTarget = identityMatches(root, table) ? root : undefined;
|
|
886
|
+
const liveDescendants = [...descendants.values()].filter((identity) => identityMatches(identity, table));
|
|
251
887
|
const liveDescendantPids = new Set(liveDescendants.map((identity) => identity.pid));
|
|
252
888
|
const targets = rootTarget
|
|
253
889
|
? [rootTarget]
|
|
254
890
|
: liveDescendants.filter((identity) => !liveDescendantPids.has(table.get(identity.pid).ppid));
|
|
255
|
-
const taskkillResults =
|
|
891
|
+
const taskkillResults = hasInvalidCandidateEdge || hasUnobservedCandidate
|
|
892
|
+
? []
|
|
893
|
+
: await Promise.all(targets.map((identity) => runTaskkill(identity.pid, execFileImpl)));
|
|
256
894
|
if (taskkillResults.some((result) => !result)) {
|
|
257
895
|
throw cleanupUnconfirmed('taskkill did not confirm termination.');
|
|
258
896
|
}
|
|
259
897
|
const verifiedTable = await readWindowsProcessTable(execFileImpl);
|
|
260
898
|
if (!verifiedTable)
|
|
261
899
|
throw cleanupUnconfirmed('Windows termination verification failed.');
|
|
900
|
+
const verifiedCandidatePids = collectDescendantProcessIds(state.pid, verifiedTable);
|
|
262
901
|
const verifiedIdentities = [rootTarget, ...liveDescendants].filter((identity) => identity !== undefined);
|
|
263
902
|
if (verifiedIdentities.some((identity) => identityMatches(identity, verifiedTable))) {
|
|
264
903
|
throw cleanupUnconfirmed('a verified Windows process is still running.');
|
|
265
904
|
}
|
|
266
|
-
if (
|
|
267
|
-
throw cleanupUnconfirmed(
|
|
905
|
+
if (hasInvalidCandidateEdge) {
|
|
906
|
+
throw cleanupUnconfirmed(hasUnobservedCandidate
|
|
907
|
+
? 'an unobserved Windows descendant candidate has invalid process creation order.'
|
|
908
|
+
: 'a Windows descendant candidate has invalid process creation order.');
|
|
909
|
+
}
|
|
910
|
+
if (hasUnobservedCandidate) {
|
|
911
|
+
throw cleanupUnconfirmed('an unobserved Windows descendant candidate was found after the root exited.');
|
|
912
|
+
}
|
|
913
|
+
if (verifiedCandidatePids.size > 0) {
|
|
914
|
+
throw cleanupUnconfirmed('a Windows descendant candidate is still running.');
|
|
915
|
+
}
|
|
916
|
+
const unobservedNormalCompletionIsAllowed = allowUnobservedProcessOnNormalCompletion &&
|
|
917
|
+
state.initialWindowsLookupSucceeded &&
|
|
918
|
+
!table.has(state.pid) &&
|
|
919
|
+
candidatePids.size === 0 &&
|
|
920
|
+
!verifiedTable.has(state.pid) &&
|
|
921
|
+
verifiedCandidatePids.size === 0;
|
|
922
|
+
if (requireConfirmedCleanup &&
|
|
923
|
+
targets.length === 0 &&
|
|
924
|
+
(observedPidWasReused(table) ||
|
|
925
|
+
observedPidWasReused(verifiedTable) ||
|
|
926
|
+
(observedIdentities.length === 0 && !unobservedNormalCompletionIsAllowed))) {
|
|
927
|
+
throw cleanupUnconfirmed(observedIdentities.length > 0
|
|
928
|
+
? 'an observed Windows process identifier was reused.'
|
|
929
|
+
: 'no inherited-pipe process identity was observed.');
|
|
268
930
|
}
|
|
269
931
|
}
|
|
270
|
-
async function terminatePosixProcessTree(state, requireConfirmedCleanup, allowInspectionUnavailable) {
|
|
932
|
+
async function terminatePosixProcessTree(state, requireConfirmedCleanup, allowInspectionUnavailable, requireDescendantsExitedBeforeCompletion) {
|
|
271
933
|
await state.initialLookup;
|
|
272
|
-
|
|
934
|
+
state.disposed = true;
|
|
935
|
+
stopObservingPosixState(state);
|
|
273
936
|
const kill = state.dependencies.kill ?? process.kill.bind(process);
|
|
274
|
-
|
|
275
|
-
let snapshot = await readPosixProcessTable(execFileImpl, state.markerEnvironmentVariable, state.marker);
|
|
937
|
+
let snapshot = await readImmediatePosixSnapshot(state, 'termination-start');
|
|
276
938
|
if (!snapshot) {
|
|
277
939
|
if (!requireConfirmedCleanup && allowInspectionUnavailable)
|
|
278
940
|
return;
|
|
279
|
-
throw cleanupUnconfirmed('POSIX process identity lookup failed.');
|
|
941
|
+
throw state.failure ?? cleanupUnconfirmed('POSIX process identity lookup failed.');
|
|
280
942
|
}
|
|
281
|
-
if (
|
|
282
|
-
|
|
943
|
+
if (requireDescendantsExitedBeforeCompletion) {
|
|
944
|
+
latchLiveDescendantCompletionFailure(state, snapshot);
|
|
283
945
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const next = await readPosixProcessTable(execFileImpl, state.markerEnvironmentVariable, state.marker, Math.min(PROCESS_TREE_LOOKUP_TIMEOUT_MS, Math.max(1, deadline - Date.now())));
|
|
297
|
-
if (!next)
|
|
298
|
-
break;
|
|
299
|
-
markerLookupEverFailed ||=
|
|
300
|
-
state.marker !== undefined && !next.markerLookupSucceeded;
|
|
301
|
-
if (markerLookupEverFailed)
|
|
302
|
-
break;
|
|
303
|
-
snapshot = next;
|
|
304
|
-
const previousSize = state.descendants.size;
|
|
305
|
-
refreshObservedProcesses(state, snapshot.table, snapshot.markerPids);
|
|
306
|
-
if (posixIdentityMatches(state.root, snapshot)) {
|
|
307
|
-
signalIdentity(state.root, snapshot, 'SIGSTOP', kill);
|
|
946
|
+
const liveIdentities = (current) => [state.root, ...state.descendants.values()].filter((identity) => posixIdentityMatches(identity, current) &&
|
|
947
|
+
!current.table.get(identity.pid)?.state?.startsWith('Z'));
|
|
948
|
+
const liveMarkerPids = (current) => state.marker === undefined
|
|
949
|
+
? []
|
|
950
|
+
: [...current.markerPids].filter((pid) => pid !== state.lease?.sentinel.pid &&
|
|
951
|
+
!current.table.get(pid)?.state?.startsWith('Z'));
|
|
952
|
+
const signalOwned = (current, signal) => {
|
|
953
|
+
if (state.marker !== undefined && !current.markerLookupSucceeded)
|
|
954
|
+
return;
|
|
955
|
+
signalProcessGroup(safelyOwnedProcessGroup(state, current), signal, kill);
|
|
956
|
+
for (const identity of liveIdentities(current).reverse()) {
|
|
957
|
+
signalIdentity(identity, current, signal, kill);
|
|
308
958
|
}
|
|
309
|
-
|
|
310
|
-
|
|
959
|
+
};
|
|
960
|
+
const initialTargets = liveIdentities(snapshot);
|
|
961
|
+
const initialMarkerPids = liveMarkerPids(snapshot);
|
|
962
|
+
const initialProcessGroup = safelyOwnedProcessGroup(state, snapshot);
|
|
963
|
+
if (initialTargets.length === 0 &&
|
|
964
|
+
initialMarkerPids.length === 0 &&
|
|
965
|
+
initialProcessGroup === undefined) {
|
|
966
|
+
if (requireConfirmedCleanup && state.marker === undefined) {
|
|
967
|
+
throw cleanupUnconfirmed('no inherited-pipe process identity was observed.');
|
|
311
968
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const processState = snapshot.table.get(identity.pid)?.state;
|
|
316
|
-
return processState?.startsWith('T') || processState?.startsWith('Z');
|
|
317
|
-
});
|
|
318
|
-
if (allFrozen && state.descendants.size === previousSize)
|
|
319
|
-
break;
|
|
320
|
-
}
|
|
321
|
-
const killed = [state.root, ...state.descendants.values()].filter((identity) => identity !== undefined &&
|
|
322
|
-
posixIdentityMatches(identity, snapshot));
|
|
323
|
-
for (const identity of killed.reverse()) {
|
|
324
|
-
signalIdentity(identity, snapshot, 'SIGKILL', kill);
|
|
969
|
+
if (state.failure)
|
|
970
|
+
throw state.failure;
|
|
971
|
+
return;
|
|
325
972
|
}
|
|
326
|
-
|
|
327
|
-
|
|
973
|
+
signalOwned(snapshot, 'SIGTERM');
|
|
974
|
+
await new Promise((resolve) => {
|
|
975
|
+
setTimeout(resolve, PROCESS_TREE_TERMINATION_GRACE_MS);
|
|
976
|
+
});
|
|
977
|
+
const killSnapshot = await readImmediatePosixSnapshot(state, 'termination-kill');
|
|
978
|
+
if (!killSnapshot) {
|
|
979
|
+
throw state.failure ?? cleanupUnconfirmed('POSIX termination audit failed.');
|
|
328
980
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
throw cleanupUnconfirmed('POSIX termination verification failed.');
|
|
334
|
-
if (!verified.markerLookupSucceeded) {
|
|
335
|
-
throw cleanupUnconfirmed('POSIX process marker verification failed.');
|
|
336
|
-
}
|
|
337
|
-
const stillRunning = killed.some((identity) => {
|
|
338
|
-
if (!posixIdentityMatches(identity, verified))
|
|
339
|
-
return false;
|
|
340
|
-
return !verified.table.get(identity.pid)?.state?.startsWith('Z');
|
|
981
|
+
snapshot = killSnapshot;
|
|
982
|
+
signalOwned(snapshot, 'SIGKILL');
|
|
983
|
+
await new Promise((resolve) => {
|
|
984
|
+
setTimeout(resolve, PROCESS_TREE_TERMINATION_SETTLE_MS);
|
|
341
985
|
});
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
? false
|
|
346
|
-
: [...verified.markerPids].some((pid) => !verified.table.get(pid)?.state?.startsWith('Z'));
|
|
347
|
-
if (unobservedMarkedProcess) {
|
|
348
|
-
throw cleanupUnconfirmed('an unverified marker process is still running.');
|
|
986
|
+
const verified = await readImmediatePosixSnapshot(state, 'termination-verification');
|
|
987
|
+
if (!verified) {
|
|
988
|
+
throw state.failure ?? cleanupUnconfirmed('POSIX termination verification failed.');
|
|
349
989
|
}
|
|
350
|
-
if (
|
|
351
|
-
state.
|
|
352
|
-
state.root === undefined &&
|
|
353
|
-
state.descendants.size === 0) {
|
|
354
|
-
throw cleanupUnconfirmed('no marked process identity was observed.');
|
|
990
|
+
if (liveIdentities(verified).length > 0) {
|
|
991
|
+
throw state.failure ?? cleanupUnconfirmed('a verified POSIX descendant is still running.');
|
|
355
992
|
}
|
|
356
|
-
if (
|
|
357
|
-
throw cleanupUnconfirmed('
|
|
993
|
+
if (liveMarkerPids(verified).length > 0) {
|
|
994
|
+
throw state.failure ?? cleanupUnconfirmed('an unverified marker process is still running.');
|
|
358
995
|
}
|
|
996
|
+
if (state.failure)
|
|
997
|
+
throw state.failure;
|
|
359
998
|
}
|
|
360
999
|
export async function terminateProcessTree(pid, killDirectChild, dependencies = {}, options = {}) {
|
|
361
1000
|
const platform = dependencies.platform ?? process.platform;
|
|
@@ -367,10 +1006,10 @@ export async function terminateProcessTree(pid, killDirectChild, dependencies =
|
|
|
367
1006
|
throw cleanupUnconfirmed('process observation is invalid.');
|
|
368
1007
|
}
|
|
369
1008
|
if (platform === 'win32') {
|
|
370
|
-
await terminateWindowsProcessTree(state, options.requireConfirmedCleanup ?? false);
|
|
1009
|
+
await terminateWindowsProcessTree(state, options.requireConfirmedCleanup ?? false, options.allowUnobservedWindowsProcessOnNormalCompletion ?? false);
|
|
371
1010
|
return;
|
|
372
1011
|
}
|
|
373
|
-
await terminatePosixProcessTree(state, options.requireConfirmedCleanup ?? false, options.allowInspectionUnavailable ?? false);
|
|
1012
|
+
await terminatePosixProcessTree(state, options.requireConfirmedCleanup ?? false, options.allowInspectionUnavailable ?? false, options.requireDescendantsExitedBeforeCompletion ?? false);
|
|
374
1013
|
}
|
|
375
1014
|
finally {
|
|
376
1015
|
observation.dispose();
|