trigger.dev 0.0.0-v3-prerelease-20250108142426 → 0.0.0-v4-prerelease-20250916125920
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/README.md +28 -1
- package/dist/esm/apiClient.d.ts +158 -74
- package/dist/esm/apiClient.js +341 -76
- package/dist/esm/apiClient.js.map +1 -1
- package/dist/esm/build/buildWorker.d.ts +10 -6
- package/dist/esm/build/buildWorker.js +22 -38
- package/dist/esm/build/buildWorker.js.map +1 -1
- package/dist/esm/build/bundle.d.ts +18 -1
- package/dist/esm/build/bundle.js +142 -52
- package/dist/esm/build/bundle.js.map +1 -1
- package/dist/esm/build/entryPoints.d.ts +12 -0
- package/dist/esm/build/entryPoints.js +127 -0
- package/dist/esm/build/entryPoints.js.map +1 -0
- package/dist/esm/build/extensions.js +17 -0
- package/dist/esm/build/extensions.js.map +1 -1
- package/dist/esm/build/externals.js +184 -2
- package/dist/esm/build/externals.js.map +1 -1
- package/dist/esm/build/packageModules.d.ts +15 -5
- package/dist/esm/build/packageModules.js +154 -36
- package/dist/esm/build/packageModules.js.map +1 -1
- package/dist/esm/cli/common.d.ts +1 -1
- package/dist/esm/cli/common.js +38 -45
- package/dist/esm/cli/common.js.map +1 -1
- package/dist/esm/cli/index.js +19 -3
- package/dist/esm/cli/index.js.map +1 -1
- package/dist/esm/commands/analyze.d.ts +23 -0
- package/dist/esm/commands/analyze.js +122 -0
- package/dist/esm/commands/analyze.js.map +1 -0
- package/dist/esm/commands/deploy.d.ts +5 -2
- package/dist/esm/commands/deploy.js +271 -160
- package/dist/esm/commands/deploy.js.map +1 -1
- package/dist/esm/commands/dev.d.ts +36 -6
- package/dist/esm/commands/dev.js +71 -2
- package/dist/esm/commands/dev.js.map +1 -1
- package/dist/esm/commands/env.d.ts +2 -0
- package/dist/esm/commands/env.js +298 -0
- package/dist/esm/commands/env.js.map +1 -0
- package/dist/esm/commands/init.d.ts +9 -1
- package/dist/esm/commands/init.js +132 -65
- package/dist/esm/commands/init.js.map +1 -1
- package/dist/esm/commands/install-mcp.d.ts +50 -0
- package/dist/esm/commands/install-mcp.js +497 -0
- package/dist/esm/commands/install-mcp.js.map +1 -0
- package/dist/esm/commands/install-rules.d.ts +11 -0
- package/dist/esm/commands/install-rules.js +381 -0
- package/dist/esm/commands/install-rules.js.map +1 -0
- package/dist/esm/commands/list-profiles.d.ts +3 -7
- package/dist/esm/commands/list-profiles.js +7 -4
- package/dist/esm/commands/list-profiles.js.map +1 -1
- package/dist/esm/commands/login.d.ts +9 -5
- package/dist/esm/commands/login.js +40 -7
- package/dist/esm/commands/login.js.map +1 -1
- package/dist/esm/commands/logout.d.ts +1 -1
- package/dist/esm/commands/mcp.d.ts +38 -0
- package/dist/esm/commands/mcp.js +82 -0
- package/dist/esm/commands/mcp.js.map +1 -0
- package/dist/esm/commands/preview.d.ts +5 -0
- package/dist/esm/commands/preview.js +93 -0
- package/dist/esm/commands/preview.js.map +1 -0
- package/dist/esm/commands/promote.d.ts +3 -0
- package/dist/esm/commands/promote.js +86 -0
- package/dist/esm/commands/promote.js.map +1 -0
- package/dist/esm/commands/switch.d.ts +19 -0
- package/dist/esm/commands/switch.js +93 -0
- package/dist/esm/commands/switch.js.map +1 -0
- package/dist/esm/commands/trigger.d.ts +33 -0
- package/dist/esm/commands/trigger.js +88 -0
- package/dist/esm/commands/trigger.js.map +1 -0
- package/dist/esm/commands/update.d.ts +3 -2
- package/dist/esm/commands/update.js +23 -9
- package/dist/esm/commands/update.js.map +1 -1
- package/dist/esm/commands/whoami.d.ts +12 -1
- package/dist/esm/commands/whoami.js +36 -6
- package/dist/esm/commands/whoami.js.map +1 -1
- package/dist/esm/commands/workers/build.d.ts +4 -0
- package/dist/esm/commands/workers/build.js +345 -0
- package/dist/esm/commands/workers/build.js.map +1 -0
- package/dist/esm/commands/workers/create.d.ts +2 -0
- package/dist/esm/commands/workers/create.js +91 -0
- package/dist/esm/commands/workers/create.js.map +1 -0
- package/dist/esm/commands/workers/index.d.ts +2 -0
- package/dist/esm/commands/workers/index.js +13 -0
- package/dist/esm/commands/workers/index.js.map +1 -0
- package/dist/esm/commands/workers/list.d.ts +2 -0
- package/dist/esm/commands/workers/list.js +80 -0
- package/dist/esm/commands/workers/list.js.map +1 -0
- package/dist/esm/commands/workers/run.d.ts +2 -0
- package/dist/esm/commands/workers/run.js +105 -0
- package/dist/esm/commands/workers/run.js.map +1 -0
- package/dist/esm/config.d.ts +2 -1
- package/dist/esm/config.js +35 -8
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/deploy/buildImage.d.ts +12 -11
- package/dist/esm/deploy/buildImage.js +360 -115
- package/dist/esm/deploy/buildImage.js.map +1 -1
- package/dist/esm/dev/backgroundWorker.d.ts +6 -245
- package/dist/esm/dev/backgroundWorker.js +11 -320
- package/dist/esm/dev/backgroundWorker.js.map +1 -1
- package/dist/esm/dev/devOutput.js +48 -9
- package/dist/esm/dev/devOutput.js.map +1 -1
- package/dist/esm/dev/devSession.d.ts +2 -1
- package/dist/esm/dev/devSession.js +68 -65
- package/dist/esm/dev/devSession.js.map +1 -1
- package/dist/esm/dev/devSupervisor.d.ts +12 -0
- package/dist/esm/dev/devSupervisor.js +568 -0
- package/dist/esm/dev/devSupervisor.js.map +1 -0
- package/dist/esm/dev/lock.d.ts +1 -0
- package/dist/esm/dev/lock.js +80 -0
- package/dist/esm/dev/lock.js.map +1 -0
- package/dist/esm/dev/mcpServer.d.ts +10 -0
- package/dist/esm/dev/mcpServer.js +201 -0
- package/dist/esm/dev/mcpServer.js.map +1 -0
- package/dist/esm/dev/taskRunProcessPool.d.ts +39 -0
- package/dist/esm/dev/taskRunProcessPool.js +220 -0
- package/dist/esm/dev/taskRunProcessPool.js.map +1 -0
- package/dist/esm/dev/workerRuntime.d.ts +2 -2
- package/dist/esm/dev/workerRuntime.js +1 -265
- package/dist/esm/dev/workerRuntime.js.map +1 -1
- package/dist/esm/entryPoints/dev-index-worker.js +45 -8
- package/dist/esm/entryPoints/dev-index-worker.js.map +1 -1
- package/dist/esm/entryPoints/dev-run-controller.d.ts +58 -0
- package/dist/esm/entryPoints/dev-run-controller.js +652 -0
- package/dist/esm/entryPoints/dev-run-controller.js.map +1 -0
- package/dist/esm/entryPoints/dev-run-worker.js +394 -199
- package/dist/esm/entryPoints/dev-run-worker.js.map +1 -1
- package/dist/esm/entryPoints/managed/controller.d.ts +63 -0
- package/dist/esm/entryPoints/managed/controller.js +482 -0
- package/dist/esm/entryPoints/managed/controller.js.map +1 -0
- package/dist/esm/entryPoints/managed/env.d.ts +170 -0
- package/dist/esm/entryPoints/managed/env.js +199 -0
- package/dist/esm/entryPoints/managed/env.js.map +1 -0
- package/dist/esm/entryPoints/managed/execution.d.ts +141 -0
- package/dist/esm/entryPoints/managed/execution.js +869 -0
- package/dist/esm/entryPoints/managed/execution.js.map +1 -0
- package/dist/esm/entryPoints/managed/logger.d.ts +30 -0
- package/dist/esm/entryPoints/managed/logger.js +47 -0
- package/dist/esm/entryPoints/managed/logger.js.map +1 -0
- package/dist/esm/entryPoints/managed/notifier.d.ts +30 -0
- package/dist/esm/entryPoints/managed/notifier.js +63 -0
- package/dist/esm/entryPoints/managed/notifier.js.map +1 -0
- package/dist/esm/entryPoints/managed/overrides.d.ts +18 -0
- package/dist/esm/entryPoints/managed/overrides.js +19 -0
- package/dist/esm/entryPoints/managed/overrides.js.map +1 -0
- package/dist/esm/entryPoints/managed/poller.d.ts +31 -0
- package/dist/esm/entryPoints/managed/poller.js +89 -0
- package/dist/esm/entryPoints/managed/poller.js.map +1 -0
- package/dist/esm/entryPoints/managed/snapshot.d.ts +54 -0
- package/dist/esm/entryPoints/managed/snapshot.js +293 -0
- package/dist/esm/entryPoints/managed/snapshot.js.map +1 -0
- package/dist/esm/entryPoints/managed/taskRunProcessProvider.d.ts +62 -0
- package/dist/esm/entryPoints/managed/taskRunProcessProvider.js +252 -0
- package/dist/esm/entryPoints/managed/taskRunProcessProvider.js.map +1 -0
- package/dist/esm/entryPoints/{deploy-index-controller.js → managed-index-controller.js} +27 -3
- package/dist/esm/entryPoints/managed-index-controller.js.map +1 -0
- package/dist/esm/entryPoints/{deploy-index-worker.js → managed-index-worker.js} +50 -21
- package/dist/esm/entryPoints/managed-index-worker.js.map +1 -0
- package/dist/esm/entryPoints/managed-run-controller.js +13 -0
- package/dist/esm/entryPoints/managed-run-controller.js.map +1 -0
- package/dist/esm/entryPoints/managed-run-worker.js +512 -0
- package/dist/esm/entryPoints/managed-run-worker.js.map +1 -0
- package/dist/esm/executions/taskRunProcess.d.ts +28 -82
- package/dist/esm/executions/taskRunProcess.js +130 -74
- package/dist/esm/executions/taskRunProcess.js.map +1 -1
- package/dist/esm/indexing/indexWorkerManifest.d.ts +21 -5
- package/dist/esm/indexing/indexWorkerManifest.js +4 -4
- package/dist/esm/indexing/indexWorkerManifest.js.map +1 -1
- package/dist/esm/indexing/registerResources.d.ts +5 -0
- package/dist/esm/indexing/registerResources.js +44 -0
- package/dist/esm/indexing/registerResources.js.map +1 -0
- package/dist/esm/mcp/auth.d.ts +12 -0
- package/dist/esm/mcp/auth.js +152 -0
- package/dist/esm/mcp/auth.js.map +1 -0
- package/dist/esm/mcp/capabilities.d.ts +4 -0
- package/dist/esm/mcp/capabilities.js +22 -0
- package/dist/esm/mcp/capabilities.js.map +1 -0
- package/dist/esm/mcp/config.d.ts +82 -0
- package/dist/esm/mcp/config.js +87 -0
- package/dist/esm/mcp/config.js.map +1 -0
- package/dist/esm/mcp/context.d.ts +45 -0
- package/dist/esm/mcp/context.js +129 -0
- package/dist/esm/mcp/context.js.map +1 -0
- package/dist/esm/mcp/formatters.d.ts +7 -0
- package/dist/esm/mcp/formatters.js +330 -0
- package/dist/esm/mcp/formatters.js.map +1 -0
- package/dist/esm/mcp/logger.d.ts +11 -0
- package/dist/esm/mcp/logger.js +34 -0
- package/dist/esm/mcp/logger.js.map +1 -0
- package/dist/esm/mcp/mintlifyClient.d.ts +1 -0
- package/dist/esm/mcp/mintlifyClient.js +65 -0
- package/dist/esm/mcp/mintlifyClient.js.map +1 -0
- package/dist/esm/mcp/schemas.d.ts +324 -0
- package/dist/esm/mcp/schemas.js +144 -0
- package/dist/esm/mcp/schemas.js.map +1 -0
- package/dist/esm/mcp/tools/deploys.d.ts +174 -0
- package/dist/esm/mcp/tools/deploys.js +161 -0
- package/dist/esm/mcp/tools/deploys.js.map +1 -0
- package/dist/esm/mcp/tools/docs.d.ts +77 -0
- package/dist/esm/mcp/tools/docs.js +18 -0
- package/dist/esm/mcp/tools/docs.js.map +1 -0
- package/dist/esm/mcp/tools/orgs.d.ts +172 -0
- package/dist/esm/mcp/tools/orgs.js +172 -0
- package/dist/esm/mcp/tools/orgs.js.map +1 -0
- package/dist/esm/mcp/tools/previewBranches.d.ts +78 -0
- package/dist/esm/mcp/tools/previewBranches.js +28 -0
- package/dist/esm/mcp/tools/previewBranches.js.map +1 -0
- package/dist/esm/mcp/tools/runs.d.ts +335 -0
- package/dist/esm/mcp/tools/runs.js +160 -0
- package/dist/esm/mcp/tools/runs.js.map +1 -0
- package/dist/esm/mcp/tools/tasks.d.ts +200 -0
- package/dist/esm/mcp/tools/tasks.js +117 -0
- package/dist/esm/mcp/tools/tasks.js.map +1 -0
- package/dist/esm/mcp/tools.d.ts +2 -0
- package/dist/esm/mcp/tools.js +40 -0
- package/dist/esm/mcp/tools.js.map +1 -0
- package/dist/esm/mcp/types.d.ts +6 -0
- package/dist/esm/mcp/types.js +2 -0
- package/dist/esm/mcp/types.js.map +1 -0
- package/dist/esm/mcp/utils.d.ts +89 -0
- package/dist/esm/mcp/utils.js +95 -0
- package/dist/esm/mcp/utils.js.map +1 -0
- package/dist/esm/rules/install.d.ts +1 -0
- package/dist/esm/rules/install.js +2 -0
- package/dist/esm/rules/install.js.map +1 -0
- package/dist/esm/rules/manifest.d.ts +145 -0
- package/dist/esm/rules/manifest.js +110 -0
- package/dist/esm/rules/manifest.js.map +1 -0
- package/dist/esm/rules/types.d.ts +3 -0
- package/dist/esm/rules/types.js +3 -0
- package/dist/esm/rules/types.js.map +1 -0
- package/dist/esm/utilities/accessTokens.d.ts +12 -0
- package/dist/esm/utilities/accessTokens.js +30 -0
- package/dist/esm/utilities/accessTokens.js.map +1 -0
- package/dist/esm/utilities/analyze.d.ts +13 -0
- package/dist/esm/utilities/analyze.js +463 -0
- package/dist/esm/utilities/analyze.js.map +1 -0
- package/dist/esm/utilities/cliOutput.d.ts +6 -1
- package/dist/esm/utilities/cliOutput.js +11 -2
- package/dist/esm/utilities/cliOutput.js.map +1 -1
- package/dist/esm/utilities/configFiles.d.ts +65 -15
- package/dist/esm/utilities/configFiles.js +124 -26
- package/dist/esm/utilities/configFiles.js.map +1 -1
- package/dist/esm/utilities/eventBus.d.ts +7 -3
- package/dist/esm/utilities/eventBus.js.map +1 -1
- package/dist/esm/utilities/fileSystem.d.ts +7 -1
- package/dist/esm/utilities/fileSystem.js +42 -4
- package/dist/esm/utilities/fileSystem.js.map +1 -1
- package/dist/esm/utilities/gitMeta.d.ts +2 -0
- package/dist/esm/utilities/gitMeta.js +220 -0
- package/dist/esm/utilities/gitMeta.js.map +1 -0
- package/dist/esm/utilities/githubActions.d.ts +4 -0
- package/dist/esm/utilities/githubActions.js +18 -0
- package/dist/esm/utilities/githubActions.js.map +1 -0
- package/dist/esm/utilities/initialBanner.js +18 -6
- package/dist/esm/utilities/initialBanner.js.map +1 -1
- package/dist/esm/utilities/localEnvVars.d.ts +3 -0
- package/dist/esm/utilities/localEnvVars.js +19 -0
- package/dist/esm/utilities/localEnvVars.js.map +1 -0
- package/dist/esm/utilities/sanitizeEnvVars.d.ts +16 -3
- package/dist/esm/utilities/sanitizeEnvVars.js +15 -0
- package/dist/esm/utilities/sanitizeEnvVars.js.map +1 -1
- package/dist/esm/utilities/session.d.ts +14 -0
- package/dist/esm/utilities/session.js +44 -47
- package/dist/esm/utilities/session.js.map +1 -1
- package/dist/esm/utilities/supportsHyperlinks.d.ts +15 -0
- package/dist/esm/utilities/supportsHyperlinks.js +122 -0
- package/dist/esm/utilities/supportsHyperlinks.js.map +1 -0
- package/dist/esm/utilities/tempDirectories.d.ts +1 -0
- package/dist/esm/utilities/tempDirectories.js +19 -2
- package/dist/esm/utilities/tempDirectories.js.map +1 -1
- package/dist/esm/utilities/terminalLink.d.ts +56 -0
- package/dist/esm/utilities/terminalLink.js +76 -0
- package/dist/esm/utilities/terminalLink.js.map +1 -0
- package/dist/esm/utilities/windows.js +51 -1
- package/dist/esm/utilities/windows.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +44 -25
- package/dist/esm/entryPoints/deploy-index-controller.js.map +0 -1
- package/dist/esm/entryPoints/deploy-index-worker.js.map +0 -1
- package/dist/esm/entryPoints/deploy-run-controller.js +0 -1099
- package/dist/esm/entryPoints/deploy-run-controller.js.map +0 -1
- package/dist/esm/entryPoints/deploy-run-worker.js +0 -366
- package/dist/esm/entryPoints/deploy-run-worker.js.map +0 -1
- package/dist/esm/indexing/registerTasks.d.ts +0 -2
- package/dist/esm/indexing/registerTasks.js +0 -62
- package/dist/esm/indexing/registerTasks.js.map +0 -1
- package/dist/esm/telemetry/tracing.d.ts +0 -3
- package/dist/esm/telemetry/tracing.js +0 -58
- package/dist/esm/telemetry/tracing.js.map +0 -1
- /package/dist/esm/entryPoints/{deploy-index-controller.d.ts → managed-index-controller.d.ts} +0 -0
- /package/dist/esm/entryPoints/{deploy-index-worker.d.ts → managed-index-worker.d.ts} +0 -0
- /package/dist/esm/entryPoints/{deploy-run-controller.d.ts → managed-run-controller.d.ts} +0 -0
- /package/dist/esm/entryPoints/{deploy-run-worker.d.ts → managed-run-worker.d.ts} +0 -0
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
import { SuspendedProcessError, } from "@trigger.dev/core/v3";
|
|
2
|
+
import { TaskRunProcess } from "../../executions/taskRunProcess.js";
|
|
3
|
+
import { setTimeout as sleep } from "timers/promises";
|
|
4
|
+
import { RunExecutionSnapshotPoller } from "./poller.js";
|
|
5
|
+
import { assertExhaustive, tryCatch } from "@trigger.dev/core/utils";
|
|
6
|
+
import { MetadataClient } from "./overrides.js";
|
|
7
|
+
import { randomBytes } from "node:crypto";
|
|
8
|
+
import { SnapshotManager } from "./snapshot.js";
|
|
9
|
+
import { RunNotifier } from "./notifier.js";
|
|
10
|
+
class ExecutionAbortError extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "ExecutionAbortError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class RunExecution {
|
|
17
|
+
id;
|
|
18
|
+
executionAbortController;
|
|
19
|
+
_runFriendlyId;
|
|
20
|
+
currentAttemptNumber;
|
|
21
|
+
currentTaskRunEnv;
|
|
22
|
+
snapshotManager;
|
|
23
|
+
dequeuedAt;
|
|
24
|
+
podScheduledAt;
|
|
25
|
+
workerManifest;
|
|
26
|
+
env;
|
|
27
|
+
httpClient;
|
|
28
|
+
logger;
|
|
29
|
+
restoreCount;
|
|
30
|
+
taskRunProcess;
|
|
31
|
+
snapshotPoller;
|
|
32
|
+
lastHeartbeat;
|
|
33
|
+
isShuttingDown = false;
|
|
34
|
+
shutdownReason;
|
|
35
|
+
isCompletingRun = false;
|
|
36
|
+
ignoreSnapshotChanges = false;
|
|
37
|
+
supervisorSocket;
|
|
38
|
+
notifier;
|
|
39
|
+
metadataClient;
|
|
40
|
+
taskRunProcessProvider;
|
|
41
|
+
constructor(opts) {
|
|
42
|
+
this.id = randomBytes(4).toString("hex");
|
|
43
|
+
this.workerManifest = opts.workerManifest;
|
|
44
|
+
this.env = opts.env;
|
|
45
|
+
this.httpClient = opts.httpClient;
|
|
46
|
+
this.logger = opts.logger;
|
|
47
|
+
this.supervisorSocket = opts.supervisorSocket;
|
|
48
|
+
this.taskRunProcessProvider = opts.taskRunProcessProvider;
|
|
49
|
+
this.restoreCount = 0;
|
|
50
|
+
this.executionAbortController = new AbortController();
|
|
51
|
+
if (this.env.TRIGGER_METADATA_URL) {
|
|
52
|
+
this.metadataClient = new MetadataClient(this.env.TRIGGER_METADATA_URL);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Cancels the current execution.
|
|
57
|
+
*/
|
|
58
|
+
async cancel() {
|
|
59
|
+
if (this.isShuttingDown) {
|
|
60
|
+
throw new Error("cancel called after execution shut down");
|
|
61
|
+
}
|
|
62
|
+
this.sendDebugLog("cancelling attempt", { runId: this.runFriendlyId });
|
|
63
|
+
await this.taskRunProcess?.cancel();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Kills the current execution.
|
|
67
|
+
*/
|
|
68
|
+
async kill({ exitExecution = true } = {}) {
|
|
69
|
+
if (this.taskRunProcess) {
|
|
70
|
+
await this.taskRunProcessProvider.handleProcessAbort(this.taskRunProcess);
|
|
71
|
+
}
|
|
72
|
+
if (exitExecution) {
|
|
73
|
+
this.shutdownExecution("kill");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async shutdown() {
|
|
77
|
+
if (this.taskRunProcess) {
|
|
78
|
+
await this.taskRunProcessProvider.handleProcessAbort(this.taskRunProcess);
|
|
79
|
+
}
|
|
80
|
+
this.shutdownExecution("shutdown");
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Prepares the execution with task run environment variables.
|
|
84
|
+
* This should be called before executing, typically after a successful run to prepare for the next one.
|
|
85
|
+
*/
|
|
86
|
+
async prepareForExecution(opts) {
|
|
87
|
+
if (this.isShuttingDown) {
|
|
88
|
+
throw new Error("prepareForExecution called after execution shut down");
|
|
89
|
+
}
|
|
90
|
+
if (this.taskRunProcess) {
|
|
91
|
+
throw new Error("prepareForExecution called after process was already created");
|
|
92
|
+
}
|
|
93
|
+
// Set the task run environment so canExecute returns true
|
|
94
|
+
this.currentTaskRunEnv = opts.taskRunEnv;
|
|
95
|
+
this.taskRunProcess = await this.taskRunProcessProvider.getProcess({
|
|
96
|
+
taskRunEnv: opts.taskRunEnv,
|
|
97
|
+
isWarmStart: true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
attachTaskRunProcessHandlers(taskRunProcess) {
|
|
101
|
+
taskRunProcess.unsafeDetachEvtHandlers();
|
|
102
|
+
taskRunProcess.onTaskRunHeartbeat.attach(async (runId) => {
|
|
103
|
+
if (!this.runFriendlyId) {
|
|
104
|
+
this.sendDebugLog("onTaskRunHeartbeat: missing run ID", { heartbeatRunId: runId });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (runId !== this.runFriendlyId) {
|
|
108
|
+
this.sendDebugLog("onTaskRunHeartbeat: mismatched run ID", {
|
|
109
|
+
heartbeatRunId: runId,
|
|
110
|
+
expectedRunId: this.runFriendlyId,
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const [error] = await tryCatch(this.onHeartbeat());
|
|
115
|
+
if (error) {
|
|
116
|
+
this.sendDebugLog("onTaskRunHeartbeat: failed", { error: error.message });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
taskRunProcess.onSendDebugLog.attach(async (debugLog) => {
|
|
120
|
+
this.sendRuntimeDebugLog(debugLog.message, debugLog.properties);
|
|
121
|
+
});
|
|
122
|
+
taskRunProcess.onSetSuspendable.attach(async ({ suspendable }) => {
|
|
123
|
+
this.suspendable = suspendable;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Returns true if no run has been started yet and we're prepared for the next run.
|
|
128
|
+
*/
|
|
129
|
+
get canExecute() {
|
|
130
|
+
if (this.taskRunProcessProvider.hasPersistentProcess) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
// If we've ever had a run ID, this execution can't be reused
|
|
134
|
+
if (this._runFriendlyId) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
// We can execute if we have the task run environment ready
|
|
138
|
+
return !!this.currentTaskRunEnv;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Called by the RunController when it receives a websocket notification
|
|
142
|
+
* or when the snapshot poller detects a change.
|
|
143
|
+
*
|
|
144
|
+
* This is the main entry point for snapshot changes, but processing is deferred to the snapshot manager.
|
|
145
|
+
*/
|
|
146
|
+
async enqueueSnapshotChangesAndWait(snapshots) {
|
|
147
|
+
if (this.isShuttingDown) {
|
|
148
|
+
this.sendDebugLog("enqueueSnapshotChangeAndWait: shutting down, skipping");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (!this.snapshotManager) {
|
|
152
|
+
this.sendDebugLog("enqueueSnapshotChangeAndWait: missing snapshot manager");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
await this.snapshotManager.handleSnapshotChanges(snapshots);
|
|
156
|
+
}
|
|
157
|
+
async processSnapshotChange(runData, deprecated) {
|
|
158
|
+
const { run, snapshot, completedWaitpoints } = runData;
|
|
159
|
+
const snapshotMetadata = {
|
|
160
|
+
incomingSnapshotId: snapshot.friendlyId,
|
|
161
|
+
completedWaitpoints: completedWaitpoints.length,
|
|
162
|
+
};
|
|
163
|
+
if (this.ignoreSnapshotChanges) {
|
|
164
|
+
this.sendDebugLog("processSnapshotChange: ignoring snapshot change", {
|
|
165
|
+
incomingSnapshotId: snapshot.friendlyId,
|
|
166
|
+
completedWaitpoints: completedWaitpoints.length,
|
|
167
|
+
currentAttemptNumber: this.currentAttemptNumber,
|
|
168
|
+
newAttemptNumber: run.attemptNumber,
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (!this.snapshotManager) {
|
|
173
|
+
this.sendDebugLog("handleSnapshotChange: missing snapshot manager", snapshotMetadata);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (this.currentAttemptNumber && this.currentAttemptNumber !== run.attemptNumber) {
|
|
177
|
+
this.sendDebugLog("error: attempt number mismatch", snapshotMetadata);
|
|
178
|
+
// This is a rogue execution, a new one will already have been created elsewhere
|
|
179
|
+
await this.exitTaskRunProcessWithoutFailingRun({
|
|
180
|
+
flush: false,
|
|
181
|
+
reason: "attempt number mismatch",
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// DO NOT REMOVE (very noisy, but helpful for debugging)
|
|
186
|
+
// this.sendDebugLog(`processing snapshot change: ${snapshot.executionStatus}`, snapshotMetadata);
|
|
187
|
+
// Reset the snapshot poll interval so we don't do unnecessary work
|
|
188
|
+
this.snapshotPoller?.updateSnapshotId(snapshot.friendlyId);
|
|
189
|
+
this.snapshotPoller?.resetCurrentInterval();
|
|
190
|
+
if (deprecated) {
|
|
191
|
+
this.sendDebugLog("run execution is deprecated", { incomingSnapshot: snapshot });
|
|
192
|
+
await this.exitTaskRunProcessWithoutFailingRun({
|
|
193
|
+
flush: false,
|
|
194
|
+
reason: "deprecated execution",
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
switch (snapshot.executionStatus) {
|
|
199
|
+
case "PENDING_CANCEL": {
|
|
200
|
+
this.sendDebugLog("run was cancelled", snapshotMetadata);
|
|
201
|
+
const [error] = await tryCatch(this.cancel());
|
|
202
|
+
if (error) {
|
|
203
|
+
this.sendDebugLog("snapshot change: failed to cancel attempt", {
|
|
204
|
+
...snapshotMetadata,
|
|
205
|
+
error: error.message,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
this.abortExecution();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
case "QUEUED": {
|
|
212
|
+
this.sendDebugLog("run was re-queued", snapshotMetadata);
|
|
213
|
+
await this.exitTaskRunProcessWithoutFailingRun({ flush: true, reason: "re-queued" });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
case "FINISHED": {
|
|
217
|
+
this.sendDebugLog("run is finished", snapshotMetadata);
|
|
218
|
+
// We are finishing the run in handleCompletionResult, so we don't need to do anything here
|
|
219
|
+
if (this.isCompletingRun) {
|
|
220
|
+
this.sendDebugLog("run is finished but we're completing it, skipping", snapshotMetadata);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
await this.exitTaskRunProcessWithoutFailingRun({ flush: true, reason: "already-finished" });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
case "QUEUED_EXECUTING":
|
|
227
|
+
case "EXECUTING_WITH_WAITPOINTS": {
|
|
228
|
+
this.sendDebugLog("run is executing with waitpoints", snapshotMetadata);
|
|
229
|
+
// Wait for next status change - suspension is handled by the snapshot manager
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
case "SUSPENDED": {
|
|
233
|
+
this.sendDebugLog("run was suspended", snapshotMetadata);
|
|
234
|
+
// This will kill the process and fail the execution with a SuspendedProcessError
|
|
235
|
+
// We don't flush because we already did before suspending
|
|
236
|
+
await this.exitTaskRunProcessWithoutFailingRun({ flush: false, reason: "suspended" });
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
case "PENDING_EXECUTING": {
|
|
240
|
+
this.sendDebugLog("run is pending execution", snapshotMetadata);
|
|
241
|
+
if (completedWaitpoints.length === 0) {
|
|
242
|
+
this.sendDebugLog("no waitpoints to complete, nothing to do", snapshotMetadata);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const [error] = await tryCatch(this.restore());
|
|
246
|
+
if (error) {
|
|
247
|
+
this.sendDebugLog("failed to restore execution", {
|
|
248
|
+
...snapshotMetadata,
|
|
249
|
+
error: error.message,
|
|
250
|
+
});
|
|
251
|
+
this.abortExecution();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
case "EXECUTING": {
|
|
257
|
+
if (completedWaitpoints.length === 0) {
|
|
258
|
+
this.sendDebugLog("run is executing without completed waitpoints", snapshotMetadata);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
this.sendDebugLog("run is executing with completed waitpoints", snapshotMetadata);
|
|
262
|
+
if (!this.taskRunProcess) {
|
|
263
|
+
this.sendDebugLog("no task run process, ignoring completed waitpoints", snapshotMetadata);
|
|
264
|
+
this.abortExecution();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
for (const waitpoint of completedWaitpoints) {
|
|
268
|
+
this.taskRunProcess.waitpointCompleted(waitpoint);
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
case "RUN_CREATED": {
|
|
273
|
+
this.sendDebugLog("aborting execution: invalid status change: RUN_CREATED", snapshotMetadata);
|
|
274
|
+
this.abortExecution();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
default: {
|
|
278
|
+
assertExhaustive(snapshot.executionStatus);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async startAttempt({ isWarmStart, }) {
|
|
283
|
+
if (!this.runFriendlyId || !this.snapshotManager) {
|
|
284
|
+
throw new Error("Cannot start attempt: missing run or snapshot manager");
|
|
285
|
+
}
|
|
286
|
+
// Reset this for the new attempt
|
|
287
|
+
this.isCompletingRun = false;
|
|
288
|
+
this.sendDebugLog("starting attempt", { isWarmStart: String(isWarmStart) });
|
|
289
|
+
const attemptStartedAt = Date.now();
|
|
290
|
+
// Check for abort before each major async operation
|
|
291
|
+
if (this.executionAbortController.signal.aborted) {
|
|
292
|
+
throw new ExecutionAbortError("Execution aborted before start");
|
|
293
|
+
}
|
|
294
|
+
const start = await this.httpClient.startRunAttempt(this.runFriendlyId, this.snapshotManager.snapshotId, { isWarmStart });
|
|
295
|
+
if (this.executionAbortController.signal.aborted) {
|
|
296
|
+
throw new ExecutionAbortError("Execution aborted after start");
|
|
297
|
+
}
|
|
298
|
+
if (!start.success) {
|
|
299
|
+
throw new Error(`Start API call failed: ${start.error}`);
|
|
300
|
+
}
|
|
301
|
+
// A snapshot was just created, so update the snapshot ID
|
|
302
|
+
this.snapshotManager.updateSnapshot(start.data.snapshot.friendlyId, start.data.snapshot.executionStatus);
|
|
303
|
+
// Also set or update the attempt number - we do this to detect illegal attempt number changes, e.g. from stalled runners coming back online
|
|
304
|
+
const attemptNumber = start.data.run.attemptNumber;
|
|
305
|
+
if (attemptNumber && attemptNumber > 0) {
|
|
306
|
+
this.currentAttemptNumber = attemptNumber;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
this.sendDebugLog("error: invalid attempt number returned from start attempt", {
|
|
310
|
+
attemptNumber: String(attemptNumber),
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
const metrics = this.measureExecutionMetrics({
|
|
314
|
+
attemptCreatedAt: attemptStartedAt,
|
|
315
|
+
dequeuedAt: this.dequeuedAt?.getTime(),
|
|
316
|
+
podScheduledAt: this.podScheduledAt?.getTime(),
|
|
317
|
+
});
|
|
318
|
+
this.sendDebugLog("started attempt", { start: start.data });
|
|
319
|
+
return { ...start.data, metrics };
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Executes the run. This will return when the execution is complete and we should warm start.
|
|
323
|
+
* When this returns, the child process will have been cleaned up.
|
|
324
|
+
*/
|
|
325
|
+
async execute(runOpts) {
|
|
326
|
+
if (this.isShuttingDown) {
|
|
327
|
+
throw new Error("execute called after execution shut down");
|
|
328
|
+
}
|
|
329
|
+
// Setup initial state
|
|
330
|
+
this.runFriendlyId = runOpts.runFriendlyId;
|
|
331
|
+
// Create snapshot manager
|
|
332
|
+
this.snapshotManager = new SnapshotManager({
|
|
333
|
+
runFriendlyId: runOpts.runFriendlyId,
|
|
334
|
+
runnerId: this.env.TRIGGER_RUNNER_ID,
|
|
335
|
+
initialSnapshotId: runOpts.snapshotFriendlyId,
|
|
336
|
+
// We're just guessing here, but "PENDING_EXECUTING" is probably fine
|
|
337
|
+
initialStatus: "PENDING_EXECUTING",
|
|
338
|
+
logger: this.logger,
|
|
339
|
+
metadataClient: this.metadataClient,
|
|
340
|
+
onSnapshotChange: this.processSnapshotChange.bind(this),
|
|
341
|
+
onSuspendable: this.handleSuspendable.bind(this),
|
|
342
|
+
});
|
|
343
|
+
this.dequeuedAt = runOpts.dequeuedAt;
|
|
344
|
+
this.podScheduledAt = runOpts.podScheduledAt;
|
|
345
|
+
// Create and start services
|
|
346
|
+
this.snapshotPoller = new RunExecutionSnapshotPoller({
|
|
347
|
+
runFriendlyId: this.runFriendlyId,
|
|
348
|
+
snapshotFriendlyId: this.snapshotManager.snapshotId,
|
|
349
|
+
logger: this.logger,
|
|
350
|
+
snapshotPollIntervalSeconds: this.env.TRIGGER_SNAPSHOT_POLL_INTERVAL_SECONDS,
|
|
351
|
+
onPoll: this.fetchAndProcessSnapshotChanges.bind(this),
|
|
352
|
+
}).start();
|
|
353
|
+
this.notifier = new RunNotifier({
|
|
354
|
+
runFriendlyId: this.runFriendlyId,
|
|
355
|
+
supervisorSocket: this.supervisorSocket,
|
|
356
|
+
onNotify: this.fetchAndProcessSnapshotChanges.bind(this),
|
|
357
|
+
logger: this.logger,
|
|
358
|
+
}).start();
|
|
359
|
+
const [startError, start] = await tryCatch(this.startAttempt({ isWarmStart: runOpts.isWarmStart }));
|
|
360
|
+
if (startError) {
|
|
361
|
+
this.sendDebugLog("failed to start attempt", { error: startError.message });
|
|
362
|
+
this.shutdownExecution("failed to start attempt");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const [executeError] = await tryCatch(this.executeRunWrapper({ ...start, isWarmStart: runOpts.isWarmStart }));
|
|
366
|
+
if (executeError) {
|
|
367
|
+
this.sendDebugLog("failed to execute run", { error: executeError.message });
|
|
368
|
+
this.shutdownExecution("failed to execute run");
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
// This is here for safety, but it
|
|
372
|
+
this.shutdownExecution("execute call finished");
|
|
373
|
+
}
|
|
374
|
+
async executeRunWrapper({ run, snapshot, envVars, execution, metrics, isWarmStart, isImmediateRetry, }) {
|
|
375
|
+
this.currentTaskRunEnv = envVars;
|
|
376
|
+
const [executeError] = await tryCatch(this.executeRun({
|
|
377
|
+
run,
|
|
378
|
+
snapshot,
|
|
379
|
+
envVars,
|
|
380
|
+
execution,
|
|
381
|
+
metrics,
|
|
382
|
+
isWarmStart,
|
|
383
|
+
isImmediateRetry,
|
|
384
|
+
}));
|
|
385
|
+
if (!executeError) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (executeError instanceof SuspendedProcessError) {
|
|
389
|
+
this.sendDebugLog("execution was suspended", {
|
|
390
|
+
run: run.friendlyId,
|
|
391
|
+
snapshot: snapshot.friendlyId,
|
|
392
|
+
error: executeError.message,
|
|
393
|
+
});
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (executeError instanceof ExecutionAbortError) {
|
|
397
|
+
this.sendDebugLog("execution was aborted", {
|
|
398
|
+
run: run.friendlyId,
|
|
399
|
+
snapshot: snapshot.friendlyId,
|
|
400
|
+
error: executeError.message,
|
|
401
|
+
});
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
this.sendDebugLog("error while executing attempt", {
|
|
405
|
+
error: executeError.message,
|
|
406
|
+
runId: run.friendlyId,
|
|
407
|
+
snapshotId: snapshot.friendlyId,
|
|
408
|
+
});
|
|
409
|
+
const completion = {
|
|
410
|
+
id: execution.run.id,
|
|
411
|
+
ok: false,
|
|
412
|
+
retry: undefined,
|
|
413
|
+
error: TaskRunProcess.parseExecuteError(executeError),
|
|
414
|
+
};
|
|
415
|
+
const [completeError] = await tryCatch(this.complete({ completion }));
|
|
416
|
+
if (completeError) {
|
|
417
|
+
this.sendDebugLog("failed to complete run", { error: completeError.message });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async executeRun({ run, snapshot, envVars, execution, metrics, isWarmStart, isImmediateRetry, }) {
|
|
421
|
+
if (isImmediateRetry) {
|
|
422
|
+
await this.taskRunProcessProvider.handleImmediateRetry();
|
|
423
|
+
}
|
|
424
|
+
const taskRunEnv = this.currentTaskRunEnv ?? envVars;
|
|
425
|
+
if (!this.taskRunProcess || this.taskRunProcess.isBeingKilled) {
|
|
426
|
+
this.sendDebugLog("getting new task run process", { runId: execution.run.id });
|
|
427
|
+
this.taskRunProcess = await this.taskRunProcessProvider.getProcess({
|
|
428
|
+
taskRunEnv: { ...taskRunEnv, TRIGGER_PROJECT_REF: execution.project.ref },
|
|
429
|
+
isWarmStart,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
this.sendDebugLog("using prepared task run process", { runId: execution.run.id });
|
|
434
|
+
}
|
|
435
|
+
this.attachTaskRunProcessHandlers(this.taskRunProcess);
|
|
436
|
+
this.sendDebugLog("executing task run process", { runId: execution.run.id });
|
|
437
|
+
const abortHandler = async () => {
|
|
438
|
+
this.sendDebugLog("execution aborted during task run, cleaning up process", {
|
|
439
|
+
runId: execution.run.id,
|
|
440
|
+
});
|
|
441
|
+
if (this.taskRunProcess) {
|
|
442
|
+
await this.taskRunProcessProvider.handleProcessAbort(this.taskRunProcess);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
// Set up an abort handler that will cleanup the task run process
|
|
446
|
+
this.executionAbortController.signal.addEventListener("abort", abortHandler);
|
|
447
|
+
const completion = await this.taskRunProcess.execute({
|
|
448
|
+
payload: {
|
|
449
|
+
execution,
|
|
450
|
+
traceContext: execution.run.traceContext ?? {},
|
|
451
|
+
metrics,
|
|
452
|
+
},
|
|
453
|
+
messageId: run.friendlyId,
|
|
454
|
+
env: envVars,
|
|
455
|
+
}, isWarmStart);
|
|
456
|
+
this.executionAbortController.signal.removeEventListener("abort", abortHandler);
|
|
457
|
+
// If we get here, the task completed normally
|
|
458
|
+
this.sendDebugLog("completed run attempt", { attemptSuccess: completion.ok });
|
|
459
|
+
// Return the process to the provider - this handles all cleanup logic
|
|
460
|
+
const [returnError] = await tryCatch(this.taskRunProcessProvider.returnProcess(this.taskRunProcess));
|
|
461
|
+
if (returnError) {
|
|
462
|
+
this.sendDebugLog("failed to return task run process, submitting completion anyway", {
|
|
463
|
+
error: returnError.message,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
const [completionError] = await tryCatch(this.complete({ completion }));
|
|
467
|
+
if (completionError) {
|
|
468
|
+
this.sendDebugLog("failed to complete run", { error: completionError.message });
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
async complete({ completion }) {
|
|
472
|
+
if (!this.runFriendlyId || !this.snapshotManager) {
|
|
473
|
+
throw new Error("cannot complete run: missing run or snapshot manager");
|
|
474
|
+
}
|
|
475
|
+
this.isCompletingRun = true;
|
|
476
|
+
const completionResult = await this.httpClient.completeRunAttempt(this.runFriendlyId, this.snapshotManager.snapshotId, { completion });
|
|
477
|
+
if (!completionResult.success) {
|
|
478
|
+
throw new Error(`failed to submit completion: ${completionResult.error}`);
|
|
479
|
+
}
|
|
480
|
+
await this.handleCompletionResult({
|
|
481
|
+
completion,
|
|
482
|
+
result: completionResult.data.result,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
async handleCompletionResult({ completion, result, }) {
|
|
486
|
+
this.sendDebugLog(`completion result: ${result.attemptStatus}`, {
|
|
487
|
+
attemptSuccess: completion.ok,
|
|
488
|
+
attemptStatus: result.attemptStatus,
|
|
489
|
+
snapshotId: result.snapshot.friendlyId,
|
|
490
|
+
runId: result.run.friendlyId,
|
|
491
|
+
});
|
|
492
|
+
const snapshotStatus = this.convertAttemptStatusToSnapshotStatus(result.attemptStatus);
|
|
493
|
+
// Update our snapshot ID to match the completion result to ensure any subsequent API calls use the correct snapshot
|
|
494
|
+
this.updateSnapshotAfterCompletion(result.snapshot.friendlyId, snapshotStatus);
|
|
495
|
+
const { attemptStatus } = result;
|
|
496
|
+
switch (attemptStatus) {
|
|
497
|
+
case "RUN_FINISHED":
|
|
498
|
+
case "RUN_PENDING_CANCEL":
|
|
499
|
+
case "RETRY_QUEUED": {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
case "RETRY_IMMEDIATELY": {
|
|
503
|
+
if (attemptStatus !== "RETRY_IMMEDIATELY") {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (completion.ok) {
|
|
507
|
+
throw new Error("Should retry but completion OK.");
|
|
508
|
+
}
|
|
509
|
+
if (!completion.retry) {
|
|
510
|
+
throw new Error("Should retry but missing retry params.");
|
|
511
|
+
}
|
|
512
|
+
await this.retryImmediately({ retryOpts: completion.retry });
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
default: {
|
|
516
|
+
assertExhaustive(attemptStatus);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
updateSnapshotAfterCompletion(snapshotId, status) {
|
|
521
|
+
this.snapshotManager?.updateSnapshot(snapshotId, status);
|
|
522
|
+
this.snapshotPoller?.updateSnapshotId(snapshotId);
|
|
523
|
+
}
|
|
524
|
+
convertAttemptStatusToSnapshotStatus(attemptStatus) {
|
|
525
|
+
switch (attemptStatus) {
|
|
526
|
+
case "RUN_FINISHED":
|
|
527
|
+
return "FINISHED";
|
|
528
|
+
case "RUN_PENDING_CANCEL":
|
|
529
|
+
return "PENDING_CANCEL";
|
|
530
|
+
case "RETRY_QUEUED":
|
|
531
|
+
return "QUEUED";
|
|
532
|
+
case "RETRY_IMMEDIATELY":
|
|
533
|
+
return "EXECUTING";
|
|
534
|
+
default:
|
|
535
|
+
assertExhaustive(attemptStatus);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
measureExecutionMetrics({ attemptCreatedAt, dequeuedAt, podScheduledAt, }) {
|
|
539
|
+
const metrics = [
|
|
540
|
+
{
|
|
541
|
+
name: "start",
|
|
542
|
+
event: "create_attempt",
|
|
543
|
+
timestamp: attemptCreatedAt,
|
|
544
|
+
duration: Date.now() - attemptCreatedAt,
|
|
545
|
+
},
|
|
546
|
+
];
|
|
547
|
+
if (dequeuedAt) {
|
|
548
|
+
metrics.push({
|
|
549
|
+
name: "start",
|
|
550
|
+
event: "dequeue",
|
|
551
|
+
timestamp: dequeuedAt,
|
|
552
|
+
duration: 0,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
if (podScheduledAt) {
|
|
556
|
+
metrics.push({
|
|
557
|
+
name: "start",
|
|
558
|
+
event: "pod_scheduled",
|
|
559
|
+
timestamp: podScheduledAt,
|
|
560
|
+
duration: 0,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
return metrics;
|
|
564
|
+
}
|
|
565
|
+
async retryImmediately({ retryOpts }) {
|
|
566
|
+
this.sendDebugLog("retrying run immediately", {
|
|
567
|
+
timestamp: retryOpts.timestamp,
|
|
568
|
+
delay: retryOpts.delay,
|
|
569
|
+
});
|
|
570
|
+
const delay = retryOpts.timestamp - Date.now();
|
|
571
|
+
if (delay > 0) {
|
|
572
|
+
// Wait for retry delay to pass
|
|
573
|
+
await sleep(delay);
|
|
574
|
+
}
|
|
575
|
+
// Start and execute next attempt
|
|
576
|
+
const [startError, start] = await tryCatch(this.enableIgnoreSnapshotChanges(() => this.startAttempt({ isWarmStart: true })));
|
|
577
|
+
if (startError) {
|
|
578
|
+
this.sendDebugLog("failed to start attempt for retry", { error: startError.message });
|
|
579
|
+
this.shutdownExecution("retryImmediately: failed to start attempt");
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const [executeError] = await tryCatch(this.executeRunWrapper({ ...start, isWarmStart: true, isImmediateRetry: true }));
|
|
583
|
+
if (executeError) {
|
|
584
|
+
this.sendDebugLog("failed to execute run for retry", { error: executeError.message });
|
|
585
|
+
this.shutdownExecution("retryImmediately: failed to execute run");
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
async enableIgnoreSnapshotChanges(fn) {
|
|
590
|
+
this.ignoreSnapshotChanges = true;
|
|
591
|
+
try {
|
|
592
|
+
return await fn();
|
|
593
|
+
}
|
|
594
|
+
finally {
|
|
595
|
+
this.ignoreSnapshotChanges = false;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Restores a suspended execution from PENDING_EXECUTING
|
|
600
|
+
*/
|
|
601
|
+
async restore() {
|
|
602
|
+
this.sendDebugLog("restoring execution");
|
|
603
|
+
if (!this.runFriendlyId || !this.snapshotManager) {
|
|
604
|
+
throw new Error("Cannot restore: missing run or snapshot manager");
|
|
605
|
+
}
|
|
606
|
+
// Short delay to give websocket time to reconnect
|
|
607
|
+
await sleep(100);
|
|
608
|
+
// Process any env overrides
|
|
609
|
+
await this.processEnvOverrides("restore");
|
|
610
|
+
const continuationResult = await this.httpClient.continueRunExecution(this.runFriendlyId, this.snapshotManager.snapshotId);
|
|
611
|
+
if (!continuationResult.success) {
|
|
612
|
+
throw new Error(continuationResult.error);
|
|
613
|
+
}
|
|
614
|
+
// Track restore count
|
|
615
|
+
this.restoreCount++;
|
|
616
|
+
}
|
|
617
|
+
async exitTaskRunProcessWithoutFailingRun({ flush, reason, }) {
|
|
618
|
+
await this.taskRunProcessProvider.suspendProcess(flush, this.taskRunProcess);
|
|
619
|
+
// No services should be left running after this line - let's make sure of it
|
|
620
|
+
this.shutdownExecution(`exitTaskRunProcessWithoutFailingRun: ${reason}`);
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Processes env overrides from the metadata service. Generally called when we're resuming from a suspended state.
|
|
624
|
+
*/
|
|
625
|
+
async processEnvOverrides(reason, shouldPollForSnapshotChanges) {
|
|
626
|
+
if (!this.metadataClient) {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
const [error, overrides] = await this.metadataClient.getEnvOverrides();
|
|
630
|
+
if (error) {
|
|
631
|
+
this.sendDebugLog("[override] failed to fetch", {
|
|
632
|
+
reason,
|
|
633
|
+
error: error.message,
|
|
634
|
+
});
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
if (overrides.TRIGGER_RUN_ID && overrides.TRIGGER_RUN_ID !== this.runFriendlyId) {
|
|
638
|
+
this.sendDebugLog("[override] run ID mismatch, ignoring overrides", {
|
|
639
|
+
reason,
|
|
640
|
+
currentRunId: this.runFriendlyId,
|
|
641
|
+
incomingRunId: overrides.TRIGGER_RUN_ID,
|
|
642
|
+
});
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
this.sendDebugLog(`[override] processing: ${reason}`, {
|
|
646
|
+
overrides,
|
|
647
|
+
currentEnv: this.env.raw,
|
|
648
|
+
});
|
|
649
|
+
// Override the env with the new values
|
|
650
|
+
this.env.override(overrides);
|
|
651
|
+
// Update services with new values
|
|
652
|
+
if (overrides.TRIGGER_SNAPSHOT_POLL_INTERVAL_SECONDS) {
|
|
653
|
+
this.snapshotPoller?.updateInterval(this.env.TRIGGER_SNAPSHOT_POLL_INTERVAL_SECONDS * 1000);
|
|
654
|
+
}
|
|
655
|
+
if (overrides.TRIGGER_SUPERVISOR_API_PROTOCOL ||
|
|
656
|
+
overrides.TRIGGER_SUPERVISOR_API_DOMAIN ||
|
|
657
|
+
overrides.TRIGGER_SUPERVISOR_API_PORT) {
|
|
658
|
+
this.httpClient.updateApiUrl(this.env.TRIGGER_SUPERVISOR_API_URL);
|
|
659
|
+
}
|
|
660
|
+
if (overrides.TRIGGER_RUNNER_ID) {
|
|
661
|
+
this.httpClient.updateRunnerId(this.env.TRIGGER_RUNNER_ID);
|
|
662
|
+
}
|
|
663
|
+
// Poll for snapshot changes immediately
|
|
664
|
+
if (shouldPollForSnapshotChanges) {
|
|
665
|
+
this.sendDebugLog("[override] polling for snapshot changes", { reason });
|
|
666
|
+
this.fetchAndProcessSnapshotChanges("restore").catch(() => { });
|
|
667
|
+
}
|
|
668
|
+
return {
|
|
669
|
+
overrides,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
async onHeartbeat() {
|
|
673
|
+
if (!this.runFriendlyId) {
|
|
674
|
+
this.sendDebugLog("heartbeat: missing run ID");
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
if (!this.snapshotManager) {
|
|
678
|
+
this.sendDebugLog("heartbeat: missing snapshot manager");
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
this.sendDebugLog("heartbeat");
|
|
682
|
+
const response = await this.httpClient.heartbeatRun(this.runFriendlyId, this.snapshotManager.snapshotId);
|
|
683
|
+
if (!response.success) {
|
|
684
|
+
this.sendDebugLog("heartbeat: failed", { error: response.error });
|
|
685
|
+
}
|
|
686
|
+
this.lastHeartbeat = new Date();
|
|
687
|
+
}
|
|
688
|
+
sendDebugLog(message, properties, runIdOverride) {
|
|
689
|
+
this.logger.sendDebugLog({
|
|
690
|
+
runId: runIdOverride ?? this.runFriendlyId,
|
|
691
|
+
message: `[execution] ${message}`,
|
|
692
|
+
properties: {
|
|
693
|
+
...properties,
|
|
694
|
+
runId: this.runFriendlyId,
|
|
695
|
+
snapshotId: this.currentSnapshotFriendlyId,
|
|
696
|
+
executionId: this.id,
|
|
697
|
+
executionRestoreCount: this.restoreCount,
|
|
698
|
+
lastHeartbeat: this.lastHeartbeat?.toISOString(),
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
sendRuntimeDebugLog(message, properties, runIdOverride) {
|
|
703
|
+
this.logger.sendDebugLog({
|
|
704
|
+
runId: runIdOverride ?? this.runFriendlyId,
|
|
705
|
+
message: `[runtime] ${message}`,
|
|
706
|
+
print: false,
|
|
707
|
+
properties: {
|
|
708
|
+
...properties,
|
|
709
|
+
runId: this.runFriendlyId,
|
|
710
|
+
snapshotId: this.currentSnapshotFriendlyId,
|
|
711
|
+
executionId: this.id,
|
|
712
|
+
executionRestoreCount: this.restoreCount,
|
|
713
|
+
lastHeartbeat: this.lastHeartbeat?.toISOString(),
|
|
714
|
+
},
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
set suspendable(suspendable) {
|
|
718
|
+
this.snapshotManager?.setSuspendable(suspendable).catch((error) => {
|
|
719
|
+
this.sendDebugLog("failed to set suspendable", { error: error.message });
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
// Ensure we can only set this once
|
|
723
|
+
set runFriendlyId(id) {
|
|
724
|
+
if (this._runFriendlyId) {
|
|
725
|
+
throw new Error("Run ID already set");
|
|
726
|
+
}
|
|
727
|
+
this._runFriendlyId = id;
|
|
728
|
+
}
|
|
729
|
+
get runFriendlyId() {
|
|
730
|
+
return this._runFriendlyId;
|
|
731
|
+
}
|
|
732
|
+
get currentSnapshotFriendlyId() {
|
|
733
|
+
return this.snapshotManager?.snapshotId;
|
|
734
|
+
}
|
|
735
|
+
get taskRunEnv() {
|
|
736
|
+
return this.currentTaskRunEnv;
|
|
737
|
+
}
|
|
738
|
+
get metrics() {
|
|
739
|
+
return {
|
|
740
|
+
execution: {
|
|
741
|
+
restoreCount: this.restoreCount,
|
|
742
|
+
lastHeartbeat: this.lastHeartbeat,
|
|
743
|
+
},
|
|
744
|
+
poller: this.snapshotPoller?.metrics,
|
|
745
|
+
notifier: this.notifier?.metrics,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
get isAborted() {
|
|
749
|
+
return this.executionAbortController.signal.aborted;
|
|
750
|
+
}
|
|
751
|
+
abortExecution() {
|
|
752
|
+
if (this.isAborted) {
|
|
753
|
+
this.sendDebugLog("execution already aborted");
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
this.executionAbortController.abort();
|
|
757
|
+
this.shutdownExecution("abortExecution");
|
|
758
|
+
}
|
|
759
|
+
shutdownExecution(reason) {
|
|
760
|
+
if (this.isShuttingDown) {
|
|
761
|
+
this.sendDebugLog(`[shutdown] ${reason} (already shutting down)`, {
|
|
762
|
+
firstShutdownReason: this.shutdownReason,
|
|
763
|
+
});
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
this.sendDebugLog(`[shutdown] ${reason}`);
|
|
767
|
+
this.isShuttingDown = true;
|
|
768
|
+
this.shutdownReason = reason;
|
|
769
|
+
this.snapshotPoller?.stop();
|
|
770
|
+
this.snapshotManager?.stop();
|
|
771
|
+
this.notifier?.stop();
|
|
772
|
+
this.taskRunProcess?.unsafeDetachEvtHandlers();
|
|
773
|
+
}
|
|
774
|
+
async handleSuspendable(suspendableSnapshot) {
|
|
775
|
+
this.sendDebugLog("handleSuspendable", { suspendableSnapshot });
|
|
776
|
+
if (!this.snapshotManager) {
|
|
777
|
+
this.sendDebugLog("handleSuspendable: missing snapshot manager", { suspendableSnapshot });
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
// Ensure this is the current snapshot
|
|
781
|
+
if (suspendableSnapshot.id !== this.currentSnapshotFriendlyId) {
|
|
782
|
+
this.sendDebugLog("snapshot changed before cleanup, abort", {
|
|
783
|
+
suspendableSnapshot,
|
|
784
|
+
currentSnapshotId: this.currentSnapshotFriendlyId,
|
|
785
|
+
});
|
|
786
|
+
this.abortExecution();
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
// First cleanup the task run process
|
|
790
|
+
const [error] = await tryCatch(this.taskRunProcess?.cleanup(false));
|
|
791
|
+
if (error) {
|
|
792
|
+
this.sendDebugLog("failed to cleanup task run process, carrying on", {
|
|
793
|
+
suspendableSnapshot,
|
|
794
|
+
error: error.message,
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
// Double check snapshot hasn't changed after cleanup
|
|
798
|
+
if (suspendableSnapshot.id !== this.currentSnapshotFriendlyId) {
|
|
799
|
+
this.sendDebugLog("snapshot changed after cleanup, abort", {
|
|
800
|
+
suspendableSnapshot,
|
|
801
|
+
currentSnapshotId: this.currentSnapshotFriendlyId,
|
|
802
|
+
});
|
|
803
|
+
this.abortExecution();
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (!this.runFriendlyId) {
|
|
807
|
+
this.sendDebugLog("missing run ID for suspension, abort", { suspendableSnapshot });
|
|
808
|
+
this.abortExecution();
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
// Call the suspend API with the current snapshot ID
|
|
812
|
+
const suspendResult = await this.httpClient.suspendRun(this.runFriendlyId, suspendableSnapshot.id);
|
|
813
|
+
if (!suspendResult.success) {
|
|
814
|
+
this.sendDebugLog("suspension request failed, staying alive 🎶", {
|
|
815
|
+
suspendableSnapshot,
|
|
816
|
+
error: suspendResult.error,
|
|
817
|
+
});
|
|
818
|
+
// This is fine, we'll wait for the next status change
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (!suspendResult.data.ok) {
|
|
822
|
+
this.sendDebugLog("suspension request returned error, staying alive 🎶", {
|
|
823
|
+
suspendableSnapshot,
|
|
824
|
+
error: suspendResult.data.error,
|
|
825
|
+
});
|
|
826
|
+
// This is fine, we'll wait for the next status change
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
this.sendDebugLog("suspending, any day now 🚬", { suspendableSnapshot });
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Fetches the latest execution data and enqueues snapshot changes. Used by both poller and notification handlers.
|
|
833
|
+
* @param source string - where this call originated (e.g. 'poller', 'notification')
|
|
834
|
+
*/
|
|
835
|
+
async fetchAndProcessSnapshotChanges(source) {
|
|
836
|
+
if (!this.runFriendlyId) {
|
|
837
|
+
this.sendDebugLog(`fetchAndProcessSnapshotChanges: missing runFriendlyId`, { source });
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
// Use the last processed snapshot as the since parameter
|
|
841
|
+
const sinceSnapshotId = this.currentSnapshotFriendlyId;
|
|
842
|
+
if (!sinceSnapshotId) {
|
|
843
|
+
this.sendDebugLog(`fetchAndProcessSnapshotChanges: missing sinceSnapshotId`, { source });
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
const response = await this.httpClient.getSnapshotsSince(this.runFriendlyId, sinceSnapshotId);
|
|
847
|
+
if (!response.success) {
|
|
848
|
+
this.sendDebugLog(`fetchAndProcessSnapshotChanges: failed to get snapshots since`, {
|
|
849
|
+
source,
|
|
850
|
+
error: response.error,
|
|
851
|
+
});
|
|
852
|
+
await this.processEnvOverrides("snapshots since error");
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const { snapshots } = response.data;
|
|
856
|
+
if (!snapshots.length) {
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const [error] = await tryCatch(this.enqueueSnapshotChangesAndWait(snapshots));
|
|
860
|
+
if (error) {
|
|
861
|
+
this.sendDebugLog(`fetchAndProcessSnapshotChanges: failed to enqueue and process snapshot change`, {
|
|
862
|
+
source,
|
|
863
|
+
error: error.message,
|
|
864
|
+
});
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
//# sourceMappingURL=execution.js.map
|