diff --git a/internal/stacks/stackruntime/apply.go b/internal/stacks/stackruntime/apply.go index 1e92d6def8..1585cf2b78 100644 --- a/internal/stacks/stackruntime/apply.go +++ b/internal/stacks/stackruntime/apply.go @@ -44,6 +44,13 @@ func Apply(ctx context.Context, req *ApplyRequest, resp *ApplyResponse) { }, } + // Whatever return path we take, we must close our channels to allow + // a caller to see that the operation is complete. + defer func() { + close(resp.Diagnostics) + close(resp.AppliedChanges) // MUST be the last channel to close + }() + main, err := stackeval.ApplyPlan( ctx, req.Config, @@ -78,8 +85,6 @@ func Apply(ctx context.Context, req *ApplyRequest, resp *ApplyResponse) { resp.Diagnostics <- diag } - close(resp.Diagnostics) - close(resp.AppliedChanges) // MUST be the last channel to close } // ApplyRequest represents the inputs to an [Apply] call. diff --git a/internal/stacks/stackruntime/plan.go b/internal/stacks/stackruntime/plan.go index b630ed5d88..be2c855fff 100644 --- a/internal/stacks/stackruntime/plan.go +++ b/internal/stacks/stackruntime/plan.go @@ -26,9 +26,15 @@ import ( // through resp after passing it to this function, aside from the implicit // modifications to the internal state of channels caused by reading them. func Plan(ctx context.Context, req *PlanRequest, resp *PlanResponse) { + var respMu sync.Mutex // must hold this when accessing fields of resp, aside from channel sends resp.Applyable = true // we'll reset this to false later if appropriate - var respMu sync.Mutex // must hold this when accessing fields of resp, aside from channel sends + // Whatever return path we take, we must close our channels to allow + // a caller to see that the operation is complete. + defer func() { + close(resp.Diagnostics) + close(resp.PlannedChanges) // MUST be the last channel to close + }() main := stackeval.NewForPlanning(req.Config, stackeval.PlanOpts{ InputVariableValues: req.InputValues, @@ -66,9 +72,6 @@ func Plan(ctx context.Context, req *PlanRequest, resp *PlanResponse) { resp.PlannedChanges <- &stackplan.PlannedChangeApplyable{ Applyable: resp.Applyable, } - - close(resp.Diagnostics) - close(resp.PlannedChanges) // MUST be the last channel to close } // PlanRequest represents the inputs to a [Plan] call.