@sshkeda/task 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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,367 @@
|
|
|
1
|
+
import { dlopen, FFIType } from "bun:ffi";
|
|
2
|
+
import type { FFIFunction } from "bun:ffi";
|
|
3
|
+
|
|
4
|
+
import { Effect, FileSystem } from "effect";
|
|
5
|
+
import type { PlatformError } from "effect";
|
|
6
|
+
import { SecureRandom } from "effect/crypto";
|
|
7
|
+
|
|
8
|
+
import { ProcRunMarkerUnreadable, SystemLibraryNotLoaded } from "../../../shared/process-errors.ts";
|
|
9
|
+
import {
|
|
10
|
+
availableParallelism,
|
|
11
|
+
RUN_MARKERS_VARIABLE_NAME,
|
|
12
|
+
} from "../../../shared/read-environment-config.ts";
|
|
13
|
+
import { RunMarker } from "../../../shared/run-marker.ts";
|
|
14
|
+
import { nullWhenProcessGoneOrHidden } from "./list-linux-processes-from-proc.ts";
|
|
15
|
+
|
|
16
|
+
const MACOS_SYSTEM_LIBRARY_PATH = "/usr/lib/libSystem.B.dylib";
|
|
17
|
+
const LINUX_C_LIBRARY_PATH = "libc.so.6";
|
|
18
|
+
const PROC_DIRECTORY_PATH = "/proc";
|
|
19
|
+
const LINUX_PIPE_LINK_PREFIX = "pipe:";
|
|
20
|
+
|
|
21
|
+
const SUPERVISED_RUN_MARKER_FILE_DESCRIPTOR = 3;
|
|
22
|
+
|
|
23
|
+
const CTL_KERN = 1;
|
|
24
|
+
const KERN_ARGMAX = 8;
|
|
25
|
+
const KERN_PROCARGS2 = 49;
|
|
26
|
+
const PROC_PIDLISTFDS = 1;
|
|
27
|
+
const PROC_PIDFDPIPEINFO = 6;
|
|
28
|
+
const PROX_FDTYPE_PIPE = 6;
|
|
29
|
+
const PROC_FDINFO_BYTES = 8;
|
|
30
|
+
const PROC_FDINFO_TYPE_OFFSET = 4;
|
|
31
|
+
const PIPE_FDINFO_BYTES = 184;
|
|
32
|
+
const PIPE_FDINFO_HANDLE_OFFSET = 160;
|
|
33
|
+
const LISTED_FILE_DESCRIPTOR_LIMIT = 4096;
|
|
34
|
+
const PIPE_READ_END = 0;
|
|
35
|
+
const PIPE_WRITE_END = 1;
|
|
36
|
+
const PIPE_END_COUNT = 2;
|
|
37
|
+
const NO_FLAVOR_ARGUMENT = 0n;
|
|
38
|
+
const ARGUMENT_MAXIMUM_NAME = [CTL_KERN, KERN_ARGMAX];
|
|
39
|
+
const TOKEN_READ_CHUNK_BYTES = 4096;
|
|
40
|
+
|
|
41
|
+
const isMarkedPlatform = process.platform === "darwin" || process.platform === "linux";
|
|
42
|
+
|
|
43
|
+
const PIPE_SYMBOLS = {
|
|
44
|
+
pipe: { args: [FFIType.ptr], returns: FFIType.i32 },
|
|
45
|
+
close: { args: [FFIType.i32], returns: FFIType.i32 },
|
|
46
|
+
read: { args: [FFIType.i32, FFIType.ptr, FFIType.u64], returns: FFIType.i64 },
|
|
47
|
+
write: { args: [FFIType.i32, FFIType.ptr, FFIType.u64], returns: FFIType.i64 },
|
|
48
|
+
} as const satisfies Record<string, FFIFunction>;
|
|
49
|
+
|
|
50
|
+
const MACOS_PROCESS_SYMBOLS = {
|
|
51
|
+
proc_pidinfo: {
|
|
52
|
+
args: [FFIType.i32, FFIType.i32, FFIType.u64, FFIType.ptr, FFIType.i32],
|
|
53
|
+
returns: FFIType.i32,
|
|
54
|
+
},
|
|
55
|
+
proc_pidfdinfo: {
|
|
56
|
+
args: [FFIType.i32, FFIType.i32, FFIType.i32, FFIType.ptr, FFIType.i32],
|
|
57
|
+
returns: FFIType.i32,
|
|
58
|
+
},
|
|
59
|
+
sysctl: {
|
|
60
|
+
args: [FFIType.ptr, FFIType.u32, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.u64],
|
|
61
|
+
returns: FFIType.i32,
|
|
62
|
+
},
|
|
63
|
+
} as const satisfies Record<string, FFIFunction>;
|
|
64
|
+
|
|
65
|
+
const openLibrary = <Symbols extends Record<string, FFIFunction>>(
|
|
66
|
+
libraryPath: string,
|
|
67
|
+
symbols: Symbols,
|
|
68
|
+
) =>
|
|
69
|
+
Effect.acquireRelease(
|
|
70
|
+
Effect.try({
|
|
71
|
+
try: () => dlopen(libraryPath, symbols),
|
|
72
|
+
catch: (cause) => new SystemLibraryNotLoaded({ taskName: null, libraryPath, cause }),
|
|
73
|
+
}),
|
|
74
|
+
(library) =>
|
|
75
|
+
Effect.sync(() => {
|
|
76
|
+
library.close();
|
|
77
|
+
}),
|
|
78
|
+
).pipe(Effect.map((library) => library.symbols));
|
|
79
|
+
|
|
80
|
+
const openMacOsLibrary = openLibrary(MACOS_SYSTEM_LIBRARY_PATH, {
|
|
81
|
+
...PIPE_SYMBOLS,
|
|
82
|
+
...MACOS_PROCESS_SYMBOLS,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const openPipeLibrary =
|
|
86
|
+
process.platform === "darwin"
|
|
87
|
+
? openMacOsLibrary
|
|
88
|
+
: openLibrary(LINUX_C_LIBRARY_PATH, PIPE_SYMBOLS);
|
|
89
|
+
|
|
90
|
+
const readMacOsPipeHandle = (
|
|
91
|
+
symbols: Effect.Success<typeof openMacOsLibrary>,
|
|
92
|
+
processId: number,
|
|
93
|
+
fileDescriptor: number,
|
|
94
|
+
) => {
|
|
95
|
+
const pipeInformation = new Uint8Array(PIPE_FDINFO_BYTES);
|
|
96
|
+
const filled = symbols.proc_pidfdinfo(
|
|
97
|
+
processId,
|
|
98
|
+
fileDescriptor,
|
|
99
|
+
PROC_PIDFDPIPEINFO,
|
|
100
|
+
pipeInformation,
|
|
101
|
+
PIPE_FDINFO_BYTES,
|
|
102
|
+
);
|
|
103
|
+
return filled === PIPE_FDINFO_BYTES
|
|
104
|
+
? String(new DataView(pipeInformation.buffer).getBigUint64(PIPE_FDINFO_HANDLE_OFFSET, true))
|
|
105
|
+
: null;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const readProcFileUnlessGone = <Success>(
|
|
109
|
+
filePath: string,
|
|
110
|
+
readProcFile: (filePath: string) => Effect.Effect<Success, PlatformError.PlatformError>,
|
|
111
|
+
) =>
|
|
112
|
+
nullWhenProcessGoneOrHidden(readProcFile(filePath), Effect.void).pipe(
|
|
113
|
+
Effect.mapError((cause) => new ProcRunMarkerUnreadable({ taskName: null, filePath, cause })),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const readLinuxPipeLink = (
|
|
117
|
+
fileSystem: FileSystem.FileSystem,
|
|
118
|
+
processId: number,
|
|
119
|
+
fileDescriptor: string,
|
|
120
|
+
) =>
|
|
121
|
+
readProcFileUnlessGone(
|
|
122
|
+
`${PROC_DIRECTORY_PATH}/${processId}/fd/${fileDescriptor}`,
|
|
123
|
+
fileSystem.readLink,
|
|
124
|
+
).pipe(Effect.map((link) => (link?.startsWith(LINUX_PIPE_LINK_PREFIX) === true ? link : null)));
|
|
125
|
+
|
|
126
|
+
const readOwnPipeIdentity = (fileDescriptor: number) =>
|
|
127
|
+
process.platform === "darwin"
|
|
128
|
+
? Effect.scoped(
|
|
129
|
+
openMacOsLibrary.pipe(
|
|
130
|
+
Effect.map((symbols) => readMacOsPipeHandle(symbols, process.pid, fileDescriptor)),
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
: FileSystem.FileSystem.use((fileSystem) =>
|
|
134
|
+
readLinuxPipeLink(fileSystem, process.pid, String(fileDescriptor)),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const markerOf = (fileDescriptor: number, tokens: readonly string[]) => {
|
|
138
|
+
const token = tokens.at(-1);
|
|
139
|
+
return token === undefined
|
|
140
|
+
? null
|
|
141
|
+
: RunMarker.make({ fileDescriptor, token, environmentValue: tokens.join(" ") });
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const createRunMarker = (inheritedTokens: readonly string[]) =>
|
|
145
|
+
isMarkedPlatform
|
|
146
|
+
? Effect.scoped(
|
|
147
|
+
Effect.gen(function* () {
|
|
148
|
+
const secureRandom = yield* SecureRandom.SecureRandom;
|
|
149
|
+
const tokens = [...inheritedTokens, yield* secureRandom.uuidV4()];
|
|
150
|
+
const symbols = yield* openPipeLibrary;
|
|
151
|
+
const pipeEnds = new Int32Array(PIPE_END_COUNT);
|
|
152
|
+
if (symbols.pipe(pipeEnds) !== 0) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
const readEnd = pipeEnds[PIPE_READ_END] ?? -1;
|
|
156
|
+
const writeEnd = pipeEnds[PIPE_WRITE_END] ?? -1;
|
|
157
|
+
const tokenBytes = new TextEncoder().encode(tokens.join(" "));
|
|
158
|
+
const written = Number(symbols.write(writeEnd, tokenBytes, tokenBytes.byteLength));
|
|
159
|
+
symbols.close(writeEnd);
|
|
160
|
+
if (written !== tokenBytes.byteLength) {
|
|
161
|
+
symbols.close(readEnd);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return markerOf(readEnd, tokens);
|
|
165
|
+
}),
|
|
166
|
+
)
|
|
167
|
+
: Effect.succeed(null);
|
|
168
|
+
|
|
169
|
+
const readUntilTheEnd = (
|
|
170
|
+
symbols: Effect.Success<typeof openPipeLibrary>,
|
|
171
|
+
fileDescriptor: number,
|
|
172
|
+
readSoFar = "",
|
|
173
|
+
): string | null => {
|
|
174
|
+
const chunk = new Uint8Array(TOKEN_READ_CHUNK_BYTES);
|
|
175
|
+
const filled = Number(symbols.read(fileDescriptor, chunk, chunk.byteLength));
|
|
176
|
+
if (filled < 0) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
return filled > 0
|
|
180
|
+
? readUntilTheEnd(
|
|
181
|
+
symbols,
|
|
182
|
+
fileDescriptor,
|
|
183
|
+
readSoFar + new TextDecoder().decode(chunk.subarray(0, filled)),
|
|
184
|
+
)
|
|
185
|
+
: readSoFar;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const adoptSupervisorRunMarker = isMarkedPlatform
|
|
189
|
+
? Effect.scoped(
|
|
190
|
+
Effect.gen(function* () {
|
|
191
|
+
if ((yield* readOwnPipeIdentity(SUPERVISED_RUN_MARKER_FILE_DESCRIPTOR)) === null) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const symbols = yield* openPipeLibrary;
|
|
195
|
+
const tokens = readUntilTheEnd(symbols, SUPERVISED_RUN_MARKER_FILE_DESCRIPTOR);
|
|
196
|
+
if (tokens === null) {
|
|
197
|
+
return yield* new ProcRunMarkerUnreadable({
|
|
198
|
+
taskName: null,
|
|
199
|
+
filePath: `/dev/fd/${SUPERVISED_RUN_MARKER_FILE_DESCRIPTOR}`,
|
|
200
|
+
cause: "read failed",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return markerOf(
|
|
204
|
+
SUPERVISED_RUN_MARKER_FILE_DESCRIPTOR,
|
|
205
|
+
tokens.split(" ").filter((token) => token !== ""),
|
|
206
|
+
);
|
|
207
|
+
}),
|
|
208
|
+
)
|
|
209
|
+
: Effect.succeed(null);
|
|
210
|
+
|
|
211
|
+
const holdsToken = (environment: Uint8Array, token: string) => {
|
|
212
|
+
const variablePrefix = `${RUN_MARKERS_VARIABLE_NAME}=`;
|
|
213
|
+
const mentionsToken = Buffer.from(
|
|
214
|
+
environment.buffer,
|
|
215
|
+
environment.byteOffset,
|
|
216
|
+
environment.byteLength,
|
|
217
|
+
).includes(token, "latin1");
|
|
218
|
+
return (
|
|
219
|
+
mentionsToken &&
|
|
220
|
+
new TextDecoder("latin1")
|
|
221
|
+
.decode(environment)
|
|
222
|
+
.split("\0")
|
|
223
|
+
.some(
|
|
224
|
+
(entry) =>
|
|
225
|
+
entry.startsWith(variablePrefix) &&
|
|
226
|
+
entry.slice(variablePrefix.length).split(" ").includes(token),
|
|
227
|
+
)
|
|
228
|
+
);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const macOsHoldsPipe = (
|
|
232
|
+
symbols: Effect.Success<typeof openMacOsLibrary>,
|
|
233
|
+
fileDescriptors: Uint8Array,
|
|
234
|
+
processId: number,
|
|
235
|
+
pipeIdentity: string,
|
|
236
|
+
) => {
|
|
237
|
+
const filled = symbols.proc_pidinfo(
|
|
238
|
+
processId,
|
|
239
|
+
PROC_PIDLISTFDS,
|
|
240
|
+
NO_FLAVOR_ARGUMENT,
|
|
241
|
+
fileDescriptors,
|
|
242
|
+
fileDescriptors.byteLength,
|
|
243
|
+
);
|
|
244
|
+
const fields = new DataView(fileDescriptors.buffer);
|
|
245
|
+
return Array.from(
|
|
246
|
+
{ length: Math.max(filled, 0) / PROC_FDINFO_BYTES },
|
|
247
|
+
(_unused, index) => index * PROC_FDINFO_BYTES,
|
|
248
|
+
).some(
|
|
249
|
+
(offset) =>
|
|
250
|
+
fields.getUint32(offset + PROC_FDINFO_TYPE_OFFSET, true) === PROX_FDTYPE_PIPE &&
|
|
251
|
+
readMacOsPipeHandle(symbols, processId, fields.getInt32(offset, true)) === pipeIdentity,
|
|
252
|
+
);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const readArgumentMaximum = (symbols: Effect.Success<typeof openMacOsLibrary>) => {
|
|
256
|
+
const value = new Int32Array(1);
|
|
257
|
+
const valueSize = new BigUint64Array([BigInt(value.byteLength)]);
|
|
258
|
+
const name = Int32Array.from(ARGUMENT_MAXIMUM_NAME);
|
|
259
|
+
return symbols.sysctl(name, name.length, value, valueSize, null, 0) === 0 ? (value[0] ?? 0) : 0;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const macOsEnvironmentHoldsToken = (
|
|
263
|
+
symbols: Effect.Success<typeof openMacOsLibrary>,
|
|
264
|
+
processArguments: Uint8Array,
|
|
265
|
+
processId: number,
|
|
266
|
+
token: string,
|
|
267
|
+
) => {
|
|
268
|
+
const valueSize = new BigUint64Array([BigInt(processArguments.byteLength)]);
|
|
269
|
+
const name = Int32Array.from([CTL_KERN, KERN_PROCARGS2, processId]);
|
|
270
|
+
return (
|
|
271
|
+
symbols.sysctl(name, name.length, processArguments, valueSize, null, 0) === 0 &&
|
|
272
|
+
holdsToken(processArguments.subarray(0, Number(valueSize[0])), token)
|
|
273
|
+
);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const findMacOsMarkedProcessIds = (
|
|
277
|
+
marker: typeof RunMarker.Type,
|
|
278
|
+
candidateProcessIds: readonly number[],
|
|
279
|
+
) =>
|
|
280
|
+
Effect.scoped(
|
|
281
|
+
openMacOsLibrary.pipe(
|
|
282
|
+
Effect.map((symbols) => {
|
|
283
|
+
const pipeIdentity = readMacOsPipeHandle(symbols, process.pid, marker.fileDescriptor);
|
|
284
|
+
const fileDescriptors = new Uint8Array(PROC_FDINFO_BYTES * LISTED_FILE_DESCRIPTOR_LIMIT);
|
|
285
|
+
const processArguments = new Uint8Array(readArgumentMaximum(symbols));
|
|
286
|
+
return candidateProcessIds.filter(
|
|
287
|
+
(processId) =>
|
|
288
|
+
(pipeIdentity !== null &&
|
|
289
|
+
macOsHoldsPipe(symbols, fileDescriptors, processId, pipeIdentity)) ||
|
|
290
|
+
macOsEnvironmentHoldsToken(symbols, processArguments, processId, marker.token),
|
|
291
|
+
);
|
|
292
|
+
}),
|
|
293
|
+
),
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const linuxHoldsPipe = (
|
|
297
|
+
fileSystem: FileSystem.FileSystem,
|
|
298
|
+
processId: number,
|
|
299
|
+
pipeIdentity: string,
|
|
300
|
+
) =>
|
|
301
|
+
readProcFileUnlessGone(`${PROC_DIRECTORY_PATH}/${processId}/fd`, (directoryPath) =>
|
|
302
|
+
fileSystem.readDirectory(directoryPath),
|
|
303
|
+
).pipe(
|
|
304
|
+
Effect.flatMap((fileDescriptors) =>
|
|
305
|
+
Effect.forEach(fileDescriptors ?? [], (fileDescriptor) =>
|
|
306
|
+
readLinuxPipeLink(fileSystem, processId, fileDescriptor),
|
|
307
|
+
),
|
|
308
|
+
),
|
|
309
|
+
Effect.map((links) => links.includes(pipeIdentity)),
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const linuxEnvironmentHoldsToken = (
|
|
313
|
+
fileSystem: FileSystem.FileSystem,
|
|
314
|
+
processId: number,
|
|
315
|
+
token: string,
|
|
316
|
+
) =>
|
|
317
|
+
readProcFileUnlessGone(`${PROC_DIRECTORY_PATH}/${processId}/environ`, fileSystem.readFile).pipe(
|
|
318
|
+
Effect.map((environment) => environment !== null && holdsToken(environment, token)),
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const findLinuxMarkedProcessIds = (
|
|
322
|
+
marker: typeof RunMarker.Type,
|
|
323
|
+
candidateProcessIds: readonly number[],
|
|
324
|
+
) =>
|
|
325
|
+
Effect.gen(function* () {
|
|
326
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
327
|
+
const pipeIdentity = yield* readLinuxPipeLink(
|
|
328
|
+
fileSystem,
|
|
329
|
+
process.pid,
|
|
330
|
+
String(marker.fileDescriptor),
|
|
331
|
+
);
|
|
332
|
+
const holdsMarkerPipe = (processId: number) =>
|
|
333
|
+
pipeIdentity === null
|
|
334
|
+
? Effect.succeed(false)
|
|
335
|
+
: linuxHoldsPipe(fileSystem, processId, pipeIdentity);
|
|
336
|
+
return yield* Effect.filter(
|
|
337
|
+
candidateProcessIds,
|
|
338
|
+
(processId) =>
|
|
339
|
+
Effect.gen(function* () {
|
|
340
|
+
return (
|
|
341
|
+
(yield* linuxEnvironmentHoldsToken(fileSystem, processId, marker.token)) ||
|
|
342
|
+
(yield* holdsMarkerPipe(processId))
|
|
343
|
+
);
|
|
344
|
+
}),
|
|
345
|
+
{ concurrency: yield* availableParallelism },
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
export const findMarkedProcessIds = (
|
|
350
|
+
marker: typeof RunMarker.Type | null,
|
|
351
|
+
candidateProcessIds: Iterable<number>,
|
|
352
|
+
) => {
|
|
353
|
+
const runProcessIds = new Set([process.pid, process.ppid]);
|
|
354
|
+
const otherProcessIds = [...candidateProcessIds].filter(
|
|
355
|
+
(processId) => !runProcessIds.has(processId),
|
|
356
|
+
);
|
|
357
|
+
if (marker === null) {
|
|
358
|
+
return Effect.succeed(new Set<number>());
|
|
359
|
+
}
|
|
360
|
+
return Effect.gen(function* () {
|
|
361
|
+
return new Set(
|
|
362
|
+
process.platform === "darwin"
|
|
363
|
+
? yield* findMacOsMarkedProcessIds(marker, otherProcessIds)
|
|
364
|
+
: yield* findLinuxMarkedProcessIds(marker, otherProcessIds),
|
|
365
|
+
);
|
|
366
|
+
});
|
|
367
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
import { TASK_ERROR_FIELDS } from "../../../shared/error-fields.ts";
|
|
4
|
+
|
|
5
|
+
export class ProcessNotKillable extends Schema.TaggedError<ProcessNotKillable>()(
|
|
6
|
+
"ProcessNotKillable",
|
|
7
|
+
{
|
|
8
|
+
...TASK_ERROR_FIELDS,
|
|
9
|
+
processId: Schema.Int,
|
|
10
|
+
command: Schema.String,
|
|
11
|
+
ownerTaskName: Schema.NullOr(Schema.String),
|
|
12
|
+
},
|
|
13
|
+
) {
|
|
14
|
+
public override get message() {
|
|
15
|
+
return `could not kill ${this.processId} (${this.command}), started by ${this.ownerTaskName === null ? "no known task" : `"${this.ownerTaskName}"`}, because the signal returned EPERM`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Effect, Predicate } from "effect";
|
|
2
|
+
|
|
3
|
+
import { ProcessNotSignaled } from "../../../shared/process-errors.ts";
|
|
4
|
+
import { isStillListedAs } from "./list-processes.ts";
|
|
5
|
+
|
|
6
|
+
export const sendSignal = (
|
|
7
|
+
taskName: string | null,
|
|
8
|
+
processId: number,
|
|
9
|
+
signal: ProcessNotSignaled["signal"],
|
|
10
|
+
) =>
|
|
11
|
+
Effect.try(() => process.kill(processId, signal)).pipe(
|
|
12
|
+
Effect.as("sent" as const),
|
|
13
|
+
Effect.catchTag("UnknownError", (error) => {
|
|
14
|
+
const code = Predicate.hasProperty(error.cause, "code") ? error.cause.code : undefined;
|
|
15
|
+
const processIsGoneOrNotOurs = code === "ESRCH" || code === "EPERM";
|
|
16
|
+
return processIsGoneOrNotOurs ? Effect.succeed<"EPERM" | "ESRCH">(code) : Effect.fail(error);
|
|
17
|
+
}),
|
|
18
|
+
Effect.mapError((cause) => new ProcessNotSignaled({ taskName, processId, signal, cause })),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const signalUnlessReplaced = (
|
|
22
|
+
taskName: string | null,
|
|
23
|
+
listed: Parameters<typeof isStillListedAs>[1],
|
|
24
|
+
signal: ProcessNotSignaled["signal"],
|
|
25
|
+
) =>
|
|
26
|
+
isStillListedAs(taskName, listed).pipe(
|
|
27
|
+
Effect.flatMap((isTheListedProcess) =>
|
|
28
|
+
isTheListedProcess
|
|
29
|
+
? sendSignal(taskName, listed.processId, signal)
|
|
30
|
+
: Effect.succeed("ESRCH" as const),
|
|
31
|
+
),
|
|
32
|
+
);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
|
|
3
|
+
import type { ProcessNotSignaled } from "../../../shared/process-errors.ts";
|
|
4
|
+
import type { RunMarker } from "../../../shared/run-marker.ts";
|
|
5
|
+
import { findDescendantProcessIds, findSessionProcessIds } from "./find-process-ids.ts";
|
|
6
|
+
import { listProcesses } from "./list-processes.ts";
|
|
7
|
+
import { findMarkedProcessIds } from "./mark-run-processes.ts";
|
|
8
|
+
import { signalUnlessReplaced } from "./send-signal.ts";
|
|
9
|
+
import { findStartedGroupWitness } from "./started-process-groups.ts";
|
|
10
|
+
import type { startedProcessGroup } from "./started-process-groups.ts";
|
|
11
|
+
|
|
12
|
+
export const signalRunProcesses = Effect.fn("signalRunProcesses")(function* (
|
|
13
|
+
run: {
|
|
14
|
+
readonly sessionId: number;
|
|
15
|
+
readonly descendantsOfProcessId: number | null;
|
|
16
|
+
readonly startedProcessGroups: ReadonlyMap<number, ReturnType<typeof startedProcessGroup>>;
|
|
17
|
+
readonly runMarker: typeof RunMarker.Type | null;
|
|
18
|
+
},
|
|
19
|
+
signal: Exclude<ProcessNotSignaled["signal"], 0>,
|
|
20
|
+
listProcessesNow: typeof listProcesses = listProcesses,
|
|
21
|
+
) {
|
|
22
|
+
const listedProcesses = yield* listProcessesNow(null);
|
|
23
|
+
const startedProcessGroupIds = new Set(
|
|
24
|
+
[...run.startedProcessGroups].flatMap(([processGroupId, startedGroup]) =>
|
|
25
|
+
findStartedGroupWitness(listedProcesses, processGroupId, startedGroup) === null
|
|
26
|
+
? []
|
|
27
|
+
: [processGroupId],
|
|
28
|
+
),
|
|
29
|
+
);
|
|
30
|
+
const processIds = new Set([
|
|
31
|
+
...findSessionProcessIds(listedProcesses, run.sessionId),
|
|
32
|
+
...(run.descendantsOfProcessId === null
|
|
33
|
+
? []
|
|
34
|
+
: findDescendantProcessIds(listedProcesses, [run.descendantsOfProcessId]).slice(1)),
|
|
35
|
+
...(yield* findMarkedProcessIds(run.runMarker, listedProcesses.keys())),
|
|
36
|
+
...[...listedProcesses]
|
|
37
|
+
.filter(([, listedProcess]) => startedProcessGroupIds.has(listedProcess.processGroupId))
|
|
38
|
+
.map(([processId]) => processId),
|
|
39
|
+
]);
|
|
40
|
+
yield* Effect.forEach(
|
|
41
|
+
[...processIds].flatMap((processId) => {
|
|
42
|
+
const listedProcess = listedProcesses.get(processId);
|
|
43
|
+
return listedProcess?.hasEnded === false ? [{ ...listedProcess, processId }] : [];
|
|
44
|
+
}),
|
|
45
|
+
(listedProcess) => signalUnlessReplaced(null, listedProcess, signal),
|
|
46
|
+
{ discard: true },
|
|
47
|
+
);
|
|
48
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
|
|
3
|
+
import { listProcesses } from "./list-processes.ts";
|
|
4
|
+
import type { isStillListedAs } from "./list-processes.ts";
|
|
5
|
+
import { sendSignal } from "./send-signal.ts";
|
|
6
|
+
|
|
7
|
+
export const startedProcessGroup = (
|
|
8
|
+
taskName: string,
|
|
9
|
+
leaderStartTime: string | null,
|
|
10
|
+
latestMemberStartTimeAtLeaderExit: string | null,
|
|
11
|
+
) => ({ taskName, leaderStartTime, latestMemberStartTimeAtLeaderExit });
|
|
12
|
+
|
|
13
|
+
export const recordLeaderExit = (
|
|
14
|
+
startedProcessGroups: Map<number, ReturnType<typeof startedProcessGroup>>,
|
|
15
|
+
processId: number,
|
|
16
|
+
latestMemberStartTime: string | null,
|
|
17
|
+
) => {
|
|
18
|
+
const startedGroup = startedProcessGroups.get(processId);
|
|
19
|
+
if (startedGroup === undefined) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (latestMemberStartTime === null) {
|
|
23
|
+
startedProcessGroups.delete(processId);
|
|
24
|
+
} else {
|
|
25
|
+
startedProcessGroups.set(
|
|
26
|
+
processId,
|
|
27
|
+
startedProcessGroup(
|
|
28
|
+
startedGroup.taskName,
|
|
29
|
+
startedGroup.leaderStartTime,
|
|
30
|
+
latestMemberStartTime,
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const MICROSECONDS_PER_SECOND = 1_000_000n;
|
|
37
|
+
|
|
38
|
+
const orderOfStartTime = (startTime: string) => {
|
|
39
|
+
const [seconds = "0", microseconds] = startTime.split(".");
|
|
40
|
+
return microseconds === undefined
|
|
41
|
+
? BigInt(seconds)
|
|
42
|
+
: BigInt(seconds) * MICROSECONDS_PER_SECOND + BigInt(microseconds);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const findLatestMemberStartTime = (
|
|
46
|
+
listedProcesses: Effect.Success<ReturnType<typeof listProcesses>>,
|
|
47
|
+
processGroupId: number,
|
|
48
|
+
) =>
|
|
49
|
+
[...listedProcesses.values()]
|
|
50
|
+
.filter(
|
|
51
|
+
(listedProcess) => listedProcess.processGroupId === processGroupId && !listedProcess.hasEnded,
|
|
52
|
+
)
|
|
53
|
+
.map((listedProcess) => listedProcess.startTime)
|
|
54
|
+
.toSorted((left, right) => Number(orderOfStartTime(left) - orderOfStartTime(right)))
|
|
55
|
+
.at(-1) ?? null;
|
|
56
|
+
|
|
57
|
+
export const findStartedGroupWitness = (
|
|
58
|
+
listedProcesses: Effect.Success<ReturnType<typeof listProcesses>>,
|
|
59
|
+
processGroupId: number,
|
|
60
|
+
startedGroup: Omit<ReturnType<typeof startedProcessGroup>, "taskName">,
|
|
61
|
+
): Parameters<typeof isStillListedAs>[1] | null => {
|
|
62
|
+
const { leaderStartTime, latestMemberStartTimeAtLeaderExit } = startedGroup;
|
|
63
|
+
if (latestMemberStartTimeAtLeaderExit === null) {
|
|
64
|
+
const leader = listedProcesses.get(processGroupId);
|
|
65
|
+
const isTheLeaderTaskStarted =
|
|
66
|
+
leader?.processGroupId === processGroupId && leader.startTime === leaderStartTime;
|
|
67
|
+
return isTheLeaderTaskStarted
|
|
68
|
+
? { processId: processGroupId, processGroupId, startTime: leader.startTime }
|
|
69
|
+
: null;
|
|
70
|
+
}
|
|
71
|
+
const witness = [...listedProcesses].find(
|
|
72
|
+
([, listedProcess]) =>
|
|
73
|
+
listedProcess.processGroupId === processGroupId &&
|
|
74
|
+
!listedProcess.hasEnded &&
|
|
75
|
+
orderOfStartTime(listedProcess.startTime) <=
|
|
76
|
+
orderOfStartTime(latestMemberStartTimeAtLeaderExit) &&
|
|
77
|
+
(leaderStartTime === null ||
|
|
78
|
+
orderOfStartTime(listedProcess.startTime) >= orderOfStartTime(leaderStartTime)),
|
|
79
|
+
);
|
|
80
|
+
return witness === undefined
|
|
81
|
+
? null
|
|
82
|
+
: { processId: witness[0], processGroupId, startTime: witness[1].startTime };
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const findStartedGroupWitnessNow = (
|
|
86
|
+
taskName: string | null,
|
|
87
|
+
processGroupId: number,
|
|
88
|
+
startedGroup: Omit<ReturnType<typeof startedProcessGroup>, "taskName">,
|
|
89
|
+
) =>
|
|
90
|
+
listProcesses(taskName).pipe(
|
|
91
|
+
Effect.map((listedProcesses) =>
|
|
92
|
+
findStartedGroupWitness(listedProcesses, processGroupId, startedGroup),
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
export const readLatestMemberStartTime = (taskName: string | null, processGroupId: number) =>
|
|
97
|
+
sendSignal(taskName, -processGroupId, 0).pipe(
|
|
98
|
+
Effect.flatMap((result) =>
|
|
99
|
+
result === "ESRCH"
|
|
100
|
+
? Effect.succeed(null)
|
|
101
|
+
: listProcesses(taskName).pipe(
|
|
102
|
+
Effect.map((listedProcesses) =>
|
|
103
|
+
findLatestMemberStartTime(listedProcesses, processGroupId),
|
|
104
|
+
),
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Deferred, Duration, Effect, Layer, Option, Stream } from "effect";
|
|
2
|
+
import type { Queue } from "effect";
|
|
3
|
+
|
|
4
|
+
import { findUserLocationInStack } from "../../../shared/find-user-locations-in-stack-traces.ts";
|
|
5
|
+
import { TASK_TRACE_LABEL_PREFIX } from "../../../shared/names-from-package-json.ts";
|
|
6
|
+
import {
|
|
7
|
+
printLine,
|
|
8
|
+
standardErrorClosedDeferred,
|
|
9
|
+
standardErrorWriteFailedDeferred,
|
|
10
|
+
} from "../../../shared/print-to-standard-error.ts";
|
|
11
|
+
import {
|
|
12
|
+
laterRunCallDeferred,
|
|
13
|
+
runningRootTaskDeferred,
|
|
14
|
+
} from "../../../shared/signal-run-called-twice.ts";
|
|
15
|
+
import type { STOP_SIGNALS } from "../../../shared/stop-signals.ts";
|
|
16
|
+
import { RunCalledTwice } from "../../../shared/task-errors.ts";
|
|
17
|
+
import { StopRequested } from "../shared/main-thread-errors.ts";
|
|
18
|
+
import { RunRootTaskConfig } from "../shared/provide-run-root-task-config.ts";
|
|
19
|
+
import { StopState } from "../shared/provide-stop-state.ts";
|
|
20
|
+
|
|
21
|
+
const STOPPING_LINES = {
|
|
22
|
+
SIGINT: "stopping, press ctrl-c again to force",
|
|
23
|
+
SIGTERM: "stopping, send SIGTERM or press ctrl-c again to force",
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
const GRACE_AFTER_STANDARD_ERROR_CLOSED = Duration.seconds(1);
|
|
27
|
+
|
|
28
|
+
const requestStopOnSignal = Effect.fn("requestStopOnSignal")(function* (
|
|
29
|
+
signal: (typeof STOP_SIGNALS)[number],
|
|
30
|
+
) {
|
|
31
|
+
yield* Effect.annotateCurrentSpan(`${TASK_TRACE_LABEL_PREFIX}.signal`, signal);
|
|
32
|
+
const shutdown = yield* StopState;
|
|
33
|
+
if (signal === "SIGHUP") {
|
|
34
|
+
return yield* shutdown.forceStop(new StopRequested({ signal }));
|
|
35
|
+
}
|
|
36
|
+
yield* Effect.gen(function* () {
|
|
37
|
+
if (yield* shutdown.requestStop(signal)) {
|
|
38
|
+
yield* printLine("Info", STOPPING_LINES[signal]);
|
|
39
|
+
}
|
|
40
|
+
}).pipe(shutdown.holdTaskLogsDuring);
|
|
41
|
+
}, Effect.uninterruptible);
|
|
42
|
+
|
|
43
|
+
export const stopRequestsLayer = (signals: Queue.Dequeue<(typeof STOP_SIGNALS)[number]>) =>
|
|
44
|
+
Layer.effectDiscard(
|
|
45
|
+
Effect.gen(function* () {
|
|
46
|
+
yield* Stream.fromQueue(signals).pipe(
|
|
47
|
+
Stream.runForEach(requestStopOnSignal),
|
|
48
|
+
Effect.interruptible,
|
|
49
|
+
Effect.forkScoped,
|
|
50
|
+
);
|
|
51
|
+
const shutdown = yield* StopState;
|
|
52
|
+
yield* Deferred.await(standardErrorWriteFailedDeferred).pipe(
|
|
53
|
+
Effect.flatMap(shutdown.forceStop),
|
|
54
|
+
Effect.interruptible,
|
|
55
|
+
Effect.forkScoped,
|
|
56
|
+
);
|
|
57
|
+
yield* Deferred.await(standardErrorClosedDeferred).pipe(
|
|
58
|
+
Effect.andThen(shutdown.isStopping),
|
|
59
|
+
Effect.flatMap((isStopping) =>
|
|
60
|
+
isStopping ? Effect.void : Effect.asVoid(shutdown.requestStop("SIGPIPE")),
|
|
61
|
+
),
|
|
62
|
+
Effect.andThen(
|
|
63
|
+
shutdown.awaitForcedStop.pipe(Effect.timeoutOption(GRACE_AFTER_STANDARD_ERROR_CLOSED)),
|
|
64
|
+
),
|
|
65
|
+
Effect.flatMap((forcedStop) =>
|
|
66
|
+
Option.isSome(forcedStop)
|
|
67
|
+
? Effect.void
|
|
68
|
+
: shutdown.forceStop(new StopRequested({ signal: "SIGPIPE" })),
|
|
69
|
+
),
|
|
70
|
+
Effect.interruptible,
|
|
71
|
+
Effect.forkScoped,
|
|
72
|
+
);
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
export const runCalledTwiceLayer = Layer.effectDiscard(
|
|
77
|
+
Effect.gen(function* () {
|
|
78
|
+
const shutdown = yield* StopState;
|
|
79
|
+
yield* RunRootTaskConfig;
|
|
80
|
+
yield* Effect.all([
|
|
81
|
+
Deferred.await(runningRootTaskDeferred),
|
|
82
|
+
Deferred.await(laterRunCallDeferred),
|
|
83
|
+
]).pipe(
|
|
84
|
+
Effect.map(
|
|
85
|
+
([runningCall, laterCall]) =>
|
|
86
|
+
new RunCalledTwice({
|
|
87
|
+
runningRootTaskName: runningCall.rootTaskName,
|
|
88
|
+
runningRootTaskLocation:
|
|
89
|
+
runningCall.stack === undefined
|
|
90
|
+
? undefined
|
|
91
|
+
: findUserLocationInStack(runningCall.stack),
|
|
92
|
+
laterRootTaskName: laterCall.rootTaskName,
|
|
93
|
+
location:
|
|
94
|
+
laterCall.stack === undefined ? undefined : findUserLocationInStack(laterCall.stack),
|
|
95
|
+
}),
|
|
96
|
+
),
|
|
97
|
+
Effect.flatMap(shutdown.recordFirstFailure),
|
|
98
|
+
Effect.interruptible,
|
|
99
|
+
Effect.forkScoped,
|
|
100
|
+
);
|
|
101
|
+
}),
|
|
102
|
+
);
|