UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

721 lines (720 loc) • 33.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TaskOrchestrator = void 0; exports.getThreadCount = getThreadCount; const events_1 = require("events"); const fs_1 = require("fs"); const path_1 = require("path"); const perf_hooks_1 = require("perf_hooks"); const run_commands_impl_1 = require("../executors/run-commands/run-commands.impl"); const hash_task_1 = require("../hasher/hash-task"); const native_1 = require("../native"); const db_connection_1 = require("../utils/db-connection"); const output_1 = require("../utils/output"); const params_1 = require("../utils/params"); const workspace_root_1 = require("../utils/workspace-root"); const cache_1 = require("./cache"); const forked_process_task_runner_1 = require("./forked-process-task-runner"); const is_tui_enabled_1 = require("./is-tui-enabled"); const pseudo_terminal_1 = require("./pseudo-terminal"); const noop_child_process_1 = require("./running-tasks/noop-child-process"); const task_env_1 = require("./task-env"); const tasks_schedule_1 = require("./tasks-schedule"); const utils_1 = require("./utils"); const shared_running_task_1 = require("./running-tasks/shared-running-task"); class TaskOrchestrator { // endregion internal state constructor(hasher, initiatingProject, initiatingTasks, projectGraph, taskGraph, nxJson, options, bail, daemon, outputStyle, taskGraphForHashing = taskGraph) { this.hasher = hasher; this.initiatingProject = initiatingProject; this.initiatingTasks = initiatingTasks; this.projectGraph = projectGraph; this.taskGraph = taskGraph; this.nxJson = nxJson; this.options = options; this.bail = bail; this.daemon = daemon; this.outputStyle = outputStyle; this.taskGraphForHashing = taskGraphForHashing; this.taskDetails = (0, hash_task_1.getTaskDetails)(); this.cache = (0, cache_1.getCache)(this.options); this.tuiEnabled = (0, is_tui_enabled_1.isTuiEnabled)(); this.forkedProcessTaskRunner = new forked_process_task_runner_1.ForkedProcessTaskRunner(this.options, this.tuiEnabled); this.runningTasksService = !native_1.IS_WASM ? new native_1.RunningTasksService((0, db_connection_1.getDbConnection)()) : null; this.tasksSchedule = new tasks_schedule_1.TasksSchedule(this.projectGraph, this.taskGraph, this.options); // region internal state this.batchEnv = (0, task_env_1.getEnvVariablesForBatchProcess)(this.options.skipNxCache, this.options.captureStderr); this.reverseTaskDeps = (0, utils_1.calculateReverseDeps)(this.taskGraph); this.initializingTaskIds = new Set(this.initiatingTasks.map((t) => t.id)); this.processedTasks = new Map(); this.processedBatches = new Map(); this.completedTasks = {}; this.waitingForTasks = []; this.groups = []; this.bailed = false; this.runningContinuousTasks = new Map(); this.runningRunCommandsTasks = new Map(); } async init() { // Init the ForkedProcessTaskRunner, TasksSchedule, and Cache await Promise.all([ this.forkedProcessTaskRunner.init(), this.tasksSchedule.init().then(() => { return this.tasksSchedule.scheduleNextTasks(); }), 'init' in this.cache ? this.cache.init() : null, ]); } async run() { await this.init(); perf_hooks_1.performance.mark('task-execution:start'); const threadCount = getThreadCount(this.options, this.taskGraph); const threads = []; process.stdout.setMaxListeners(threadCount + events_1.defaultMaxListeners); process.stderr.setMaxListeners(threadCount + events_1.defaultMaxListeners); // initial seeding of the queue for (let i = 0; i < threadCount; ++i) { threads.push(this.executeNextBatchOfTasksUsingTaskSchedule()); } await Promise.race([ Promise.all(threads), ...(this.tuiEnabled ? [ new Promise((resolve) => { this.options.lifeCycle.registerForcedShutdownCallback(() => { // The user force quit the TUI with ctrl+c, so proceed onto cleanup resolve(undefined); }); }), ] : []), ]); perf_hooks_1.performance.mark('task-execution:end'); perf_hooks_1.performance.measure('task-execution', 'task-execution:start', 'task-execution:end'); this.cache.removeOldCacheRecords(); await this.cleanup(); return this.completedTasks; } nextBatch() { return this.tasksSchedule.nextBatch(); } async executeNextBatchOfTasksUsingTaskSchedule() { // completed all the tasks if (!this.tasksSchedule.hasTasks() || this.bailed) { return null; } const doNotSkipCache = this.options.skipNxCache === false || this.options.skipNxCache === undefined; this.processAllScheduledTasks(); const batch = this.nextBatch(); if (batch) { const groupId = this.closeGroup(); await this.applyFromCacheOrRunBatch(doNotSkipCache, batch, groupId); this.openGroup(groupId); return this.executeNextBatchOfTasksUsingTaskSchedule(); } const task = this.tasksSchedule.nextTask(); if (task) { const groupId = this.closeGroup(); if (task.continuous) { await this.startContinuousTask(task, groupId); } else { await this.applyFromCacheOrRunTask(doNotSkipCache, task, groupId); } this.openGroup(groupId); return this.executeNextBatchOfTasksUsingTaskSchedule(); } // block until some other task completes, then try again return new Promise((res) => this.waitingForTasks.push(res)).then(() => this.executeNextBatchOfTasksUsingTaskSchedule()); } processTasks(taskIds) { for (const taskId of taskIds) { // Task is already handled or being handled if (!this.processedTasks.has(taskId)) { this.processedTasks.set(taskId, this.processTask(taskId)); } } } // region Processing Scheduled Tasks async processTask(taskId) { const task = this.taskGraph.tasks[taskId]; const taskSpecificEnv = (0, task_env_1.getTaskSpecificEnv)(task); if (!task.hash) { await (0, hash_task_1.hashTask)(this.hasher, this.projectGraph, this.taskGraphForHashing, task, taskSpecificEnv, this.taskDetails); } await this.options.lifeCycle.scheduleTask(task); return taskSpecificEnv; } async processScheduledBatch(batch) { await Promise.all(Object.values(batch.taskGraph.tasks).map(async (task) => { if (!task.hash) { await (0, hash_task_1.hashTask)(this.hasher, this.projectGraph, this.taskGraphForHashing, task, this.batchEnv, this.taskDetails); } await this.options.lifeCycle.scheduleTask(task); })); } processAllScheduledTasks() { const { scheduledTasks, scheduledBatches } = this.tasksSchedule.getAllScheduledTasks(); for (const batch of scheduledBatches) { this.processedBatches.set(batch, this.processScheduledBatch(batch)); } this.processTasks(scheduledTasks); } // endregion Processing Scheduled Tasks // region Applying Cache async applyCachedResults(tasks) { const cacheableTasks = tasks.filter((t) => (0, utils_1.isCacheableTask)(t, this.options)); const res = await Promise.all(cacheableTasks.map((t) => this.applyCachedResult(t))); return res.filter((r) => r !== null); } async applyCachedResult(task) { const cachedResult = await this.cache.get(task); if (!cachedResult || cachedResult.code !== 0) return null; const outputs = task.outputs; const shouldCopyOutputsFromCache = // No output files to restore !!outputs.length && // Remote caches are restored to output dirs when applied and using db cache (!cachedResult.remote || !(0, cache_1.dbCacheEnabled)()) && // Output files have not been touched since last run (await this.shouldCopyOutputsFromCache(outputs, task.hash)); if (shouldCopyOutputsFromCache) { await this.cache.copyFilesFromCache(task.hash, cachedResult, outputs); } const status = cachedResult.remote ? 'remote-cache' : shouldCopyOutputsFromCache ? 'local-cache' : 'local-cache-kept-existing'; this.options.lifeCycle.printTaskTerminalOutput(task, status, cachedResult.terminalOutput); return { code: cachedResult.code, task, status, }; } // endregion Applying Cache // region Batch async applyFromCacheOrRunBatch(doNotSkipCache, batch, groupId) { const applyFromCacheOrRunBatchStart = perf_hooks_1.performance.mark('TaskOrchestrator-apply-from-cache-or-run-batch:start'); const taskEntries = Object.entries(batch.taskGraph.tasks); const tasks = taskEntries.map(([, task]) => task); // Wait for batch to be processed await this.processedBatches.get(batch); await this.preRunSteps(tasks, { groupId }); let results = doNotSkipCache ? await this.applyCachedResults(tasks) : []; // Run tasks that were not cached if (results.length !== taskEntries.length) { const unrunTaskGraph = (0, utils_1.removeTasksFromTaskGraph)(batch.taskGraph, results.map(({ task }) => task.id)); const batchResults = await this.runBatch({ executorName: batch.executorName, taskGraph: unrunTaskGraph, }, this.batchEnv); results.push(...batchResults); } await this.postRunSteps(tasks, results, doNotSkipCache, { groupId }); this.forkedProcessTaskRunner.cleanUpBatchProcesses(); const tasksCompleted = taskEntries.filter(([taskId]) => this.completedTasks[taskId]); // Batch is still not done, run it again if (tasksCompleted.length !== taskEntries.length) { await this.applyFromCacheOrRunBatch(doNotSkipCache, { executorName: batch.executorName, taskGraph: (0, utils_1.removeTasksFromTaskGraph)(batch.taskGraph, tasksCompleted.map(([taskId]) => taskId)), }, groupId); } // Batch is done, mark it as completed const applyFromCacheOrRunBatchEnd = perf_hooks_1.performance.mark('TaskOrchestrator-apply-from-cache-or-run-batch:end'); perf_hooks_1.performance.measure('TaskOrchestrator-apply-from-cache-or-run-batch', applyFromCacheOrRunBatchStart.name, applyFromCacheOrRunBatchEnd.name); return results; } async runBatch(batch, env) { const runBatchStart = perf_hooks_1.performance.mark('TaskOrchestrator-run-batch:start'); try { const batchProcess = await this.forkedProcessTaskRunner.forkProcessForBatch(batch, this.projectGraph, this.taskGraph, env); const results = await batchProcess.getResults(); const batchResultEntries = Object.entries(results); return batchResultEntries.map(([taskId, result]) => ({ ...result, code: result.success ? 0 : 1, task: { ...this.taskGraph.tasks[taskId], startTime: result.startTime, endTime: result.endTime, }, status: (result.success ? 'success' : 'failure'), terminalOutput: result.terminalOutput, })); } catch (e) { return batch.taskGraph.roots.map((rootTaskId) => ({ task: this.taskGraph.tasks[rootTaskId], code: 1, status: 'failure', })); } finally { const runBatchEnd = perf_hooks_1.performance.mark('TaskOrchestrator-run-batch:end'); perf_hooks_1.performance.measure('TaskOrchestrator-run-batch', runBatchStart.name, runBatchEnd.name); } } // endregion Batch // region Single Task async applyFromCacheOrRunTask(doNotSkipCache, task, groupId) { // Wait for task to be processed const taskSpecificEnv = await this.processedTasks.get(task.id); await this.preRunSteps([task], { groupId }); const pipeOutput = await this.pipeOutputCapture(task); // obtain metadata const temporaryOutputPath = this.cache.temporaryOutputPath(task); const streamOutput = this.outputStyle === 'static' ? false : (0, utils_1.shouldStreamOutput)(task, this.initiatingProject); let env = pipeOutput ? (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, process.env.FORCE_COLOR === undefined ? 'true' : process.env.FORCE_COLOR, this.options.skipNxCache, this.options.captureStderr, null, null) : (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, undefined, this.options.skipNxCache, this.options.captureStderr, temporaryOutputPath, streamOutput); let results = doNotSkipCache ? await this.applyCachedResults([task]) : []; // the task wasn't cached if (results.length === 0) { const childProcess = await this.runTask(task, streamOutput, env, temporaryOutputPath, pipeOutput); const { code, terminalOutput } = await childProcess.getResults(); results.push({ task, code, status: code === 0 ? 'success' : 'failure', terminalOutput, }); } await this.postRunSteps([task], results, doNotSkipCache, { groupId }); return results[0]; } async runTask(task, streamOutput, env, temporaryOutputPath, pipeOutput) { const shouldPrefix = streamOutput && process.env.NX_PREFIX_OUTPUT === 'true'; const targetConfiguration = (0, utils_1.getTargetConfigurationForTask)(task, this.projectGraph); if (process.env.NX_RUN_COMMANDS_DIRECTLY !== 'false' && targetConfiguration.executor === 'nx:run-commands' && !shouldPrefix) { try { const { schema } = (0, utils_1.getExecutorForTask)(task, this.projectGraph); 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'); if (combinedOptions.env) { env = { ...env, ...combinedOptions.env, }; } if (streamOutput) { const args = (0, utils_1.getPrintableCommandArgsForTask)(task); output_1.output.logCommand(args.join(' ')); } const runCommandsOptions = { ...combinedOptions, env, usePty: this.tuiEnabled || (!this.tasksSchedule.hasTasks() && this.runningContinuousTasks.size === 0), streamOutput, }; const runningTask = await (0, run_commands_impl_1.runCommands)(runCommandsOptions, { root: workspace_root_1.workspaceRoot, // only root is needed in runCommands }); this.runningRunCommandsTasks.set(task.id, runningTask); runningTask.onExit(() => { this.runningRunCommandsTasks.delete(task.id); }); if (this.tuiEnabled) { if (runningTask instanceof pseudo_terminal_1.PseudoTtyProcess) { // This is an external of a the pseudo terminal where a task is running and can be passed to the TUI this.options.lifeCycle.registerRunningTask(task.id, runningTask.getParserAndWriter()); runningTask.onOutput((output) => { this.options.lifeCycle.appendTaskOutput(task.id, output, true); }); } else { this.options.lifeCycle.registerRunningTaskWithEmptyParser(task.id); runningTask.onOutput((output) => { this.options.lifeCycle.appendTaskOutput(task.id, output, false); }); } } if (!streamOutput) { if (runningTask instanceof pseudo_terminal_1.PseudoTtyProcess) { // TODO: shouldn't this be checking if the task is continuous before writing anything to disk or calling printTaskTerminalOutput? let terminalOutput = ''; runningTask.onOutput((data) => { terminalOutput += data; }); runningTask.onExit((code) => { this.options.lifeCycle.printTaskTerminalOutput(task, code === 0 ? 'success' : 'failure', terminalOutput); (0, fs_1.writeFileSync)(temporaryOutputPath, terminalOutput); }); } else { runningTask.onExit((code, terminalOutput) => { this.options.lifeCycle.printTaskTerminalOutput(task, code === 0 ? 'success' : 'failure', terminalOutput); (0, fs_1.writeFileSync)(temporaryOutputPath, terminalOutput); }); } } return runningTask; } catch (e) { if (process.env.NX_VERBOSE_LOGGING === 'true') { console.error(e); } else { console.error(e.message); } const terminalOutput = e.stack ?? e.message ?? ''; (0, fs_1.writeFileSync)(temporaryOutputPath, terminalOutput); return new noop_child_process_1.NoopChildProcess({ code: 1, terminalOutput, }); } } else if (targetConfiguration.executor === 'nx:noop') { (0, fs_1.writeFileSync)(temporaryOutputPath, ''); return new noop_child_process_1.NoopChildProcess({ code: 0, terminalOutput: '', }); } else { // cache prep const runningTask = await this.runTaskInForkedProcess(task, env, pipeOutput, temporaryOutputPath, streamOutput); if (this.tuiEnabled) { if (runningTask instanceof pseudo_terminal_1.PseudoTtyProcess) { // This is an external of a the pseudo terminal where a task is running and can be passed to the TUI this.options.lifeCycle.registerRunningTask(task.id, runningTask.getParserAndWriter()); runningTask.onOutput((output) => { this.options.lifeCycle.appendTaskOutput(task.id, output, true); }); } else if ('onOutput' in runningTask && typeof runningTask.onOutput === 'function') { // Register task that can provide progressive output but isn't interactive (e.g., NodeChildProcessWithNonDirectOutput) this.options.lifeCycle.registerRunningTaskWithEmptyParser(task.id); runningTask.onOutput((output) => { this.options.lifeCycle.appendTaskOutput(task.id, output, false); }); } else { // Fallback for tasks that don't support progressive output this.options.lifeCycle.registerRunningTaskWithEmptyParser(task.id); } } return runningTask; } } async runTaskInForkedProcess(task, env, pipeOutput, temporaryOutputPath, streamOutput) { try { const usePtyFork = process.env.NX_NATIVE_COMMAND_RUNNER !== 'false'; // Disable the pseudo terminal if this is a run-many or when running a continuous task as part of a run-one const disablePseudoTerminal = !this.tuiEnabled && (!this.initiatingProject || task.continuous); // execution const childProcess = usePtyFork ? await this.forkedProcessTaskRunner.forkProcess(task, { temporaryOutputPath, streamOutput, pipeOutput, taskGraph: this.taskGraph, env, disablePseudoTerminal, }) : await this.forkedProcessTaskRunner.forkProcessLegacy(task, { temporaryOutputPath, streamOutput, pipeOutput, taskGraph: this.taskGraph, env, }); return childProcess; } catch (e) { if (process.env.NX_VERBOSE_LOGGING === 'true') { console.error(e); } return new noop_child_process_1.NoopChildProcess({ code: 1, terminalOutput: undefined, }); } } async startContinuousTask(task, groupId) { if (this.runningTasksService && this.runningTasksService.getRunningTasks([task.id]).length) { await this.preRunSteps([task], { groupId }); if (this.tuiEnabled) { this.options.lifeCycle.setTaskStatus(task.id, 8 /* NativeTaskStatus.Shared */); } const runningTask = new shared_running_task_1.SharedRunningTask(this.runningTasksService, task.id); this.runningContinuousTasks.set(task.id, runningTask); runningTask.onExit(() => { if (this.tuiEnabled) { this.options.lifeCycle.setTaskStatus(task.id, 9 /* NativeTaskStatus.Stopped */); } this.runningContinuousTasks.delete(task.id); }); // task is already running by another process, we schedule the next tasks // and release the threads await this.scheduleNextTasksAndReleaseThreads(); if (this.initializingTaskIds.has(task.id)) { await new Promise((res) => { runningTask.onExit((code) => { if (!this.tuiEnabled) { if (code > 128) { process.exit(code); } } res(); }); }); } return runningTask; } const taskSpecificEnv = await this.processedTasks.get(task.id); await this.preRunSteps([task], { groupId }); const pipeOutput = await this.pipeOutputCapture(task); // obtain metadata const temporaryOutputPath = this.cache.temporaryOutputPath(task); const streamOutput = this.outputStyle === 'static' ? false : (0, utils_1.shouldStreamOutput)(task, this.initiatingProject); let env = pipeOutput ? (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, process.env.FORCE_COLOR === undefined ? 'true' : process.env.FORCE_COLOR, this.options.skipNxCache, this.options.captureStderr, null, null) : (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, undefined, this.options.skipNxCache, this.options.captureStderr, temporaryOutputPath, streamOutput); const childProcess = await this.runTask(task, streamOutput, env, temporaryOutputPath, pipeOutput); this.runningTasksService.addRunningTask(task.id); this.runningContinuousTasks.set(task.id, childProcess); childProcess.onExit(() => { if (this.tuiEnabled) { this.options.lifeCycle.setTaskStatus(task.id, 9 /* NativeTaskStatus.Stopped */); } this.runningTasksService.removeRunningTask(task.id); this.runningContinuousTasks.delete(task.id); }); await this.scheduleNextTasksAndReleaseThreads(); if (this.initializingTaskIds.has(task.id)) { await new Promise((res) => { childProcess.onExit((code) => { if (!this.tuiEnabled) { if (code > 128) { process.exit(code); } } res(); }); }); } return childProcess; } // endregion Single Task // region Lifecycle async preRunSteps(tasks, metadata) { const now = Date.now(); for (const task of tasks) { task.startTime = now; } await this.options.lifeCycle.startTasks(tasks, metadata); } async postRunSteps(tasks, results, doNotSkipCache, { groupId }) { const now = Date.now(); for (const task of tasks) { task.endTime = now; await this.recordOutputsHash(task); } if (doNotSkipCache) { // cache the results perf_hooks_1.performance.mark('cache-results-start'); await Promise.all(results .filter(({ status }) => status !== 'local-cache' && status !== 'local-cache-kept-existing' && status !== 'remote-cache' && status !== 'skipped') .map((result) => ({ ...result, code: result.status === 'local-cache' || result.status === 'local-cache-kept-existing' || result.status === 'remote-cache' || result.status === 'success' ? 0 : 1, outputs: result.task.outputs, })) .filter(({ task, code }) => this.shouldCacheTaskResult(task, code)) .filter(({ terminalOutput, outputs }) => terminalOutput || outputs) .map(async ({ task, code, terminalOutput, outputs }) => this.cache.put(task, terminalOutput, outputs, code))); perf_hooks_1.performance.mark('cache-results-end'); perf_hooks_1.performance.measure('cache-results', 'cache-results-start', 'cache-results-end'); } await this.options.lifeCycle.endTasks(results.map((result) => { const code = result.status === 'success' || result.status === 'local-cache' || result.status === 'local-cache-kept-existing' || result.status === 'remote-cache' ? 0 : 1; return { ...result, task: result.task, status: result.status, code, }; }), { groupId }); this.complete(results.map(({ task, status }) => { return { taskId: task.id, status, }; })); await this.scheduleNextTasksAndReleaseThreads(); } async scheduleNextTasksAndReleaseThreads() { await this.tasksSchedule.scheduleNextTasks(); // release blocked threads this.waitingForTasks.forEach((f) => f(null)); this.waitingForTasks.length = 0; } complete(taskResults) { this.tasksSchedule.complete(taskResults.map(({ taskId }) => taskId)); this.cleanUpUnneededContinuousTasks(); for (const { taskId, status } of taskResults) { if (this.completedTasks[taskId] === undefined) { this.completedTasks[taskId] = status; if (this.tuiEnabled) { this.options.lifeCycle.setTaskStatus(taskId, (0, native_1.parseTaskStatus)(status)); } if (status === 'failure' || status === 'skipped') { if (this.bail) { // mark the execution as bailed which will stop all further execution // only the tasks that are currently running will finish this.bailed = true; } else { // only mark the packages that depend on the current task as skipped // other tasks will continue to execute this.complete(this.reverseTaskDeps[taskId].map((depTaskId) => ({ taskId: depTaskId, status: 'skipped', }))); } } } } } //endregion Lifecycle // region utils async pipeOutputCapture(task) { try { if (process.env.NX_NATIVE_COMMAND_RUNNER !== 'false') { return true; } const { schema } = (0, utils_1.getExecutorForTask)(task, this.projectGraph); return (schema.outputCapture === 'pipe' || process.env.NX_STREAM_OUTPUT === 'true'); } catch (e) { return false; } } shouldCacheTaskResult(task, code) { return ((0, utils_1.isCacheableTask)(task, this.options) && (process.env.NX_CACHE_FAILURES == 'true' ? true : code === 0)); } closeGroup() { for (let i = 0; i < this.options.parallel; i++) { if (!this.groups[i]) { this.groups[i] = true; return i; } } } openGroup(id) { this.groups[id] = false; } async shouldCopyOutputsFromCache(outputs, hash) { if (this.daemon?.enabled()) { return !(await this.daemon.outputsHashesMatch(outputs, hash)); } else { return true; } } async recordOutputsHash(task) { if (this.daemon?.enabled()) { return this.daemon.recordOutputsHash(task.outputs, task.hash); } } // endregion utils async cleanup() { this.forkedProcessTaskRunner.cleanup(); await Promise.all([ ...Array.from(this.runningContinuousTasks).map(async ([taskId, t]) => { try { await t.kill(); this.options.lifeCycle.setTaskStatus(taskId, 9 /* NativeTaskStatus.Stopped */); } catch (e) { console.error(`Unable to terminate ${taskId}\nError:`, e); } finally { this.runningTasksService.removeRunningTask(taskId); } }), ...Array.from(this.runningRunCommandsTasks).map(async ([taskId, t]) => { try { await t.kill(); } catch (e) { console.error(`Unable to terminate ${taskId}\nError:`, e); } }), ]); } cleanUpUnneededContinuousTasks() { const incompleteTasks = this.tasksSchedule.getIncompleteTasks(); const neededContinuousTasks = new Set(this.initializingTaskIds); for (const task of incompleteTasks) { const continuousDependencies = this.taskGraph.continuousDependencies[task.id]; for (const continuousDependency of continuousDependencies) { neededContinuousTasks.add(continuousDependency); } } for (const taskId of this.runningContinuousTasks.keys()) { if (!neededContinuousTasks.has(taskId)) { const runningTask = this.runningContinuousTasks.get(taskId); if (runningTask) { runningTask.kill(); this.options.lifeCycle.setTaskStatus(taskId, 9 /* NativeTaskStatus.Stopped */); } } } } } exports.TaskOrchestrator = TaskOrchestrator; function getThreadCount(options, taskGraph) { if (options['parallel'] === 'false' || options['parallel'] === false) { options['parallel'] = 1; } else if (options['parallel'] === 'true' || options['parallel'] === true || options['parallel'] === undefined || options['parallel'] === '') { options['parallel'] = Number(options['maxParallel'] || 3); } const maxParallel = options['parallel'] + Object.values(taskGraph.tasks).filter((t) => t.continuous).length; const totalTasks = Object.keys(taskGraph.tasks).length; return Math.min(maxParallel, totalTasks); }