@sshkeda/task 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +131 -2
- package/package.json +41 -4
- package/src/index.ts +2 -0
- package/src/lib/errors/index.ts +15 -0
- package/src/lib/errors/internal/errors.ts +147 -0
- package/src/lib/kill-leftover-processes/index.ts +17 -0
- package/src/lib/kill-leftover-processes/internal/command-words.ts +16 -0
- package/src/lib/kill-leftover-processes/internal/find-process-ids.ts +36 -0
- package/src/lib/kill-leftover-processes/internal/kill-process-groups.ts +270 -0
- package/src/lib/kill-leftover-processes/internal/list-linux-processes-from-proc.ts +191 -0
- package/src/lib/kill-leftover-processes/internal/list-macos-processes-with-sysctl.ts +335 -0
- package/src/lib/kill-leftover-processes/internal/list-processes.ts +58 -0
- package/src/lib/kill-leftover-processes/internal/mark-run-processes.ts +367 -0
- package/src/lib/kill-leftover-processes/internal/process-cleanup-errors.ts +17 -0
- package/src/lib/kill-leftover-processes/internal/send-signal.ts +32 -0
- package/src/lib/kill-leftover-processes/internal/signal-run-processes.ts +48 -0
- package/src/lib/kill-leftover-processes/internal/started-process-groups.ts +107 -0
- package/src/lib/main-thread/index.ts +3 -0
- package/src/lib/main-thread/internal/listen-for-stop-requests.ts +102 -0
- package/src/lib/main-thread/internal/run-main-thread.ts +81 -0
- package/src/lib/main-thread/internal/run-root-task-fiber.ts +191 -0
- package/src/lib/main-thread/internal/stops-at-first-failure.ts +20 -0
- package/src/lib/main-thread/internal/trace-the-run.ts +107 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/index.ts +7 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/follow-imports-from-entry-files.ts +306 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/load-all-reporting-first-failure.ts +11 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/make-module-graph.ts +48 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/read-configured-loaders.ts +64 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/resolve-module-specifiers.ts +60 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/rewrite-worker-imports.ts +118 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/worker-import-errors.ts +19 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/index.ts +7 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/internal/handle-worker-events.ts +348 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/internal/make-task-runs.ts +207 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/index.ts +1 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/await-within-counted-limit.ts +29 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/execute-task.ts +272 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/make-task-setup-clock.ts +129 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/run-requested-tasks.ts +135 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/define-task-run-state.ts +288 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/make-stopwatch.ts +39 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/share-standard-input-with-root-task.ts +36 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/stop-task.ts +166 -0
- package/src/lib/main-thread/lib/scan-module-source/index.ts +1 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/find-lexical-bindings.ts +154 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/find-task-declarations.ts +205 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/parse-module-text.ts +34 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/read-literal-string.ts +15 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/read-module-loads.ts +316 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/scan-module-text.ts +70 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/unwrap-type-assertions.ts +10 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/visit-non-reference-identifiers.ts +21 -0
- package/src/lib/main-thread/shared/fail-run.ts +36 -0
- package/src/lib/main-thread/shared/find-failure-reason.ts +43 -0
- package/src/lib/main-thread/shared/find-package-directories.ts +34 -0
- package/src/lib/main-thread/shared/find-tasks-reachable-from-root-task-file.ts +246 -0
- package/src/lib/main-thread/shared/list-directory-paths-up-to-root.ts +13 -0
- package/src/lib/main-thread/shared/main-thread-errors.ts +373 -0
- package/src/lib/main-thread/shared/package-json-errors.ts +93 -0
- package/src/lib/main-thread/shared/provide-discovered-tasks.ts +26 -0
- package/src/lib/main-thread/shared/provide-process-groups.ts +63 -0
- package/src/lib/main-thread/shared/provide-run-root-task-config.ts +66 -0
- package/src/lib/main-thread/shared/provide-stop-state.ts +150 -0
- package/src/lib/main-thread/shared/provide-worker-pool.ts +607 -0
- package/src/lib/main-thread/shared/read-bare-package-name.ts +3 -0
- package/src/lib/main-thread/shared/read-package-json.ts +149 -0
- package/src/lib/main-thread/shared/report-task-failure.ts +31 -0
- package/src/lib/preload-rewritten-imports-into-each-worker/index.ts +5 -0
- package/src/lib/preload-rewritten-imports-into-each-worker/internal/load-rewritten-imports.ts +34 -0
- package/src/lib/supervise-the-run/index.ts +15 -0
- package/src/lib/supervise-the-run/internal/adopt-orphans-on-linux.ts +73 -0
- package/src/lib/supervise-the-run/internal/check-root-task-file.ts +8 -0
- package/src/lib/supervise-the-run/internal/report-ready.ts +12 -0
- package/src/lib/supervise-the-run/internal/share-process-groups.ts +25 -0
- package/src/lib/supervise-the-run/internal/start-watching-the-supervisor.ts +6 -0
- package/src/lib/supervise-the-run/internal/supervise-run.ts +155 -0
- package/src/lib/supervise-the-run/internal/talk-to-the-supervisor.ts +53 -0
- package/src/lib/supervise-the-run/internal/watch-the-supervisor.ts +61 -0
- package/src/lib/task/index.ts +5 -0
- package/src/lib/task/internal/make-tasks.ts +82 -0
- package/src/lib/task/internal/run-command.ts +194 -0
- package/src/lib/task/internal/run-root-task.ts +263 -0
- package/src/lib/task/internal/run-task-lists.ts +51 -0
- package/src/lib/task/internal/trust-output-mode-names.ts +18 -0
- package/src/lib/task/internal/trust-task-results.ts +35 -0
- package/src/lib/task/internal/use-task-services.ts +4 -0
- package/src/lib/worker-thread/index.ts +3 -0
- package/src/lib/worker-thread/internal/answer-main-thread-messages.ts +141 -0
- package/src/lib/worker-thread/internal/describe-task-body-result.ts +91 -0
- package/src/lib/worker-thread/internal/name-config-key-not-set.ts +57 -0
- package/src/lib/worker-thread/internal/run-task-body.ts +117 -0
- package/src/lib/worker-thread/internal/serve-on-the-parent-port.ts +63 -0
- package/src/lib/worker-thread/internal/start-worker-thread.ts +41 -0
- package/src/lib/worker-thread/lib/provide-task-services/index.ts +1 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/make-task-logger.ts +34 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/provide-task-services.ts +86 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/read-standard-input-from-main-thread.ts +58 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/resolve-paths-from-package-directory.ts +61 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/run-commands-in-package-directory.ts +24 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-commands-with-bun.ts +545 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-reported-process.ts +151 -0
- package/src/lib/worker-thread/shared/await-main-thread-answers.ts +76 -0
- package/src/lib/worker-thread/shared/decode-task-result-json.ts +114 -0
- package/src/lib/worker-thread/shared/define-task-body-record.ts +11 -0
- package/src/lib/worker-thread/shared/describe-issue-input.ts +52 -0
- package/src/lib/worker-thread/shared/plain-object.ts +8 -0
- package/src/lib/worker-thread/shared/provide-worker-state.ts +160 -0
- package/src/lib/worker-thread/shared/worker-thread-errors.ts +41 -0
- package/src/shared/attribute-reported-failures.ts +25 -0
- package/src/shared/claim-processes-to-report.ts +22 -0
- package/src/shared/command-errors.ts +44 -0
- package/src/shared/complete-discarded-calls-once-sent.ts +12 -0
- package/src/shared/cut-to-graphemes.ts +12 -0
- package/src/shared/environment-config-errors.ts +74 -0
- package/src/shared/error-fields.ts +13 -0
- package/src/shared/find-bun-loader-for-script.ts +32 -0
- package/src/shared/find-node-modules-package-directory.ts +35 -0
- package/src/shared/find-user-locations-in-stack-traces.ts +64 -0
- package/src/shared/format-error-lines.ts +53 -0
- package/src/shared/format-task-lines.ts +21 -0
- package/src/shared/hold-task-logs.ts +24 -0
- package/src/shared/invariant-errors.ts +28 -0
- package/src/shared/mark-task-worker-thread-contexts.ts +29 -0
- package/src/shared/module-graph-errors.ts +49 -0
- package/src/shared/name-config-key.ts +42 -0
- package/src/shared/names-from-package-json.ts +7 -0
- package/src/shared/print-to-standard-error.ts +48 -0
- package/src/shared/process-errors.ts +191 -0
- package/src/shared/read-environment-config.ts +239 -0
- package/src/shared/record-tasks-made.ts +10 -0
- package/src/shared/report-cause.ts +140 -0
- package/src/shared/report-errors.ts +121 -0
- package/src/shared/resolve-task-options.ts +20 -0
- package/src/shared/rpc.ts +161 -0
- package/src/shared/run-in-child-fiber-and-join.ts +16 -0
- package/src/shared/run-marker.ts +7 -0
- package/src/shared/save-standard-stream-flags.ts +57 -0
- package/src/shared/select-cause-reasons.ts +14 -0
- package/src/shared/signal-run-called-twice.ts +11 -0
- package/src/shared/standard-input-streams.ts +41 -0
- package/src/shared/stop-signals.ts +41 -0
- package/src/shared/task-errors.ts +224 -0
- package/src/shared/task-package.ts +5 -0
- package/src/shared/task-results.ts +55 -0
- package/src/shared/task-services.ts +30 -0
- package/src/shared/write-trace-file.ts +112 -0
|
@@ -0,0 +1,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
|
+
});
|