@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.
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,272 @@
1
+ import {
2
+ Cause,
3
+ Clock,
4
+ Deferred,
5
+ Duration,
6
+ Effect,
7
+ Exit,
8
+ FiberSet,
9
+ Option,
10
+ Ref,
11
+ Schema,
12
+ } from "effect";
13
+
14
+ import {
15
+ formatTaskPassed,
16
+ formatTaskServing,
17
+ } from "../../../../../../../shared/format-task-lines.ts";
18
+ import { dieOnInvariantFailure } from "../../../../../../../shared/invariant-errors.ts";
19
+ import { TASK_TRACE_LABEL_PREFIX } from "../../../../../../../shared/names-from-package-json.ts";
20
+ import { printTaskLines } from "../../../../../../../shared/print-to-standard-error.ts";
21
+ import { WorkerExited } from "../../../../../../../shared/process-errors.ts";
22
+ import {
23
+ TaskBodyFailed,
24
+ TaskSetupTimedOut,
25
+ TaskTimedOut,
26
+ } from "../../../../../../../shared/task-errors.ts";
27
+ import { failRun, failTask } from "../../../../../shared/fail-run.ts";
28
+ import { StopState } from "../../../../../shared/provide-stop-state.ts";
29
+ import { FromWorker, WorkerPool } from "../../../../../shared/provide-worker-pool.ts";
30
+ import { reportFailure, reportTaskFailure } from "../../../../../shared/report-task-failure.ts";
31
+ import {
32
+ formatTaskRunStatusForTrace,
33
+ findAssignedWorkerId,
34
+ findTaskNamesOccupyingWorker,
35
+ formatWaitingForWorker,
36
+ } from "../../../shared/define-task-run-state.ts";
37
+ import type { makeTaskRunsState } from "../../../shared/define-task-run-state.ts";
38
+ import type { makeStopwatch } from "../../../shared/make-stopwatch.ts";
39
+ import { stopTask } from "../../../shared/stop-task.ts";
40
+ import { awaitWithinCountedLimit } from "./await-within-counted-limit.ts";
41
+ import { makeTaskSetupClock } from "./make-task-setup-clock.ts";
42
+
43
+ const isTaskTimedOut = Schema.is(TaskTimedOut);
44
+
45
+ const STILL_BEFORE_BODY_NOTICE_SECONDS = 2;
46
+
47
+ const STILL_BEFORE_BODY_NOTICE_AFTER = Duration.seconds(STILL_BEFORE_BODY_NOTICE_SECONDS);
48
+
49
+ const isTaskSetupTimedOut = Schema.is(TaskSetupTimedOut);
50
+
51
+ const taskTimedOut = (taskName: string, timeoutDuration: Duration.Duration) =>
52
+ new TaskTimedOut({ taskName, timeoutMilliseconds: Duration.toMillis(timeoutDuration) });
53
+
54
+ const awaitWithinTimeout = <Success, Failure, Services>(
55
+ awaiting: Effect.Effect<Success, Failure, Services>,
56
+ bodyWaitingOnRequestedTasks: ReturnType<typeof makeStopwatch>,
57
+ bodyStartedAtMilliseconds: number,
58
+ timeoutDuration: Duration.Duration,
59
+ ) =>
60
+ awaitWithinCountedLimit(awaiting, Duration.toMillis(timeoutDuration), (nowMilliseconds) =>
61
+ Effect.succeed(
62
+ nowMilliseconds -
63
+ bodyStartedAtMilliseconds -
64
+ bodyWaitingOnRequestedTasks.readMilliseconds(nowMilliseconds),
65
+ ),
66
+ );
67
+
68
+ const failRunWhenCleanupOutlastsTimeout = Effect.fn("failRunWhenCleanupOutlastsTimeout")(function* (
69
+ taskRunsState: Effect.Success<typeof makeTaskRunsState>,
70
+ taskName: string,
71
+ timeoutDuration: Duration.Duration,
72
+ ) {
73
+ const taskRun = yield* Effect.fromNullishOr(taskRunsState.runsByTaskName.get(taskName)).pipe(
74
+ dieOnInvariantFailure("task timeouts", "a task whose cleanup is timed has a run"),
75
+ );
76
+ const bodyStartedAtMilliseconds = yield* Deferred.await(
77
+ taskRun.bodyStartedAtMillisecondsDeferred,
78
+ ).pipe(Effect.raceFirst(Deferred.await(taskRun.bodyFinishedDeferred).pipe(Effect.as(null))));
79
+ if (bodyStartedAtMilliseconds === null) {
80
+ return;
81
+ }
82
+ const bodyFinished = yield* awaitWithinTimeout(
83
+ Deferred.await(taskRun.bodyFinishedDeferred),
84
+ taskRun.bodyWaitingOnRequestedTasks,
85
+ bodyStartedAtMilliseconds,
86
+ timeoutDuration,
87
+ );
88
+ if (Option.isNone(bodyFinished)) {
89
+ yield* failRun(taskTimedOut(taskName, timeoutDuration), taskRunsState.markFailed);
90
+ }
91
+ });
92
+
93
+ const endsRunDirectly = Schema.is(Schema.Union([TaskSetupTimedOut, WorkerExited]));
94
+
95
+ export const executeTask = Effect.fn("executeTaskFiber")(
96
+ function* (taskRunsState: Effect.Success<typeof makeTaskRunsState>, taskName: string) {
97
+ yield* Effect.annotateCurrentSpan(`${TASK_TRACE_LABEL_PREFIX}.task`, taskName);
98
+ yield* Effect.logInfo("task started").pipe(
99
+ Effect.annotateLogs(`${TASK_TRACE_LABEL_PREFIX}.task`, taskName),
100
+ );
101
+ const discoveredTask = yield* Effect.fromNullishOr(
102
+ taskRunsState.discoveredTasksByName.get(taskName),
103
+ ).pipe(dieOnInvariantFailure("task execution", "the requested task is registered and started"));
104
+ const taskRun = yield* Effect.fromNullishOr(taskRunsState.runsByTaskName.get(taskName)).pipe(
105
+ dieOnInvariantFailure("task execution", "the requested task is registered and started"),
106
+ );
107
+
108
+ const span = yield* Effect.currentSpan.pipe(
109
+ dieOnInvariantFailure("task execution", "the task has a tracing span"),
110
+ );
111
+ const setupClock = yield* makeTaskSetupClock(
112
+ taskRunsState,
113
+ taskName,
114
+ discoveredTask.cleanupDuration,
115
+ );
116
+ const waitingForWorkerIdRef = yield* Ref.make<number | null>(null);
117
+ const describeStepBeforeBody = () => {
118
+ const waitingForWorkerId = Ref.getUnsafe(waitingForWorkerIdRef);
119
+ if (waitingForWorkerId === null) {
120
+ return taskRun.stepBeforeBody;
121
+ }
122
+ if (Deferred.isDoneUnsafe(taskRun.bodyStartedAtMillisecondsDeferred)) {
123
+ return null;
124
+ }
125
+ const occupyingTaskNames = findTaskNamesOccupyingWorker(
126
+ taskRunsState.runsByTaskName,
127
+ waitingForWorkerId,
128
+ ).filter((occupyingTaskName) => occupyingTaskName !== taskName);
129
+ const [longestOccupyingTaskName, ...otherTaskNames] = occupyingTaskNames;
130
+ if (longestOccupyingTaskName !== undefined) {
131
+ return formatWaitingForWorker(waitingForWorkerId, longestOccupyingTaskName, otherTaskNames);
132
+ }
133
+ return Deferred.isDoneUnsafe(taskRun.bodyLoadingDeferred)
134
+ ? `loading ${discoveredTask.filePath}`
135
+ : `waiting for worker ${waitingForWorkerId} to take it`;
136
+ };
137
+ const awaitBodyStart = Effect.gen(function* () {
138
+ yield* printTaskLines("Info", taskName, "started");
139
+
140
+ const workerPool = yield* WorkerPool;
141
+ taskRun.stepBeforeBody = "starting a worker";
142
+ const workerId = yield* workerPool
143
+ .selectWorkerIdForTask(taskRunsState.readWorkerLoad, (selectedWorkerId) =>
144
+ taskRunsState.recordWorkerAssignment(taskName, selectedWorkerId),
145
+ )
146
+ .pipe(
147
+ Effect.mapError(
148
+ (failure) => new TaskBodyFailed({ taskName, failures: reportFailure(failure) }),
149
+ ),
150
+ );
151
+ yield* setupClock.recordWorkerAssignment(workerId);
152
+ yield* workerPool.startTaskFiber(workerId, {
153
+ taskName,
154
+ taskFilePath: discoveredTask.filePath,
155
+ taskPackageDirectoryPath: discoveredTask.packageJsonDirectoryPath,
156
+ cleanupMilliseconds: Duration.toMillis(discoveredTask.cleanupDuration),
157
+ traceId: span.traceId,
158
+ spanId: span.spanId,
159
+ standardInput: taskRunsState.rootTaskStandardInput.standardInput,
160
+ });
161
+ yield* Ref.set(waitingForWorkerIdRef, workerId);
162
+ const awaitBodyStarted = Deferred.await(taskRun.bodyStartedAtMillisecondsDeferred).pipe(
163
+ Effect.raceFirst(Deferred.await(taskRun.bodyFinishedDeferred)),
164
+ );
165
+ const startedBeforeNotice = yield* awaitBodyStarted.pipe(
166
+ Effect.timeoutOption(STILL_BEFORE_BODY_NOTICE_AFTER),
167
+ );
168
+ const stepAtNotice = describeStepBeforeBody();
169
+ const isStillBeforeBodyAtNotice = Option.isNone(startedBeforeNotice) && stepAtNotice !== null;
170
+ if (isStillBeforeBodyAtNotice) {
171
+ yield* printTaskLines("Info", taskName, stepAtNotice);
172
+ }
173
+ yield* awaitBodyStarted;
174
+ }).pipe((setup) =>
175
+ setupClock.limit(setup, () => describeStepBeforeBody() ?? "waiting for its body to start"),
176
+ );
177
+ const timeoutDuration = discoveredTask.timeoutDuration;
178
+ const awaitBodyOutcome = Effect.gen(function* () {
179
+ yield* awaitBodyStart;
180
+ const awaitOutcome = Deferred.await(taskRun.bodyOutcomeDeferred);
181
+ const bodyStartedAtMilliseconds = yield* Deferred.poll(
182
+ taskRun.bodyStartedAtMillisecondsDeferred,
183
+ ).pipe(Effect.flatMap(Effect.transposeOption));
184
+ const hasNoBodyTimeout = timeoutDuration === null || Option.isNone(bodyStartedAtMilliseconds);
185
+ if (hasNoBodyTimeout) {
186
+ return yield* awaitOutcome;
187
+ }
188
+ const outcome = yield* awaitWithinTimeout(
189
+ awaitOutcome,
190
+ taskRun.bodyWaitingOnRequestedTasks,
191
+ bodyStartedAtMilliseconds.value,
192
+ timeoutDuration,
193
+ );
194
+ if (Option.isNone(outcome)) {
195
+ const timedOut = taskTimedOut(taskName, timeoutDuration);
196
+ yield* failTask(timedOut, taskRunsState.markFailed);
197
+ yield* stopTask(taskRunsState, taskName, (watch) =>
198
+ FiberSet.run(taskRunsState.taskFibers, watch).pipe(Effect.asVoid),
199
+ );
200
+ return yield* timedOut;
201
+ }
202
+ return outcome.value;
203
+ });
204
+ const outcome = yield* Effect.uninterruptibleMask((restore) =>
205
+ Effect.gen(function* () {
206
+ const outcomeExit = yield* Effect.exit(restore(awaitBodyOutcome));
207
+ const cleanupMayOutlastTimeout =
208
+ Exit.isFailure(outcomeExit) &&
209
+ timeoutDuration !== null &&
210
+ findAssignedWorkerId(taskRun.status) !== null &&
211
+ !Deferred.isDoneUnsafe(taskRun.bodyFinishedDeferred) &&
212
+ !Option.exists(
213
+ Exit.findErrorOption(outcomeExit),
214
+ (failure) => isTaskTimedOut(failure) || isTaskSetupTimedOut(failure),
215
+ );
216
+ if (cleanupMayOutlastTimeout) {
217
+ yield* FiberSet.run(
218
+ taskRunsState.taskFibers,
219
+ failRunWhenCleanupOutlastsTimeout(taskRunsState, taskName, timeoutDuration),
220
+ );
221
+ }
222
+ return yield* outcomeExit;
223
+ }),
224
+ );
225
+ const taskBodyStartedAtMilliseconds = yield* Deferred.await(
226
+ taskRun.bodyStartedAtMillisecondsDeferred,
227
+ );
228
+ if (FromWorker.guards.TaskFiberServed(outcome)) {
229
+ yield* printTaskLines(
230
+ "Info",
231
+ taskName,
232
+ formatTaskServing((yield* Clock.currentTimeMillis) - taskBodyStartedAtMilliseconds),
233
+ );
234
+ return outcome.value;
235
+ }
236
+
237
+ yield* taskRunsState.markPassed(taskName);
238
+ yield* printTaskLines("Info", taskName, formatTaskPassed(outcome.elapsedMilliseconds));
239
+ return outcome.value;
240
+ },
241
+ (effect, taskRunsState, taskName) =>
242
+ effect.pipe(
243
+ Effect.scoped,
244
+ Effect.onExit((taskExit) =>
245
+ Effect.gen(function* () {
246
+ const taskRun = taskRunsState.runsByTaskName.get(taskName);
247
+ const isTaskRunMissing = taskRun === undefined;
248
+ if (isTaskRunMissing) {
249
+ return;
250
+ }
251
+
252
+ taskRunsState.markEndedFromExit(taskName, taskExit);
253
+ yield* Effect.annotateCurrentSpan(
254
+ `${TASK_TRACE_LABEL_PREFIX}.state`,
255
+ formatTaskRunStatusForTrace(taskRun.status),
256
+ );
257
+ const failure = Exit.findErrorOption(taskExit);
258
+ const failedUnreportedWithMoreThanInterrupts =
259
+ Exit.isFailure(taskExit) &&
260
+ !Cause.hasInterruptsOnly(taskExit.cause) &&
261
+ !Option.exists(failure, isTaskTimedOut);
262
+ if (failedUnreportedWithMoreThanInterrupts) {
263
+ yield* reportTaskFailure(taskName, taskExit.cause);
264
+ }
265
+ const failureEndsRun = Option.isSome(failure) && endsRunDirectly(failure.value);
266
+ if (failureEndsRun) {
267
+ yield* (yield* StopState).recordFirstFailure(failure.value);
268
+ }
269
+ }),
270
+ ),
271
+ ),
272
+ );
@@ -0,0 +1,129 @@
1
+ import { Cause, Clock, Duration, Effect, Exit, Fiber, Option, Ref } from "effect";
2
+
3
+ import { TaskSetupTimedOut } from "../../../../../../../shared/task-errors.ts";
4
+ import { failRun } from "../../../../../shared/fail-run.ts";
5
+ import { TaskCleanupTimedOut } from "../../../../../shared/main-thread-errors.ts";
6
+ import { RunRootTaskConfig } from "../../../../../shared/provide-run-root-task-config.ts";
7
+ import type { makeTaskRunsState } from "../../../shared/define-task-run-state.ts";
8
+ import { awaitWithinCountedLimit } from "./await-within-counted-limit.ts";
9
+
10
+ export const makeTaskSetupClock = Effect.fn("makeTaskSetupClock")(function* (
11
+ taskRunsState: Effect.Success<typeof makeTaskRunsState>,
12
+ taskName: string,
13
+ cleanupDuration: Duration.Duration,
14
+ ) {
15
+ const limitMilliseconds = Duration.toMillis((yield* RunRootTaskConfig).setupLimit);
16
+ const setupStartedAtMilliseconds = yield* Clock.currentTimeMillis;
17
+ const workerAssignment = yield* Ref.make(
18
+ Option.none<{
19
+ readonly workerId: number;
20
+ readonly busyMillisecondsAtAssignment: number;
21
+ }>(),
22
+ );
23
+ const countSetupMilliseconds = (nowMilliseconds: number) =>
24
+ Effect.map(Ref.get(workerAssignment), (assignedWorker) => {
25
+ const workerBusyMilliseconds = Option.match(assignedWorker, {
26
+ onNone: () => 0,
27
+ onSome: (worker) =>
28
+ taskRunsState.readWorkerBodyBusyMilliseconds(worker.workerId, nowMilliseconds) -
29
+ worker.busyMillisecondsAtAssignment,
30
+ });
31
+ return nowMilliseconds - setupStartedAtMilliseconds - workerBusyMilliseconds;
32
+ });
33
+ const interruptSetupKeepingFailures = <Success, Failure>(
34
+ setupFiber: Fiber.Fiber<Success, Failure>,
35
+ ) =>
36
+ Effect.gen(function* () {
37
+ const cleanup = Fiber.interrupt(setupFiber).pipe(Effect.andThen(Fiber.await(setupFiber)));
38
+ const interruptedSetupExit = yield* cleanup.pipe(
39
+ Effect.interruptible,
40
+ Effect.timeoutOrElse({
41
+ duration: cleanupDuration,
42
+ orElse: () =>
43
+ Effect.succeed(
44
+ Exit.fail<Failure | TaskCleanupTimedOut>(
45
+ new TaskCleanupTimedOut({
46
+ taskName,
47
+ cleanupMilliseconds: Duration.toMillis(cleanupDuration),
48
+ }),
49
+ ),
50
+ ),
51
+ }),
52
+ );
53
+ const failuresWhileInterrupted = Exit.isFailure(interruptedSetupExit)
54
+ ? interruptedSetupExit.cause.reasons.filter((reason) => !Cause.isInterruptReason(reason))
55
+ : [];
56
+ const failures = Cause.fromReasons(failuresWhileInterrupted);
57
+ if (failures.reasons.length > 0) {
58
+ return yield* Effect.failCause(failures);
59
+ }
60
+ });
61
+ const awaitSetupWithinLimit = <Success, Failure>(
62
+ setupFiber: Fiber.Fiber<Success, Failure>,
63
+ describeUnfinishedStep: () => string,
64
+ ) =>
65
+ Effect.gen(function* () {
66
+ const setupExit = yield* awaitWithinCountedLimit(
67
+ Fiber.await(setupFiber),
68
+ limitMilliseconds,
69
+ countSetupMilliseconds,
70
+ );
71
+ if (Option.isSome(setupExit)) {
72
+ return setupExit.value;
73
+ }
74
+ yield* failRun(
75
+ new TaskSetupTimedOut({
76
+ taskName,
77
+ limitMilliseconds,
78
+ unfinishedStep: describeUnfinishedStep(),
79
+ }),
80
+ taskRunsState.markFailed,
81
+ );
82
+ return null;
83
+ });
84
+ return {
85
+ recordWorkerAssignment: (workerId: number) =>
86
+ Effect.flatMap(Clock.currentTimeMillis, (nowMilliseconds) =>
87
+ Ref.set(
88
+ workerAssignment,
89
+ Option.some({
90
+ workerId,
91
+ busyMillisecondsAtAssignment: taskRunsState.readWorkerBodyBusyMilliseconds(
92
+ workerId,
93
+ nowMilliseconds,
94
+ ),
95
+ }),
96
+ ),
97
+ ),
98
+ limit: <Success, Failure, Services>(
99
+ setup: Effect.Effect<Success, Failure, Services>,
100
+ describeUnfinishedStep: () => string,
101
+ ) =>
102
+ Effect.uninterruptibleMask((restore) =>
103
+ Effect.gen(function* () {
104
+ const context = yield* Effect.context<Services>();
105
+ const hasReadSetupExit = yield* Ref.make(false);
106
+ return yield* Effect.acquireUseRelease(
107
+ Effect.sync(() =>
108
+ Effect.runForkWith(context)(restore(setup), { uninterruptible: true }),
109
+ ),
110
+ (setupFiber) =>
111
+ Effect.interruptible(awaitSetupWithinLimit(setupFiber, describeUnfinishedStep)).pipe(
112
+ Effect.flatMap((setupExit) => {
113
+ if (setupExit === null) {
114
+ return Effect.interrupt;
115
+ }
116
+ return Ref.set(hasReadSetupExit, true).pipe(Effect.andThen(setupExit));
117
+ }),
118
+ ),
119
+ (setupFiber) =>
120
+ Ref.get(hasReadSetupExit).pipe(
121
+ Effect.flatMap((hasReadExit) =>
122
+ hasReadExit ? Effect.void : interruptSetupKeepingFailures(setupFiber),
123
+ ),
124
+ ),
125
+ );
126
+ }),
127
+ ),
128
+ };
129
+ });
@@ -0,0 +1,135 @@
1
+ import { Deferred, Effect, FiberSet } from "effect";
2
+
3
+ import { TASK_TRACE_LABEL_PREFIX } from "../../../../../../../shared/names-from-package-json.ts";
4
+ import {
5
+ AwaitedTaskFailed,
6
+ TaskNotMadeAtTopLevel,
7
+ TaskWaitsOnItself,
8
+ } from "../../../../../../errors/index.ts";
9
+ import type { makeTaskRun, makeTaskRunsState } from "../../../shared/define-task-run-state.ts";
10
+ import { executeTask } from "./execute-task.ts";
11
+
12
+ const requestedTaskNotDiscovered = (requestedTaskName: string, requestingTaskName: string) =>
13
+ new TaskNotMadeAtTopLevel({ taskName: requestingTaskName, requestedTaskName });
14
+
15
+ const runTaskOnce = Effect.fn("runTaskOnce")(function* (
16
+ taskRunsState: Effect.Success<typeof makeTaskRunsState>,
17
+ resultDeferreds: Map<
18
+ string,
19
+ Deferred.Deferred<
20
+ Effect.Success<ReturnType<typeof executeTask>>,
21
+ Effect.Error<ReturnType<typeof executeTask>>
22
+ >
23
+ >,
24
+ taskName: string,
25
+ ) {
26
+ const existingResultDeferred = resultDeferreds.get(taskName);
27
+ if (existingResultDeferred !== undefined) {
28
+ return existingResultDeferred;
29
+ }
30
+ taskRunsState.createRecord(taskName);
31
+ const resultDeferred: Parameters<typeof resultDeferreds.set>[1] = Deferred.makeUnsafe();
32
+ resultDeferreds.set(taskName, resultDeferred);
33
+ yield* FiberSet.run(
34
+ taskRunsState.taskFibers,
35
+ executeTask(taskRunsState, taskName).pipe(
36
+ Effect.onExit((taskExit) => Deferred.done(resultDeferred, taskExit)),
37
+ Effect.withParentSpan(taskRunsState.runSpan),
38
+ ),
39
+ );
40
+ return resultDeferred;
41
+ });
42
+
43
+ const findDependencyPathToRequestingTask = (
44
+ taskRunsByName: ReadonlyMap<string, ReturnType<typeof makeTaskRun>>,
45
+ dependencyTaskName: string,
46
+ requestingTaskName: string,
47
+ ): readonly string[] | null => {
48
+ const visitedTaskNames = new Set<string>();
49
+ const findDependencyPath = (currentTaskName: string): readonly string[] | null => {
50
+ if (currentTaskName === requestingTaskName) {
51
+ return [currentTaskName];
52
+ }
53
+ if (visitedTaskNames.has(currentTaskName)) {
54
+ return null;
55
+ }
56
+
57
+ visitedTaskNames.add(currentTaskName);
58
+ for (const nextDependencyTaskName of taskRunsByName.get(currentTaskName)?.awaitedTaskNames ??
59
+ []) {
60
+ const remainingDependencyPath = findDependencyPath(nextDependencyTaskName);
61
+ if (remainingDependencyPath !== null) {
62
+ return [currentTaskName, ...remainingDependencyPath];
63
+ }
64
+ }
65
+ return null;
66
+ };
67
+ return findDependencyPath(dependencyTaskName);
68
+ };
69
+
70
+ export const runRequestedTask = Effect.fn("runRequestedTask")(function* (
71
+ taskRunsState: Effect.Success<typeof makeTaskRunsState>,
72
+ resultDeferreds: Parameters<typeof runTaskOnce>[1],
73
+ taskName: string,
74
+ ) {
75
+ yield* Effect.annotateCurrentSpan(`${TASK_TRACE_LABEL_PREFIX}.task`, taskName);
76
+ if (!taskRunsState.discoveredTasksByName.has(taskName)) {
77
+ return yield* requestedTaskNotDiscovered(taskName, taskName);
78
+ }
79
+ const resultDeferred = yield* runTaskOnce(taskRunsState, resultDeferreds, taskName);
80
+ return yield* Deferred.await(resultDeferred);
81
+ });
82
+
83
+ export const runRequestedDependencyTask = Effect.fn("runRequestedDependencyTask")(function* (
84
+ taskRunsState: Effect.Success<typeof makeTaskRunsState>,
85
+ resultDeferreds: Parameters<typeof runTaskOnce>[1],
86
+ dependencyTaskName: string,
87
+ requestingTaskName: string,
88
+ ) {
89
+ yield* Effect.annotateCurrentSpan({
90
+ [`${TASK_TRACE_LABEL_PREFIX}.task`]: dependencyTaskName,
91
+ [`${TASK_TRACE_LABEL_PREFIX}.requested_by`]: requestingTaskName,
92
+ });
93
+ if (!taskRunsState.discoveredTasksByName.has(dependencyTaskName)) {
94
+ return yield* requestedTaskNotDiscovered(dependencyTaskName, requestingTaskName);
95
+ }
96
+ const existingResultDeferred = resultDeferreds.get(dependencyTaskName);
97
+ const isTaskRunInProgress =
98
+ existingResultDeferred !== undefined && !Deferred.isDoneUnsafe(existingResultDeferred);
99
+ if (isTaskRunInProgress) {
100
+ const dependencyCycle = findDependencyPathToRequestingTask(
101
+ taskRunsState.runsByTaskName,
102
+ dependencyTaskName,
103
+ requestingTaskName,
104
+ );
105
+ if (dependencyCycle !== null) {
106
+ return yield* new TaskWaitsOnItself({
107
+ taskName: requestingTaskName,
108
+ cycle: [requestingTaskName, ...dependencyCycle],
109
+ });
110
+ }
111
+ }
112
+ const requestingTaskDependencies =
113
+ taskRunsState.runsByTaskName.get(requestingTaskName)?.awaitedTaskNames;
114
+ requestingTaskDependencies?.push(dependencyTaskName);
115
+ const awaitedResultDeferred = yield* runTaskOnce(
116
+ taskRunsState,
117
+ resultDeferreds,
118
+ dependencyTaskName,
119
+ );
120
+ return yield* Deferred.await(awaitedResultDeferred).pipe(
121
+ Effect.map((result) => structuredClone(result)),
122
+ Effect.mapError(() => new AwaitedTaskFailed({ awaitedTaskName: dependencyTaskName })),
123
+ Effect.ensuring(
124
+ Effect.sync(() => {
125
+ if (requestingTaskDependencies === undefined) {
126
+ return;
127
+ }
128
+ requestingTaskDependencies.splice(
129
+ requestingTaskDependencies.indexOf(dependencyTaskName),
130
+ 1,
131
+ );
132
+ }),
133
+ ),
134
+ );
135
+ });