@ohos-ports/nx 23.3.0-beta.2 → 24.0.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -6
- package/dist/bin/nx.js +2 -2
- package/dist/bin/post-install.js +5 -3
- package/dist/nx-cloud-internals.d.ts +1 -0
- package/dist/nx-cloud-internals.js +7 -1
- package/dist/src/adapter/ngcli-adapter.js +2 -2
- package/dist/src/ai/clone-ai-config-repo.js +49 -12
- package/dist/src/command-line/add/add.d.ts +2 -0
- package/dist/src/command-line/add/add.js +23 -5
- package/dist/src/command-line/affected/affected.js +2 -2
- package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +0 -12
- package/dist/src/command-line/graph/graph.js +38 -10
- package/dist/src/command-line/init/command-object.js +0 -12
- package/dist/src/command-line/init/implementation/add-nx-to-nest.js +21 -62
- package/dist/src/command-line/init/init-v2.js +0 -2
- package/dist/src/command-line/migrate/agentic/close-agent-session.d.ts +1 -1
- package/dist/src/command-line/migrate/agentic/close-agent-session.js +4 -3
- package/dist/src/command-line/migrate/agentic/definitions.d.ts +1 -3
- package/dist/src/command-line/migrate/agentic/definitions.js +5 -97
- package/dist/src/command-line/migrate/agentic/handoff-gitignore.d.ts +7 -45
- package/dist/src/command-line/migrate/agentic/handoff-gitignore.js +9 -51
- package/dist/src/command-line/migrate/agentic/handoff.d.ts +1 -43
- package/dist/src/command-line/migrate/agentic/handoff.js +3 -119
- package/dist/src/command-line/migrate/agentic/instruction-files.d.ts +0 -21
- package/dist/src/command-line/migrate/agentic/instruction-files.js +6 -27
- package/dist/src/command-line/migrate/agentic/master/run-master-session.d.ts +19 -1
- package/dist/src/command-line/migrate/agentic/master/run-master-session.js +88 -49
- package/dist/src/command-line/migrate/agentic/master/spawn-master.d.ts +1 -0
- package/dist/src/command-line/migrate/agentic/master/spawn-master.js +86 -24
- package/dist/src/command-line/migrate/agentic/print-dropped-agent-context.d.ts +2 -2
- package/dist/src/command-line/migrate/agentic/print-dropped-agent-context.js +2 -2
- package/dist/src/command-line/migrate/agentic/prompts/fragments.d.ts +1 -12
- package/dist/src/command-line/migrate/agentic/prompts/fragments.js +8 -17
- package/dist/src/command-line/migrate/agentic/prompts/shared-rendering.d.ts +0 -15
- package/dist/src/command-line/migrate/agentic/prompts/shared-rendering.js +3 -100
- package/dist/src/command-line/migrate/agentic/select.d.ts +12 -6
- package/dist/src/command-line/migrate/agentic/select.js +38 -8
- package/dist/src/command-line/migrate/agentic/types.d.ts +9 -68
- package/dist/src/command-line/migrate/agentic/types.js +6 -8
- package/dist/src/command-line/migrate/command-object.js +6 -10
- package/dist/src/command-line/migrate/execute-migration.d.ts +6 -6
- package/dist/src/command-line/migrate/execute-migration.js +25 -15
- package/dist/src/command-line/migrate/migrate-analytics.d.ts +18 -1
- package/dist/src/command-line/migrate/migrate-analytics.js +38 -0
- package/dist/src/command-line/migrate/migrate-commits.d.ts +29 -17
- package/dist/src/command-line/migrate/migrate-commits.js +86 -32
- package/dist/src/command-line/migrate/migrate-config.d.ts +6 -6
- package/dist/src/command-line/migrate/migrate-config.js +12 -6
- package/dist/src/command-line/migrate/migrate-output.d.ts +1 -6
- package/dist/src/command-line/migrate/migrate-output.js +1 -10
- package/dist/src/command-line/migrate/migrate.d.ts +6 -30
- package/dist/src/command-line/migrate/migrate.js +257 -255
- package/dist/src/command-line/migrate/resolve-package-version.d.ts +2 -4
- package/dist/src/command-line/migrate/resolve-package-version.js +2 -35
- package/dist/src/command-line/migrate/run/agent-output.d.ts +2 -0
- package/dist/src/command-line/migrate/run/agent-output.js +9 -0
- package/dist/src/command-line/migrate/run/broker.d.ts +14 -5
- package/dist/src/command-line/migrate/run/broker.js +69 -21
- package/dist/src/command-line/migrate/run/clean-retry.js +4 -2
- package/dist/src/command-line/migrate/run/existing-run-report.d.ts +1 -1
- package/dist/src/command-line/migrate/run/existing-run-report.js +7 -5
- package/dist/src/command-line/migrate/run/give-up.d.ts +4 -3
- package/dist/src/command-line/migrate/run/give-up.js +8 -5
- package/dist/src/command-line/migrate/run/index.d.ts +7 -5
- package/dist/src/command-line/migrate/run/index.js +16 -1
- package/dist/src/command-line/migrate/run/issues.js +7 -2
- package/dist/src/command-line/migrate/run/orchestrator.d.ts +61 -4
- package/dist/src/command-line/migrate/run/orchestrator.js +397 -133
- package/dist/src/command-line/migrate/run/run-state.d.ts +17 -1
- package/dist/src/command-line/migrate/run/run-state.js +41 -2
- package/dist/src/command-line/migrate/run/runbook.d.ts +1 -0
- package/dist/src/command-line/migrate/run/runbook.js +115 -18
- package/dist/src/command-line/migrate/run/state-lock.d.ts +29 -4
- package/dist/src/command-line/migrate/run/state-lock.js +102 -10
- package/dist/src/command-line/migrate/run/state-machine.js +4 -1
- package/dist/src/command-line/migrate/run/util.d.ts +7 -2
- package/dist/src/command-line/migrate/run/util.js +30 -3
- package/dist/src/command-line/migrate/run/worker.d.ts +1 -1
- package/dist/src/command-line/migrate/run/worker.js +252 -250
- package/dist/src/command-line/migrate/sort-migrations.d.ts +1 -7
- package/dist/src/command-line/migrate/sort-migrations.js +1 -12
- package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +17 -12
- package/dist/src/command-line/release/changelog.js +9 -6
- package/dist/src/command-line/release/config/config.d.ts +1 -0
- package/dist/src/command-line/release/config/config.js +48 -9
- package/dist/src/command-line/release/release.js +2 -2
- package/dist/src/command-line/release/utils/git.js +8 -7
- package/dist/src/command-line/release/utils/interpolate-release-config.d.ts +6 -0
- package/dist/src/command-line/release/utils/interpolate-release-config.js +39 -0
- package/dist/src/command-line/release/utils/release-graph.d.ts +5 -0
- package/dist/src/command-line/release/utils/release-graph.js +1 -0
- package/dist/src/command-line/release/utils/remote-release-clients/github.js +5 -6
- package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +5 -6
- package/dist/src/command-line/release/utils/resolve-changelog-renderer.js +2 -2
- package/dist/src/command-line/release/utils/shared.js +10 -10
- package/dist/src/command-line/release/utils/version-plan-utils.d.ts +3 -12
- package/dist/src/command-line/release/utils/version-plan-utils.js +33 -55
- package/dist/src/command-line/release/version/version-actions.js +2 -1
- package/dist/src/command-line/release/version.js +5 -2
- package/dist/src/command-line/reset/reset.js +58 -62
- package/dist/src/command-line/run/executor-utils.js +1 -0
- package/dist/src/command-line/run/run-one.d.ts +20 -1
- package/dist/src/command-line/run/run-one.js +153 -5
- package/dist/src/command-line/run/run.js +2 -1
- package/dist/src/command-line/show/projects.js +5 -3
- package/dist/src/command-line/show/show-target/info.d.ts +10 -1
- package/dist/src/command-line/show/show-target/info.js +100 -15
- package/dist/src/command-line/show/show-target/inputs.js +10 -2
- package/dist/src/command-line/show/show-target/utils.js +1 -0
- package/dist/src/command-line/watch/command-object.js +3 -22
- package/dist/src/command-line/watch/watch.d.ts +0 -7
- package/dist/src/command-line/watch/watch.js +1 -1
- package/dist/src/command-line/yargs-utils/shared-options.js +1 -1
- package/dist/src/config/misc-interfaces.d.ts +18 -0
- package/dist/src/config/nx-json.d.ts +22 -11
- package/dist/src/config/task-graph.d.ts +1 -1
- package/dist/src/config/workspace-json-project-json.d.ts +88 -0
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/core/graph/runtime.js +1 -1
- package/dist/src/core/graph/styles.css +1 -1
- package/dist/src/core/graph/styles.js +1 -1
- package/dist/src/daemon/client/client.d.ts +15 -0
- package/dist/src/daemon/client/client.js +181 -71
- package/dist/src/daemon/client/daemon-socket-messenger.d.ts +1 -1
- package/dist/src/daemon/client/daemon-socket-messenger.js +2 -2
- package/dist/src/daemon/server/client-socket-context.js +1 -1
- package/dist/src/daemon/server/handle-process-in-background.js +21 -9
- package/dist/src/daemon/server/handle-select-affected-tasks.d.ts +2 -1
- package/dist/src/daemon/server/handle-select-affected-tasks.js +21 -13
- package/dist/src/daemon/server/native-js-loader.d.ts +1 -0
- package/dist/src/daemon/server/native-js-loader.js +21 -0
- package/dist/src/daemon/server/project-graph-incremental-recomputation.js +4 -8
- package/dist/src/daemon/server/server.d.ts +2 -1
- package/dist/src/daemon/server/server.js +17 -4
- package/dist/src/daemon/server/shutdown-utils.js +0 -3
- package/dist/src/daemon/server/start.d.ts +1 -0
- package/dist/src/daemon/server/start.js +2 -0
- package/dist/src/daemon/socket-utils.d.ts +11 -9
- package/dist/src/daemon/socket-utils.js +31 -61
- package/dist/src/daemon/tmp-dir.d.ts +19 -14
- package/dist/src/daemon/tmp-dir.js +142 -354
- package/dist/src/devkit-exports.d.ts +9 -1
- package/dist/src/devkit-exports.js +12 -1
- package/dist/src/devkit-internals.d.ts +6 -5
- package/dist/src/devkit-internals.js +13 -4
- package/dist/src/executors/run-commands/run-commands.impl.d.ts +4 -2
- package/dist/src/executors/run-commands/run-commands.impl.js +53 -27
- package/dist/src/executors/run-commands/running-tasks.d.ts +13 -4
- package/dist/src/executors/run-commands/running-tasks.js +148 -95
- package/dist/src/executors/run-commands/schema.json +1 -1
- package/dist/src/hasher/check-task-files.js +5 -8
- package/dist/src/hasher/file-hasher.js +9 -7
- package/dist/src/hasher/hash-task.js +4 -0
- package/dist/src/hasher/native-task-hasher-impl.js +3 -1
- package/dist/src/hasher/task-hasher.d.ts +2 -0
- package/dist/src/hasher/task-hasher.js +4 -1
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.d.ts +1 -2
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +6 -11
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md +2 -2
- package/dist/src/migrations/update-24-0-0/enable-legacy-affected.d.ts +4 -0
- package/dist/src/migrations/update-24-0-0/enable-legacy-affected.js +15 -0
- package/dist/src/migrations/update-24-0-0/enable-legacy-affected.md +30 -0
- package/dist/src/migrations/update-24-0-0/find-custom-hashers.d.ts +3 -0
- package/dist/src/migrations/update-24-0-0/find-custom-hashers.js +100 -0
- package/dist/src/migrations/update-24-0-0/find-custom-hashers.md +63 -0
- package/dist/src/migrations/update-24-0-0/move-custom-hashers-to-inputs.md +146 -0
- package/dist/src/migrations/update-24-0-0/rename-ultracache-ignore-options.d.ts +3 -0
- package/dist/src/migrations/update-24-0-0/rename-ultracache-ignore-options.js +130 -0
- package/dist/src/migrations/update-24-0-0/rename-ultracache-ignore-options.md +62 -0
- package/dist/src/native/assert-supported-platform.js +1 -1
- package/dist/src/native/index.d.ts +238 -18
- package/dist/src/native/index.js +6 -1
- package/dist/src/native/native-bindings.js +15 -6
- package/dist/src/native/nx.openharmony-arm64.node +0 -0
- package/dist/src/native/nx.wasi-browser.js +1 -0
- package/dist/src/native/nx.wasi.cjs +1 -0
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/nx-cloud/update-manager.d.ts +14 -0
- package/dist/src/nx-cloud/update-manager.js +237 -50
- package/dist/src/plugins/js/lock-file/pnpm-parser.js +3 -4
- package/dist/src/plugins/js/lock-file/pruned-output.d.ts +2 -2
- package/dist/src/plugins/js/lock-file/pruned-output.js +4 -4
- package/dist/src/plugins/js/utils/register.js +9 -4
- package/dist/src/project-graph/affected/affected-tasks.d.ts +9 -6
- package/dist/src/project-graph/affected/affected-tasks.js +31 -7
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +7 -0
- package/dist/src/project-graph/plugins/loaded-nx-plugin.js +49 -6
- package/dist/src/project-graph/utils/project-configuration/target-merging.js +1 -1
- package/dist/src/project-graph/utils/project-configuration/target-normalization.js +12 -133
- package/dist/src/project-graph/utils/project-configuration-utils.js +2 -2
- package/dist/src/project-graph/utils/retrieve-workspace-files.js +27 -6
- package/dist/src/tasks-runner/create-task-graph.d.ts +8 -7
- package/dist/src/tasks-runner/create-task-graph.js +50 -27
- package/dist/src/tasks-runner/default-tasks-runner.js +1 -1
- package/dist/src/tasks-runner/init-tasks-runner.js +116 -59
- package/dist/src/tasks-runner/life-cycle.d.ts +4 -1
- package/dist/src/tasks-runner/life-cycle.js +7 -0
- package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +5 -2
- package/dist/src/tasks-runner/life-cycles/performance-analysis.js +13 -7
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.d.ts +4 -3
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +9 -2
- package/dist/src/tasks-runner/life-cycles/static-run-many-terminal-output-life-cycle.js +5 -2
- package/dist/src/tasks-runner/life-cycles/static-run-one-terminal-output-life-cycle.js +4 -1
- package/dist/src/tasks-runner/life-cycles/summary-terminal-output-life-cycle.js +5 -1
- package/dist/src/tasks-runner/pseudo-ipc.d.ts +1 -0
- package/dist/src/tasks-runner/pseudo-ipc.js +4 -0
- package/dist/src/tasks-runner/pseudo-terminal.d.ts +1 -1
- package/dist/src/tasks-runner/pseudo-terminal.js +2 -2
- package/dist/src/tasks-runner/readiness/executor-ready-signal.d.ts +7 -0
- package/dist/src/tasks-runner/readiness/executor-ready-signal.js +43 -0
- package/dist/src/tasks-runner/readiness/probes.d.ts +14 -0
- package/dist/src/tasks-runner/readiness/probes.js +101 -0
- package/dist/src/tasks-runner/readiness/ready-signal-server.d.ts +18 -0
- package/dist/src/tasks-runner/readiness/ready-signal-server.js +72 -0
- package/dist/src/tasks-runner/readiness/ready-signal.d.ts +33 -0
- package/dist/src/tasks-runner/readiness/ready-signal.js +92 -0
- package/dist/src/tasks-runner/readiness/ready-when.d.ts +84 -0
- package/dist/src/tasks-runner/readiness/ready-when.js +371 -0
- package/dist/src/tasks-runner/readiness/startup-host.d.ts +1 -0
- package/dist/src/tasks-runner/readiness/startup-host.js +32 -0
- package/dist/src/tasks-runner/run-command.d.ts +6 -2
- package/dist/src/tasks-runner/run-command.js +21 -31
- package/dist/src/tasks-runner/run-outcome.d.ts +16 -0
- package/dist/src/tasks-runner/run-outcome.js +39 -0
- package/dist/src/tasks-runner/running-tasks/batch-process.js +6 -0
- package/dist/src/tasks-runner/running-tasks/node-child-process.js +5 -4
- package/dist/src/tasks-runner/running-tasks/running-task.d.ts +1 -1
- package/dist/src/tasks-runner/task-env.d.ts +10 -0
- package/dist/src/tasks-runner/task-env.js +37 -5
- package/dist/src/tasks-runner/task-graph-utils.d.ts +9 -6
- package/dist/src/tasks-runner/task-graph-utils.js +17 -12
- package/dist/src/tasks-runner/task-orchestrator.d.ts +62 -18
- package/dist/src/tasks-runner/task-orchestrator.js +750 -150
- package/dist/src/tasks-runner/tasks-runner.d.ts +1 -1
- package/dist/src/tasks-runner/tasks-schedule.d.ts +18 -1
- package/dist/src/tasks-runner/tasks-schedule.js +63 -6
- package/dist/src/tasks-runner/tokens.d.ts +54 -0
- package/dist/src/tasks-runner/tokens.js +161 -0
- package/dist/src/tasks-runner/utils.d.ts +35 -6
- package/dist/src/tasks-runner/utils.js +149 -68
- package/dist/src/ultracache/ci-status.d.ts +24 -0
- package/dist/src/ultracache/ci-status.js +87 -0
- package/dist/src/ultracache/config.d.ts +3 -3
- package/dist/src/ultracache/config.js +4 -4
- package/dist/src/ultracache/fetch.d.ts +2 -0
- package/dist/src/ultracache/fetch.js +1 -0
- package/dist/src/ultracache/overrides.js +4 -0
- package/dist/src/utils/acknowledge-build-scripts.d.ts +34 -1
- package/dist/src/utils/acknowledge-build-scripts.js +171 -32
- package/dist/src/utils/child-process.js +2 -1
- package/dist/src/utils/find-matching-projects.js +9 -6
- package/dist/src/utils/git-utils.d.ts +12 -5
- package/dist/src/utils/git-utils.js +69 -11
- package/dist/src/utils/long-running-target.d.ts +1 -3
- package/dist/src/utils/long-running-target.js +1 -3
- package/dist/src/utils/output.d.ts +4 -3
- package/dist/src/utils/output.js +4 -3
- package/dist/src/utils/owned-private-dir.d.ts +1 -17
- package/dist/src/utils/owned-private-dir.js +10 -62
- package/dist/src/utils/package-manager.d.ts +8 -0
- package/dist/src/utils/package-manager.js +65 -8
- package/dist/src/utils/perf-hooks.d.ts +1 -0
- package/dist/src/utils/plugins/core-plugins.js +5 -1
- package/dist/src/utils/progress-topics.d.ts +1 -0
- package/dist/src/utils/progress-topics.js +1 -0
- package/dist/src/utils/safe-spawn.js +1 -5
- package/dist/src/utils/shell-quoting.d.ts +8 -1
- package/dist/src/utils/shell-quoting.js +12 -1
- package/dist/src/utils/spawn-without-terminal.d.ts +27 -0
- package/dist/src/utils/spawn-without-terminal.js +110 -0
- package/dist/src/utils/string-similarity.d.ts +38 -0
- package/dist/src/utils/string-similarity.js +108 -0
- package/migrations.json +20 -6
- package/package.json +15 -24
- package/schemas/migrations-schema.json +11 -0
- package/schemas/nx-schema.json +133 -11
- package/schemas/project-schema.json +114 -4
- package/dist/src/command-line/migrate/agentic/prompts/generic-validation.d.ts +0 -56
- package/dist/src/command-line/migrate/agentic/prompts/generic-validation.js +0 -67
- package/dist/src/command-line/migrate/agentic/prompts/hybrid-prompt-migration.d.ts +0 -49
- package/dist/src/command-line/migrate/agentic/prompts/hybrid-prompt-migration.js +0 -53
- package/dist/src/command-line/migrate/agentic/prompts/prompt-migration.d.ts +0 -26
- package/dist/src/command-line/migrate/agentic/prompts/prompt-migration.js +0 -27
- package/dist/src/command-line/migrate/agentic/prompts/system-prompt.d.ts +0 -70
- package/dist/src/command-line/migrate/agentic/prompts/system-prompt.js +0 -113
- package/dist/src/command-line/migrate/agentic/run-step.d.ts +0 -68
- package/dist/src/command-line/migrate/agentic/run-step.js +0 -148
- package/dist/src/command-line/migrate/agentic/runner.d.ts +0 -21
- package/dist/src/command-line/migrate/agentic/runner.js +0 -262
- package/dist/src/migrations/update-23-0-0/consolidate-release-tag-config.d.ts +0 -9
- package/dist/src/migrations/update-23-0-0/consolidate-release-tag-config.js +0 -18
- package/dist/src/tasks-runner/legacy-depends-on-warning.d.ts +0 -18
- package/dist/src/tasks-runner/legacy-depends-on-warning.js +0 -109
|
@@ -22,6 +22,10 @@ const cache_1 = require("./cache");
|
|
|
22
22
|
const forked_process_task_runner_1 = require("./forked-process-task-runner");
|
|
23
23
|
const is_tui_enabled_1 = require("./is-tui-enabled");
|
|
24
24
|
const pseudo_terminal_1 = require("./pseudo-terminal");
|
|
25
|
+
const probes_1 = require("./readiness/probes");
|
|
26
|
+
const ready_signal_1 = require("./readiness/ready-signal");
|
|
27
|
+
const ready_signal_server_1 = require("./readiness/ready-signal-server");
|
|
28
|
+
const ready_when_1 = require("./readiness/ready-when");
|
|
25
29
|
const noop_child_process_1 = require("./running-tasks/noop-child-process");
|
|
26
30
|
const output_prefix_1 = require("./running-tasks/output-prefix");
|
|
27
31
|
const shared_running_task_1 = require("./running-tasks/shared-running-task");
|
|
@@ -36,6 +40,28 @@ const utils_1 = require("./utils");
|
|
|
36
40
|
function resolveBatchTaskStatus(result) {
|
|
37
41
|
return result.status ?? (result.success ? 'success' : 'failure');
|
|
38
42
|
}
|
|
43
|
+
function batchWorkerLogExplains(status) {
|
|
44
|
+
switch (status) {
|
|
45
|
+
case 'failure':
|
|
46
|
+
case 'stopped':
|
|
47
|
+
// The worker never reached it, e.g. `@nx/gradle` after a build failure
|
|
48
|
+
// outside the batch's own tasks
|
|
49
|
+
case 'skipped':
|
|
50
|
+
return true;
|
|
51
|
+
case 'success':
|
|
52
|
+
case 'local-cache':
|
|
53
|
+
case 'local-cache-kept-existing':
|
|
54
|
+
case 'remote-cache':
|
|
55
|
+
return false;
|
|
56
|
+
default: {
|
|
57
|
+
const unhandled = status;
|
|
58
|
+
void unhandled;
|
|
59
|
+
// Batch executors report their statuses untyped, so an unknown one can
|
|
60
|
+
// still arrive here. Nx can't tell it succeeded, so its log is shown
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
39
65
|
/** Whether a path names a file with bytes in it. */
|
|
40
66
|
function hasContent(path) {
|
|
41
67
|
try {
|
|
@@ -45,6 +71,31 @@ function hasContent(path) {
|
|
|
45
71
|
return false;
|
|
46
72
|
}
|
|
47
73
|
}
|
|
74
|
+
function createReadinessState(onSettled) {
|
|
75
|
+
const state = {
|
|
76
|
+
settled: false,
|
|
77
|
+
abort: new AbortController(),
|
|
78
|
+
};
|
|
79
|
+
state.promise = new Promise((resolve, reject) => {
|
|
80
|
+
state.resolve = () => {
|
|
81
|
+
if (state.settled)
|
|
82
|
+
return;
|
|
83
|
+
state.settled = true;
|
|
84
|
+
resolve();
|
|
85
|
+
onSettled('ready');
|
|
86
|
+
};
|
|
87
|
+
state.reject = (error) => {
|
|
88
|
+
if (state.settled)
|
|
89
|
+
return;
|
|
90
|
+
state.settled = true;
|
|
91
|
+
reject(error);
|
|
92
|
+
onSettled('failed');
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
// A producer with no waiter still settles; that must not be unhandled
|
|
96
|
+
state.promise.catch(() => { });
|
|
97
|
+
return state;
|
|
98
|
+
}
|
|
48
99
|
class TaskOrchestrator {
|
|
49
100
|
// endregion internal state
|
|
50
101
|
constructor(hasher, initiatingProject, initiatingTasks, projectGraph, taskGraph, nxJson, options, bail, daemon,
|
|
@@ -53,7 +104,7 @@ class TaskOrchestrator {
|
|
|
53
104
|
/** What this run renders with, after defaults. */
|
|
54
105
|
resolvedOutputStyle, fullTaskGraph = taskGraph,
|
|
55
106
|
/** Tasks the up-front pass left to hash once the tasks they read from have run. */
|
|
56
|
-
|
|
107
|
+
hashingDeferredTaskIds) {
|
|
57
108
|
this.hasher = hasher;
|
|
58
109
|
this.initiatingProject = initiatingProject;
|
|
59
110
|
this.initiatingTasks = initiatingTasks;
|
|
@@ -66,7 +117,7 @@ class TaskOrchestrator {
|
|
|
66
117
|
this.specifiedOutputStyle = specifiedOutputStyle;
|
|
67
118
|
this.resolvedOutputStyle = resolvedOutputStyle;
|
|
68
119
|
this.fullTaskGraph = fullTaskGraph;
|
|
69
|
-
this.
|
|
120
|
+
this.hashingDeferredTaskIds = hashingDeferredTaskIds;
|
|
70
121
|
this.taskDetails = (0, hash_task_1.getTaskDetails)();
|
|
71
122
|
this.cache = (0, cache_1.getCache)(this.options);
|
|
72
123
|
this.tuiEnabled = (0, is_tui_enabled_1.isTuiEnabled)();
|
|
@@ -78,13 +129,17 @@ class TaskOrchestrator {
|
|
|
78
129
|
? new native_1.RunningTasksService((0, db_connection_1.getLocalDbConnection)())
|
|
79
130
|
: null;
|
|
80
131
|
this.taskInvocationTracker = !native_1.IS_WASM
|
|
81
|
-
? new native_1.TaskInvocationTracker((0, db_connection_1.getLocalDbConnection)(),
|
|
132
|
+
? new native_1.TaskInvocationTracker((0, db_connection_1.getLocalDbConnection)(), (0, task_env_1.getInvocationRootPid)(), (0, task_env_1.getInvocationAncestorPids)())
|
|
82
133
|
: null;
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
134
|
+
// Task ids this process has already registered. Recursive code paths (e.g.
|
|
135
|
+
// applyFromCacheOrRunBatch looping on incomplete batches) re-enter the
|
|
136
|
+
// detector for the same task; skipping them saves a DB round trip. A task id
|
|
137
|
+
// is unique within one process, so it is enough to identify the row.
|
|
86
138
|
this.registeredInvocations = new Set();
|
|
87
|
-
this.tasksSchedule = new tasks_schedule_1.TasksSchedule(this.projectGraph, this.projects, this.taskGraph, this.options
|
|
139
|
+
this.tasksSchedule = new tasks_schedule_1.TasksSchedule(this.projectGraph, this.projects, this.taskGraph, this.options, this.fullTaskGraph, {
|
|
140
|
+
readinessElsewhere: (producerId) => this.runningTasksService?.getTaskReadiness(producerId) ?? null,
|
|
141
|
+
onReadinessHold: (producerId) => this.onReadinessHold(producerId),
|
|
142
|
+
});
|
|
88
143
|
// region internal state
|
|
89
144
|
this.batchEnv = (0, task_env_1.getEnvVariablesForBatchProcess)(this.options.skipNxCache, this.options.captureStderr);
|
|
90
145
|
this.reverseTaskDeps = (0, utils_1.calculateReverseDeps)(this.taskGraph);
|
|
@@ -123,6 +178,16 @@ class TaskOrchestrator {
|
|
|
123
178
|
this.runningDiscreteTasks = new Map();
|
|
124
179
|
this.discreteTaskExitHandled = new Map();
|
|
125
180
|
this.continuousTaskExitHandled = new Map();
|
|
181
|
+
// Keyed by producer id: a producer this process started or shares
|
|
182
|
+
this.readiness = new Map();
|
|
183
|
+
// Producers that failed their check while still running: the exit must not
|
|
184
|
+
// report them again, and the saved log gets the reason after the transcript
|
|
185
|
+
this.readinessFailures = new Map();
|
|
186
|
+
this.readinessFailureReasons = new Map();
|
|
187
|
+
this.readinessSettled = false;
|
|
188
|
+
// The url each producer reported with its readiness signal
|
|
189
|
+
this.readyUrls = new Map();
|
|
190
|
+
this.waitingLogged = new Set();
|
|
126
191
|
this.cleanupPromise = null;
|
|
127
192
|
this.signalHandlers = [];
|
|
128
193
|
// Tasks whose runner already owns the write to
|
|
@@ -210,8 +275,22 @@ class TaskOrchestrator {
|
|
|
210
275
|
*/
|
|
211
276
|
async executeCoordinatorLoop(doNotSkipCache, parallelism) {
|
|
212
277
|
while (true) {
|
|
213
|
-
if (this.
|
|
278
|
+
if (this.stopRequested)
|
|
279
|
+
break;
|
|
280
|
+
// On bail, start nothing new but let running tasks finish. Skip unstarted
|
|
281
|
+
// discrete tasks unless stopped, so the run still reads as interrupted
|
|
282
|
+
if (this.bailed) {
|
|
283
|
+
await Promise.allSettled(this.pendingDiscreteWorkers);
|
|
284
|
+
if (this.stopRequested)
|
|
285
|
+
break;
|
|
286
|
+
for (const task of Object.values(this.taskGraph.tasks)) {
|
|
287
|
+
if (!task.continuous && !this.completedTasks.has(task.id)) {
|
|
288
|
+
this.completedTasks.set(task.id, 'skipped');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
214
291
|
break;
|
|
292
|
+
}
|
|
293
|
+
await this.scheduleAfterReadiness();
|
|
215
294
|
// 1. Hash BEFORE processAll so processTask sees hashes set, and so
|
|
216
295
|
// resolveCachedTasksBulk can look them up in the cache. Each task
|
|
217
296
|
// is hashed with its own task-specific env (project/target .env
|
|
@@ -223,7 +302,7 @@ class TaskOrchestrator {
|
|
|
223
302
|
const unhashed = scheduledTasks
|
|
224
303
|
.map((id) => this.taskGraph.tasks[id])
|
|
225
304
|
.filter((t) => !t.hash &&
|
|
226
|
-
this.taskGraph.dependencies[t.id].every((
|
|
305
|
+
this.taskGraph.dependencies[t.id].every(({ id }) => this.completedTasks.has(id)));
|
|
227
306
|
if (unhashed.length > 0) {
|
|
228
307
|
const perTaskEnvs = {};
|
|
229
308
|
for (const task of unhashed) {
|
|
@@ -238,6 +317,11 @@ class TaskOrchestrator {
|
|
|
238
317
|
const resolved = await this.resolveCachedTasksBulk();
|
|
239
318
|
if (resolved)
|
|
240
319
|
continue;
|
|
320
|
+
// A pass from a continuous loop during the waits above may have
|
|
321
|
+
// admitted tasks this iteration never looked up; they must not reach
|
|
322
|
+
// step 5 without a confirmed miss
|
|
323
|
+
if (this.hasUnresolvedCacheableTask())
|
|
324
|
+
continue;
|
|
241
325
|
}
|
|
242
326
|
// 3. Process remaining scheduled tasks (cache misses + non-cacheable).
|
|
243
327
|
this.processAllScheduledTasks();
|
|
@@ -249,6 +333,9 @@ class TaskOrchestrator {
|
|
|
249
333
|
this.openGroup(groupId);
|
|
250
334
|
continue;
|
|
251
335
|
}
|
|
336
|
+
// A task may have failed during one of the awaits above
|
|
337
|
+
if (this.bailed)
|
|
338
|
+
continue;
|
|
252
339
|
// 5. Dispatch cache misses as individual workers
|
|
253
340
|
while (this.pendingDiscreteWorkers.size < parallelism) {
|
|
254
341
|
const task = this.tasksSchedule.nextTask((t) => !t.continuous);
|
|
@@ -263,6 +350,10 @@ class TaskOrchestrator {
|
|
|
263
350
|
break;
|
|
264
351
|
}
|
|
265
352
|
// 7. Wait for a worker to finish (woken by scheduleNextTasksAndReleaseThreads)
|
|
353
|
+
// A verdict that landed during this iteration found nothing parked,
|
|
354
|
+
// so run its pass instead of parking
|
|
355
|
+
if (this.readinessSettled)
|
|
356
|
+
continue;
|
|
266
357
|
await new Promise((res) => this.waitingForTasks.push(res));
|
|
267
358
|
}
|
|
268
359
|
}
|
|
@@ -272,6 +363,7 @@ class TaskOrchestrator {
|
|
|
272
363
|
if (!this.tasksSchedule.hasTasks() || this.bailed || this.stopRequested) {
|
|
273
364
|
return null;
|
|
274
365
|
}
|
|
366
|
+
await this.scheduleAfterReadiness();
|
|
275
367
|
this.processAllScheduledTasks();
|
|
276
368
|
const task = this.tasksSchedule.nextTask((t) => t.continuous);
|
|
277
369
|
if (task) {
|
|
@@ -293,9 +385,29 @@ class TaskOrchestrator {
|
|
|
293
385
|
return null;
|
|
294
386
|
}
|
|
295
387
|
// block until some other task completes, then try again
|
|
388
|
+
if (this.readinessSettled)
|
|
389
|
+
continue;
|
|
296
390
|
await new Promise((res) => this.waitingForTasks.push(res));
|
|
297
391
|
}
|
|
298
392
|
}
|
|
393
|
+
hasUnresolvedCacheableTask() {
|
|
394
|
+
return this.tasksSchedule
|
|
395
|
+
.getAllScheduledTasks()
|
|
396
|
+
.scheduledTasks.some((id) => {
|
|
397
|
+
const task = this.taskGraph.tasks[id];
|
|
398
|
+
return (!task.continuous &&
|
|
399
|
+
task.cache &&
|
|
400
|
+
(!task.hash || !this.cacheMissedHashes.has(task.hash)));
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
// A verdict releases tasks the schedule has not admitted yet. Whichever
|
|
404
|
+
// loop wakes first runs the pass; the coordinator may be inside a batch.
|
|
405
|
+
async scheduleAfterReadiness() {
|
|
406
|
+
if (this.readinessSettled) {
|
|
407
|
+
this.readinessSettled = false;
|
|
408
|
+
await this.scheduleNextTasksAndReleaseThreads();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
299
411
|
// region Processing Scheduled Tasks
|
|
300
412
|
async processTask(taskId) {
|
|
301
413
|
const task = this.taskGraph.tasks[taskId];
|
|
@@ -308,7 +420,10 @@ class TaskOrchestrator {
|
|
|
308
420
|
}
|
|
309
421
|
processAllScheduledTasks() {
|
|
310
422
|
const { scheduledTasks } = this.tasksSchedule.getAllScheduledTasks();
|
|
311
|
-
|
|
423
|
+
this.processTasks(scheduledTasks);
|
|
424
|
+
}
|
|
425
|
+
processTasks(taskIds) {
|
|
426
|
+
for (const taskId of taskIds) {
|
|
312
427
|
// Task is already handled or being handled
|
|
313
428
|
if (!this.processedTasks.has(taskId)) {
|
|
314
429
|
this.processedTasks.set(taskId, this.processTask(taskId));
|
|
@@ -316,39 +431,59 @@ class TaskOrchestrator {
|
|
|
316
431
|
}
|
|
317
432
|
}
|
|
318
433
|
/**
|
|
319
|
-
* Registers a task invocation and checks for loops across nested Nx
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
434
|
+
* Registers a task invocation and checks for loops across nested Nx
|
|
435
|
+
* processes. registerTask() returns the invocation chain only when an
|
|
436
|
+
* *ancestor* Nx process is already running this task — a genuine loop.
|
|
437
|
+
* Sibling processes running the same task are legitimate and register
|
|
438
|
+
* without complaint.
|
|
439
|
+
*
|
|
440
|
+
* The overrides hash rides along with the task id so the tracker can tell an
|
|
441
|
+
* ancestor re-invoking a task with different arguments from one repeating
|
|
442
|
+
* the same invocation.
|
|
323
443
|
*/
|
|
324
444
|
detectTaskInvocationLoop(task) {
|
|
325
445
|
if (!this.taskInvocationTracker)
|
|
326
446
|
return;
|
|
327
447
|
if (this.registeredInvocations.has(task.id))
|
|
328
448
|
return;
|
|
449
|
+
let chain;
|
|
329
450
|
try {
|
|
330
|
-
this.taskInvocationTracker.registerTask(process.pid, task.id);
|
|
451
|
+
chain = this.taskInvocationTracker.registerTask(process.pid, task.id, (0, utils_1.hashTaskOverrides)(task));
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
// Loop detection is diagnostic only; a DB failure must not fail the run
|
|
455
|
+
// or be mistaken for a loop.
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (!chain) {
|
|
331
459
|
this.registeredInvocations.add(task.id);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const chainDisplay = chain.map((r) => r.taskId).join(' -> ');
|
|
463
|
+
output_1.output.error({
|
|
464
|
+
title: 'Recursive task invocation detected',
|
|
465
|
+
bodyLines: [
|
|
466
|
+
`Nx detected a recursive loop of task invocations:`,
|
|
467
|
+
``,
|
|
468
|
+
` ${chainDisplay} -> ${task.id}`,
|
|
469
|
+
``,
|
|
470
|
+
`Task "${task.id}" was already invoked by a parent Nx process in this chain.`,
|
|
471
|
+
`This typically happens when a task's command (e.g., "nx ${task.target.target} ${task.target.project}")`,
|
|
472
|
+
`triggers a chain of tasks that eventually re-invokes itself.`,
|
|
473
|
+
``,
|
|
474
|
+
`To fix this, review the command configuration for the tasks in the chain above.`,
|
|
475
|
+
],
|
|
476
|
+
});
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
releaseTaskInvocation(task) {
|
|
480
|
+
if (!this.registeredInvocations.delete(task.id))
|
|
481
|
+
return;
|
|
482
|
+
try {
|
|
483
|
+
this.taskInvocationTracker?.unregisterTask(process.pid, task.id);
|
|
332
484
|
}
|
|
333
485
|
catch {
|
|
334
|
-
//
|
|
335
|
-
const chain = this.taskInvocationTracker.getInvocationChain();
|
|
336
|
-
const chainDisplay = chain.map((r) => r.taskId).join(' -> ');
|
|
337
|
-
output_1.output.error({
|
|
338
|
-
title: 'Recursive task invocation detected',
|
|
339
|
-
bodyLines: [
|
|
340
|
-
`Nx detected a recursive loop of task invocations:`,
|
|
341
|
-
``,
|
|
342
|
-
` ${chainDisplay} -> ${task.id}`,
|
|
343
|
-
``,
|
|
344
|
-
`Task "${task.id}" was already invoked by a parent Nx process in this chain.`,
|
|
345
|
-
`This typically happens when a task's command (e.g., "nx ${task.target.target} ${task.target.project}")`,
|
|
346
|
-
`triggers a chain of tasks that eventually re-invokes itself.`,
|
|
347
|
-
``,
|
|
348
|
-
`To fix this, review the command configuration for the tasks in the chain above.`,
|
|
349
|
-
],
|
|
350
|
-
});
|
|
351
|
-
process.exit(1);
|
|
486
|
+
// Diagnostic only, like registration. A leftover row is swept as stale.
|
|
352
487
|
}
|
|
353
488
|
}
|
|
354
489
|
// endregion Processing Scheduled Tasks
|
|
@@ -483,38 +618,63 @@ class TaskOrchestrator {
|
|
|
483
618
|
/**
|
|
484
619
|
* Hash all batch tasks and resolve cache hits topologically.
|
|
485
620
|
*
|
|
486
|
-
* Walks the task graph level by level.
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
*
|
|
621
|
+
* Walks the task graph level by level. A deferred task (its hash reads
|
|
622
|
+
* another task's outputs) behind an upstream batch task that still has to
|
|
623
|
+
* run is never looked up yet:
|
|
624
|
+
* - behind a non-cacheable task only, it is held back for a later wave,
|
|
625
|
+
* where it hashes after that task ran;
|
|
626
|
+
* - behind a cache miss, it runs in this wave and re-hashes after execution.
|
|
627
|
+
* Every task run in this wave gets a hash first, for Cloud's startTasks.
|
|
490
628
|
*/
|
|
491
629
|
async applyBatchCachedResults(batch, doNotSkipCache, groupId) {
|
|
492
630
|
const cachedResults = [];
|
|
493
|
-
const
|
|
631
|
+
const rehashAfterRun = new Set();
|
|
494
632
|
const tasks = Object.values(batch.taskGraph.tasks);
|
|
495
633
|
if (!doNotSkipCache) {
|
|
496
634
|
// Cache skipped — just hash so startTasks has valid hashes
|
|
497
635
|
await this.hashBatchTasks(tasks);
|
|
498
|
-
return { cachedResults,
|
|
499
|
-
}
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
636
|
+
return { cachedResults, tasksToRun: tasks, rehashAfterRun };
|
|
637
|
+
}
|
|
638
|
+
const toRun = new Map();
|
|
639
|
+
// Neither restored nor run in this wave; a later wave picks them up.
|
|
640
|
+
const heldBack = new Set();
|
|
641
|
+
// Tasks with a cache miss / a non-cacheable task upstream that still has
|
|
642
|
+
// to run in this wave.
|
|
643
|
+
const behindMiss = new Set();
|
|
644
|
+
const behindUncacheable = new Set();
|
|
645
|
+
const pending = (id, cacheable) => toRun.has(id) && !!toRun.get(id).cache === cacheable;
|
|
646
|
+
await (0, task_graph_utils_1.walkTaskGraph)(batch.taskGraph, async (levelIds) => {
|
|
647
|
+
const runNow = [];
|
|
648
|
+
const lookUp = [];
|
|
649
|
+
for (const id of levelIds) {
|
|
650
|
+
const task = batch.taskGraph.tasks[id];
|
|
651
|
+
const deps = batch.taskGraph.dependencies[id].map((dep) => dep.id);
|
|
652
|
+
if (deps.some((dep) => heldBack.has(dep))) {
|
|
653
|
+
heldBack.add(id);
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
const afterMiss = deps.some((dep) => behindMiss.has(dep) || pending(dep, true));
|
|
657
|
+
const afterUncacheable = deps.some((dep) => behindUncacheable.has(dep) || pending(dep, false));
|
|
658
|
+
if (afterMiss)
|
|
659
|
+
behindMiss.add(id);
|
|
660
|
+
if (afterUncacheable)
|
|
661
|
+
behindUncacheable.add(id);
|
|
662
|
+
const deferred = this.hashingDeferredTaskIds?.has(id);
|
|
663
|
+
if (deferred && afterMiss) {
|
|
664
|
+
runNow.push(task);
|
|
665
|
+
toRun.set(id, task);
|
|
666
|
+
rehashAfterRun.add(id);
|
|
667
|
+
}
|
|
668
|
+
else if (deferred && afterUncacheable) {
|
|
669
|
+
heldBack.add(id);
|
|
511
670
|
}
|
|
512
671
|
else {
|
|
513
|
-
|
|
672
|
+
lookUp.push(task);
|
|
514
673
|
}
|
|
515
674
|
}
|
|
516
|
-
|
|
517
|
-
|
|
675
|
+
await this.hashBatchTasks([...runNow, ...lookUp]);
|
|
676
|
+
if (lookUp.length > 0) {
|
|
677
|
+
const cacheResults = await this.applyCachedResults(lookUp);
|
|
518
678
|
const cachedIds = new Set(cacheResults.map((r) => r.task.id));
|
|
519
679
|
cachedResults.push(...cacheResults);
|
|
520
680
|
if (cacheResults.length > 0) {
|
|
@@ -524,14 +684,14 @@ class TaskOrchestrator {
|
|
|
524
684
|
// Replayed from the cache — don't write the results back.
|
|
525
685
|
await this.postRunSteps(cacheResults, false, groupId);
|
|
526
686
|
}
|
|
527
|
-
for (const task of
|
|
687
|
+
for (const task of lookUp) {
|
|
528
688
|
if (!cachedIds.has(task.id)) {
|
|
529
|
-
|
|
689
|
+
toRun.set(task.id, task);
|
|
530
690
|
}
|
|
531
691
|
}
|
|
532
692
|
}
|
|
533
693
|
});
|
|
534
|
-
return { cachedResults,
|
|
694
|
+
return { cachedResults, tasksToRun: [...toRun.values()], rehashAfterRun };
|
|
535
695
|
}
|
|
536
696
|
async hashBatchTasks(tasks) {
|
|
537
697
|
// Batch executors run every task in the same forked process, but
|
|
@@ -545,27 +705,52 @@ class TaskOrchestrator {
|
|
|
545
705
|
await (0, hash_task_1.hashTasks)(this.hasher, this.projectGraph, this.fullTaskGraph, perTaskEnvs, this.taskDetails, tasks);
|
|
546
706
|
}
|
|
547
707
|
async applyFromCacheOrRunBatch(doNotSkipCache, batch, groupId) {
|
|
708
|
+
if (this.bailed || this.stopRequested) {
|
|
709
|
+
return [];
|
|
710
|
+
}
|
|
548
711
|
const applyFromCacheOrRunBatchStart = perf_hooks_1.performance.mark('TaskOrchestrator-apply-from-cache-or-run-batch:start');
|
|
549
|
-
const
|
|
550
|
-
const tasks = taskEntries.map(([, task]) => task);
|
|
712
|
+
const taskIds = Object.keys(batch.taskGraph.tasks);
|
|
551
713
|
this.options.lifeCycle.registerRunningBatch?.(batch.id, {
|
|
552
714
|
executorName: batch.executorName,
|
|
553
|
-
taskIds
|
|
715
|
+
taskIds,
|
|
554
716
|
});
|
|
555
|
-
|
|
556
|
-
//
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
await this.
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
717
|
+
// Each wave restores or runs what it can; tasks held back for a later
|
|
718
|
+
// wave (or left out by the executor) stay incomplete and go again.
|
|
719
|
+
const results = [];
|
|
720
|
+
let taskGraph = batch.taskGraph;
|
|
721
|
+
while (Object.keys(taskGraph.tasks).length > 0 &&
|
|
722
|
+
!this.bailed &&
|
|
723
|
+
!this.stopRequested) {
|
|
724
|
+
results.push(...(await this.runBatchWave(doNotSkipCache, { ...batch, taskGraph }, groupId)));
|
|
725
|
+
this.forkedProcessTaskRunner.cleanUpBatchProcesses();
|
|
726
|
+
taskGraph = (0, utils_1.removeTasksFromTaskGraph)(taskGraph, Object.keys(taskGraph.tasks).filter((id) => this.completedTasks.has(id)));
|
|
727
|
+
}
|
|
728
|
+
// Once, after every wave: an earlier status would end the batch in the TUI.
|
|
729
|
+
const hasFailures = taskIds.some((taskId) => {
|
|
730
|
+
const status = this.completedTasks.get(taskId);
|
|
731
|
+
return status === 'failure' || status === 'skipped';
|
|
732
|
+
});
|
|
733
|
+
this.options.lifeCycle.setBatchStatus?.(batch.id, hasFailures ? "Failure" /* BatchStatus.Failure */ : "Success" /* BatchStatus.Success */);
|
|
734
|
+
const applyFromCacheOrRunBatchEnd = perf_hooks_1.performance.mark('TaskOrchestrator-apply-from-cache-or-run-batch:end');
|
|
735
|
+
perf_hooks_1.performance.measure('TaskOrchestrator-apply-from-cache-or-run-batch', applyFromCacheOrRunBatchStart.name, applyFromCacheOrRunBatchEnd.name);
|
|
736
|
+
return results;
|
|
737
|
+
}
|
|
738
|
+
async runBatchWave(doNotSkipCache, batch, groupId) {
|
|
739
|
+
const tasks = Object.values(batch.taskGraph.tasks);
|
|
740
|
+
const { cachedResults, tasksToRun: wave, rehashAfterRun, } = await this.applyBatchCachedResults(batch, doNotSkipCache, groupId);
|
|
741
|
+
// Cached tasks were started and completed inside applyBatchCachedResults;
|
|
742
|
+
// a member skipped meanwhile (a dependency failed) must not run either
|
|
743
|
+
const tasksToRun = wave.filter((t) => !this.completedTasks.has(t.id));
|
|
744
|
+
if (tasksToRun.length > 0) {
|
|
745
|
+
await Promise.all(tasksToRun.map((task) => this.options.lifeCycle.scheduleTask(task)));
|
|
746
|
+
await this.preRunSteps(tasksToRun, { groupId });
|
|
747
|
+
}
|
|
748
|
+
// Phase 2: Run this wave's tasks, then re-hash those run without a lookup
|
|
566
749
|
let batchResults = [];
|
|
567
|
-
|
|
568
|
-
|
|
750
|
+
// Read again: a member can be skipped while the hooks above ran
|
|
751
|
+
const runIds = new Set(tasksToRun.filter((t) => !this.completedTasks.has(t.id)).map((t) => t.id));
|
|
752
|
+
if (runIds.size > 0) {
|
|
753
|
+
const runGraph = (0, utils_1.removeTasksFromTaskGraph)(batch.taskGraph, tasks.filter((t) => !runIds.has(t.id)).map((t) => t.id));
|
|
569
754
|
for (const task of Object.values(runGraph.tasks)) {
|
|
570
755
|
this.detectTaskInvocationLoop(task);
|
|
571
756
|
}
|
|
@@ -576,7 +761,7 @@ class TaskOrchestrator {
|
|
|
576
761
|
}, this.batchEnv, groupId);
|
|
577
762
|
// Re-hash tasks that read dep outputs — those outputs are now on disk
|
|
578
763
|
const tasksToRehash = batchResults
|
|
579
|
-
.filter((r) =>
|
|
764
|
+
.filter((r) => rehashAfterRun.has(r.task.id) &&
|
|
580
765
|
(r.status === 'success' || r.status === 'failure'))
|
|
581
766
|
.map((r) => r.task);
|
|
582
767
|
if (tasksToRehash.length > 0) {
|
|
@@ -592,25 +777,6 @@ class TaskOrchestrator {
|
|
|
592
777
|
if (batchResults.length > 0) {
|
|
593
778
|
await this.postRunSteps(batchResults, doNotSkipCache, groupId);
|
|
594
779
|
}
|
|
595
|
-
// Update batch status based on all task results
|
|
596
|
-
const hasFailures = taskEntries.some(([taskId]) => {
|
|
597
|
-
const status = this.completedTasks.get(taskId);
|
|
598
|
-
return status === 'failure' || status === 'skipped';
|
|
599
|
-
});
|
|
600
|
-
this.options.lifeCycle.setBatchStatus?.(batch.id, hasFailures ? "Failure" /* BatchStatus.Failure */ : "Success" /* BatchStatus.Success */);
|
|
601
|
-
this.forkedProcessTaskRunner.cleanUpBatchProcesses();
|
|
602
|
-
const tasksCompleted = taskEntries.filter(([taskId]) => this.completedTasks.has(taskId));
|
|
603
|
-
// Batch is still not done, run it again
|
|
604
|
-
if (tasksCompleted.length !== taskEntries.length) {
|
|
605
|
-
await this.applyFromCacheOrRunBatch(doNotSkipCache, {
|
|
606
|
-
id: batch.id,
|
|
607
|
-
executorName: batch.executorName,
|
|
608
|
-
taskGraph: (0, utils_1.removeTasksFromTaskGraph)(batch.taskGraph, tasksCompleted.map(([taskId]) => taskId)),
|
|
609
|
-
}, groupId);
|
|
610
|
-
}
|
|
611
|
-
// Batch is done, mark it as completed
|
|
612
|
-
const applyFromCacheOrRunBatchEnd = perf_hooks_1.performance.mark('TaskOrchestrator-apply-from-cache-or-run-batch:end');
|
|
613
|
-
perf_hooks_1.performance.measure('TaskOrchestrator-apply-from-cache-or-run-batch', applyFromCacheOrRunBatchStart.name, applyFromCacheOrRunBatchEnd.name);
|
|
614
780
|
return [...cachedResults, ...batchResults];
|
|
615
781
|
}
|
|
616
782
|
async runBatch(batch, env, groupId) {
|
|
@@ -639,7 +805,8 @@ class TaskOrchestrator {
|
|
|
639
805
|
// When the batch is being folded, printing is deferred to batch end
|
|
640
806
|
// (printGroupedBatchOutput), which always renders each task through the
|
|
641
807
|
// life cycle and adds the worker's whole log as a fold when the run
|
|
642
|
-
// asked for full output or any task failed or was
|
|
808
|
+
// asked for full output or any task failed, was stopped or was never
|
|
809
|
+
// reached.
|
|
643
810
|
if (status !== 'skipped' && !(0, output_1.shouldGroupBatchOutput)()) {
|
|
644
811
|
this.options.lifeCycle.printTaskTerminalOutput(task, status, result.terminalOutput ?? '');
|
|
645
812
|
}
|
|
@@ -734,7 +901,7 @@ class TaskOrchestrator {
|
|
|
734
901
|
if (!capturedOutputPath || (0, output_1.printsTaskOutput)(this.resolvedOutputStyle)) {
|
|
735
902
|
return;
|
|
736
903
|
}
|
|
737
|
-
const failed = results.some((r) => r.status
|
|
904
|
+
const failed = results.some((r) => batchWorkerLogExplains(r.status));
|
|
738
905
|
if (!failed) {
|
|
739
906
|
return;
|
|
740
907
|
}
|
|
@@ -784,8 +951,9 @@ class TaskOrchestrator {
|
|
|
784
951
|
* those per-project summaries to the worker's stdio at all.
|
|
785
952
|
*
|
|
786
953
|
* The worker's whole captured log is rendered as a fold above them when the
|
|
787
|
-
* run asked for full output, or when any task failed
|
|
788
|
-
*
|
|
954
|
+
* run asked for full output, or when any task failed, was stopped or was
|
|
955
|
+
* never reached.
|
|
956
|
+
* A diagnostic that explains a failure is routinely one no task claimed:
|
|
789
957
|
* `@nx/maven`'s batch impl writes its exit-code dump and failed-task outputs
|
|
790
958
|
* to the worker's stderr via `console.error`, and the Maven JVM it spawns
|
|
791
959
|
* points slf4j at `System.out` because its own stderr carries the result
|
|
@@ -821,7 +989,7 @@ class TaskOrchestrator {
|
|
|
821
989
|
verbose: this.options.verbose,
|
|
822
990
|
outputStyle: this.resolvedOutputStyle,
|
|
823
991
|
});
|
|
824
|
-
const batchOwnsTheDiagnostic = taskResults.some((r) => r.status
|
|
992
|
+
const batchOwnsTheDiagnostic = taskResults.some((r) => batchWorkerLogExplains(r.status));
|
|
825
993
|
if ((0, output_1.printsTaskOutput)(this.resolvedOutputStyle) &&
|
|
826
994
|
(printsFullOutput || batchOwnsTheDiagnostic) &&
|
|
827
995
|
capturedOutputPath) {
|
|
@@ -855,7 +1023,9 @@ class TaskOrchestrator {
|
|
|
855
1023
|
? 'failure'
|
|
856
1024
|
: taskResults.some((r) => r.status === 'stopped')
|
|
857
1025
|
? 'stopped'
|
|
858
|
-
: '
|
|
1026
|
+
: taskResults.some((r) => r.status === 'skipped')
|
|
1027
|
+
? 'skipped'
|
|
1028
|
+
: 'success';
|
|
859
1029
|
output_1.output.logBatchGroup(label, body, worst);
|
|
860
1030
|
if (!redirectLines) {
|
|
861
1031
|
return;
|
|
@@ -876,8 +1046,8 @@ class TaskOrchestrator {
|
|
|
876
1046
|
* for running those via {@link runTaskDirectly}.
|
|
877
1047
|
*
|
|
878
1048
|
* Fires scheduleTask lifecycle for hits that haven't been through
|
|
879
|
-
*
|
|
880
|
-
*
|
|
1049
|
+
* processTasks yet. That's a coordinator gap-filler and a no-op for
|
|
1050
|
+
* callers that process their tasks first.
|
|
881
1051
|
*
|
|
882
1052
|
* The caller provides `groupId` — cache hits share one slot since they
|
|
883
1053
|
* don't actually compete for parallelism.
|
|
@@ -896,7 +1066,7 @@ class TaskOrchestrator {
|
|
|
896
1066
|
return [];
|
|
897
1067
|
// scheduleTask lifecycle for hits the coordinator resolved before
|
|
898
1068
|
// processAllScheduledTasks could fire it. No-op for callers that
|
|
899
|
-
// already ran
|
|
1069
|
+
// already ran processTasks (every hit is in processedTasks).
|
|
900
1070
|
await Promise.all(cacheHits
|
|
901
1071
|
.filter(({ task }) => !this.processedTasks.has(task.id))
|
|
902
1072
|
.map(({ task }) => this.options.lifeCycle.scheduleTask(task)));
|
|
@@ -923,8 +1093,7 @@ class TaskOrchestrator {
|
|
|
923
1093
|
// Wake coordinator — the delete above may satisfy the exit condition
|
|
924
1094
|
// (pendingDiscreteWorkers.size === 0) that was missed when
|
|
925
1095
|
// scheduleNextTasksAndReleaseThreads fired earlier.
|
|
926
|
-
this.
|
|
927
|
-
this.waitingForTasks.length = 0;
|
|
1096
|
+
this.releaseThreads();
|
|
928
1097
|
});
|
|
929
1098
|
this.pendingDiscreteWorkers.add(worker);
|
|
930
1099
|
}
|
|
@@ -947,13 +1116,36 @@ class TaskOrchestrator {
|
|
|
947
1116
|
await this.postRunSteps([{ task, status: 'failure', terminalOutput }], doNotSkipCache, groupId);
|
|
948
1117
|
}
|
|
949
1118
|
/**
|
|
950
|
-
* Spawn and wait on a task's child process
|
|
951
|
-
* lookup. Callers must have already confirmed the task
|
|
952
|
-
* (or disabled caching entirely).
|
|
1119
|
+
* Spawn and wait on a task's child process once its ready dependencies are
|
|
1120
|
+
* ready, with no cache lookup. Callers must have already confirmed the task
|
|
1121
|
+
* is a cache miss (or disabled caching entirely).
|
|
953
1122
|
*/
|
|
954
1123
|
async runTaskDirectly(doNotSkipCache, task, groupId) {
|
|
955
1124
|
// Wait for task to be processed
|
|
956
1125
|
const taskSpecificEnv = await this.processedTasks.get(task.id);
|
|
1126
|
+
const waitError = await this.waitForReadyDependencies(task).then(() => null, (e) => e);
|
|
1127
|
+
// Skipped by a failed dependency's propagation while waiting
|
|
1128
|
+
if (this.completedTasks.has(task.id)) {
|
|
1129
|
+
return {
|
|
1130
|
+
task,
|
|
1131
|
+
code: 1,
|
|
1132
|
+
status: this.completedTasks.get(task.id),
|
|
1133
|
+
terminalOutput: '',
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
if (waitError instanceof ready_when_1.ProducerFailedError) {
|
|
1137
|
+
await this.skipWaiter(task, groupId);
|
|
1138
|
+
return { task, code: 1, status: 'skipped', terminalOutput: '' };
|
|
1139
|
+
}
|
|
1140
|
+
if (waitError) {
|
|
1141
|
+
await this.failTaskBeforeStart(task, groupId, waitError, doNotSkipCache);
|
|
1142
|
+
return {
|
|
1143
|
+
task,
|
|
1144
|
+
code: 1,
|
|
1145
|
+
status: 'failure',
|
|
1146
|
+
terminalOutput: waitError.message,
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
957
1149
|
await this.preRunSteps([task], { groupId });
|
|
958
1150
|
const pipeOutput = await this.pipeOutputCapture(task);
|
|
959
1151
|
const temporaryOutputPath = this.cache.temporaryOutputPath(task);
|
|
@@ -968,6 +1160,7 @@ class TaskOrchestrator {
|
|
|
968
1160
|
const env = pipeOutput
|
|
969
1161
|
? (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, (0, task_env_1.getForceColorForChild)(), this.options.skipNxCache, this.options.captureStderr, null, null)
|
|
970
1162
|
: (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, undefined, this.options.skipNxCache, this.options.captureStderr, temporaryOutputPath, streamOutput);
|
|
1163
|
+
Object.assign(env, this.getReadyUrlEnv(task));
|
|
971
1164
|
let resolveDiscreteExit;
|
|
972
1165
|
const discreteExitHandled = new Promise((r) => (resolveDiscreteExit = r));
|
|
973
1166
|
this.discreteTaskExitHandled.set(task.id, discreteExitHandled);
|
|
@@ -1005,27 +1198,21 @@ class TaskOrchestrator {
|
|
|
1005
1198
|
try {
|
|
1006
1199
|
const { schema } = (0, utils_1.getExecutorForTask)(task, this.projects);
|
|
1007
1200
|
const combinedOptions = (0, params_1.combineOptionsForExecutor)(task.overrides, task.target.configuration ?? targetConfiguration.defaultConfiguration, targetConfiguration, schema, task.target.project, (0, path_1.relative)(task.projectRoot ?? workspace_root_1.workspaceRoot, process.cwd()), process.env.NX_VERBOSE_LOGGING === 'true');
|
|
1008
|
-
if (combinedOptions.env) {
|
|
1009
|
-
env = {
|
|
1010
|
-
...env,
|
|
1011
|
-
...combinedOptions.env,
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
1201
|
if (streamOutput) {
|
|
1015
1202
|
const args = (0, utils_1.getPrintableCommandArgsForTask)(task);
|
|
1016
1203
|
output_1.output.logCommand(args.join(' '));
|
|
1017
1204
|
}
|
|
1018
1205
|
const runCommandsOptions = {
|
|
1019
1206
|
...combinedOptions,
|
|
1020
|
-
env,
|
|
1021
1207
|
usePty: this.tuiEnabled ||
|
|
1022
1208
|
(!this.tasksSchedule.hasTasks() &&
|
|
1023
1209
|
this.runningContinuousTasks.size === 0),
|
|
1024
1210
|
streamOutput: streamOutput && !shouldPrefix,
|
|
1025
1211
|
};
|
|
1212
|
+
// As in a forked run, run-commands builds its env on the task env
|
|
1026
1213
|
const runningTask = await (0, run_commands_impl_1.runCommands)(runCommandsOptions, {
|
|
1027
1214
|
root: workspace_root_1.workspaceRoot, // only root is needed in runCommands
|
|
1028
|
-
}, task);
|
|
1215
|
+
}, task, env);
|
|
1029
1216
|
this.runningRunCommandsTasks.set(task.id, runningTask);
|
|
1030
1217
|
runningTask.onExit(() => {
|
|
1031
1218
|
this.runningRunCommandsTasks.delete(task.id);
|
|
@@ -1165,11 +1352,37 @@ class TaskOrchestrator {
|
|
|
1165
1352
|
async startContinuousTask(task, groupId) {
|
|
1166
1353
|
if (this.runningTasksService &&
|
|
1167
1354
|
this.runningTasksService.getRunningTasks([task.id]).length) {
|
|
1355
|
+
// This branch never reads the hash, so a failure to compute it is dropped
|
|
1356
|
+
void this.processedTasks.get(task.id)?.catch(() => { });
|
|
1357
|
+
let readyWhen;
|
|
1358
|
+
let uncheckedReason;
|
|
1359
|
+
try {
|
|
1360
|
+
({ readyWhen, uncheckedReason } = this.getReadinessCheck(task, () => this.getReadyWhenWait(task)));
|
|
1361
|
+
}
|
|
1362
|
+
catch (e) {
|
|
1363
|
+
return this.failTaskBeforeStart(task, groupId, e);
|
|
1364
|
+
}
|
|
1168
1365
|
await this.preRunSteps([task], { groupId });
|
|
1169
1366
|
if (this.tuiEnabled) {
|
|
1170
1367
|
this.options.lifeCycle.setTaskStatus(task.id, 8 /* NativeTaskStatus.Shared */);
|
|
1171
1368
|
}
|
|
1172
1369
|
const runningTask = new shared_running_task_1.SharedRunningTask(this.runningTasksService, task.id);
|
|
1370
|
+
if (uncheckedReason) {
|
|
1371
|
+
this.warnUncheckedReadiness(task, uncheckedReason);
|
|
1372
|
+
}
|
|
1373
|
+
if (readyWhen) {
|
|
1374
|
+
const state = this.armReadiness(task.id);
|
|
1375
|
+
this.pollReadinessRow(task, readyWhen, {
|
|
1376
|
+
signal: state.abort.signal,
|
|
1377
|
+
ownerObserved: true,
|
|
1378
|
+
}).then(() => {
|
|
1379
|
+
if (!state.abort.signal.aborted)
|
|
1380
|
+
state.resolve();
|
|
1381
|
+
}, (e) => {
|
|
1382
|
+
if (!state.abort.signal.aborted)
|
|
1383
|
+
this.failReadiness(task, state, e);
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1173
1386
|
this.runningContinuousTasks.set(task.id, {
|
|
1174
1387
|
runningTask,
|
|
1175
1388
|
groupId,
|
|
@@ -1183,34 +1396,95 @@ class TaskOrchestrator {
|
|
|
1183
1396
|
}));
|
|
1184
1397
|
// task is already running by another process, we schedule the next tasks
|
|
1185
1398
|
// and release the threads
|
|
1399
|
+
this.tasksSchedule.markContinuousTaskStarted(task.id);
|
|
1186
1400
|
await this.scheduleNextTasksAndReleaseThreads();
|
|
1187
1401
|
return runningTask;
|
|
1188
1402
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
//
|
|
1193
|
-
const temporaryOutputPath = this.cache.temporaryOutputPath(task);
|
|
1194
|
-
// Deliberately not gated on `printsTaskOutput`, unlike `runTaskDirectly`.
|
|
1195
|
-
// `SummaryTerminalOutputLifeCycle.printTaskTerminalOutput` is a no-op and it
|
|
1196
|
-
// only reports at `endCommand`, which never runs for a task that does not
|
|
1197
|
-
// end - so suppressing here would leave a `nx serve` under `summary` with a
|
|
1198
|
-
// permanently silent terminal and no file to read yet. Streaming is the only
|
|
1199
|
-
// channel a continuous task has.
|
|
1200
|
-
const streamOutput = (0, output_1.isStaticOutputStyle)(this.specifiedOutputStyle)
|
|
1201
|
-
? false
|
|
1202
|
-
: (0, utils_1.shouldStreamOutput)(task, this.initiatingProject);
|
|
1203
|
-
let env = pipeOutput
|
|
1204
|
-
? (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, (0, task_env_1.getForceColorForChild)(), this.options.skipNxCache, this.options.captureStderr, null, null)
|
|
1205
|
-
: (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, undefined, this.options.skipNxCache, this.options.captureStderr, temporaryOutputPath, streamOutput);
|
|
1206
|
-
this.detectTaskInvocationLoop(task);
|
|
1207
|
-
const childProcess = await this.runTask(task, streamOutput, env, temporaryOutputPath, pipeOutput);
|
|
1403
|
+
// Claim the task before doing any of the work below. getRunningTasks()
|
|
1404
|
+
// above is a check-then-act: until this row exists, a sibling Nx process
|
|
1405
|
+
// asking the same question is told the task is not running and starts a
|
|
1406
|
+
// duplicate of it.
|
|
1208
1407
|
this.runningTasksService?.addRunningTask(task.id);
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1408
|
+
let pipeOutput;
|
|
1409
|
+
let temporaryOutputPath;
|
|
1410
|
+
let streamOutput;
|
|
1411
|
+
let env;
|
|
1412
|
+
let readyWhen;
|
|
1413
|
+
let uncheckedReason;
|
|
1414
|
+
let readyOnStart;
|
|
1415
|
+
try {
|
|
1416
|
+
const taskSpecificEnv = await this.processedTasks.get(task.id);
|
|
1417
|
+
pipeOutput = await this.pipeOutputCapture(task);
|
|
1418
|
+
// obtain metadata
|
|
1419
|
+
temporaryOutputPath = this.cache.temporaryOutputPath(task);
|
|
1420
|
+
// Deliberately not gated on `printsTaskOutput`, unlike `runTaskDirectly`.
|
|
1421
|
+
// `SummaryTerminalOutputLifeCycle.printTaskTerminalOutput` is a no-op and it
|
|
1422
|
+
// only reports at `endCommand`, which never runs for a task that does not
|
|
1423
|
+
// end - so suppressing here would leave a `nx serve` under `summary` with a
|
|
1424
|
+
// permanently silent terminal and no file to read yet. Streaming is the only
|
|
1425
|
+
// channel a continuous task has.
|
|
1426
|
+
streamOutput = (0, output_1.isStaticOutputStyle)(this.specifiedOutputStyle)
|
|
1427
|
+
? false
|
|
1428
|
+
: (0, utils_1.shouldStreamOutput)(task, this.initiatingProject);
|
|
1429
|
+
env = pipeOutput
|
|
1430
|
+
? (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, (0, task_env_1.getForceColorForChild)(), this.options.skipNxCache, this.options.captureStderr, null, null)
|
|
1431
|
+
: (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, undefined, this.options.skipNxCache, this.options.captureStderr, temporaryOutputPath, streamOutput);
|
|
1432
|
+
const readyWhenInputs = (0, ready_when_1.getReadyWhenInputs)(task, this.projectGraph, this.projects, workspace_root_1.workspaceRoot);
|
|
1433
|
+
this.checkReadyWhenBeforeWait(task, env, readyWhenInputs);
|
|
1434
|
+
await this.waitForReadyDependencies(task);
|
|
1435
|
+
Object.assign(env, this.getReadyUrlEnv(task));
|
|
1436
|
+
({ readyWhen, uncheckedReason, readyOnStart } = this.getReadinessCheck(task, () => this.getReadyWhen(task, env, readyWhenInputs)));
|
|
1437
|
+
if (readyWhen?.kind === 'signal') {
|
|
1438
|
+
this.readySignal ??= new ready_signal_server_1.ReadySignalServer();
|
|
1439
|
+
Object.assign(env, await this.readySignal.env(task.id));
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
catch (e) {
|
|
1443
|
+
this.runningTasksService?.removeRunningTask(task.id);
|
|
1444
|
+
if (e instanceof ready_when_1.ProducerFailedError) {
|
|
1445
|
+
await this.skipWaiter(task, groupId);
|
|
1446
|
+
return new noop_child_process_1.NoopChildProcess({ code: 1, terminalOutput: '' });
|
|
1447
|
+
}
|
|
1448
|
+
return this.failTaskBeforeStart(task, groupId, e);
|
|
1449
|
+
}
|
|
1450
|
+
// Skipped by a failed dependency's propagation while waiting
|
|
1451
|
+
if (this.completedTasks.has(task.id)) {
|
|
1452
|
+
this.runningTasksService?.removeRunningTask(task.id);
|
|
1453
|
+
return new noop_child_process_1.NoopChildProcess({ code: 1, terminalOutput: '' });
|
|
1454
|
+
}
|
|
1455
|
+
let childProcess;
|
|
1456
|
+
try {
|
|
1457
|
+
await this.preRunSteps([task], { groupId });
|
|
1458
|
+
this.detectTaskInvocationLoop(task);
|
|
1459
|
+
childProcess = await this.runTask(task, streamOutput, env, temporaryOutputPath, pipeOutput);
|
|
1460
|
+
this.tasksSchedule.markContinuousTaskStarted(task.id);
|
|
1461
|
+
if (uncheckedReason) {
|
|
1462
|
+
this.warnUncheckedReadiness(task, uncheckedReason);
|
|
1463
|
+
// No probe settles the row, and a waiter in another process polls it
|
|
1464
|
+
this.publishReadiness(task, 2 /* TaskReadiness.Failed */);
|
|
1465
|
+
}
|
|
1466
|
+
if (readyWhen) {
|
|
1467
|
+
this.startReadinessProbe(task, readyWhen, childProcess);
|
|
1468
|
+
}
|
|
1469
|
+
// Waiters here, in other processes and in the TUI hold on a configured
|
|
1470
|
+
// readyWhen until it gets a verdict
|
|
1471
|
+
if (readyOnStart) {
|
|
1472
|
+
this.recordReadiness(task, 1 /* TaskReadiness.Ready */);
|
|
1473
|
+
this.armReadiness(task.id).resolve();
|
|
1474
|
+
}
|
|
1475
|
+
this.runningContinuousTasks.set(task.id, {
|
|
1476
|
+
runningTask: childProcess,
|
|
1477
|
+
groupId,
|
|
1478
|
+
ownsRunningTasksService: true,
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
catch (e) {
|
|
1482
|
+
// Nothing owns the claim until runningContinuousTasks holds it — the
|
|
1483
|
+
// release paths (completeContinuousTask, the signal handler) both iterate
|
|
1484
|
+
// that map. Drop it here or siblings wait on a task no one is running.
|
|
1485
|
+
this.runningTasksService?.removeRunningTask(task.id);
|
|
1486
|
+
throw e;
|
|
1487
|
+
}
|
|
1214
1488
|
this.continuousTaskExitHandled.set(task.id, new Promise((resolve) => {
|
|
1215
1489
|
childProcess.onExit(async (code) => {
|
|
1216
1490
|
await this.handleContinuousTaskExit(code, task, groupId, true);
|
|
@@ -1224,6 +1498,299 @@ class TaskOrchestrator {
|
|
|
1224
1498
|
return childProcess;
|
|
1225
1499
|
}
|
|
1226
1500
|
// endregion Single Task
|
|
1501
|
+
// region Readiness
|
|
1502
|
+
getReadyWhen(task, env, inputs) {
|
|
1503
|
+
return (0, ready_when_1.getReadyWhen)(task, this.projectGraph, this.projects, workspace_root_1.workspaceRoot, env, inputs);
|
|
1504
|
+
}
|
|
1505
|
+
// NX_READY_URL is known only after the wait, so fail now only when the
|
|
1506
|
+
// check fails without it and, if a producer may report one, with a URL too
|
|
1507
|
+
checkReadyWhenBeforeWait(task, env, inputs) {
|
|
1508
|
+
const producerIds = (0, ready_when_1.getReadyProducerIds)(task, this.fullTaskGraph);
|
|
1509
|
+
if (!inputs || !producerIds.length) {
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
const envs = [env];
|
|
1513
|
+
if (producerIds.some((id) => (0, ready_when_1.mayReportReadyUrl)(this.fullTaskGraph.tasks[id], this.projectGraph))) {
|
|
1514
|
+
envs.push({ ...env, [ready_signal_1.READY_URL_ENV]: 'http://localhost/' });
|
|
1515
|
+
}
|
|
1516
|
+
const errors = envs.map((checkEnv) => {
|
|
1517
|
+
try {
|
|
1518
|
+
this.getReadyWhen(task, checkEnv, inputs);
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
catch (e) {
|
|
1522
|
+
return e;
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
if (errors.every(Boolean)) {
|
|
1526
|
+
this.getReadinessCheck(task, () => {
|
|
1527
|
+
throw errors[errors.length - 1];
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
// Only the process that runs a producer knows the env its probe reads
|
|
1532
|
+
getReadyWhenWait(task) {
|
|
1533
|
+
return (0, ready_when_1.getReadyWhenWait)(task, this.projectGraph, this.projects, workspace_root_1.workspaceRoot);
|
|
1534
|
+
}
|
|
1535
|
+
// An invalid readyWhen fails the task before it starts only when something
|
|
1536
|
+
// in this run waits for it; otherwise the task runs without a check
|
|
1537
|
+
getReadinessCheck(task, resolve) {
|
|
1538
|
+
try {
|
|
1539
|
+
const readyWhen = resolve();
|
|
1540
|
+
return {
|
|
1541
|
+
readyWhen,
|
|
1542
|
+
// A readyWhen resolves to no check when the server picks its port
|
|
1543
|
+
readyOnStart: readyWhen === null &&
|
|
1544
|
+
(0, ready_when_1.getReadyWhenConfig)(task, this.projectGraph) != null,
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
catch (e) {
|
|
1548
|
+
if (!(e instanceof ready_when_1.InvalidReadyWhenError) ||
|
|
1549
|
+
this.hasReadyWaiter(task.id)) {
|
|
1550
|
+
throw e;
|
|
1551
|
+
}
|
|
1552
|
+
return { readyWhen: null, uncheckedReason: e.message };
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
// Call it after the start: the TUI replaces the task's terminal when it
|
|
1556
|
+
// starts, dropping output appended before
|
|
1557
|
+
warnUncheckedReadiness(task, reason) {
|
|
1558
|
+
const message = `${reason} Nothing in this run waits for it to be ready, so it runs without a readiness check.`;
|
|
1559
|
+
if (this.tuiEnabled) {
|
|
1560
|
+
this.options.lifeCycle.appendTaskOutput(task.id, `\r\n${message}\r\n`, false);
|
|
1561
|
+
}
|
|
1562
|
+
else {
|
|
1563
|
+
console.warn(message);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
async waitForReadyDependencies(task) {
|
|
1567
|
+
const producerIds = (0, ready_when_1.getReadyProducerIds)(task, this.fullTaskGraph);
|
|
1568
|
+
for (const producerId of producerIds) {
|
|
1569
|
+
const producer = this.fullTaskGraph.tasks[producerId];
|
|
1570
|
+
let readyWhen;
|
|
1571
|
+
try {
|
|
1572
|
+
readyWhen = this.getReadyWhenWait(producer);
|
|
1573
|
+
}
|
|
1574
|
+
catch (e) {
|
|
1575
|
+
// An owner in another process runs it unchecked and marks its row
|
|
1576
|
+
// failed, or drops the row. Without a row, nothing will settle it.
|
|
1577
|
+
if (!(e instanceof ready_when_1.InvalidReadyWhenError) ||
|
|
1578
|
+
this.taskGraph.tasks[producerId]) {
|
|
1579
|
+
throw e;
|
|
1580
|
+
}
|
|
1581
|
+
const owned = await this.pollReadinessRow(producer, { timeout: ready_when_1.DEFAULT_READY_TIMEOUT, description: 'invalid' }, { onPending: () => this.onReadinessHold(producerId) });
|
|
1582
|
+
if (!owned) {
|
|
1583
|
+
throw e;
|
|
1584
|
+
}
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
if (!readyWhen) {
|
|
1588
|
+
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
|
1589
|
+
console.log(`Task "${producer.id}" declares no "readyWhen", so "${task.id}" runs once it has started.`);
|
|
1590
|
+
}
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
// A producer in this task graph is held by dispatch and settled by its
|
|
1594
|
+
// probe or row poll; one outside it (an agent's flat graph) has only its row
|
|
1595
|
+
if (this.taskGraph.tasks[producerId]) {
|
|
1596
|
+
await this.readinessOf(producerId).promise;
|
|
1597
|
+
}
|
|
1598
|
+
else {
|
|
1599
|
+
await this.pollReadinessRow(producer, readyWhen, {
|
|
1600
|
+
onPending: () => this.onReadinessHold(producerId),
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
// A waiter learns the url of the one ready producer that reported it
|
|
1606
|
+
getReadyUrlEnv(task) {
|
|
1607
|
+
const urls = (0, ready_when_1.getReadyProducerIds)(task, this.fullTaskGraph)
|
|
1608
|
+
.map((id) => this.readyUrls.get(id))
|
|
1609
|
+
.filter(Boolean);
|
|
1610
|
+
return urls.length === 1 ? { [ready_signal_1.READY_URL_ENV]: urls[0] } : {};
|
|
1611
|
+
}
|
|
1612
|
+
onReadinessHold(producerId) {
|
|
1613
|
+
if (!this.waitingLogged.has(producerId) &&
|
|
1614
|
+
!this.tuiEnabled &&
|
|
1615
|
+
(0, output_1.printsTaskOutput)(this.resolvedOutputStyle)) {
|
|
1616
|
+
this.waitingLogged.add(producerId);
|
|
1617
|
+
console.log(`Waiting for "${producerId}" to be ready...`);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
// A row absent from the start means no Nx process this one can see owns the
|
|
1621
|
+
// producer (resolves false), unless the caller already saw the owner: then it
|
|
1622
|
+
// exited. A row that disappears mid-wait means the producer exited.
|
|
1623
|
+
async pollReadinessRow(producer, readyWhen, { signal, ownerObserved = false, onPending, } = {}) {
|
|
1624
|
+
const deadline = Date.now() + readyWhen.timeout;
|
|
1625
|
+
let lastStatus = null;
|
|
1626
|
+
while (true) {
|
|
1627
|
+
if (signal?.aborted) {
|
|
1628
|
+
throw (0, ready_when_1.notReadyError)(producer.id, 'exited');
|
|
1629
|
+
}
|
|
1630
|
+
if (this.stopRequested || this.bailed) {
|
|
1631
|
+
throw (0, ready_when_1.notReadyError)(producer.id, 'was stopped');
|
|
1632
|
+
}
|
|
1633
|
+
const status = this.runningTasksService?.getTaskReadiness(producer.id) ?? null;
|
|
1634
|
+
if (status === null) {
|
|
1635
|
+
if (lastStatus !== null || ownerObserved) {
|
|
1636
|
+
throw (0, ready_when_1.notReadyError)(producer.id, 'exited');
|
|
1637
|
+
}
|
|
1638
|
+
return false;
|
|
1639
|
+
}
|
|
1640
|
+
if (status !== lastStatus) {
|
|
1641
|
+
lastStatus = status;
|
|
1642
|
+
this.options.lifeCycle.setTaskReadiness?.(producer.id, status);
|
|
1643
|
+
}
|
|
1644
|
+
if (status === 1 /* TaskReadiness.Ready */) {
|
|
1645
|
+
this.readReadyUrl(producer.id);
|
|
1646
|
+
return true;
|
|
1647
|
+
}
|
|
1648
|
+
if (status === 2 /* TaskReadiness.Failed */) {
|
|
1649
|
+
throw new ready_when_1.ProducerFailedError(producer.id);
|
|
1650
|
+
}
|
|
1651
|
+
if (Date.now() >= deadline) {
|
|
1652
|
+
throw (0, ready_when_1.readinessTimeoutError)(producer.id, readyWhen);
|
|
1653
|
+
}
|
|
1654
|
+
onPending?.();
|
|
1655
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
startReadinessProbe(task, readyWhen, runningTask) {
|
|
1659
|
+
const state = this.armReadiness(task.id);
|
|
1660
|
+
this.options.lifeCycle.setTaskReadiness?.(task.id, 0 /* TaskReadiness.Pending */);
|
|
1661
|
+
(0, probes_1.waitForReadiness)(readyWhen, {
|
|
1662
|
+
taskId: task.id,
|
|
1663
|
+
runningTask,
|
|
1664
|
+
cwd: workspace_root_1.workspaceRoot,
|
|
1665
|
+
signal: state.abort.signal,
|
|
1666
|
+
readySignal: this.readySignal,
|
|
1667
|
+
}).then(({ url }) => {
|
|
1668
|
+
if (state.abort.signal.aborted)
|
|
1669
|
+
return;
|
|
1670
|
+
if (url)
|
|
1671
|
+
this.readyUrls.set(task.id, url);
|
|
1672
|
+
this.recordReadiness(task, 1 /* TaskReadiness.Ready */, url);
|
|
1673
|
+
state.resolve();
|
|
1674
|
+
}, (e) => {
|
|
1675
|
+
if (state.abort.signal.aborted)
|
|
1676
|
+
return;
|
|
1677
|
+
this.recordReadiness(task, 2 /* TaskReadiness.Failed */);
|
|
1678
|
+
this.failReadiness(task, state, e);
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
// A verdict against a producer something in this run waits for fails the
|
|
1682
|
+
// producer, and the skip propagation covers the waiters. Without a waiter
|
|
1683
|
+
// the producer is left alone, as a plain `nx serve` with a probe would be.
|
|
1684
|
+
async failReadiness(task, state, e) {
|
|
1685
|
+
const message = e?.message ?? String(e);
|
|
1686
|
+
const entry = this.runningContinuousTasks.get(task.id);
|
|
1687
|
+
if (entry &&
|
|
1688
|
+
!(e instanceof ready_when_1.NotReadyError) &&
|
|
1689
|
+
this.hasReadyWaiter(task.id)) {
|
|
1690
|
+
if (this.tuiEnabled) {
|
|
1691
|
+
this.options.lifeCycle.appendTaskOutput(task.id, `\r\n${message}\r\n`, false);
|
|
1692
|
+
}
|
|
1693
|
+
else {
|
|
1694
|
+
console.error(message);
|
|
1695
|
+
}
|
|
1696
|
+
this.readinessFailureReasons.set(task.id, message);
|
|
1697
|
+
const completion = this.complete([{ task, status: 'failure', terminalOutput: message }], entry.groupId);
|
|
1698
|
+
this.readinessFailures.set(task.id, completion);
|
|
1699
|
+
try {
|
|
1700
|
+
await completion;
|
|
1701
|
+
}
|
|
1702
|
+
finally {
|
|
1703
|
+
this.readinessFailures.delete(task.id);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
else if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
|
1707
|
+
console.error(message);
|
|
1708
|
+
}
|
|
1709
|
+
state.reject(e);
|
|
1710
|
+
}
|
|
1711
|
+
// On an agent the waiter runs in another process with its own flat graph
|
|
1712
|
+
hasReadyWaiter(producerId) {
|
|
1713
|
+
return Object.values(this.fullTaskGraph.tasks).some((task) => (0, ready_when_1.getReadyProducerIds)(task, this.fullTaskGraph).includes(producerId));
|
|
1714
|
+
}
|
|
1715
|
+
// The producer is not in this task graph, so no skip propagates to the waiter
|
|
1716
|
+
async skipWaiter(task, groupId) {
|
|
1717
|
+
if (!this.completedTasks.has(task.id)) {
|
|
1718
|
+
await this.complete([{ task, status: 'skipped' }], groupId);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
recordReadiness(task, status, url) {
|
|
1722
|
+
this.options.lifeCycle.setTaskReadiness?.(task.id, status);
|
|
1723
|
+
this.publishReadiness(task, status, url);
|
|
1724
|
+
}
|
|
1725
|
+
// Only other processes read the row, so a failed write must not fail this one
|
|
1726
|
+
publishReadiness(task, status, url) {
|
|
1727
|
+
try {
|
|
1728
|
+
this.runningTasksService?.setTaskReadiness(task.id, status, url ?? null);
|
|
1729
|
+
}
|
|
1730
|
+
catch (e) {
|
|
1731
|
+
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
|
1732
|
+
console.error(`Failed to record readiness of "${task.id}":`, e);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
// The url a producer another process runs reported with its readiness
|
|
1737
|
+
readReadyUrl(producerId) {
|
|
1738
|
+
try {
|
|
1739
|
+
const url = this.runningTasksService?.getTaskReadyUrl(producerId);
|
|
1740
|
+
if (url)
|
|
1741
|
+
this.readyUrls.set(producerId, url);
|
|
1742
|
+
}
|
|
1743
|
+
catch (e) {
|
|
1744
|
+
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
|
1745
|
+
console.error(`Failed to read the url of "${producerId}":`, e);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
readinessOf(taskId) {
|
|
1750
|
+
return this.readiness.get(taskId) ?? this.armReadiness(taskId);
|
|
1751
|
+
}
|
|
1752
|
+
// Replaces a state settled by an earlier run so a restart re-arms it
|
|
1753
|
+
armReadiness(taskId) {
|
|
1754
|
+
let state = this.readiness.get(taskId);
|
|
1755
|
+
if (!state || state.settled) {
|
|
1756
|
+
this.tasksSchedule.markReadinessPending(taskId);
|
|
1757
|
+
state = createReadinessState((outcome) => {
|
|
1758
|
+
if (outcome === 'ready') {
|
|
1759
|
+
this.tasksSchedule.markReady(taskId);
|
|
1760
|
+
}
|
|
1761
|
+
else {
|
|
1762
|
+
this.tasksSchedule.markReadinessFailed(taskId);
|
|
1763
|
+
}
|
|
1764
|
+
this.readinessSettled = true;
|
|
1765
|
+
this.releaseThreads();
|
|
1766
|
+
});
|
|
1767
|
+
this.readiness.set(taskId, state);
|
|
1768
|
+
}
|
|
1769
|
+
return state;
|
|
1770
|
+
}
|
|
1771
|
+
abortReadiness(taskId, reason) {
|
|
1772
|
+
const state = this.readiness.get(taskId);
|
|
1773
|
+
if (!state)
|
|
1774
|
+
return;
|
|
1775
|
+
state.abort.abort();
|
|
1776
|
+
state.reject((0, ready_when_1.notReadyError)(taskId, reason));
|
|
1777
|
+
}
|
|
1778
|
+
abortAllReadiness() {
|
|
1779
|
+
for (const taskId of this.readiness.keys()) {
|
|
1780
|
+
this.abortReadiness(taskId, 'was stopped');
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
async failTaskBeforeStart(task, groupId, e, doNotSkipCache = false) {
|
|
1784
|
+
this.abortReadiness(task.id, 'failed');
|
|
1785
|
+
// Already skipped by a failed dependency's propagation
|
|
1786
|
+
if (!this.completedTasks.has(task.id)) {
|
|
1787
|
+
// Lifecycles pair endTasks with startTasks, so start it before failing it
|
|
1788
|
+
await this.preRunSteps([task], { groupId });
|
|
1789
|
+
await this.handleDiscreteWorkerFailure(doNotSkipCache, task, groupId, e);
|
|
1790
|
+
}
|
|
1791
|
+
return new noop_child_process_1.NoopChildProcess({ code: 1, terminalOutput: e?.message ?? '' });
|
|
1792
|
+
}
|
|
1793
|
+
// endregion Readiness
|
|
1227
1794
|
// region Lifecycle
|
|
1228
1795
|
async preRunSteps(tasks, metadata) {
|
|
1229
1796
|
const now = Date.now();
|
|
@@ -1364,7 +1931,13 @@ class TaskOrchestrator {
|
|
|
1364
1931
|
if (!task.hash)
|
|
1365
1932
|
return;
|
|
1366
1933
|
try {
|
|
1367
|
-
const
|
|
1934
|
+
const outputPath = this.cache.temporaryOutputPath(task);
|
|
1935
|
+
// The runner's exit handler wrote the child's transcript alone
|
|
1936
|
+
const reason = this.readinessFailureReasons.get(task.id);
|
|
1937
|
+
if (reason) {
|
|
1938
|
+
(0, fs_1.appendFileSync)(outputPath, `\n${reason}\n`);
|
|
1939
|
+
}
|
|
1940
|
+
const { size } = (0, fs_1.statSync)(outputPath);
|
|
1368
1941
|
this.cache.recordTerminalOutputs([{ hash: task.hash, size }]);
|
|
1369
1942
|
}
|
|
1370
1943
|
catch {
|
|
@@ -1373,12 +1946,13 @@ class TaskOrchestrator {
|
|
|
1373
1946
|
}
|
|
1374
1947
|
async scheduleNextTasksAndReleaseThreads() {
|
|
1375
1948
|
if (this.stopRequested) {
|
|
1376
|
-
this.
|
|
1377
|
-
this.waitingForTasks.length = 0;
|
|
1949
|
+
this.releaseThreads();
|
|
1378
1950
|
return;
|
|
1379
1951
|
}
|
|
1380
1952
|
await this.tasksSchedule.scheduleNextTasks();
|
|
1381
|
-
|
|
1953
|
+
this.releaseThreads();
|
|
1954
|
+
}
|
|
1955
|
+
releaseThreads() {
|
|
1382
1956
|
this.waitingForTasks.forEach((f) => f(null));
|
|
1383
1957
|
this.waitingForTasks.length = 0;
|
|
1384
1958
|
}
|
|
@@ -1425,8 +1999,7 @@ class TaskOrchestrator {
|
|
|
1425
1999
|
if (this.completedTasks.has(task.id))
|
|
1426
2000
|
continue;
|
|
1427
2001
|
this.completedTasks.set(task.id, status);
|
|
1428
|
-
this.
|
|
1429
|
-
this.registeredInvocations.delete(task.id);
|
|
2002
|
+
this.releaseTaskInvocation(task);
|
|
1430
2003
|
if (this.tuiEnabled) {
|
|
1431
2004
|
this.options.lifeCycle.setTaskStatus(task.id, displayStatus ?? (0, native_1.parseTaskStatus)(status));
|
|
1432
2005
|
}
|
|
@@ -1437,6 +2010,7 @@ class TaskOrchestrator {
|
|
|
1437
2010
|
// mark the execution as bailed which will stop all further execution
|
|
1438
2011
|
// only the tasks that are currently running will finish
|
|
1439
2012
|
this.bailed = true;
|
|
2013
|
+
this.abortAllReadiness();
|
|
1440
2014
|
}
|
|
1441
2015
|
else {
|
|
1442
2016
|
// Collect reverse deps to skip
|
|
@@ -1467,6 +2041,14 @@ class TaskOrchestrator {
|
|
|
1467
2041
|
if (process.env.NX_NATIVE_COMMAND_RUNNER !== 'false') {
|
|
1468
2042
|
return true;
|
|
1469
2043
|
}
|
|
2044
|
+
// The log probe reads the captured output. Read from the config, because
|
|
2045
|
+
// the probe resolves against the env that depends on this
|
|
2046
|
+
const readyWhen = (0, ready_when_1.getReadyWhenConfig)(task, this.projectGraph);
|
|
2047
|
+
if (typeof readyWhen === 'object' &&
|
|
2048
|
+
readyWhen &&
|
|
2049
|
+
'logMatches' in readyWhen) {
|
|
2050
|
+
return true;
|
|
2051
|
+
}
|
|
1470
2052
|
// When TUI is enabled, we need to use pipe output capture to support
|
|
1471
2053
|
// progressive output streaming via the onOutput callback
|
|
1472
2054
|
if (this.tuiEnabled) {
|
|
@@ -1520,8 +2102,13 @@ class TaskOrchestrator {
|
|
|
1520
2102
|
}
|
|
1521
2103
|
// endregion utils
|
|
1522
2104
|
async handleContinuousTaskExit(code, task, groupId, ownsRunningTasksService) {
|
|
1523
|
-
|
|
2105
|
+
await this.readinessFailures.get(task.id);
|
|
2106
|
+
// Completed by cleanup, or failed its readiness check while still running
|
|
1524
2107
|
if (this.completedTasks.has(task.id)) {
|
|
2108
|
+
if (this.runningContinuousTasks.delete(task.id) &&
|
|
2109
|
+
ownsRunningTasksService) {
|
|
2110
|
+
this.runningTasksService?.removeRunningTask(task.id);
|
|
2111
|
+
}
|
|
1525
2112
|
return;
|
|
1526
2113
|
}
|
|
1527
2114
|
const stoppingReason = this.runningContinuousTasks.get(task.id)?.stoppingReason;
|
|
@@ -1542,15 +2129,20 @@ class TaskOrchestrator {
|
|
|
1542
2129
|
isContinuousTaskNeeded(taskId) {
|
|
1543
2130
|
return this.tasksSchedule
|
|
1544
2131
|
.getIncompleteTasks()
|
|
1545
|
-
.some((t) => this.taskGraph.continuousDependencies[t.id]?.
|
|
2132
|
+
.some((t) => this.taskGraph.continuousDependencies[t.id]?.some((edge) => edge.id === taskId));
|
|
1546
2133
|
}
|
|
1547
2134
|
async completeContinuousTask(task, groupId, ownsRunningTasksService, reason) {
|
|
1548
|
-
|
|
1549
|
-
return;
|
|
2135
|
+
await this.readinessFailures.get(task.id);
|
|
1550
2136
|
this.runningContinuousTasks.delete(task.id);
|
|
1551
2137
|
if (ownsRunningTasksService) {
|
|
1552
2138
|
this.runningTasksService?.removeRunningTask(task.id);
|
|
1553
2139
|
}
|
|
2140
|
+
// A probe that does not need the child (url, port, command) could still
|
|
2141
|
+
// pass during completion and release dependents of an exited producer
|
|
2142
|
+
this.readiness.get(task.id)?.abort.abort();
|
|
2143
|
+
// Failed its readiness check while still running
|
|
2144
|
+
if (this.completedTasks.has(task.id))
|
|
2145
|
+
return;
|
|
1554
2146
|
task.endTime = Date.now();
|
|
1555
2147
|
if (reason === 'fulfilled') {
|
|
1556
2148
|
await this.complete([
|
|
@@ -1567,6 +2159,11 @@ class TaskOrchestrator {
|
|
|
1567
2159
|
else {
|
|
1568
2160
|
await this.complete([{ task, status: 'stopped' }], groupId);
|
|
1569
2161
|
}
|
|
2162
|
+
// After the skip propagation above, so a waiter is reported as skipped
|
|
2163
|
+
// rather than as failed
|
|
2164
|
+
this.abortReadiness(task.id, 'exited');
|
|
2165
|
+
// Skipped dependents leave nothing in flight to wake the loops
|
|
2166
|
+
this.releaseThreads();
|
|
1570
2167
|
}
|
|
1571
2168
|
async cleanup() {
|
|
1572
2169
|
if (this.cleanupPromise) {
|
|
@@ -1576,6 +2173,9 @@ class TaskOrchestrator {
|
|
|
1576
2173
|
return this.cleanupPromise;
|
|
1577
2174
|
}
|
|
1578
2175
|
async performCleanup() {
|
|
2176
|
+
// Waiters must not sit out a probe once the run is stopping
|
|
2177
|
+
this.abortAllReadiness();
|
|
2178
|
+
this.readySignal?.close();
|
|
1579
2179
|
// Mark all running tasks for intentional stop
|
|
1580
2180
|
const reason = this.stopRequested ? 'interrupted' : 'fulfilled';
|
|
1581
2181
|
for (const entry of this.runningContinuousTasks.values()) {
|
|
@@ -1716,7 +2316,7 @@ class TaskOrchestrator {
|
|
|
1716
2316
|
}
|
|
1717
2317
|
const continuousDependencies = this.taskGraph.continuousDependencies[task.id];
|
|
1718
2318
|
for (const continuousDependency of continuousDependencies) {
|
|
1719
|
-
neededContinuousTasks.add(continuousDependency);
|
|
2319
|
+
neededContinuousTasks.add(continuousDependency.id);
|
|
1720
2320
|
}
|
|
1721
2321
|
}
|
|
1722
2322
|
for (const [taskId, entry] of this.runningContinuousTasks) {
|