@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,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
|
+
});
|