@lesliechan721/agent-skill-eval 0.1.0-beta.10 → 0.1.0-beta.12
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/codex-eval-app-server.js +13 -5
- package/dist/codex-eval-app-server.js.map +1 -1
- package/dist/codex-eval-process-tree.d.ts +3 -0
- package/dist/codex-eval-process-tree.js +55 -0
- package/dist/codex-eval-process-tree.js.map +1 -1
- package/dist/custom-process.js +2 -1
- package/dist/custom-process.js.map +1 -1
- package/dist/eval-process-management.d.ts +1 -0
- package/dist/eval-process-management.js +9 -3
- package/dist/eval-process-management.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/process-tree.d.ts +2 -0
- package/dist/process-tree.js +483 -158
- package/dist/process-tree.js.map +1 -1
- package/dist/process.d.ts +2 -0
- package/dist/process.js +24 -10
- package/dist/process.js.map +1 -1
- package/dist/provider-doctor.d.ts +2 -1
- package/dist/provider-doctor.js +14 -6
- package/dist/provider-doctor.js.map +1 -1
- package/dist/schemas.js +23 -4
- package/dist/schemas.js.map +1 -1
- 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 +12 -3
package/dist/process-tree.js
CHANGED
|
@@ -3,9 +3,14 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { once } from 'node:events';
|
|
4
4
|
const DEFAULT_PROCESS_MARKER_ENV = 'AW_NATIVE_CLI_PROCESS_MARKER';
|
|
5
5
|
const PROCESS_TREE_COMMAND_TIMEOUT_MS = 1_000;
|
|
6
|
-
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;
|
|
7
11
|
const PROCESS_TREE_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
8
12
|
const observationStates = new WeakMap();
|
|
13
|
+
const posixSamplers = new WeakMap();
|
|
9
14
|
const defaultExecFile = (command, args, options, callback) => execFile(command, args, options, (error, stdout) => callback(error, stdout));
|
|
10
15
|
function safeCommandFailureValue(value) {
|
|
11
16
|
if (typeof value === 'number' && Number.isFinite(value))
|
|
@@ -84,27 +89,46 @@ function parsePosixProcessTable(stdout) {
|
|
|
84
89
|
}
|
|
85
90
|
return table;
|
|
86
91
|
}
|
|
87
|
-
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) {
|
|
88
113
|
const processIds = new Set();
|
|
89
114
|
const assignment = `${markerEnvironmentVariable}=${marker}`;
|
|
90
|
-
for (const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
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);
|
|
94
118
|
}
|
|
95
119
|
return processIds;
|
|
96
120
|
}
|
|
97
|
-
async function readPosixProcessTable(execFileImpl, markerEnvironmentVariable, marker, timeoutMs =
|
|
121
|
+
async function readPosixProcessTable(execFileImpl, markerEnvironmentVariable, marker, timeoutMs = POSIX_PROCESS_TREE_COMMAND_TIMEOUT_MS, confirmUnmarkedPid) {
|
|
98
122
|
const identityArgs = ['-A', '-o', 'pid=,ppid=,pgid=,state=,lstart='];
|
|
99
|
-
const markerArgs = ['eww', '-A', '-o', 'pid=,command='];
|
|
123
|
+
const markerArgs = ['eww', '-A', '-o', 'pid=,ppid=,pgid=,state=,lstart=,command='];
|
|
100
124
|
const lookupStartedAt = Date.now();
|
|
101
|
-
const [
|
|
102
|
-
|
|
125
|
+
const [identityResult, initialMarkerResult] = await Promise.all([
|
|
126
|
+
runCommandWithDiagnostics('ps', identityArgs, execFileImpl, timeoutMs),
|
|
103
127
|
marker === undefined
|
|
104
128
|
? Promise.resolve({ output: '' })
|
|
105
129
|
: runCommandWithDiagnostics('ps', markerArgs, execFileImpl, timeoutMs),
|
|
106
130
|
]);
|
|
107
|
-
if (
|
|
131
|
+
if (identityResult.output === null)
|
|
108
132
|
return null;
|
|
109
133
|
let markerResult = initialMarkerResult;
|
|
110
134
|
let markerLookupAttempts = marker === undefined ? 0 : 1;
|
|
@@ -114,20 +138,23 @@ async function readPosixProcessTable(execFileImpl, markerEnvironmentVariable, ma
|
|
|
114
138
|
markerResult = await runCommandWithDiagnostics('ps', markerArgs, execFileImpl, retryTimeoutMs);
|
|
115
139
|
}
|
|
116
140
|
const markerOutput = markerResult.output;
|
|
117
|
-
const
|
|
118
|
-
const
|
|
141
|
+
const markerScan = markerOutput === null ? undefined : parsePosixMarkerScan(markerOutput);
|
|
142
|
+
const table = markerScan?.table ?? parsePosixProcessTable(identityResult.output);
|
|
143
|
+
const markerPids = marker === undefined || !markerScan
|
|
119
144
|
? new Set()
|
|
120
|
-
: parseMarkerProcessIds(
|
|
145
|
+
: parseMarkerProcessIds(markerScan, markerEnvironmentVariable, marker);
|
|
121
146
|
const confirmedUnmarkedPids = new Set();
|
|
122
147
|
const unmarkedEntry = confirmUnmarkedPid === undefined || markerPids.has(confirmUnmarkedPid)
|
|
123
148
|
? undefined
|
|
124
149
|
: table.get(confirmUnmarkedPid);
|
|
125
150
|
if (unmarkedEntry && !unmarkedEntry.state?.startsWith('Z')) {
|
|
126
|
-
const confirmationOutput = await runCommand('ps',
|
|
151
|
+
const confirmationOutput = await runCommand('ps', markerArgs, execFileImpl, timeoutMs);
|
|
127
152
|
if (confirmationOutput === null)
|
|
128
153
|
return null;
|
|
129
|
-
const
|
|
154
|
+
const confirmationScan = parsePosixMarkerScan(confirmationOutput);
|
|
155
|
+
const confirmedEntry = confirmationScan.table.get(confirmUnmarkedPid);
|
|
130
156
|
if (confirmedEntry?.startTime === unmarkedEntry.startTime &&
|
|
157
|
+
!parseMarkerProcessIds(confirmationScan, markerEnvironmentVariable, marker).has(confirmUnmarkedPid) &&
|
|
131
158
|
!confirmedEntry.state?.startsWith('Z')) {
|
|
132
159
|
confirmedUnmarkedPids.add(confirmUnmarkedPid);
|
|
133
160
|
}
|
|
@@ -141,6 +168,12 @@ async function readPosixProcessTable(execFileImpl, markerEnvironmentVariable, ma
|
|
|
141
168
|
confirmedUnmarkedPids,
|
|
142
169
|
};
|
|
143
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
|
+
}
|
|
144
177
|
function posixMarkerLookupFailure(snapshot, stage) {
|
|
145
178
|
const fields = [
|
|
146
179
|
`stage=${stage}`,
|
|
@@ -236,9 +269,12 @@ function parseWindowsProcessTable(stdout) {
|
|
|
236
269
|
const match = /^\s*(\d+)\|(\d+)\|(.+?)\s*$/u.exec(line);
|
|
237
270
|
if (!match)
|
|
238
271
|
continue;
|
|
272
|
+
const startTime = match[3];
|
|
273
|
+
const creationTime = /^\d+$/u.test(startTime) ? BigInt(startTime) : undefined;
|
|
239
274
|
table.set(Number(match[1]), {
|
|
240
275
|
ppid: Number(match[2]),
|
|
241
|
-
startTime
|
|
276
|
+
startTime,
|
|
277
|
+
...(creationTime !== undefined ? { creationTime } : {}),
|
|
242
278
|
});
|
|
243
279
|
}
|
|
244
280
|
return table;
|
|
@@ -251,7 +287,62 @@ function readWindowsProcessTable(execFileImpl) {
|
|
|
251
287
|
].join(' ');
|
|
252
288
|
return runCommand('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], execFileImpl, PROCESS_TREE_COMMAND_TIMEOUT_MS).then((stdout) => stdout === null ? null : parseWindowsProcessTable(stdout));
|
|
253
289
|
}
|
|
254
|
-
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) {
|
|
255
346
|
const childrenByParent = new Map();
|
|
256
347
|
for (const [processId, entry] of table) {
|
|
257
348
|
const children = childrenByParent.get(entry.ppid) ?? [];
|
|
@@ -259,20 +350,30 @@ function refreshDescendants(root, knownDescendants, table) {
|
|
|
259
350
|
childrenByParent.set(entry.ppid, children);
|
|
260
351
|
}
|
|
261
352
|
const roots = [root, ...knownDescendants.values()].filter((identity) => identity !== undefined && table.get(identity.pid)?.startTime === identity.startTime);
|
|
262
|
-
const pending = roots
|
|
263
|
-
const visited = new Set(pending);
|
|
353
|
+
const pending = [...roots];
|
|
354
|
+
const visited = new Set(pending.map((identity) => identity.pid));
|
|
264
355
|
while (pending.length > 0) {
|
|
265
356
|
const parent = pending.shift();
|
|
266
|
-
for (const descendantPid of childrenByParent.get(parent) ?? []) {
|
|
357
|
+
for (const descendantPid of childrenByParent.get(parent.pid) ?? []) {
|
|
267
358
|
if (visited.has(descendantPid))
|
|
268
359
|
continue;
|
|
269
360
|
visited.add(descendantPid);
|
|
270
361
|
const entry = table.get(descendantPid);
|
|
362
|
+
if (requireComparableCreationOrder &&
|
|
363
|
+
(parent.creationTime === undefined ||
|
|
364
|
+
entry.creationTime === undefined ||
|
|
365
|
+
entry.creationTime < parent.creationTime))
|
|
366
|
+
continue;
|
|
271
367
|
const knownIdentity = knownDescendants.get(descendantPid);
|
|
272
368
|
if (knownIdentity && knownIdentity.startTime !== entry.startTime)
|
|
273
369
|
continue;
|
|
274
|
-
|
|
275
|
-
|
|
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);
|
|
276
377
|
}
|
|
277
378
|
}
|
|
278
379
|
}
|
|
@@ -282,8 +383,13 @@ function refreshObservedProcesses(state, table, markerPids = new Set(), allowRoo
|
|
|
282
383
|
allowRootWithoutMarker ||
|
|
283
384
|
(state.rootDiscoveryPending && (state.platform === 'win32' || state.marker === undefined)))) {
|
|
284
385
|
const entry = table.get(state.pid);
|
|
285
|
-
if (entry)
|
|
286
|
-
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
|
+
}
|
|
287
393
|
}
|
|
288
394
|
state.rootDiscoveryPending = false;
|
|
289
395
|
for (const processId of markerPids) {
|
|
@@ -294,12 +400,20 @@ function refreshObservedProcesses(state, table, markerPids = new Set(), allowRoo
|
|
|
294
400
|
const entry = table.get(processId);
|
|
295
401
|
if (!entry)
|
|
296
402
|
continue;
|
|
403
|
+
if (state.platform === 'win32' && entry.creationTime === undefined)
|
|
404
|
+
continue;
|
|
297
405
|
const known = state.descendants.get(processId);
|
|
298
406
|
if (!known || known.startTime === entry.startTime) {
|
|
299
|
-
state.descendants.set(processId, {
|
|
407
|
+
state.descendants.set(processId, {
|
|
408
|
+
pid: processId,
|
|
409
|
+
startTime: entry.startTime,
|
|
410
|
+
...(entry.creationTime !== undefined ? { creationTime: entry.creationTime } : {}),
|
|
411
|
+
});
|
|
300
412
|
}
|
|
301
413
|
}
|
|
302
|
-
|
|
414
|
+
if (state.platform === 'win32' || state.marker === undefined) {
|
|
415
|
+
refreshDescendants(state.root, state.descendants, table, state.platform === 'win32');
|
|
416
|
+
}
|
|
303
417
|
}
|
|
304
418
|
function latchObservationFailure(state, error) {
|
|
305
419
|
if (state.failure)
|
|
@@ -321,19 +435,30 @@ function acceptPosixSnapshot(state, snapshot, stage) {
|
|
|
321
435
|
}
|
|
322
436
|
}
|
|
323
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
|
+
}
|
|
324
451
|
if (state.marker !== undefined &&
|
|
325
452
|
state.dependencies.enforceCooperativeProcessGroup === true &&
|
|
326
453
|
rootEntry !== undefined &&
|
|
327
454
|
!rootEntry.state?.startsWith('Z') &&
|
|
328
|
-
|
|
329
|
-
state.root = { pid: state.pid, startTime: rootEntry.startTime };
|
|
455
|
+
confirmedUnmarkedRoot) {
|
|
330
456
|
latchObservationFailure(state, cleanupUnconfirmed('custom process contract violation: a live provider process lost its process marker.'));
|
|
331
457
|
}
|
|
332
458
|
refreshObservedProcesses(state, snapshot.table, snapshot.markerPids);
|
|
333
459
|
if (state.marker !== undefined) {
|
|
334
460
|
const providerMarkerPids = [...snapshot.markerPids].filter((pid) => pid !== state.lease?.sentinel.pid);
|
|
335
|
-
if (state.
|
|
336
|
-
state.rootPgidMode === undefined) {
|
|
461
|
+
if (state.rootPgidMode === undefined) {
|
|
337
462
|
if (state.root?.pid === state.pid &&
|
|
338
463
|
rootEntry?.startTime === state.root.startTime &&
|
|
339
464
|
rootEntry.pgid !== undefined &&
|
|
@@ -344,6 +469,9 @@ function acceptPosixSnapshot(state, snapshot, stage) {
|
|
|
344
469
|
}
|
|
345
470
|
else {
|
|
346
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
|
+
}
|
|
347
475
|
}
|
|
348
476
|
}
|
|
349
477
|
else if (rootEntry === undefined &&
|
|
@@ -360,7 +488,8 @@ function acceptPosixSnapshot(state, snapshot, stage) {
|
|
|
360
488
|
: state.rootPgidMode === 'missing'
|
|
361
489
|
? state.pid
|
|
362
490
|
: undefined;
|
|
363
|
-
if (
|
|
491
|
+
if (state.dependencies.enforceCooperativeProcessGroup === true &&
|
|
492
|
+
expectedPgid !== undefined &&
|
|
364
493
|
providerMarkerPids.some((pid) => {
|
|
365
494
|
const entry = snapshot.table.get(pid);
|
|
366
495
|
return entry?.pgid !== undefined && entry.pgid !== expectedPgid;
|
|
@@ -369,31 +498,215 @@ function acceptPosixSnapshot(state, snapshot, stage) {
|
|
|
369
498
|
}
|
|
370
499
|
const liveKnownIdentities = [state.root, ...state.descendants.values()].filter((identity) => identityMatches(identity, snapshot.table) &&
|
|
371
500
|
!snapshot.table.get(identity.pid)?.state?.startsWith('Z'));
|
|
372
|
-
const markerlessKnownIdentity =
|
|
373
|
-
liveKnownIdentities.some((identity) => identity.pid !== state.pid && !snapshot.markerPids.has(identity.pid));
|
|
501
|
+
const markerlessKnownIdentity = liveKnownIdentities.some((identity) => !snapshot.markerPids.has(identity.pid));
|
|
374
502
|
let markerlessInitialGroupMember = false;
|
|
375
|
-
|
|
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) {
|
|
376
510
|
for (const [pid, entry] of snapshot.table) {
|
|
377
511
|
if (entry.pgid !== state.rootPgid || entry.state?.startsWith('Z'))
|
|
378
512
|
continue;
|
|
379
|
-
if (pid !== state.root?.pid) {
|
|
380
|
-
const known = state.descendants.get(pid);
|
|
381
|
-
if (!known || known.startTime === entry.startTime) {
|
|
382
|
-
state.descendants.set(pid, { pid, startTime: entry.startTime });
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
513
|
if (pid !== state.pid && !snapshot.markerPids.has(pid)) {
|
|
386
514
|
markerlessInitialGroupMember = true;
|
|
387
515
|
}
|
|
388
516
|
}
|
|
389
517
|
}
|
|
390
|
-
if (
|
|
518
|
+
if (state.dependencies.enforceCooperativeProcessGroup === true &&
|
|
519
|
+
(markerlessKnownIdentity || markerlessInitialGroupMember)) {
|
|
391
520
|
latchObservationFailure(state, cleanupUnconfirmed('custom process contract violation: a live provider process lost its process marker.'));
|
|
392
521
|
}
|
|
393
522
|
}
|
|
394
|
-
state.lastSnapshot = snapshot;
|
|
395
523
|
return true;
|
|
396
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;
|
|
537
|
+
}
|
|
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
|
+
}
|
|
397
710
|
function createProcessTreeObservation(pid, marker, dependencies, startLookup, lease) {
|
|
398
711
|
const observation = {
|
|
399
712
|
pid,
|
|
@@ -410,8 +723,7 @@ function createProcessTreeObservation(pid, marker, dependencies, startLookup, le
|
|
|
410
723
|
const state = observationStates.get(observation);
|
|
411
724
|
if (state) {
|
|
412
725
|
state.disposed = true;
|
|
413
|
-
|
|
414
|
-
clearTimeout(state.pollTimer);
|
|
726
|
+
stopObservingPosixState(state);
|
|
415
727
|
}
|
|
416
728
|
},
|
|
417
729
|
};
|
|
@@ -423,6 +735,7 @@ function createProcessTreeObservation(pid, marker, dependencies, startLookup, le
|
|
|
423
735
|
lease,
|
|
424
736
|
dependencies,
|
|
425
737
|
rootDiscoveryPending: true,
|
|
738
|
+
initialWindowsLookupSucceeded: false,
|
|
426
739
|
descendants: new Map(),
|
|
427
740
|
failureListeners: new Set(),
|
|
428
741
|
disposed: false,
|
|
@@ -432,34 +745,37 @@ function createProcessTreeObservation(pid, marker, dependencies, startLookup, le
|
|
|
432
745
|
if (!startLookup)
|
|
433
746
|
return observation;
|
|
434
747
|
const execFileImpl = dependencies.execFile ?? defaultExecFile;
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
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;
|
|
438
752
|
refreshObservedProcesses(state, table);
|
|
439
|
-
})
|
|
440
|
-
: readPosixProcessTable(execFileImpl, state.markerEnvironmentVariable, marker, PROCESS_TREE_COMMAND_TIMEOUT_MS, dependencies.enforceCooperativeProcessGroup === true && marker !== undefined
|
|
441
|
-
? state.pid
|
|
442
|
-
: undefined).then((snapshot) => {
|
|
443
|
-
if (state.disposed)
|
|
444
|
-
return;
|
|
445
|
-
if (!snapshot) {
|
|
446
|
-
latchObservationFailure(state, cleanupUnconfirmed('POSIX process identity lookup failed.'));
|
|
447
|
-
return;
|
|
448
753
|
}
|
|
449
|
-
acceptPosixSnapshot(state, snapshot, 'observation');
|
|
450
754
|
});
|
|
451
|
-
|
|
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(() => {
|
|
452
769
|
if (state.disposed)
|
|
453
770
|
return;
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
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));
|
|
463
779
|
return observation;
|
|
464
780
|
}
|
|
465
781
|
export function observeProcessTree(pid, marker, dependencies = {}, lease) {
|
|
@@ -489,8 +805,7 @@ function signalIdentity(identity, snapshot, signal, kill) {
|
|
|
489
805
|
}
|
|
490
806
|
}
|
|
491
807
|
function safelyOwnedProcessGroup(state, snapshot) {
|
|
492
|
-
if (state.
|
|
493
|
-
state.marker === undefined ||
|
|
808
|
+
if (state.marker === undefined ||
|
|
494
809
|
state.rootPgid === undefined ||
|
|
495
810
|
state.rootPgid !== state.pid)
|
|
496
811
|
return undefined;
|
|
@@ -498,6 +813,13 @@ function safelyOwnedProcessGroup(state, snapshot) {
|
|
|
498
813
|
const members = [...snapshot.table].filter(([, entry]) => entry.pgid === pgid && !entry.state?.startsWith('Z'));
|
|
499
814
|
if (members.length === 0)
|
|
500
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
|
+
}
|
|
501
823
|
const known = [state.root, ...state.descendants.values()];
|
|
502
824
|
if (members.some(([pid, entry]) => !known.some((identity) => identity?.pid === pid && identity.startTime === entry.startTime)))
|
|
503
825
|
return undefined;
|
|
@@ -527,7 +849,7 @@ function latchLiveDescendantCompletionFailure(state, snapshot) {
|
|
|
527
849
|
async function runTaskkill(pid, execFileImpl) {
|
|
528
850
|
return (await runCommand('taskkill', ['/PID', String(pid), '/T', '/F'], execFileImpl, PROCESS_TREE_COMMAND_TIMEOUT_MS)) !== null;
|
|
529
851
|
}
|
|
530
|
-
async function terminateWindowsProcessTree(state, requireConfirmedCleanup) {
|
|
852
|
+
async function terminateWindowsProcessTree(state, requireConfirmedCleanup, allowUnobservedProcessOnNormalCompletion) {
|
|
531
853
|
const execFileImpl = state.dependencies.execFile ?? defaultExecFile;
|
|
532
854
|
if (!requireConfirmedCleanup) {
|
|
533
855
|
if (!(await runTaskkill(state.pid, execFileImpl))) {
|
|
@@ -535,139 +857,142 @@ async function terminateWindowsProcessTree(state, requireConfirmedCleanup) {
|
|
|
535
857
|
}
|
|
536
858
|
return;
|
|
537
859
|
}
|
|
860
|
+
const identitiesBeforeNormalCompletion = allowUnobservedProcessOnNormalCompletion
|
|
861
|
+
? {
|
|
862
|
+
root: state.root,
|
|
863
|
+
descendants: new Map(state.descendants),
|
|
864
|
+
}
|
|
865
|
+
: undefined;
|
|
538
866
|
await state.initialLookup;
|
|
539
867
|
const table = await readWindowsProcessTable(execFileImpl);
|
|
540
868
|
if (!table) {
|
|
541
869
|
throw cleanupUnconfirmed('Windows process identity lookup failed.');
|
|
542
870
|
}
|
|
543
871
|
refreshObservedProcesses(state, table);
|
|
544
|
-
const
|
|
545
|
-
|
|
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));
|
|
546
887
|
const liveDescendantPids = new Set(liveDescendants.map((identity) => identity.pid));
|
|
547
888
|
const targets = rootTarget
|
|
548
889
|
? [rootTarget]
|
|
549
890
|
: liveDescendants.filter((identity) => !liveDescendantPids.has(table.get(identity.pid).ppid));
|
|
550
|
-
const taskkillResults =
|
|
891
|
+
const taskkillResults = hasInvalidCandidateEdge || hasUnobservedCandidate
|
|
892
|
+
? []
|
|
893
|
+
: await Promise.all(targets.map((identity) => runTaskkill(identity.pid, execFileImpl)));
|
|
551
894
|
if (taskkillResults.some((result) => !result)) {
|
|
552
895
|
throw cleanupUnconfirmed('taskkill did not confirm termination.');
|
|
553
896
|
}
|
|
554
897
|
const verifiedTable = await readWindowsProcessTable(execFileImpl);
|
|
555
898
|
if (!verifiedTable)
|
|
556
899
|
throw cleanupUnconfirmed('Windows termination verification failed.');
|
|
900
|
+
const verifiedCandidatePids = collectDescendantProcessIds(state.pid, verifiedTable);
|
|
557
901
|
const verifiedIdentities = [rootTarget, ...liveDescendants].filter((identity) => identity !== undefined);
|
|
558
902
|
if (verifiedIdentities.some((identity) => identityMatches(identity, verifiedTable))) {
|
|
559
903
|
throw cleanupUnconfirmed('a verified Windows process is still running.');
|
|
560
904
|
}
|
|
561
|
-
if (
|
|
562
|
-
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.');
|
|
563
930
|
}
|
|
564
931
|
}
|
|
565
932
|
async function terminatePosixProcessTree(state, requireConfirmedCleanup, allowInspectionUnavailable, requireDescendantsExitedBeforeCompletion) {
|
|
566
933
|
await state.initialLookup;
|
|
567
934
|
state.disposed = true;
|
|
568
|
-
|
|
569
|
-
clearTimeout(state.pollTimer);
|
|
570
|
-
const execFileImpl = state.dependencies.execFile ?? defaultExecFile;
|
|
935
|
+
stopObservingPosixState(state);
|
|
571
936
|
const kill = state.dependencies.kill ?? process.kill.bind(process);
|
|
572
|
-
|
|
573
|
-
const inspect = async (timeoutMs, stage) => {
|
|
574
|
-
const next = await readPosixProcessTable(execFileImpl, state.markerEnvironmentVariable, state.marker, timeoutMs, state.dependencies.enforceCooperativeProcessGroup === true && state.marker !== undefined
|
|
575
|
-
? state.pid
|
|
576
|
-
: undefined);
|
|
577
|
-
if (!next) {
|
|
578
|
-
latchObservationFailure(state, cleanupUnconfirmed('POSIX process identity lookup failed.'));
|
|
579
|
-
return undefined;
|
|
580
|
-
}
|
|
581
|
-
return acceptPosixSnapshot(state, next, stage) ? next : undefined;
|
|
582
|
-
};
|
|
583
|
-
let snapshot = await inspect(PROCESS_TREE_COMMAND_TIMEOUT_MS, 'termination-start');
|
|
584
|
-
let processGroupCurrentlyVerified = snapshot !== undefined;
|
|
585
|
-
if (!snapshot)
|
|
586
|
-
snapshot = state.lastSnapshot;
|
|
937
|
+
let snapshot = await readImmediatePosixSnapshot(state, 'termination-start');
|
|
587
938
|
if (!snapshot) {
|
|
588
939
|
if (!requireConfirmedCleanup && allowInspectionUnavailable)
|
|
589
940
|
return;
|
|
590
941
|
throw state.failure ?? cleanupUnconfirmed('POSIX process identity lookup failed.');
|
|
591
942
|
}
|
|
592
|
-
if (!processGroupCurrentlyVerified && safelyOwnedProcessGroup(state, snapshot) === undefined) {
|
|
593
|
-
if (!requireConfirmedCleanup && allowInspectionUnavailable)
|
|
594
|
-
return;
|
|
595
|
-
throw state.failure ?? cleanupUnconfirmed('POSIX process identity lookup failed.');
|
|
596
|
-
}
|
|
597
943
|
if (requireDescendantsExitedBeforeCompletion) {
|
|
598
944
|
latchLiveDescendantCompletionFailure(state, snapshot);
|
|
599
945
|
}
|
|
600
|
-
const
|
|
601
|
-
.
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if (!next) {
|
|
613
|
-
processGroupCurrentlyVerified = false;
|
|
614
|
-
break;
|
|
615
|
-
}
|
|
616
|
-
snapshot = next;
|
|
617
|
-
processGroupCurrentlyVerified = true;
|
|
618
|
-
const previousSize = state.descendants.size;
|
|
619
|
-
signalProcessGroup(safelyOwnedProcessGroup(state, snapshot), 'SIGSTOP', kill);
|
|
620
|
-
if (posixIdentityMatches(state.root, snapshot)) {
|
|
621
|
-
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);
|
|
622
958
|
}
|
|
623
|
-
|
|
624
|
-
|
|
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.');
|
|
625
968
|
}
|
|
626
|
-
const allFrozen = [state.root, ...state.descendants.values()].every((identity) => {
|
|
627
|
-
if (!posixIdentityMatches(identity, snapshot))
|
|
628
|
-
return true;
|
|
629
|
-
const processState = snapshot.table.get(identity.pid)?.state;
|
|
630
|
-
return processState?.startsWith('T') || processState?.startsWith('Z');
|
|
631
|
-
});
|
|
632
|
-
if (allFrozen && state.descendants.size === previousSize)
|
|
633
|
-
break;
|
|
634
|
-
}
|
|
635
|
-
const killed = [state.root, ...state.descendants.values()].filter((identity) => identity !== undefined &&
|
|
636
|
-
posixIdentityMatches(identity, snapshot));
|
|
637
|
-
const ownedPgid = processGroupCurrentlyVerified
|
|
638
|
-
? safelyOwnedProcessGroup(state, snapshot)
|
|
639
|
-
: undefined;
|
|
640
|
-
for (const identity of killed.reverse()) {
|
|
641
|
-
signalIdentity(identity, snapshot, 'SIGKILL', kill);
|
|
642
|
-
}
|
|
643
|
-
signalProcessGroup(ownedPgid, 'SIGKILL', kill);
|
|
644
|
-
if (!requireConfirmedCleanup) {
|
|
645
969
|
if (state.failure)
|
|
646
970
|
throw state.failure;
|
|
647
971
|
return;
|
|
648
972
|
}
|
|
649
|
-
|
|
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.');
|
|
980
|
+
}
|
|
981
|
+
snapshot = killSnapshot;
|
|
982
|
+
signalOwned(snapshot, 'SIGKILL');
|
|
983
|
+
await new Promise((resolve) => {
|
|
984
|
+
setTimeout(resolve, PROCESS_TREE_TERMINATION_SETTLE_MS);
|
|
985
|
+
});
|
|
986
|
+
const verified = await readImmediatePosixSnapshot(state, 'termination-verification');
|
|
650
987
|
if (!verified) {
|
|
651
988
|
throw state.failure ?? cleanupUnconfirmed('POSIX termination verification failed.');
|
|
652
989
|
}
|
|
653
|
-
|
|
654
|
-
if (!posixIdentityMatches(identity, verified))
|
|
655
|
-
return false;
|
|
656
|
-
return !verified.table.get(identity.pid)?.state?.startsWith('Z');
|
|
657
|
-
});
|
|
658
|
-
if (stillRunning) {
|
|
990
|
+
if (liveIdentities(verified).length > 0) {
|
|
659
991
|
throw state.failure ?? cleanupUnconfirmed('a verified POSIX descendant is still running.');
|
|
660
992
|
}
|
|
661
|
-
|
|
662
|
-
? false
|
|
663
|
-
: [...verified.markerPids].some((pid) => pid !== state.lease?.sentinel.pid &&
|
|
664
|
-
!verified.table.get(pid)?.state?.startsWith('Z'));
|
|
665
|
-
if (unobservedMarkedProcess) {
|
|
993
|
+
if (liveMarkerPids(verified).length > 0) {
|
|
666
994
|
throw state.failure ?? cleanupUnconfirmed('an unverified marker process is still running.');
|
|
667
995
|
}
|
|
668
|
-
if (requireConfirmedCleanup && state.marker === undefined && observedAtTermination.size === 0) {
|
|
669
|
-
throw cleanupUnconfirmed('no inherited-pipe process identity was observed.');
|
|
670
|
-
}
|
|
671
996
|
if (state.failure)
|
|
672
997
|
throw state.failure;
|
|
673
998
|
}
|
|
@@ -681,7 +1006,7 @@ export async function terminateProcessTree(pid, killDirectChild, dependencies =
|
|
|
681
1006
|
throw cleanupUnconfirmed('process observation is invalid.');
|
|
682
1007
|
}
|
|
683
1008
|
if (platform === 'win32') {
|
|
684
|
-
await terminateWindowsProcessTree(state, options.requireConfirmedCleanup ?? false);
|
|
1009
|
+
await terminateWindowsProcessTree(state, options.requireConfirmedCleanup ?? false, options.allowUnobservedWindowsProcessOnNormalCompletion ?? false);
|
|
685
1010
|
return;
|
|
686
1011
|
}
|
|
687
1012
|
await terminatePosixProcessTree(state, options.requireConfirmedCleanup ?? false, options.allowInspectionUnavailable ?? false, options.requireDescendantsExitedBeforeCompletion ?? false);
|