Skip to content

feat: thread a context through Plan and Execute - #503

Merged
josegonzalez merged 1 commit into
mainfrom
424-thread-a-context-context-through-plan-and-execute
Aug 31, 2026
Merged

feat: thread a context through Plan and Execute#503
josegonzalez merged 1 commit into
mainfrom
424-thread-a-context-context-through-plan-and-execute

Conversation

@josegonzalez

Copy link
Copy Markdown
Member

A task had no way to be cancelled. Plan() and Execute() took no arguments, so every task bottomed out in a context.Background() manufactured inside subprocess, and a long git:sync, a slow letsencrypt:enable, or an SSH connection that hung after the TCP handshake ran to completion no matter what the caller wanted. Both methods now take a context, as do the shared plan helpers, the apply closure on PlanResult, and the exporters. The closure takes one rather than capturing the one Plan() ran under, since ExecutePlan invokes it separately and should hand it the caller's current context. DispatchPlan keeps its argument-free branch signature and the branches capture the context lexically.

The signal handler moves with it. Every subprocess call used to register its own handler with signal.Notify and never call signal.Stop, leaking a goroutine, a channel registration and a derived context per dokku command, and cancelling only the child in flight - so an interrupt aborted one task and the run marched on to the next. One signal.NotifyContext in main.go now cancels the run context instead: apply and plan stop at the next task, report run cancelled, and exit 1 rather than the --detailed-exitcode code for "completed with changes". A second interrupt kills the process, which was previously impossible.

Nothing about the transport changes. The host, sudo setting and host-key policy still come from the package globals; making those per-invocation is #423, which this makes possible.

Refs #424.

A task had no way to be cancelled. `Plan()` and `Execute()` took no arguments, so every task bottomed out in a `context.Background()` manufactured inside `subprocess`, and a long `git:sync`, a slow `letsencrypt:enable`, or an SSH connection that hung after the TCP handshake ran to completion no matter what the caller wanted. Both methods now take a context, as do the shared plan helpers, the `apply` closure on `PlanResult`, and the exporters. The closure takes one rather than capturing the one `Plan()` ran under, since `ExecutePlan` invokes it separately and should hand it the caller's current context. `DispatchPlan` keeps its argument-free branch signature and the branches capture the context lexically.

The signal handler moves with it. Every subprocess call used to register its own handler with `signal.Notify` and never call `signal.Stop`, leaking a goroutine, a channel registration and a derived context per dokku command, and cancelling only the child in flight - so an interrupt aborted one task and the run marched on to the next. One `signal.NotifyContext` in `main.go` now cancels the run context instead: `apply` and `plan` stop at the next task, report `run cancelled`, and exit 1 rather than the `--detailed-exitcode` code for "completed with changes". A second interrupt kills the process, which was previously impossible.

Nothing about the transport changes. The host, sudo setting and host-key policy still come from the package globals; making those per-invocation is #423, which this makes possible.

Refs #424.
@josegonzalez
josegonzalez force-pushed the 424-thread-a-context-context-through-plan-and-execute branch from 05afa92 to 638c434 Compare August 31, 2026 05:17
@josegonzalez
josegonzalez merged commit 57d413b into main Aug 31, 2026
19 checks passed
@josegonzalez
josegonzalez deleted the 424-thread-a-context-context-through-plan-and-execute branch August 31, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant