fix(workflow): cancel setup commands during startup shutdown

Reuse the existing sandbox cancellation bridge for workflow setup
commands so server-side startup cancellation interrupts setup work
promptly and preserves the cancelled terminal state under nextest.
This commit is contained in:
Bryan Helmkamp 2026-04-08 14:58:32 -04:00
parent f7d86d72e2
commit bbd04111ad
2 changed files with 38 additions and 24 deletions

View file

@ -74,29 +74,7 @@ impl EngineServices {
/// Bridge the core executor's atomic cancel flag to sandbox command cancellation.
pub fn sandbox_cancel_token(&self) -> Option<CancellationToken> {
let cancel_requested = self.cancel_requested.clone()?;
let token = CancellationToken::new();
if cancel_requested.load(Ordering::Relaxed) {
token.cancel();
return Some(token);
}
let token_clone = token.clone();
tokio::spawn(async move {
loop {
if token_clone.is_cancelled() {
return;
}
if cancel_requested.load(Ordering::Relaxed) {
token_clone.cancel();
return;
}
time::sleep(Duration::from_millis(10)).await;
}
});
Some(token)
sandbox_cancel_token(self.cancel_requested.clone())
}
/// Run lifecycle hooks and return the merged decision.
@ -150,6 +128,34 @@ impl EngineServices {
}
}
pub(crate) fn sandbox_cancel_token(
cancel_requested: Option<Arc<AtomicBool>>,
) -> Option<CancellationToken> {
let cancel_requested = cancel_requested?;
let token = CancellationToken::new();
if cancel_requested.load(Ordering::Relaxed) {
token.cancel();
return Some(token);
}
let token_clone = token.clone();
tokio::spawn(async move {
loop {
if token_clone.is_cancelled() {
return;
}
if cancel_requested.load(Ordering::Relaxed) {
token_clone.cancel();
return;
}
time::sleep(Duration::from_millis(10)).await;
}
});
Some(token)
}
/// The handler interface for node execution.
#[async_trait]
pub trait Handler: Send + Sync {

View file

@ -589,16 +589,24 @@ pub async fn initialize(
index,
});
let cmd_start = Instant::now();
let cancel_token =
crate::handler::sandbox_cancel_token(options.run_options.cancel_token.clone());
let result = sandbox
.exec_command(
command,
options.lifecycle.setup_command_timeout_ms,
None,
None,
None,
cancel_token.clone(),
)
.await
.map_err(|e| FabroError::engine(format!("Setup command failed: {e}")))?;
if let Some(token) = &cancel_token {
if token.is_cancelled() {
return Err(FabroError::Cancelled);
}
token.cancel();
}
let duration_ms = crate::millis_u64(cmd_start.elapsed());
if result.exit_code != 0 {
options.emitter.emit(&Event::SetupFailed {