diff --git a/core/src/graph/nodes.ts b/core/src/graph/nodes.ts index 8ab7f9d2e1..f4555d0dc7 100644 --- a/core/src/graph/nodes.ts +++ b/core/src/graph/nodes.ts @@ -218,7 +218,7 @@ export class RequestTaskNode extends TaskNode extends TaskNode extends TaskNode { getDependencies() { const statusTask = this.getNode("status", this.task) - const statusResult = this.getDependencyResult(statusTask) as GraphResult + const statusResult = this.getDependencyResult(statusTask) - if (statusResult === undefined) { + if (statusResult === undefined && this.task.needsStatus) { // Status is still missing return [statusTask] } // Either forcing, or status is not ready - const processDeps = this.task.getProcessDependencies({ status: statusResult.result }) + const processDeps = this.task.getProcessDependencies({ status: statusResult?.result ?? null }) return processDeps.map((task) => this.getNode("process", task)) } @@ -305,15 +305,15 @@ export class ProcessTaskNode extends TaskNode { const statusTask = this.getNode("status", this.task) // TODO: make this more type-safe - const statusResult = this.getDependencyResult(statusTask) as GraphResultFromTask + const statusResult = this.getDependencyResult(statusTask) - if (statusResult === undefined) { + if (statusResult === undefined && this.task.needsStatus) { throw new InternalError({ message: `Attempted to execute ${this.describe()} before resolving status.`, }) } - const status = statusResult.result + const status = statusResult?.result ?? null if (!this.task.force && status?.state === "ready") { return status diff --git a/core/src/graph/solver.ts b/core/src/graph/solver.ts index 709cd1c1f5..6f29ef8543 100644 --- a/core/src/graph/solver.ts +++ b/core/src/graph/solver.ts @@ -335,6 +335,11 @@ export class GraphSolver extends TypedEventEmitter { return } + this.log.debug(`processing ${nodesToProcess.length} nodes`) + if (nodesToProcess.length === 1) { + this.log.debug(`processing ${nodesToProcess[0]!.getKey()}`) + } + this.emit("process", { keys: nodesToProcess.map((n) => n.getKey()), inProgress: inProgressNodes.map((n) => n.getKey()), @@ -411,8 +416,14 @@ export class GraphSolver extends TypedEventEmitter { // See what is missing to fulfill the request, or resolve const task = request.task - const statusNode = this.getNode({ type: "status", task, statusOnly: request.statusOnly }) - const status = this.getPendingResult(statusNode) as GraphResult + const statusNode = task.needsStatus + ? this.getNode({ type: "status", task, statusOnly: request.statusOnly }) + : undefined + const status = ( + statusNode + ? this.getPendingResult(statusNode) + : { error: null, aborted: false, startedAt: null, result: null, node: request } + ) as GraphResult if (status?.aborted || status?.error) { // Status is either aborted or failed @@ -422,11 +433,11 @@ export class GraphSolver extends TypedEventEmitter { // Status is resolved, and that's all we need this.log.silly(() => `Request ${request.getKey()} is statusOnly and the status is available. Completing.`) this.completeTask({ ...status, node: request }) - } else if (status === undefined) { + } else if (status === undefined && statusNode) { // We're not forcing, and we don't have the status yet, so we ensure that's pending this.log.silly(() => `Request ${request.getKey()} is missing its status.`) this.ensurePendingNode(statusNode, request) - } else if (status.result?.state === "ready" && !task.force) { + } else if (status?.result?.state === "ready" && !task.force) { this.log.silly(() => `Request ${request.getKey()} has ready status and force=false, no need to process.`) this.completeTask({ ...status, node: request }) } else { diff --git a/core/src/tasks/base.ts b/core/src/tasks/base.ts index b75b1d0826..7fd895ae13 100644 --- a/core/src/tasks/base.ts +++ b/core/src/tasks/base.ts @@ -150,6 +150,8 @@ export abstract class BaseTask exte abstract getDescription(): string + abstract needsStatus: boolean + abstract getStatus(params: TaskProcessParams): null | Promise abstract process(params: TaskProcessParams): Promise diff --git a/core/src/tasks/build.ts b/core/src/tasks/build.ts index 72c2c382c4..c616e9482d 100644 --- a/core/src/tasks/build.ts +++ b/core/src/tasks/build.ts @@ -28,6 +28,8 @@ export class BuildTask extends ExecuteActionTask { return this.action.longDescription() } + override readonly needsStatus = true + @OtelTraced({ name(_params) { return `${this.action.key()}.getBuildStatus` diff --git a/core/src/tasks/delete-deploy.ts b/core/src/tasks/delete-deploy.ts index 63fd207b16..9ca9d21ec3 100644 --- a/core/src/tasks/delete-deploy.ts +++ b/core/src/tasks/delete-deploy.ts @@ -83,6 +83,8 @@ export class DeleteDeployTask extends BaseActionTask return `delete ${this.action.longDescription()})` } + override readonly needsStatus = false + async getStatus() { return null } diff --git a/core/src/tasks/deploy.ts b/core/src/tasks/deploy.ts index 1b87d532cc..950c535b82 100644 --- a/core/src/tasks/deploy.ts +++ b/core/src/tasks/deploy.ts @@ -55,6 +55,8 @@ export class DeployTask extends ExecuteActionTask { return this.action.longDescription() } + override readonly needsStatus = true + @OtelTraced({ name(_params) { return `${this.action.key()}.getDeployStatus` diff --git a/core/src/tasks/publish.ts b/core/src/tasks/publish.ts index 1503901b2e..00153053ef 100644 --- a/core/src/tasks/publish.ts +++ b/core/src/tasks/publish.ts @@ -66,6 +66,8 @@ export class PublishTask extends BaseActionTask extends ValidResultType export class ResolveActionTask extends BaseActionTask> { readonly type = "resolve-action" - // TODO: resolving template strings is CPU bound, does single-threaded concurrent execution make it faster or slower? - override readonly executeConcurrencyLimit = 10 - override readonly statusConcurrencyLimit = 10 + override readonly executeConcurrencyLimit = 1 + override readonly statusConcurrencyLimit = 1 getDescription() { return `resolve ${this.action.longDescription()}` @@ -52,6 +51,8 @@ export class ResolveActionTask extends BaseActionTask) { return null } diff --git a/core/src/tasks/resolve-provider.ts b/core/src/tasks/resolve-provider.ts index e1ae7766d0..112ca595d4 100644 --- a/core/src/tasks/resolve-provider.ts +++ b/core/src/tasks/resolve-provider.ts @@ -162,6 +162,8 @@ export class ResolveProviderTask extends BaseTask { ) } + override readonly needsStatus = false + async getStatus() { return null } diff --git a/core/src/tasks/run.ts b/core/src/tasks/run.ts index 34d88a3e0b..b99e5b6068 100644 --- a/core/src/tasks/run.ts +++ b/core/src/tasks/run.ts @@ -35,6 +35,8 @@ export class RunTask extends ExecuteActionTask { return this.action.longDescription() } + override readonly needsStatus = true + @OtelTraced({ name(_params) { return `${this.action.key()}.getRunStatus` diff --git a/core/src/tasks/test.ts b/core/src/tasks/test.ts index 25fa17a20a..0281c0eebb 100644 --- a/core/src/tasks/test.ts +++ b/core/src/tasks/test.ts @@ -55,6 +55,8 @@ export class TestTask extends ExecuteActionTask { return this.action.longDescription() } + override readonly needsStatus = true + @OtelTraced({ name: "getTestStatus", getAttributes(_params) { diff --git a/core/test/unit/src/graph/solver.ts b/core/test/unit/src/graph/solver.ts index 82cbcf56d0..76ad7b6ae1 100644 --- a/core/test/unit/src/graph/solver.ts +++ b/core/test/unit/src/graph/solver.ts @@ -87,6 +87,8 @@ export class TestTask extends BaseTask { return "v-" + this.uid.slice(0, 6) } + override readonly needsStatus = true + async getStatus(params: TaskProcessParams) { // eslint-disable-next-line @typescript-eslint/no-explicit-any let callbackResult: any = undefined diff --git a/core/test/unit/src/tasks/base.ts b/core/test/unit/src/tasks/base.ts index fede186d5d..7f594aea27 100644 --- a/core/test/unit/src/tasks/base.ts +++ b/core/test/unit/src/tasks/base.ts @@ -34,6 +34,8 @@ describe("BaseActionTask", () => { return "foo" } + override readonly needsStatus = true + async getStatus() { return { state: "ready", outputs: {} } as ValidResultType } diff --git a/plugins/pulumi/src/commands.ts b/plugins/pulumi/src/commands.ts index b8deea4ce2..f71fd9dd78 100644 --- a/plugins/pulumi/src/commands.ts +++ b/plugins/pulumi/src/commands.ts @@ -329,6 +329,8 @@ class PulumiPluginCommandTask extends PluginActionTask