From ec65db12f37d5829be61d237f2e7d8d21dfd5d92 Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Sun, 5 Jul 2026 16:05:18 -0700 Subject: [PATCH] build webhook cancel build before triggering another - better behavior for back-to-back pushes --- bin/core/src/api/listener/resources.rs | 77 ++++++++++++++++++++------ 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/bin/core/src/api/listener/resources.rs b/bin/core/src/api/listener/resources.rs index 2f1be4964..c6be345cc 100644 --- a/bin/core/src/api/listener/resources.rs +++ b/bin/core/src/api/listener/resources.rs @@ -1,4 +1,4 @@ -use std::{str::FromStr, sync::OnceLock}; +use std::{str::FromStr, sync::OnceLock, time::Duration}; use anyhow::{Context, anyhow}; use komodo_client::{ @@ -23,6 +23,7 @@ use crate::{ }, helpers::update::init_execution_update, resource, + state::action_states, }; use super::{ANY_BRANCH, ListenerLockCache}; @@ -37,11 +38,6 @@ impl super::CustomSecret for Build { } } -fn build_locks() -> &'static ListenerLockCache { - static BUILD_LOCKS: OnceLock = OnceLock::new(); - BUILD_LOCKS.get_or_init(Default::default) -} - pub async fn handle_build_webhook( build: Build, body: String, @@ -50,12 +46,6 @@ pub async fn handle_build_webhook( return Ok(()); } - // Acquire and hold lock to make a task queue for - // subsequent listener calls on same resource. - // It would fail if we let it go through from action state busy. - let lock = build_locks().get_or_insert_default(&build.id).await; - let _lock = lock.lock().await; - // Use the correct target branch when using linked repo. let branch = if build.config.linked_repo.is_empty() { build.config.branch @@ -69,13 +59,45 @@ pub async fn handle_build_webhook( B::verify_branch(&body, &branch)?; + // Cancel if currently building + if action_states() + .build + .get(&build.id) + .await + .and_then(|states| { + states.get().ok().map(|states| states.building) + }) + .unwrap_or_default() + { + let user = git_webhook_user().to_owned(); + let cancel = ExecuteRequest::CancelBuild(CancelBuild { + build: build.id.clone(), + }); + let update = init_execution_update(&cancel, &user).await?; + let ExecuteRequest::CancelBuild(cancel) = cancel else { + unreachable!() + }; + + cancel + .resolve(&ExecuteArgs { + user, + update, + task_id: Uuid::new_v4(), + }) + .await + .ok(); + + poll_build_until_cancelled(&build.id).await?; + } + let user = git_webhook_user().to_owned(); - let req = ExecuteRequest::RunBuild(RunBuild { build: build.id }); - let update = init_execution_update(&req, &user).await?; - let ExecuteRequest::RunBuild(req) = req else { + let run = ExecuteRequest::RunBuild(RunBuild { build: build.id }); + let update = init_execution_update(&run, &user).await?; + let ExecuteRequest::RunBuild(run) = run else { unreachable!() }; - req + + run .resolve(&ExecuteArgs { user, update, @@ -83,9 +105,32 @@ pub async fn handle_build_webhook( }) .await .map_err(|e| e.error)?; + Ok(()) } +async fn poll_build_until_cancelled( + build_id: &String, +) -> anyhow::Result<()> { + let action_states = action_states(); + // Poll to ensure cancelled + for _ in 0..10 { + if !action_states + .build + .get(build_id) + .await + .and_then(|states| { + states.get().ok().map(|states| states.building) + }) + .unwrap_or_default() + { + return Ok(()); + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + Err(anyhow!("Build still running after cancel")) +} + // ====== // REPO // ======