Description of feature
There is no way to cancel a task once it has started.
The Task interface takes no arguments at all: Plan() PlanResult and Execute() TaskOutputState. Underneath, tasks call subprocess.CallExecCommand and subprocess.Probe, both of which run on context.Background(). A long git:sync, a slow letsencrypt:enable, or an SSH connection that hangs after the TCP handshake runs to completion no matter what the caller wants, and there is no timeout to bound it.
Today the only thing that stops a run is a signal, handled down in subprocess. That is enough for a person at a terminal and not much else. It means a task cannot be given a deadline, a test cannot bound a misbehaving fake, and anything embedding the engine has no way to abandon work in progress.
The plumbing is already half there: CallExecCommandWithContext and CallSshCommandWithContext exist and take a context; it is only the argument-free convenience wrappers that tasks actually call. So the change is a signature change through the interface and its 73 implementations plus the shared planProperty / planToggle / planPairs* helpers, not new machinery. The one piece of design worth thinking about is the apply closure on PlanResult, which is captured during Plan() and invoked later by ExecutePlan - it needs to either capture the context or take one, and the latter is probably cleaner given ExecutePlan is its only caller.
Worth doing alongside #423, since both are about giving a task access to per-invocation state it currently reads from process globals.
Description of feature
There is no way to cancel a task once it has started.
The
Taskinterface takes no arguments at all:Plan() PlanResultandExecute() TaskOutputState. Underneath, tasks callsubprocess.CallExecCommandandsubprocess.Probe, both of which run oncontext.Background(). A longgit:sync, a slowletsencrypt:enable, or an SSH connection that hangs after the TCP handshake runs to completion no matter what the caller wants, and there is no timeout to bound it.Today the only thing that stops a run is a signal, handled down in
subprocess. That is enough for a person at a terminal and not much else. It means a task cannot be given a deadline, a test cannot bound a misbehaving fake, and anything embedding the engine has no way to abandon work in progress.The plumbing is already half there:
CallExecCommandWithContextandCallSshCommandWithContextexist and take a context; it is only the argument-free convenience wrappers that tasks actually call. So the change is a signature change through the interface and its 73 implementations plus the sharedplanProperty/planToggle/planPairs*helpers, not new machinery. The one piece of design worth thinking about is theapplyclosure onPlanResult, which is captured duringPlan()and invoked later byExecutePlan- it needs to either capture the context or take one, and the latter is probably cleaner givenExecutePlanis its only caller.Worth doing alongside #423, since both are about giving a task access to per-invocation state it currently reads from process globals.