@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.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -2
  3. package/package.json +41 -4
  4. package/src/index.ts +2 -0
  5. package/src/lib/errors/index.ts +15 -0
  6. package/src/lib/errors/internal/errors.ts +147 -0
  7. package/src/lib/kill-leftover-processes/index.ts +17 -0
  8. package/src/lib/kill-leftover-processes/internal/command-words.ts +16 -0
  9. package/src/lib/kill-leftover-processes/internal/find-process-ids.ts +36 -0
  10. package/src/lib/kill-leftover-processes/internal/kill-process-groups.ts +270 -0
  11. package/src/lib/kill-leftover-processes/internal/list-linux-processes-from-proc.ts +191 -0
  12. package/src/lib/kill-leftover-processes/internal/list-macos-processes-with-sysctl.ts +335 -0
  13. package/src/lib/kill-leftover-processes/internal/list-processes.ts +58 -0
  14. package/src/lib/kill-leftover-processes/internal/mark-run-processes.ts +367 -0
  15. package/src/lib/kill-leftover-processes/internal/process-cleanup-errors.ts +17 -0
  16. package/src/lib/kill-leftover-processes/internal/send-signal.ts +32 -0
  17. package/src/lib/kill-leftover-processes/internal/signal-run-processes.ts +48 -0
  18. package/src/lib/kill-leftover-processes/internal/started-process-groups.ts +107 -0
  19. package/src/lib/main-thread/index.ts +3 -0
  20. package/src/lib/main-thread/internal/listen-for-stop-requests.ts +102 -0
  21. package/src/lib/main-thread/internal/run-main-thread.ts +81 -0
  22. package/src/lib/main-thread/internal/run-root-task-fiber.ts +191 -0
  23. package/src/lib/main-thread/internal/stops-at-first-failure.ts +20 -0
  24. package/src/lib/main-thread/internal/trace-the-run.ts +107 -0
  25. package/src/lib/main-thread/lib/find-code-each-task-depends-on/index.ts +7 -0
  26. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/follow-imports-from-entry-files.ts +306 -0
  27. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/load-all-reporting-first-failure.ts +11 -0
  28. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/make-module-graph.ts +48 -0
  29. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/read-configured-loaders.ts +64 -0
  30. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/resolve-module-specifiers.ts +60 -0
  31. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/rewrite-worker-imports.ts +118 -0
  32. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/worker-import-errors.ts +19 -0
  33. package/src/lib/main-thread/lib/run-tasks-on-workers/index.ts +7 -0
  34. package/src/lib/main-thread/lib/run-tasks-on-workers/internal/handle-worker-events.ts +348 -0
  35. package/src/lib/main-thread/lib/run-tasks-on-workers/internal/make-task-runs.ts +207 -0
  36. package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/index.ts +1 -0
  37. package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/await-within-counted-limit.ts +29 -0
  38. package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/execute-task.ts +272 -0
  39. package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/make-task-setup-clock.ts +129 -0
  40. package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/run-requested-tasks.ts +135 -0
  41. package/src/lib/main-thread/lib/run-tasks-on-workers/shared/define-task-run-state.ts +288 -0
  42. package/src/lib/main-thread/lib/run-tasks-on-workers/shared/make-stopwatch.ts +39 -0
  43. package/src/lib/main-thread/lib/run-tasks-on-workers/shared/share-standard-input-with-root-task.ts +36 -0
  44. package/src/lib/main-thread/lib/run-tasks-on-workers/shared/stop-task.ts +166 -0
  45. package/src/lib/main-thread/lib/scan-module-source/index.ts +1 -0
  46. package/src/lib/main-thread/lib/scan-module-source/internal/find-lexical-bindings.ts +154 -0
  47. package/src/lib/main-thread/lib/scan-module-source/internal/find-task-declarations.ts +205 -0
  48. package/src/lib/main-thread/lib/scan-module-source/internal/parse-module-text.ts +34 -0
  49. package/src/lib/main-thread/lib/scan-module-source/internal/read-literal-string.ts +15 -0
  50. package/src/lib/main-thread/lib/scan-module-source/internal/read-module-loads.ts +316 -0
  51. package/src/lib/main-thread/lib/scan-module-source/internal/scan-module-text.ts +70 -0
  52. package/src/lib/main-thread/lib/scan-module-source/internal/unwrap-type-assertions.ts +10 -0
  53. package/src/lib/main-thread/lib/scan-module-source/internal/visit-non-reference-identifiers.ts +21 -0
  54. package/src/lib/main-thread/shared/fail-run.ts +36 -0
  55. package/src/lib/main-thread/shared/find-failure-reason.ts +43 -0
  56. package/src/lib/main-thread/shared/find-package-directories.ts +34 -0
  57. package/src/lib/main-thread/shared/find-tasks-reachable-from-root-task-file.ts +246 -0
  58. package/src/lib/main-thread/shared/list-directory-paths-up-to-root.ts +13 -0
  59. package/src/lib/main-thread/shared/main-thread-errors.ts +373 -0
  60. package/src/lib/main-thread/shared/package-json-errors.ts +93 -0
  61. package/src/lib/main-thread/shared/provide-discovered-tasks.ts +26 -0
  62. package/src/lib/main-thread/shared/provide-process-groups.ts +63 -0
  63. package/src/lib/main-thread/shared/provide-run-root-task-config.ts +66 -0
  64. package/src/lib/main-thread/shared/provide-stop-state.ts +150 -0
  65. package/src/lib/main-thread/shared/provide-worker-pool.ts +607 -0
  66. package/src/lib/main-thread/shared/read-bare-package-name.ts +3 -0
  67. package/src/lib/main-thread/shared/read-package-json.ts +149 -0
  68. package/src/lib/main-thread/shared/report-task-failure.ts +31 -0
  69. package/src/lib/preload-rewritten-imports-into-each-worker/index.ts +5 -0
  70. package/src/lib/preload-rewritten-imports-into-each-worker/internal/load-rewritten-imports.ts +34 -0
  71. package/src/lib/supervise-the-run/index.ts +15 -0
  72. package/src/lib/supervise-the-run/internal/adopt-orphans-on-linux.ts +73 -0
  73. package/src/lib/supervise-the-run/internal/check-root-task-file.ts +8 -0
  74. package/src/lib/supervise-the-run/internal/report-ready.ts +12 -0
  75. package/src/lib/supervise-the-run/internal/share-process-groups.ts +25 -0
  76. package/src/lib/supervise-the-run/internal/start-watching-the-supervisor.ts +6 -0
  77. package/src/lib/supervise-the-run/internal/supervise-run.ts +155 -0
  78. package/src/lib/supervise-the-run/internal/talk-to-the-supervisor.ts +53 -0
  79. package/src/lib/supervise-the-run/internal/watch-the-supervisor.ts +61 -0
  80. package/src/lib/task/index.ts +5 -0
  81. package/src/lib/task/internal/make-tasks.ts +82 -0
  82. package/src/lib/task/internal/run-command.ts +194 -0
  83. package/src/lib/task/internal/run-root-task.ts +263 -0
  84. package/src/lib/task/internal/run-task-lists.ts +51 -0
  85. package/src/lib/task/internal/trust-output-mode-names.ts +18 -0
  86. package/src/lib/task/internal/trust-task-results.ts +35 -0
  87. package/src/lib/task/internal/use-task-services.ts +4 -0
  88. package/src/lib/worker-thread/index.ts +3 -0
  89. package/src/lib/worker-thread/internal/answer-main-thread-messages.ts +141 -0
  90. package/src/lib/worker-thread/internal/describe-task-body-result.ts +91 -0
  91. package/src/lib/worker-thread/internal/name-config-key-not-set.ts +57 -0
  92. package/src/lib/worker-thread/internal/run-task-body.ts +117 -0
  93. package/src/lib/worker-thread/internal/serve-on-the-parent-port.ts +63 -0
  94. package/src/lib/worker-thread/internal/start-worker-thread.ts +41 -0
  95. package/src/lib/worker-thread/lib/provide-task-services/index.ts +1 -0
  96. package/src/lib/worker-thread/lib/provide-task-services/internal/make-task-logger.ts +34 -0
  97. package/src/lib/worker-thread/lib/provide-task-services/internal/provide-task-services.ts +86 -0
  98. package/src/lib/worker-thread/lib/provide-task-services/internal/read-standard-input-from-main-thread.ts +58 -0
  99. package/src/lib/worker-thread/lib/provide-task-services/internal/resolve-paths-from-package-directory.ts +61 -0
  100. package/src/lib/worker-thread/lib/provide-task-services/internal/run-commands-in-package-directory.ts +24 -0
  101. package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-commands-with-bun.ts +545 -0
  102. package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-reported-process.ts +151 -0
  103. package/src/lib/worker-thread/shared/await-main-thread-answers.ts +76 -0
  104. package/src/lib/worker-thread/shared/decode-task-result-json.ts +114 -0
  105. package/src/lib/worker-thread/shared/define-task-body-record.ts +11 -0
  106. package/src/lib/worker-thread/shared/describe-issue-input.ts +52 -0
  107. package/src/lib/worker-thread/shared/plain-object.ts +8 -0
  108. package/src/lib/worker-thread/shared/provide-worker-state.ts +160 -0
  109. package/src/lib/worker-thread/shared/worker-thread-errors.ts +41 -0
  110. package/src/shared/attribute-reported-failures.ts +25 -0
  111. package/src/shared/claim-processes-to-report.ts +22 -0
  112. package/src/shared/command-errors.ts +44 -0
  113. package/src/shared/complete-discarded-calls-once-sent.ts +12 -0
  114. package/src/shared/cut-to-graphemes.ts +12 -0
  115. package/src/shared/environment-config-errors.ts +74 -0
  116. package/src/shared/error-fields.ts +13 -0
  117. package/src/shared/find-bun-loader-for-script.ts +32 -0
  118. package/src/shared/find-node-modules-package-directory.ts +35 -0
  119. package/src/shared/find-user-locations-in-stack-traces.ts +64 -0
  120. package/src/shared/format-error-lines.ts +53 -0
  121. package/src/shared/format-task-lines.ts +21 -0
  122. package/src/shared/hold-task-logs.ts +24 -0
  123. package/src/shared/invariant-errors.ts +28 -0
  124. package/src/shared/mark-task-worker-thread-contexts.ts +29 -0
  125. package/src/shared/module-graph-errors.ts +49 -0
  126. package/src/shared/name-config-key.ts +42 -0
  127. package/src/shared/names-from-package-json.ts +7 -0
  128. package/src/shared/print-to-standard-error.ts +48 -0
  129. package/src/shared/process-errors.ts +191 -0
  130. package/src/shared/read-environment-config.ts +239 -0
  131. package/src/shared/record-tasks-made.ts +10 -0
  132. package/src/shared/report-cause.ts +140 -0
  133. package/src/shared/report-errors.ts +121 -0
  134. package/src/shared/resolve-task-options.ts +20 -0
  135. package/src/shared/rpc.ts +161 -0
  136. package/src/shared/run-in-child-fiber-and-join.ts +16 -0
  137. package/src/shared/run-marker.ts +7 -0
  138. package/src/shared/save-standard-stream-flags.ts +57 -0
  139. package/src/shared/select-cause-reasons.ts +14 -0
  140. package/src/shared/signal-run-called-twice.ts +11 -0
  141. package/src/shared/standard-input-streams.ts +41 -0
  142. package/src/shared/stop-signals.ts +41 -0
  143. package/src/shared/task-errors.ts +224 -0
  144. package/src/shared/task-package.ts +5 -0
  145. package/src/shared/task-results.ts +55 -0
  146. package/src/shared/task-services.ts +30 -0
  147. package/src/shared/write-trace-file.ts +112 -0
@@ -0,0 +1,348 @@
1
+ import { Clock, Deferred, Effect, Exit, Fiber, FiberSet, Match, Option, Schema } from "effect";
2
+
3
+ import {
4
+ dieOnInvariantFailure,
5
+ InternalInvariantBroken,
6
+ } from "../../../../../shared/invariant-errors.ts";
7
+ import { TASK_TRACE_LABEL_PREFIX } from "../../../../../shared/names-from-package-json.ts";
8
+ import { printLine, printTaskLines } from "../../../../../shared/print-to-standard-error.ts";
9
+ import { WorkerExited } from "../../../../../shared/process-errors.ts";
10
+ import { TaskFiberResult } from "../../../../../shared/rpc.ts";
11
+ import {
12
+ TaskCleanupFailed,
13
+ ServedTaskFailed,
14
+ TaskBodyFailed,
15
+ } from "../../../../../shared/task-errors.ts";
16
+ import type { AwaitedTasksFailure, TaskRequestFailure } from "../../../../../shared/task-errors.ts";
17
+ import { TraceFile } from "../../../../../shared/write-trace-file.ts";
18
+ import { failRun, failTask } from "../../../shared/fail-run.ts";
19
+ import { ProcessGroups } from "../../../shared/provide-process-groups.ts";
20
+ import { StopState } from "../../../shared/provide-stop-state.ts";
21
+ import { FromWorker, WorkerEvent, WorkerPool } from "../../../shared/provide-worker-pool.ts";
22
+ import { reportFailure } from "../../../shared/report-task-failure.ts";
23
+ import { findAssignedWorkerId, TaskRunStatus } from "../shared/define-task-run-state.ts";
24
+ import type { makeTaskRunsState } from "../shared/define-task-run-state.ts";
25
+
26
+ const sendDependencyResultToWorker = Effect.fn("sendDependencyResultToWorker")(function* (
27
+ taskRunsState: Effect.Success<typeof makeTaskRunsState>,
28
+ requestDependencyTaskResult: (
29
+ dependencyTaskName: string,
30
+ requestingTaskName: string,
31
+ ) => Effect.Effect<Schema.Json | undefined, typeof TaskRequestFailure.Type>,
32
+ messageFromWorker: typeof FromWorker.cases.TaskFiberRequestedTask.Type,
33
+ ) {
34
+ yield* Effect.annotateCurrentSpan({
35
+ [`${TASK_TRACE_LABEL_PREFIX}.task`]: messageFromWorker.taskName,
36
+ [`${TASK_TRACE_LABEL_PREFIX}.wanted_task`]: messageFromWorker.dependencyTaskName,
37
+ });
38
+ const requestingTaskRun = yield* Effect.fromNullishOr(
39
+ taskRunsState.runsByTaskName.get(messageFromWorker.taskName),
40
+ ).pipe(dieOnInvariantFailure("task requests", "a task that requests another task has a run"));
41
+ const answer = yield* requestDependencyTaskResult(
42
+ messageFromWorker.dependencyTaskName,
43
+ messageFromWorker.taskName,
44
+ ).pipe(
45
+ Effect.exit,
46
+ Effect.raceFirst(
47
+ Deferred.await(requestingTaskRun.bodyFinishedDeferred).pipe(Effect.andThen(Effect.interrupt)),
48
+ ),
49
+ );
50
+ const workerId = findAssignedWorkerId(requestingTaskRun.status);
51
+ if (workerId === null) {
52
+ return;
53
+ }
54
+
55
+ yield* (yield* WorkerPool).answerDependencyRequest(messageFromWorker.dependencyRequestId, answer);
56
+ });
57
+
58
+ const isTaskBodyFailed = Schema.is(TaskBodyFailed);
59
+
60
+ const isWorkerExited = Schema.is(WorkerExited);
61
+
62
+ const isTaskCleanupFailed = Schema.is(TaskCleanupFailed);
63
+
64
+ const isTaskEnded = TaskRunStatus.isAnyOf(["Failed", "Cancelled"]);
65
+
66
+ const bodyEnding = (taskName: string, result: typeof TaskFiberResult.Type) =>
67
+ TaskFiberResult.match(result, {
68
+ Returned: (
69
+ returned,
70
+ ): Exit.Exit<
71
+ typeof returned,
72
+ TaskBodyFailed | TaskCleanupFailed | WorkerExited | typeof AwaitedTasksFailure.Type
73
+ > => Exit.succeed(returned),
74
+ Failed: (failed) =>
75
+ Exit.fail(
76
+ new TaskBodyFailed({
77
+ taskName,
78
+ failures: failed.failures,
79
+ details: failed.details,
80
+ failedCommandExitCode: failed.failedCommandExitCode,
81
+ }),
82
+ ),
83
+ AwaitedTasksFailed: (awaitedTasksFailed) => Exit.fail(awaitedTasksFailed.failure),
84
+ CleanupFailed: (cleanupFailure) =>
85
+ Exit.fail(
86
+ new TaskCleanupFailed({
87
+ taskName,
88
+ failures: cleanupFailure.failures,
89
+ details: cleanupFailure.details,
90
+ }),
91
+ ),
92
+ Interrupted: () => Exit.interrupt(),
93
+ });
94
+
95
+ const settleBodyOutcome = Effect.fn("settleBodyOutcome")(function* (
96
+ taskRunsState: Effect.Success<typeof makeTaskRunsState>,
97
+ taskName: string,
98
+ ending: Exit.Exit<
99
+ Effect.Success<ReturnType<typeof bodyEnding>>,
100
+ Effect.Error<ReturnType<typeof bodyEnding>>
101
+ >,
102
+ ) {
103
+ const taskRun = taskRunsState.runsByTaskName.get(taskName);
104
+ const isOutcomeAlreadySettled =
105
+ taskRun === undefined || Deferred.isDoneUnsafe(taskRun.bodyOutcomeDeferred);
106
+ if (isOutcomeAlreadySettled) {
107
+ return;
108
+ }
109
+ const failedAfterTaskEnded = isTaskEnded(taskRun.status)
110
+ ? Option.filter(Exit.findErrorOption(ending), isTaskBodyFailed)
111
+ : Option.none();
112
+ Deferred.doneUnsafe(taskRun.bodyOutcomeDeferred, ending);
113
+ if (Option.isSome(failedAfterTaskEnded)) {
114
+ yield* failTask(failedAfterTaskEnded.value, taskRunsState.markFailed);
115
+ }
116
+ });
117
+
118
+ const endTask = Effect.fn("endTask")(function* (
119
+ taskRunsState: Effect.Success<typeof makeTaskRunsState>,
120
+ taskName: string,
121
+ ending: Exit.Exit<
122
+ Effect.Success<ReturnType<typeof bodyEnding>>,
123
+ Effect.Error<ReturnType<typeof bodyEnding>>
124
+ >,
125
+ ) {
126
+ const taskRun = taskRunsState.runsByTaskName.get(taskName);
127
+ const isBodyAlreadyFinished =
128
+ taskRun === undefined || Deferred.isDoneUnsafe(taskRun.bodyFinishedDeferred);
129
+ if (isBodyAlreadyFinished) {
130
+ return;
131
+ }
132
+
133
+ Deferred.doneUnsafe(taskRun.bodyFinishedDeferred, Exit.void);
134
+ taskRunsState.refreshBodyClocks(taskName, yield* Clock.currentTimeMillis);
135
+ const servedBeforeEnding = taskRunsState.markClosed(taskName);
136
+ if (servedBeforeEnding) {
137
+ yield* printTaskLines("Info", taskName, "closed");
138
+ }
139
+ if (!Deferred.isDoneUnsafe(taskRun.bodyOutcomeDeferred)) {
140
+ return yield* settleBodyOutcome(taskRunsState, taskName, ending);
141
+ }
142
+ const failure = Exit.findErrorOption(ending);
143
+ if (Option.isNone(failure)) {
144
+ return;
145
+ }
146
+ const failureEndsRun = isTaskCleanupFailed(failure.value) || isWorkerExited(failure.value);
147
+ if (failureEndsRun) {
148
+ yield* (yield* StopState).recordFirstFailure(failure.value);
149
+ return yield* failTask(failure.value, taskRunsState.markFailed);
150
+ }
151
+ if (!servedBeforeEnding) {
152
+ return;
153
+ }
154
+
155
+ yield* failRun(
156
+ new ServedTaskFailed({
157
+ taskName,
158
+ failures: isTaskBodyFailed(failure.value)
159
+ ? failure.value.failures
160
+ : reportFailure(failure.value),
161
+ details: isTaskBodyFailed(failure.value) ? failure.value.details : undefined,
162
+ }),
163
+ taskRunsState.markFailed,
164
+ );
165
+ });
166
+
167
+ const endTasksOfExitedWorker = Effect.fn("endTasksOfExitedWorker")(function* (
168
+ taskRunsState: Effect.Success<typeof makeTaskRunsState>,
169
+ workerEvent: typeof WorkerEvent.cases.ExitedWithCode.Type,
170
+ ) {
171
+ const processGroups = yield* ProcessGroups;
172
+ const unfinished = [...taskRunsState.runsByTaskName].filter(
173
+ ([, taskRun]) =>
174
+ findAssignedWorkerId(taskRun.status) === workerEvent.workerId &&
175
+ !Deferred.isDoneUnsafe(taskRun.bodyFinishedDeferred),
176
+ );
177
+ if (!workerEvent.terminationRequested) {
178
+ for (const [taskName] of unfinished) {
179
+ yield* processGroups.killForTask(taskName);
180
+ }
181
+ }
182
+ const workerExit = {
183
+ workerId: workerEvent.workerId,
184
+ exitCode: workerEvent.exitCode,
185
+ uncaughtError: workerEvent.uncaughtError,
186
+ };
187
+ const exitedUnexpectedlyWithoutTasks =
188
+ !workerEvent.terminationRequested && unfinished.length === 0;
189
+ if (exitedUnexpectedlyWithoutTasks) {
190
+ return yield* failRun(
191
+ new WorkerExited({ taskName: null, ...workerExit }),
192
+ taskRunsState.markFailed,
193
+ );
194
+ }
195
+ for (const [taskName] of unfinished) {
196
+ yield* endTask(
197
+ taskRunsState,
198
+ taskName,
199
+ workerEvent.terminationRequested
200
+ ? Exit.interrupt()
201
+ : Exit.fail(new WorkerExited({ taskName, ...workerExit })),
202
+ );
203
+ }
204
+ });
205
+
206
+ const onMessageAboutTask = Effect.fn("onMessageAboutTask")(function* (
207
+ taskRunsState: Effect.Success<typeof makeTaskRunsState>,
208
+ requestDependencyTaskResult: Parameters<typeof sendDependencyResultToWorker>[1],
209
+ messageFromWorker: Exclude<
210
+ typeof FromWorker.Type,
211
+ | typeof FromWorker.cases.StandardErrorLineRequested.Type
212
+ | typeof FromWorker.cases.TraceWriteFailed.Type
213
+ >,
214
+ ) {
215
+ const processGroups = yield* ProcessGroups;
216
+ const clock = yield* Clock.Clock;
217
+ const taskRun = yield* Effect.fromNullishOr(
218
+ taskRunsState.runsByTaskName.get(messageFromWorker.taskName),
219
+ ).pipe(dieOnInvariantFailure("worker protocol", "messages refer to a started task"));
220
+ yield* Match.value(messageFromWorker).pipe(
221
+ Match.tagsExhaustive({
222
+ TaskFiberStartedProcess: (started) =>
223
+ processGroups
224
+ .record(started.processId, started.taskName, started.startTime)
225
+ .pipe(
226
+ Effect.andThen(
227
+ WorkerPool.use((workerPool) =>
228
+ workerPool.acknowledgeProcessRecorded(started.recordId),
229
+ ),
230
+ ),
231
+ ),
232
+ TaskFiberProcessLeaderExited: (exited) =>
233
+ processGroups.recordLeaderExit(exited.processId, exited.latestMemberStartTime),
234
+ TaskFiberClaimingProcessReports: (claiming) =>
235
+ processGroups
236
+ .claimProcessesToReport(claiming.processes)
237
+ .pipe(
238
+ Effect.flatMap((claimed) =>
239
+ WorkerPool.use((workerPool) =>
240
+ workerPool.answerReportClaim(claiming.claimId, claimed),
241
+ ),
242
+ ),
243
+ ),
244
+ TaskFiberLoading: () => Deferred.done(taskRun.bodyLoadingDeferred, Exit.void),
245
+ TaskFiberStarted: () => {
246
+ taskRun.stepBeforeBody = null;
247
+ return Deferred.complete(
248
+ taskRun.bodyStartedAtMillisecondsDeferred,
249
+ Clock.currentTimeMillis,
250
+ );
251
+ },
252
+ TaskFiberStartedCleanUp: () => Deferred.done(taskRun.bodyCleanupStartedDeferred, Exit.void),
253
+ TaskFiberRequestedTask: (requested) => {
254
+ taskRun.bodyRequestedTaskNames.add(requested.dependencyTaskName);
255
+ return FiberSet.run(
256
+ taskRunsState.taskFibers,
257
+ sendDependencyResultToWorker(taskRunsState, requestDependencyTaskResult, requested),
258
+ { startImmediately: false },
259
+ ).pipe(
260
+ Effect.flatMap((requestFiber) =>
261
+ Effect.sync(() => {
262
+ taskRun.requestFibersByDependencyRequestId.set(
263
+ requested.dependencyRequestId,
264
+ requestFiber,
265
+ );
266
+ requestFiber.addObserver(() => {
267
+ taskRun.requestFibersByDependencyRequestId.delete(requested.dependencyRequestId);
268
+ taskRunsState.refreshBodyClocks(
269
+ requested.taskName,
270
+ clock.currentTimeMillisUnsafe(),
271
+ );
272
+ });
273
+ }),
274
+ ),
275
+ );
276
+ },
277
+ TaskFiberRequestedStandardInput: (requested) =>
278
+ taskRunsState.rootTaskStandardInput.answerNextRead((read) =>
279
+ WorkerPool.use((workerPool) =>
280
+ workerPool.answerStandardInputRead(requested.readId, read),
281
+ ),
282
+ ),
283
+ TaskFiberReceivedAwaitedFailure: (received) =>
284
+ Effect.sync(() => {
285
+ taskRunsState.recordFailureReachedAwaitingTask(received.awaitedTaskName);
286
+ }),
287
+ TaskFiberCancelledRequest: (cancelled) => {
288
+ const requestFiber = taskRun.requestFibersByDependencyRequestId.get(
289
+ cancelled.dependencyRequestId,
290
+ );
291
+ return requestFiber === undefined ? Effect.void : Fiber.interrupt(requestFiber);
292
+ },
293
+ TaskFiberFailedBeforeCleanup: (failed) =>
294
+ Deferred.done(taskRun.bodyFailedBeforeCleanupDeferred, Exit.void).pipe(
295
+ Effect.andThen(Effect.exit(bodyEnding(failed.taskName, failed.failure))),
296
+ Effect.flatMap((ending) => settleBodyOutcome(taskRunsState, failed.taskName, ending)),
297
+ ),
298
+ TaskFiberServed: (served) =>
299
+ Effect.sync(() => {
300
+ taskRunsState.markServing(served.taskName);
301
+ Deferred.doneUnsafe(taskRun.bodyOutcomeDeferred, Exit.succeed(served));
302
+ }),
303
+ TaskFiberEnded: (ended) => {
304
+ taskRun.logMessages = [...ended.logs];
305
+ return Effect.exit(bodyEnding(ended.taskName, ended.result)).pipe(
306
+ Effect.flatMap((ending) => endTask(taskRunsState, messageFromWorker.taskName, ending)),
307
+ );
308
+ },
309
+ }),
310
+ );
311
+ taskRunsState.refreshBodyClocks(messageFromWorker.taskName, yield* Clock.currentTimeMillis);
312
+ });
313
+
314
+ export const handleWorkerEvent = Effect.fn("handleWorkerEvent")(function* (
315
+ taskRunsState: Effect.Success<typeof makeTaskRunsState>,
316
+ requestDependencyTaskResult: Parameters<typeof sendDependencyResultToWorker>[1],
317
+ workerEvent: typeof WorkerEvent.Type,
318
+ ) {
319
+ yield* Effect.annotateCurrentSpan(`${TASK_TRACE_LABEL_PREFIX}.worker`, workerEvent.workerId);
320
+ if (WorkerEvent.guards.ExitedWithCode(workerEvent)) {
321
+ return yield* endTasksOfExitedWorker(taskRunsState, workerEvent);
322
+ }
323
+ if (WorkerEvent.guards.MessageUnreadable(workerEvent)) {
324
+ return yield* Effect.die(
325
+ new InternalInvariantBroken({
326
+ area: "worker protocol",
327
+ invariant: "messages match the worker protocol",
328
+ cause: workerEvent.reason,
329
+ }),
330
+ );
331
+ }
332
+ const messageFromWorker = workerEvent.message;
333
+ if (FromWorker.guards.StandardErrorLineRequested(messageFromWorker)) {
334
+ return yield* printLine(messageFromWorker.logLevel, messageFromWorker.text);
335
+ }
336
+ if (FromWorker.guards.TraceWriteFailed(messageFromWorker)) {
337
+ return yield* TraceFile.use((traceFile) =>
338
+ traceFile.markIncomplete(
339
+ `worker ${workerEvent.workerId} reported that ${messageFromWorker.reason}`,
340
+ ),
341
+ );
342
+ }
343
+ yield* Effect.annotateCurrentSpan({
344
+ [`${TASK_TRACE_LABEL_PREFIX}.message`]: messageFromWorker._tag,
345
+ [`${TASK_TRACE_LABEL_PREFIX}.task`]: messageFromWorker.taskName,
346
+ });
347
+ yield* onMessageAboutTask(taskRunsState, requestDependencyTaskResult, messageFromWorker);
348
+ });
@@ -0,0 +1,207 @@
1
+ import { Context, Deferred, Effect, Exit, FiberSet } from "effect";
2
+ import type { FileSystem, Schema } from "effect";
3
+
4
+ import { printLine, printTaskLines } from "../../../../../shared/print-to-standard-error.ts";
5
+ import type { TaskRequestFailure } from "../../../../../shared/task-errors.ts";
6
+ import { TASK_PACKAGE } from "../../../../../shared/task-package.ts";
7
+ import type { TraceFile } from "../../../../../shared/write-trace-file.ts";
8
+ import { failRunOnFailure, failTask } from "../../../shared/fail-run.ts";
9
+ import { ServingTaskRequestsItself } from "../../../shared/main-thread-errors.ts";
10
+ import type { ProcessGroups } from "../../../shared/provide-process-groups.ts";
11
+ import type { RunRootTaskConfig } from "../../../shared/provide-run-root-task-config.ts";
12
+ import { StopState } from "../../../shared/provide-stop-state.ts";
13
+ import type { WorkerEvent, WorkerPool } from "../../../shared/provide-worker-pool.ts";
14
+ import {
15
+ runRequestedTask,
16
+ runRequestedDependencyTask,
17
+ } from "../lib/run-requested-task-once/index.ts";
18
+ import { makeTaskRunsState, TaskRunStatus } from "../shared/define-task-run-state.ts";
19
+ import { stopTask } from "../shared/stop-task.ts";
20
+ import { handleWorkerEvent } from "./handle-worker-events.ts";
21
+
22
+ export const makeTaskRuns = Effect.gen(function* () {
23
+ const taskExecutionServices = yield* Effect.context<
24
+ FileSystem.FileSystem | ProcessGroups | RunRootTaskConfig | StopState | TraceFile | WorkerPool
25
+ >();
26
+ const taskRunsState = yield* makeTaskRunsState;
27
+ const resultDeferreds: Parameters<typeof runRequestedTask>[1] = new Map();
28
+ const {
29
+ discoveredTasksByName,
30
+ taskNamesWhoseFailureReachedAnAwaitingTask,
31
+ runsByTaskName: taskRunsByName,
32
+ markFailed,
33
+ } = taskRunsState;
34
+ const requestDependencyTaskResult = (
35
+ dependencyTaskName: string,
36
+ requestingTaskName: string,
37
+ ): Effect.Effect<Schema.Json | undefined, typeof TaskRequestFailure.Type> =>
38
+ runRequestedDependencyTask(
39
+ taskRunsState,
40
+ resultDeferreds,
41
+ dependencyTaskName,
42
+ requestingTaskName,
43
+ ).pipe(Effect.provide(taskExecutionServices));
44
+ return {
45
+ registerDiscoveredTasks: (
46
+ entries: ReadonlyMap<string, Parameters<typeof discoveredTasksByName.set>[1]>,
47
+ ) =>
48
+ Effect.sync(() => {
49
+ for (const [taskName, discoveredTask] of entries) {
50
+ discoveredTasksByName.set(taskName, discoveredTask);
51
+ }
52
+ }),
53
+ requestRootTaskResult: (taskName: string) =>
54
+ runRequestedTask(taskRunsState, resultDeferreds, taskName).pipe(
55
+ Effect.tapError(() =>
56
+ Effect.sync(() => taskNamesWhoseFailureReachedAnAwaitingTask.add(taskName)),
57
+ ),
58
+ Effect.provide(taskExecutionServices),
59
+ ),
60
+ requestDependencyTaskResult,
61
+ handleWorkerEvent: (event: typeof WorkerEvent.Type) =>
62
+ handleWorkerEvent(taskRunsState, requestDependencyTaskResult, event).pipe(
63
+ failRunOnFailure(markFailed),
64
+ Effect.provide(taskExecutionServices),
65
+ ),
66
+ stopAll: Effect.gen(function* () {
67
+ const stopState = yield* StopState;
68
+ const failedBeforeStopping = yield* stopState.begin;
69
+ const unfinishedTaskNames = new Set(
70
+ [...taskRunsByName]
71
+ .filter(
72
+ ([, taskRun]) =>
73
+ TaskRunStatus.guards.Preparing(taskRun.status) ||
74
+ TaskRunStatus.guards.Running(taskRun.status),
75
+ )
76
+ .map(([taskName]) => taskName),
77
+ );
78
+ yield* FiberSet.clear(taskRunsState.taskFibers);
79
+ const workerWatches = yield* FiberSet.make<void, never>();
80
+ const unorderedStops = Array.from(taskRunsByName, ([taskName, taskRun]) => ({
81
+ taskName,
82
+ isServing: TaskRunStatus.guards.Serving(taskRun.status),
83
+ isCompleted: TaskRunStatus.guards.Passed(taskRun.status),
84
+ requestedTaskNames: taskRun.bodyRequestedTaskNames,
85
+ stopped: Deferred.makeUnsafe<void>(),
86
+ }));
87
+ const findTasksRequestedBy = (requester: (typeof unorderedStops)[number]) =>
88
+ unorderedStops.filter((stop) => requester.requestedTaskNames.has(stop.taskName));
89
+ const findServingTasksReachedBy = (requester: (typeof unorderedStops)[number]) => {
90
+ const reached = new Set<(typeof unorderedStops)[number]>();
91
+ const reach = (stop: (typeof unorderedStops)[number]) => {
92
+ for (const requested of findTasksRequestedBy(stop)) {
93
+ if (!reached.has(requested)) {
94
+ reached.add(requested);
95
+ if (requested.isCompleted) {
96
+ reach(requested);
97
+ }
98
+ }
99
+ }
100
+ };
101
+ reach(requester);
102
+ return [...reached].filter((stop) => stop.isServing);
103
+ };
104
+ const requestedBeforeRequesters = new Array<(typeof unorderedStops)[number]>();
105
+ const visitedStops = new Set<(typeof unorderedStops)[number]>();
106
+ const requestPath = new Array<(typeof unorderedStops)[number]>();
107
+ const servingCycles = new Array<ServingTaskRequestsItself>();
108
+ const visit = (stop: (typeof unorderedStops)[number]) => {
109
+ if (visitedStops.has(stop)) {
110
+ return;
111
+ }
112
+ visitedStops.add(stop);
113
+ requestPath.push(stop);
114
+ for (const requested of findServingTasksReachedBy(stop)) {
115
+ if (requestPath.includes(requested)) {
116
+ servingCycles.push(
117
+ new ServingTaskRequestsItself({
118
+ taskName: stop.taskName,
119
+ cycle: [stop, ...requestPath.slice(requestPath.indexOf(requested))].map(
120
+ (cycleStop) => cycleStop.taskName,
121
+ ),
122
+ }),
123
+ );
124
+ }
125
+ visit(requested);
126
+ }
127
+ requestPath.pop();
128
+ requestedBeforeRequesters.push(stop);
129
+ };
130
+ for (const stop of unorderedStops) {
131
+ visit(stop);
132
+ }
133
+ yield* Effect.forEach(
134
+ servingCycles,
135
+ (servingCycle) =>
136
+ stopState
137
+ .recordFirstFailure(servingCycle)
138
+ .pipe(Effect.andThen(failTask(servingCycle, markFailed))),
139
+ { discard: true },
140
+ );
141
+ const stops = requestedBeforeRequesters.toReversed();
142
+ yield* Effect.forEach(
143
+ stops,
144
+ (stop, index) =>
145
+ Effect.forEach(
146
+ stops
147
+ .slice(0, index)
148
+ .filter((requester) => findServingTasksReachedBy(requester).includes(stop)),
149
+ (requester) => Deferred.await(requester.stopped),
150
+ { discard: true },
151
+ ).pipe(
152
+ Effect.andThen(
153
+ stopTask(taskRunsState, stop.taskName, (watch) =>
154
+ FiberSet.run(workerWatches, watch).pipe(Effect.asVoid),
155
+ ),
156
+ ),
157
+ failRunOnFailure(markFailed),
158
+ Effect.ensuring(
159
+ unfinishedTaskNames.has(stop.taskName)
160
+ ? printTaskLines("Info", stop.taskName, "stopped: the run ended before it finished")
161
+ : Effect.void,
162
+ ),
163
+ Effect.ensuring(Deferred.done(stop.stopped, Exit.void)),
164
+ ),
165
+ { concurrency: failedBeforeStopping ? "unbounded" : 1, discard: true },
166
+ );
167
+ }).pipe(Effect.scoped, Effect.provide(taskExecutionServices)),
168
+ awaitServingTaskClosure: (taskName: string) =>
169
+ Effect.gen(function* () {
170
+ const taskRun = taskRunsByName.get(taskName);
171
+ const isNotServing = taskRun === undefined || !TaskRunStatus.guards.Serving(taskRun.status);
172
+ if (isNotServing) {
173
+ return;
174
+ }
175
+ yield* printLine("Info", `${taskName} is serving`);
176
+ yield* Deferred.await(taskRun.bodyFinishedDeferred);
177
+ }),
178
+ markFailed,
179
+ counts: Effect.sync(() => {
180
+ const taskRuns = [...taskRunsByName];
181
+ return {
182
+ total: taskRunsByName.size,
183
+ failedTaskNames: [...discoveredTasksByName.keys()].filter((taskName) => {
184
+ const taskRun = taskRunsByName.get(taskName);
185
+ return (
186
+ taskRun !== undefined &&
187
+ TaskRunStatus.guards.Failed(taskRun.status) &&
188
+ !taskRun.status.causedByAwaitedTasks
189
+ );
190
+ }),
191
+ stopped: taskRuns.filter(([, taskRun]) => TaskRunStatus.guards.Cancelled(taskRun.status))
192
+ .length,
193
+ unawaitedFailedTaskNames: taskRuns
194
+ .filter(
195
+ ([taskName, taskRun]) =>
196
+ TaskRunStatus.guards.Failed(taskRun.status) &&
197
+ !taskNamesWhoseFailureReachedAnAwaitingTask.has(taskName),
198
+ )
199
+ .map(([taskName]) => taskName),
200
+ };
201
+ }),
202
+ };
203
+ });
204
+
205
+ export class TaskRuns extends Context.Service<TaskRuns, Effect.Success<typeof makeTaskRuns>>()(
206
+ `${TASK_PACKAGE.name}/TaskRuns`,
207
+ ) {}
@@ -0,0 +1 @@
1
+ export { runRequestedDependencyTask, runRequestedTask } from "./internal/run-requested-tasks.ts";
@@ -0,0 +1,29 @@
1
+ import { Clock, Duration, Effect, Option } from "effect";
2
+
3
+ const SHORTEST_RECHECK_SHARE_OF_LIMIT = 0.05;
4
+
5
+ export const awaitWithinCountedLimit = <Success, Failure, Services>(
6
+ awaiting: Effect.Effect<Success, Failure, Services>,
7
+ limitMilliseconds: number,
8
+ countMilliseconds: (nowMilliseconds: number) => Effect.Effect<number>,
9
+ ): Effect.Effect<Option.Option<Success>, Failure, Services> =>
10
+ Effect.gen(function* () {
11
+ const remainingMilliseconds =
12
+ limitMilliseconds - (yield* countMilliseconds(yield* Clock.currentTimeMillis));
13
+ if (remainingMilliseconds <= 0) {
14
+ return Option.none();
15
+ }
16
+ const exit = yield* Effect.exit(awaiting).pipe(
17
+ Effect.timeoutOption(
18
+ Duration.millis(
19
+ Math.max(remainingMilliseconds, limitMilliseconds * SHORTEST_RECHECK_SHARE_OF_LIMIT),
20
+ ),
21
+ ),
22
+ );
23
+ if (Option.isNone(exit)) {
24
+ return yield* awaitWithinCountedLimit(awaiting, limitMilliseconds, countMilliseconds);
25
+ }
26
+ const arrivedPastLimit =
27
+ (yield* countMilliseconds(yield* Clock.currentTimeMillis)) > limitMilliseconds;
28
+ return arrivedPastLimit ? Option.none() : Option.some(yield* exit.value);
29
+ });