@sshkeda/task 0.0.0-stage → 1.0.1
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/LICENSE +21 -0
- package/README.md +131 -2
- package/package.json +41 -4
- package/src/index.ts +2 -0
- package/src/lib/errors/index.ts +15 -0
- package/src/lib/errors/internal/errors.ts +147 -0
- package/src/lib/kill-leftover-processes/index.ts +17 -0
- package/src/lib/kill-leftover-processes/internal/command-words.ts +16 -0
- package/src/lib/kill-leftover-processes/internal/find-process-ids.ts +36 -0
- package/src/lib/kill-leftover-processes/internal/kill-process-groups.ts +270 -0
- package/src/lib/kill-leftover-processes/internal/list-linux-processes-from-proc.ts +191 -0
- package/src/lib/kill-leftover-processes/internal/list-macos-processes-with-sysctl.ts +335 -0
- package/src/lib/kill-leftover-processes/internal/list-processes.ts +58 -0
- package/src/lib/kill-leftover-processes/internal/mark-run-processes.ts +367 -0
- package/src/lib/kill-leftover-processes/internal/process-cleanup-errors.ts +17 -0
- package/src/lib/kill-leftover-processes/internal/send-signal.ts +32 -0
- package/src/lib/kill-leftover-processes/internal/signal-run-processes.ts +48 -0
- package/src/lib/kill-leftover-processes/internal/started-process-groups.ts +107 -0
- package/src/lib/main-thread/index.ts +3 -0
- package/src/lib/main-thread/internal/listen-for-stop-requests.ts +102 -0
- package/src/lib/main-thread/internal/run-main-thread.ts +81 -0
- package/src/lib/main-thread/internal/run-root-task-fiber.ts +191 -0
- package/src/lib/main-thread/internal/stops-at-first-failure.ts +20 -0
- package/src/lib/main-thread/internal/trace-the-run.ts +107 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/index.ts +7 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/follow-imports-from-entry-files.ts +306 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/load-all-reporting-first-failure.ts +11 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/make-module-graph.ts +48 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/read-configured-loaders.ts +64 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/resolve-module-specifiers.ts +60 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/rewrite-worker-imports.ts +118 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/worker-import-errors.ts +19 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/index.ts +7 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/internal/handle-worker-events.ts +348 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/internal/make-task-runs.ts +207 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/index.ts +1 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/await-within-counted-limit.ts +29 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/execute-task.ts +272 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/make-task-setup-clock.ts +129 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/run-requested-tasks.ts +135 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/define-task-run-state.ts +288 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/make-stopwatch.ts +39 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/share-standard-input-with-root-task.ts +36 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/stop-task.ts +166 -0
- package/src/lib/main-thread/lib/scan-module-source/index.ts +1 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/find-lexical-bindings.ts +154 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/find-task-declarations.ts +205 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/parse-module-text.ts +34 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/read-literal-string.ts +15 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/read-module-loads.ts +316 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/scan-module-text.ts +70 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/unwrap-type-assertions.ts +10 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/visit-non-reference-identifiers.ts +21 -0
- package/src/lib/main-thread/shared/fail-run.ts +36 -0
- package/src/lib/main-thread/shared/find-failure-reason.ts +43 -0
- package/src/lib/main-thread/shared/find-package-directories.ts +34 -0
- package/src/lib/main-thread/shared/find-tasks-reachable-from-root-task-file.ts +246 -0
- package/src/lib/main-thread/shared/list-directory-paths-up-to-root.ts +13 -0
- package/src/lib/main-thread/shared/main-thread-errors.ts +373 -0
- package/src/lib/main-thread/shared/package-json-errors.ts +93 -0
- package/src/lib/main-thread/shared/provide-discovered-tasks.ts +26 -0
- package/src/lib/main-thread/shared/provide-process-groups.ts +63 -0
- package/src/lib/main-thread/shared/provide-run-root-task-config.ts +66 -0
- package/src/lib/main-thread/shared/provide-stop-state.ts +150 -0
- package/src/lib/main-thread/shared/provide-worker-pool.ts +607 -0
- package/src/lib/main-thread/shared/read-bare-package-name.ts +3 -0
- package/src/lib/main-thread/shared/read-package-json.ts +149 -0
- package/src/lib/main-thread/shared/report-task-failure.ts +31 -0
- package/src/lib/preload-rewritten-imports-into-each-worker/index.ts +5 -0
- package/src/lib/preload-rewritten-imports-into-each-worker/internal/load-rewritten-imports.ts +34 -0
- package/src/lib/supervise-the-run/index.ts +15 -0
- package/src/lib/supervise-the-run/internal/adopt-orphans-on-linux.ts +73 -0
- package/src/lib/supervise-the-run/internal/check-root-task-file.ts +8 -0
- package/src/lib/supervise-the-run/internal/report-ready.ts +12 -0
- package/src/lib/supervise-the-run/internal/share-process-groups.ts +25 -0
- package/src/lib/supervise-the-run/internal/start-watching-the-supervisor.ts +6 -0
- package/src/lib/supervise-the-run/internal/supervise-run.ts +155 -0
- package/src/lib/supervise-the-run/internal/talk-to-the-supervisor.ts +53 -0
- package/src/lib/supervise-the-run/internal/watch-the-supervisor.ts +61 -0
- package/src/lib/task/index.ts +5 -0
- package/src/lib/task/internal/make-tasks.ts +82 -0
- package/src/lib/task/internal/run-command.ts +194 -0
- package/src/lib/task/internal/run-root-task.ts +263 -0
- package/src/lib/task/internal/run-task-lists.ts +51 -0
- package/src/lib/task/internal/trust-output-mode-names.ts +18 -0
- package/src/lib/task/internal/trust-task-results.ts +35 -0
- package/src/lib/task/internal/use-task-services.ts +4 -0
- package/src/lib/worker-thread/index.ts +3 -0
- package/src/lib/worker-thread/internal/answer-main-thread-messages.ts +141 -0
- package/src/lib/worker-thread/internal/describe-task-body-result.ts +91 -0
- package/src/lib/worker-thread/internal/name-config-key-not-set.ts +57 -0
- package/src/lib/worker-thread/internal/run-task-body.ts +117 -0
- package/src/lib/worker-thread/internal/serve-on-the-parent-port.ts +63 -0
- package/src/lib/worker-thread/internal/start-worker-thread.ts +41 -0
- package/src/lib/worker-thread/lib/provide-task-services/index.ts +1 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/make-task-logger.ts +34 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/provide-task-services.ts +86 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/read-standard-input-from-main-thread.ts +58 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/resolve-paths-from-package-directory.ts +61 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/run-commands-in-package-directory.ts +24 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-commands-with-bun.ts +545 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-reported-process.ts +151 -0
- package/src/lib/worker-thread/shared/await-main-thread-answers.ts +76 -0
- package/src/lib/worker-thread/shared/decode-task-result-json.ts +114 -0
- package/src/lib/worker-thread/shared/define-task-body-record.ts +11 -0
- package/src/lib/worker-thread/shared/describe-issue-input.ts +52 -0
- package/src/lib/worker-thread/shared/plain-object.ts +8 -0
- package/src/lib/worker-thread/shared/provide-worker-state.ts +160 -0
- package/src/lib/worker-thread/shared/worker-thread-errors.ts +41 -0
- package/src/shared/attribute-reported-failures.ts +25 -0
- package/src/shared/claim-processes-to-report.ts +22 -0
- package/src/shared/command-errors.ts +44 -0
- package/src/shared/complete-discarded-calls-once-sent.ts +12 -0
- package/src/shared/cut-to-graphemes.ts +12 -0
- package/src/shared/environment-config-errors.ts +74 -0
- package/src/shared/error-fields.ts +13 -0
- package/src/shared/find-bun-loader-for-script.ts +32 -0
- package/src/shared/find-node-modules-package-directory.ts +35 -0
- package/src/shared/find-user-locations-in-stack-traces.ts +64 -0
- package/src/shared/format-error-lines.ts +53 -0
- package/src/shared/format-task-lines.ts +21 -0
- package/src/shared/hold-task-logs.ts +24 -0
- package/src/shared/invariant-errors.ts +28 -0
- package/src/shared/mark-task-worker-thread-contexts.ts +29 -0
- package/src/shared/module-graph-errors.ts +49 -0
- package/src/shared/name-config-key.ts +42 -0
- package/src/shared/names-from-package-json.ts +7 -0
- package/src/shared/print-to-standard-error.ts +48 -0
- package/src/shared/process-errors.ts +191 -0
- package/src/shared/read-environment-config.ts +239 -0
- package/src/shared/record-tasks-made.ts +10 -0
- package/src/shared/report-cause.ts +140 -0
- package/src/shared/report-errors.ts +121 -0
- package/src/shared/resolve-task-options.ts +20 -0
- package/src/shared/rpc.ts +161 -0
- package/src/shared/run-in-child-fiber-and-join.ts +16 -0
- package/src/shared/run-marker.ts +7 -0
- package/src/shared/save-standard-stream-flags.ts +57 -0
- package/src/shared/select-cause-reasons.ts +14 -0
- package/src/shared/signal-run-called-twice.ts +11 -0
- package/src/shared/standard-input-streams.ts +41 -0
- package/src/shared/stop-signals.ts +41 -0
- package/src/shared/task-errors.ts +224 -0
- package/src/shared/task-package.ts +5 -0
- package/src/shared/task-results.ts +55 -0
- package/src/shared/task-services.ts +30 -0
- package/src/shared/write-trace-file.ts +112 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
|
|
3
|
+
import { processIdentityKey } from "../../../shared/claim-processes-to-report.ts";
|
|
4
|
+
import type { ProcessIdentity } from "../../../shared/claim-processes-to-report.ts";
|
|
5
|
+
import { formatFailureLines } from "../../../shared/format-error-lines.ts";
|
|
6
|
+
import { TASK_TRACE_LABEL_PREFIX } from "../../../shared/names-from-package-json.ts";
|
|
7
|
+
import { printLine } from "../../../shared/print-to-standard-error.ts";
|
|
8
|
+
import type { RunMarker } from "../../../shared/run-marker.ts";
|
|
9
|
+
import { findDescendantProcessIds, findSessionProcessIds } from "./find-process-ids.ts";
|
|
10
|
+
import { isStillListedAs, listProcesses } from "./list-processes.ts";
|
|
11
|
+
import { findMarkedProcessIds } from "./mark-run-processes.ts";
|
|
12
|
+
import { ProcessNotKillable } from "./process-cleanup-errors.ts";
|
|
13
|
+
import { sendSignal, signalUnlessReplaced } from "./send-signal.ts";
|
|
14
|
+
import { findStartedGroupWitness } from "./started-process-groups.ts";
|
|
15
|
+
import type { startedProcessGroup } from "./started-process-groups.ts";
|
|
16
|
+
|
|
17
|
+
const reportKilledProcesses = (
|
|
18
|
+
reportedResults: readonly {
|
|
19
|
+
readonly processId: number;
|
|
20
|
+
readonly command: string;
|
|
21
|
+
readonly ownerTaskName: string | null;
|
|
22
|
+
readonly result: "EPERM" | "ESRCH" | "sent";
|
|
23
|
+
}[],
|
|
24
|
+
) =>
|
|
25
|
+
Effect.gen(function* () {
|
|
26
|
+
for (const killResult of reportedResults) {
|
|
27
|
+
const startedBy = `started by ${killResult.ownerTaskName === null ? "no known task" : `"${killResult.ownerTaskName}"`}`;
|
|
28
|
+
yield* killResult.result === "sent"
|
|
29
|
+
? printLine("Info", `killed ${killResult.processId} (${killResult.command}), ${startedBy}`)
|
|
30
|
+
: printLine(
|
|
31
|
+
"Error",
|
|
32
|
+
formatFailureLines(
|
|
33
|
+
new ProcessNotKillable({
|
|
34
|
+
processId: killResult.processId,
|
|
35
|
+
command: killResult.command,
|
|
36
|
+
ownerTaskName: killResult.ownerTaskName,
|
|
37
|
+
}),
|
|
38
|
+
null,
|
|
39
|
+
),
|
|
40
|
+
);
|
|
41
|
+
yield* Effect.logInfo(
|
|
42
|
+
killResult.result === "sent" ? "process killed" : "process could not be killed",
|
|
43
|
+
).pipe(
|
|
44
|
+
Effect.annotateLogs({
|
|
45
|
+
"process.pid": killResult.processId,
|
|
46
|
+
"process.executable.name": killResult.command,
|
|
47
|
+
...(killResult.ownerTaskName === null
|
|
48
|
+
? {}
|
|
49
|
+
: { [`${TASK_TRACE_LABEL_PREFIX}.task`]: killResult.ownerTaskName }),
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const findLiveProcessGroups = Effect.fn("findLiveProcessGroups")(function* (
|
|
56
|
+
taskName: string | null,
|
|
57
|
+
selectedProcessGroups: ReadonlyMap<number, ReturnType<typeof startedProcessGroup>>,
|
|
58
|
+
) {
|
|
59
|
+
const liveProcessGroups = new Map<number, ReturnType<typeof startedProcessGroup>>();
|
|
60
|
+
const endedProcessGroupIds = new Set<number>();
|
|
61
|
+
for (const [processGroupId, startedGroup] of selectedProcessGroups) {
|
|
62
|
+
if ((yield* sendSignal(taskName, -processGroupId, 0)) === "sent") {
|
|
63
|
+
liveProcessGroups.set(processGroupId, startedGroup);
|
|
64
|
+
} else {
|
|
65
|
+
endedProcessGroupIds.add(processGroupId);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { liveProcessGroups, endedProcessGroupIds };
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const terminateSelectedProcesses = Effect.fn("terminateSelectedProcesses")(function* (input: {
|
|
72
|
+
readonly startedProcessGroups: ReadonlyMap<number, ReturnType<typeof startedProcessGroup>>;
|
|
73
|
+
readonly liveProcessGroups: ReadonlyMap<number, ReturnType<typeof startedProcessGroup>>;
|
|
74
|
+
readonly ownsProcess: (ownerTaskName: string | null) => boolean;
|
|
75
|
+
readonly taskName: string | null;
|
|
76
|
+
readonly sessionId: number | null;
|
|
77
|
+
readonly runMarker: typeof RunMarker.Type | null;
|
|
78
|
+
readonly adoptingProcessId: number | null;
|
|
79
|
+
readonly listProcessesNow: typeof listProcesses;
|
|
80
|
+
readonly claimProcessesToReport: (
|
|
81
|
+
processes: readonly (typeof ProcessIdentity.Type)[],
|
|
82
|
+
) => Effect.Effect<readonly (typeof ProcessIdentity.Type)[]>;
|
|
83
|
+
}) {
|
|
84
|
+
const listedProcesses = yield* input.listProcessesNow(input.taskName);
|
|
85
|
+
const findChildProcessIds = (parentProcessIds: readonly number[]) =>
|
|
86
|
+
[...listedProcesses]
|
|
87
|
+
.filter(([, listedProcess]) => parentProcessIds.includes(listedProcess.parentProcessId))
|
|
88
|
+
.map(([processId]) => processId);
|
|
89
|
+
const childProcessIds = findChildProcessIds([process.pid]);
|
|
90
|
+
const adoptedProcessIds =
|
|
91
|
+
input.adoptingProcessId === null
|
|
92
|
+
? []
|
|
93
|
+
: findChildProcessIds([input.adoptingProcessId]).filter(
|
|
94
|
+
(processId) => processId !== process.pid,
|
|
95
|
+
);
|
|
96
|
+
const killableGroups = new Map<number, string>();
|
|
97
|
+
const groupWitnesses = new Map<number, Parameters<typeof isStillListedAs>[1]>();
|
|
98
|
+
const endedProcessGroupIds = new Set<number>();
|
|
99
|
+
for (const [group, startedGroup] of input.liveProcessGroups) {
|
|
100
|
+
const witness = findStartedGroupWitness(listedProcesses, group, startedGroup);
|
|
101
|
+
if (witness === null) {
|
|
102
|
+
endedProcessGroupIds.add(group);
|
|
103
|
+
} else {
|
|
104
|
+
killableGroups.set(group, startedGroup.taskName);
|
|
105
|
+
groupWitnesses.set(group, witness);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const processIds = [
|
|
110
|
+
...findDescendantProcessIds(listedProcesses, [...childProcessIds, ...adoptedProcessIds]),
|
|
111
|
+
...findSessionProcessIds(listedProcesses, input.sessionId),
|
|
112
|
+
...(yield* findMarkedProcessIds(input.runMarker, listedProcesses.keys())),
|
|
113
|
+
...[...listedProcesses]
|
|
114
|
+
.filter(([, listedProcess]) => killableGroups.has(listedProcess.processGroupId))
|
|
115
|
+
.map(([processId]) => processId),
|
|
116
|
+
];
|
|
117
|
+
const processRows = new Map(
|
|
118
|
+
processIds.flatMap((processId) => {
|
|
119
|
+
const listedProcess = listedProcesses.get(processId);
|
|
120
|
+
const isGone = listedProcess === undefined || listedProcess.hasEnded;
|
|
121
|
+
return isGone ? [] : [[processId, listedProcess] as const];
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
yield* Effect.annotateCurrentSpan(
|
|
125
|
+
`${TASK_TRACE_LABEL_PREFIX}.processes.listed`,
|
|
126
|
+
processRows.size,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const findOwner = (processId: number): string | null => {
|
|
130
|
+
const row = processRows.get(processId);
|
|
131
|
+
if (row === undefined) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
input.startedProcessGroups.get(processId)?.taskName ??
|
|
137
|
+
findOwner(row.parentProcessId) ??
|
|
138
|
+
killableGroups.get(row.processGroupId) ??
|
|
139
|
+
null
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
const leftovers = (yield* Effect.forEach(
|
|
143
|
+
[...new Set(processIds)].flatMap((processId) => {
|
|
144
|
+
const row = processRows.get(processId);
|
|
145
|
+
const ownerTaskName = findOwner(processId);
|
|
146
|
+
const isGoneOrNotOwned = row === undefined || !input.ownsProcess(ownerTaskName);
|
|
147
|
+
return isGoneOrNotOwned ? [] : [{ processId, row, ownerTaskName }];
|
|
148
|
+
}),
|
|
149
|
+
(leftover) =>
|
|
150
|
+
Effect.gen(function* () {
|
|
151
|
+
const command = yield* leftover.row.readCommandLineUnlessGone;
|
|
152
|
+
return command === null
|
|
153
|
+
? []
|
|
154
|
+
: [
|
|
155
|
+
{
|
|
156
|
+
processId: leftover.processId,
|
|
157
|
+
startTime: leftover.row.startTime,
|
|
158
|
+
processGroupId: leftover.row.processGroupId,
|
|
159
|
+
command,
|
|
160
|
+
ownerTaskName: leftover.ownerTaskName,
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
}),
|
|
164
|
+
)).flat();
|
|
165
|
+
const groupsStillStarted = yield* Effect.filter([...groupWitnesses], ([, witness]) =>
|
|
166
|
+
isStillListedAs(input.taskName, witness),
|
|
167
|
+
);
|
|
168
|
+
const killResults = yield* Effect.forEach(leftovers, (leftover) =>
|
|
169
|
+
Effect.map(signalUnlessReplaced(input.taskName, leftover, "SIGKILL"), (result) => ({
|
|
170
|
+
...leftover,
|
|
171
|
+
result,
|
|
172
|
+
})),
|
|
173
|
+
);
|
|
174
|
+
for (const [group] of groupsStillStarted) {
|
|
175
|
+
yield* sendSignal(input.taskName, -group, "SIGKILL");
|
|
176
|
+
}
|
|
177
|
+
for (const group of killableGroups.keys()) {
|
|
178
|
+
endedProcessGroupIds.add(group);
|
|
179
|
+
}
|
|
180
|
+
const signaledResults = killResults.filter((killResult) => killResult.result !== "ESRCH");
|
|
181
|
+
const firstReportedProcessKeys = new Set(
|
|
182
|
+
(yield* input.claimProcessesToReport(
|
|
183
|
+
signaledResults.map((killResult) => ({
|
|
184
|
+
processId: killResult.processId,
|
|
185
|
+
startTime: killResult.startTime,
|
|
186
|
+
})),
|
|
187
|
+
)).map((identity) => processIdentityKey(identity)),
|
|
188
|
+
);
|
|
189
|
+
const reportedResults = signaledResults.filter((killResult) =>
|
|
190
|
+
firstReportedProcessKeys.has(processIdentityKey(killResult)),
|
|
191
|
+
);
|
|
192
|
+
const killed = reportedResults.filter((killResult) => killResult.result === "sent");
|
|
193
|
+
yield* Effect.annotateCurrentSpan({
|
|
194
|
+
[`${TASK_TRACE_LABEL_PREFIX}.processes.killed`]: killed.length,
|
|
195
|
+
[`${TASK_TRACE_LABEL_PREFIX}.processes.killed.executable_names`]: killed.map(
|
|
196
|
+
(killResult) => killResult.command,
|
|
197
|
+
),
|
|
198
|
+
});
|
|
199
|
+
yield* reportKilledProcesses(reportedResults);
|
|
200
|
+
return endedProcessGroupIds;
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
export const forgetEndedProcessGroups =
|
|
204
|
+
(startedProcessGroups: Map<number, ReturnType<typeof startedProcessGroup>>) =>
|
|
205
|
+
(endedProcessGroupIds: ReadonlySet<number>) =>
|
|
206
|
+
Effect.sync(() => {
|
|
207
|
+
for (const endedProcessGroupId of endedProcessGroupIds) {
|
|
208
|
+
startedProcessGroups.delete(endedProcessGroupId);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
export const killTaskProcessGroups = Effect.fn("killTaskProcessGroups")(function* (
|
|
213
|
+
startedProcessGroups: ReadonlyMap<number, ReturnType<typeof startedProcessGroup>>,
|
|
214
|
+
taskName: string,
|
|
215
|
+
claimProcessesToReport: Parameters<
|
|
216
|
+
typeof terminateSelectedProcesses
|
|
217
|
+
>[0]["claimProcessesToReport"],
|
|
218
|
+
listProcessesNow: typeof listProcesses = listProcesses,
|
|
219
|
+
) {
|
|
220
|
+
yield* Effect.annotateCurrentSpan(`${TASK_TRACE_LABEL_PREFIX}.task`, taskName);
|
|
221
|
+
const selectedProcessGroups = new Map(
|
|
222
|
+
[...startedProcessGroups].filter(([, startedGroup]) => startedGroup.taskName === taskName),
|
|
223
|
+
);
|
|
224
|
+
const { liveProcessGroups, endedProcessGroupIds } = yield* findLiveProcessGroups(
|
|
225
|
+
taskName,
|
|
226
|
+
selectedProcessGroups,
|
|
227
|
+
);
|
|
228
|
+
if (liveProcessGroups.size === 0) {
|
|
229
|
+
return endedProcessGroupIds;
|
|
230
|
+
}
|
|
231
|
+
const terminatedProcessGroupIds = yield* terminateSelectedProcesses({
|
|
232
|
+
startedProcessGroups,
|
|
233
|
+
liveProcessGroups,
|
|
234
|
+
ownsProcess: (ownerTaskName) => ownerTaskName === taskName,
|
|
235
|
+
taskName,
|
|
236
|
+
sessionId: null,
|
|
237
|
+
runMarker: null,
|
|
238
|
+
adoptingProcessId: null,
|
|
239
|
+
listProcessesNow,
|
|
240
|
+
claimProcessesToReport,
|
|
241
|
+
});
|
|
242
|
+
return new Set([...endedProcessGroupIds, ...terminatedProcessGroupIds]);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
export const killAllProcessGroups = Effect.fn("killAllProcessGroups")(function* (
|
|
246
|
+
startedProcessGroups: ReadonlyMap<number, ReturnType<typeof startedProcessGroup>>,
|
|
247
|
+
claimProcessesToReport: Parameters<
|
|
248
|
+
typeof terminateSelectedProcesses
|
|
249
|
+
>[0]["claimProcessesToReport"],
|
|
250
|
+
sessionId: number | null,
|
|
251
|
+
runMarker: typeof RunMarker.Type | null,
|
|
252
|
+
adoptingProcessId: number | null,
|
|
253
|
+
) {
|
|
254
|
+
const { liveProcessGroups, endedProcessGroupIds } = yield* findLiveProcessGroups(
|
|
255
|
+
null,
|
|
256
|
+
startedProcessGroups,
|
|
257
|
+
);
|
|
258
|
+
const terminatedProcessGroupIds = yield* terminateSelectedProcesses({
|
|
259
|
+
startedProcessGroups,
|
|
260
|
+
liveProcessGroups,
|
|
261
|
+
ownsProcess: () => true,
|
|
262
|
+
taskName: null,
|
|
263
|
+
sessionId,
|
|
264
|
+
runMarker,
|
|
265
|
+
adoptingProcessId,
|
|
266
|
+
listProcessesNow: listProcesses,
|
|
267
|
+
claimProcessesToReport,
|
|
268
|
+
});
|
|
269
|
+
return new Set([...endedProcessGroupIds, ...terminatedProcessGroupIds]);
|
|
270
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Effect, FileSystem, Predicate, Schema } from "effect";
|
|
2
|
+
import type { PlatformError } from "effect";
|
|
3
|
+
|
|
4
|
+
import { TASK_TRACE_LABEL_PREFIX } from "../../../shared/names-from-package-json.ts";
|
|
5
|
+
import {
|
|
6
|
+
ProcCommandLineUnreadable,
|
|
7
|
+
ProcDirectoryUnlistable,
|
|
8
|
+
ProcStatMalformed,
|
|
9
|
+
ProcStatUnreadable,
|
|
10
|
+
} from "../../../shared/process-errors.ts";
|
|
11
|
+
import { availableParallelism } from "../../../shared/read-environment-config.ts";
|
|
12
|
+
import { commandWordsOf, programNameOf } from "./command-words.ts";
|
|
13
|
+
|
|
14
|
+
const PROC_DIRECTORY_PATH = "/proc";
|
|
15
|
+
const PROC_STAT_STATE_FIELD_NUMBER = 3;
|
|
16
|
+
const PROC_STAT_START_TIME_FIELD_NUMBER = 22;
|
|
17
|
+
|
|
18
|
+
const ProcessIdFromText = Schema.FiniteFromString.check(Schema.isInt());
|
|
19
|
+
|
|
20
|
+
const decodeProcStatFields = Schema.decodeUnknownEffect(
|
|
21
|
+
Schema.TupleWithRest(
|
|
22
|
+
Schema.Tuple([Schema.NonEmptyString, ProcessIdFromText, ProcessIdFromText, ProcessIdFromText]),
|
|
23
|
+
[Schema.String],
|
|
24
|
+
),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const decodeProcStatStartTime = Schema.decodeUnknownEffect(
|
|
28
|
+
Schema.String.check(Schema.isPattern(/^\d+$/u)),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
export const decodeProcStatText = (statText: string) =>
|
|
32
|
+
Effect.gen(function* () {
|
|
33
|
+
const fieldsAfterCommand = statText
|
|
34
|
+
.slice(statText.lastIndexOf(")") + ") ".length)
|
|
35
|
+
.trim()
|
|
36
|
+
.split(" ");
|
|
37
|
+
const [state, parentProcessId, processGroupId, sessionId] =
|
|
38
|
+
yield* decodeProcStatFields(fieldsAfterCommand);
|
|
39
|
+
return {
|
|
40
|
+
processName: statText.slice(statText.indexOf("(") + 1, statText.lastIndexOf(")")),
|
|
41
|
+
state,
|
|
42
|
+
parentProcessId,
|
|
43
|
+
processGroupId,
|
|
44
|
+
sessionId,
|
|
45
|
+
startTime: yield* decodeProcStatStartTime(
|
|
46
|
+
fieldsAfterCommand[PROC_STAT_START_TIME_FIELD_NUMBER - PROC_STAT_STATE_FIELD_NUMBER],
|
|
47
|
+
),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const isEndedState = (state: string) => state.startsWith("Z") || state.startsWith("X");
|
|
52
|
+
|
|
53
|
+
const HIDDEN_PROCESS_ERROR_CODES = new Set<unknown>(["EACCES", "EPERM"]);
|
|
54
|
+
|
|
55
|
+
const errorCodeOf = (error: PlatformError.PlatformError) =>
|
|
56
|
+
Predicate.hasProperty(error.reason.cause, "code") ? error.reason.cause.code : undefined;
|
|
57
|
+
|
|
58
|
+
export const nullWhenProcessGoneOrHidden = <Success, Services>(
|
|
59
|
+
readProcessFile: Effect.Effect<Success, PlatformError.PlatformError, Services>,
|
|
60
|
+
onHidden: Effect.Effect<void>,
|
|
61
|
+
) =>
|
|
62
|
+
readProcessFile.pipe(
|
|
63
|
+
Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(null)),
|
|
64
|
+
Effect.catchTag("PlatformError", (error) => {
|
|
65
|
+
const code = errorCodeOf(error);
|
|
66
|
+
if (code === "ESRCH") {
|
|
67
|
+
return Effect.succeed(null);
|
|
68
|
+
}
|
|
69
|
+
return HIDDEN_PROCESS_ERROR_CODES.has(code) ? Effect.as(onHidden, null) : Effect.fail(error);
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const readStatFile = (
|
|
74
|
+
fileSystem: FileSystem.FileSystem,
|
|
75
|
+
taskName: string | null,
|
|
76
|
+
statFilePath: string,
|
|
77
|
+
onHidden: Effect.Effect<void>,
|
|
78
|
+
) =>
|
|
79
|
+
nullWhenProcessGoneOrHidden(fileSystem.readFileString(statFilePath), onHidden).pipe(
|
|
80
|
+
Effect.mapError((cause) => new ProcStatUnreadable({ taskName, filePath: statFilePath, cause })),
|
|
81
|
+
Effect.flatMap((statText) =>
|
|
82
|
+
statText === null
|
|
83
|
+
? Effect.succeed(null)
|
|
84
|
+
: decodeProcStatText(statText).pipe(
|
|
85
|
+
Effect.mapError(
|
|
86
|
+
(cause) => new ProcStatMalformed({ taskName, filePath: statFilePath, cause }),
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
export const readProcessFromProcDirectory = Effect.fn("readProcessFromProcDirectory")(function* (
|
|
93
|
+
taskName: string | null,
|
|
94
|
+
processId: number,
|
|
95
|
+
) {
|
|
96
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
97
|
+
const stat = yield* readStatFile(
|
|
98
|
+
fileSystem,
|
|
99
|
+
taskName,
|
|
100
|
+
`${PROC_DIRECTORY_PATH}/${processId}/stat`,
|
|
101
|
+
Effect.void,
|
|
102
|
+
);
|
|
103
|
+
return stat === null
|
|
104
|
+
? null
|
|
105
|
+
: {
|
|
106
|
+
processGroupId: stat.processGroupId,
|
|
107
|
+
startTime: stat.startTime,
|
|
108
|
+
hasEnded: isEndedState(stat.state),
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export const listProcessesFromProcDirectory = Effect.fn("listProcessesFromProcDirectory")(
|
|
113
|
+
function* (taskName: string | null) {
|
|
114
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
115
|
+
const unreadableProcessIds: number[] = [];
|
|
116
|
+
const processIdTexts = (yield* fileSystem
|
|
117
|
+
.readDirectory(PROC_DIRECTORY_PATH)
|
|
118
|
+
.pipe(
|
|
119
|
+
Effect.mapError(
|
|
120
|
+
(cause) =>
|
|
121
|
+
new ProcDirectoryUnlistable({ taskName, directoryPath: PROC_DIRECTORY_PATH, cause }),
|
|
122
|
+
),
|
|
123
|
+
)).filter((name) => /^\d+$/u.test(name));
|
|
124
|
+
const listedProcesses = yield* Effect.forEach(
|
|
125
|
+
processIdTexts,
|
|
126
|
+
(processIdText) =>
|
|
127
|
+
Effect.gen(function* () {
|
|
128
|
+
const statFilePath = `${PROC_DIRECTORY_PATH}/${processIdText}/stat`;
|
|
129
|
+
const commandLineFilePath = `${PROC_DIRECTORY_PATH}/${processIdText}/cmdline`;
|
|
130
|
+
const readStat = (onHidden: Effect.Effect<void>) =>
|
|
131
|
+
readStatFile(fileSystem, taskName, statFilePath, onHidden);
|
|
132
|
+
const stat = yield* readStat(
|
|
133
|
+
Effect.sync(() => unreadableProcessIds.push(Number(processIdText))),
|
|
134
|
+
);
|
|
135
|
+
if (stat === null) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
const { processName, state, parentProcessId, processGroupId, sessionId, startTime } =
|
|
139
|
+
stat;
|
|
140
|
+
const readCommandLineUnlessGone = Effect.gen(function* () {
|
|
141
|
+
const commandLine = yield* nullWhenProcessGoneOrHidden(
|
|
142
|
+
fileSystem
|
|
143
|
+
.readFile(commandLineFilePath)
|
|
144
|
+
.pipe(
|
|
145
|
+
Effect.map((commandLineBytes) =>
|
|
146
|
+
new TextDecoder("utf-8", { ignoreBOM: true }).decode(commandLineBytes),
|
|
147
|
+
),
|
|
148
|
+
),
|
|
149
|
+
Effect.void,
|
|
150
|
+
).pipe(
|
|
151
|
+
Effect.mapError(
|
|
152
|
+
(cause) =>
|
|
153
|
+
new ProcCommandLineUnreadable({ taskName, filePath: commandLineFilePath, cause }),
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
if (commandLine === null) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const argumentWords = commandLine.split("\0");
|
|
160
|
+
if (commandWordsOf(argumentWords).length > 0) {
|
|
161
|
+
return programNameOf(argumentWords, processName);
|
|
162
|
+
}
|
|
163
|
+
const statNow = yield* readStat(Effect.void);
|
|
164
|
+
const isGone = statNow === null || isEndedState(statNow.state);
|
|
165
|
+
return isGone ? null : processName;
|
|
166
|
+
});
|
|
167
|
+
return [
|
|
168
|
+
[
|
|
169
|
+
Number(processIdText),
|
|
170
|
+
{
|
|
171
|
+
parentProcessId,
|
|
172
|
+
processGroupId,
|
|
173
|
+
sessionId,
|
|
174
|
+
startTime,
|
|
175
|
+
hasEnded: isEndedState(state),
|
|
176
|
+
readCommandLineUnlessGone,
|
|
177
|
+
},
|
|
178
|
+
] as const,
|
|
179
|
+
];
|
|
180
|
+
}),
|
|
181
|
+
{ concurrency: yield* availableParallelism },
|
|
182
|
+
);
|
|
183
|
+
if (unreadableProcessIds.length > 0) {
|
|
184
|
+
yield* Effect.annotateCurrentSpan(
|
|
185
|
+
`${TASK_TRACE_LABEL_PREFIX}.unreadableProcessIds`,
|
|
186
|
+
unreadableProcessIds,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return new Map(listedProcesses.flat());
|
|
190
|
+
},
|
|
191
|
+
);
|