From 1c4bb8af146ddf48010fa2f2b522e1c13d378291 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 26 Jun 2026 18:35:52 +0200 Subject: [PATCH] test: de-flake asset-dispatch by bypassing cross-DB script-hash caches (#9820) Co-authored-by: Claude Opus 4.8 (1M context) --- backend/tests/asset_trigger_dispatch.rs | 8 +++++ backend/windmill-common/src/lib.rs | 39 ++++++++++++++++++------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/backend/tests/asset_trigger_dispatch.rs b/backend/tests/asset_trigger_dispatch.rs index a68a7d3310..198472d404 100644 --- a/backend/tests/asset_trigger_dispatch.rs +++ b/backend/tests/asset_trigger_dispatch.rs @@ -54,6 +54,14 @@ async fn seed_script( .bind(language) .execute(db) .await?; + // These tests use #[sqlx::test] isolated DBs that share one workspace id and + // reuse script paths, while the same path is seeded with different content + // (hence different hashes) across tests. The process-global deployed-script + // caches are keyed by (workspace, path)/(workspace, hash), so a concurrent + // test resolves a path to a hash that lives in another test's DB and the + // dispatch 404s. Disable them so every resolution reads the test's own DB. + windmill_common::DEPLOYED_SCRIPT_CACHE_DISABLED + .store(true, std::sync::atomic::Ordering::Relaxed); Ok(h) } diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index 784b7589ef..be336a84e1 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -285,6 +285,16 @@ lazy_static::lazy_static! { const LATEST_VERSION_ID_CACHE_TTL: std::time::Duration = std::time::Duration::from_secs(60); +/// Test hook: disables the process-global deployed-script hash/info caches so +/// every resolution reads the current DB. Integration tests use `#[sqlx::test]` +/// isolated DBs that share one workspace id and reuse script paths, so a cache +/// keyed by `(workspace, path)`/`(workspace, hash)` resolves a path to a hash +/// that lives in a *different* test's DB — and when the info cache misses for +/// that foreign hash the lookup 404s in the wrong DB. Always `false` in +/// production (the caches are TTL/LRU-bounded against real deploys). +pub static DEPLOYED_SCRIPT_CACHE_DISABLED: std::sync::atomic::AtomicBool = + std::sync::atomic::AtomicBool::new(false); + pub async fn shutdown_signal( tx: KillpillSender, mut rx: tokio::sync::broadcast::Receiver<()>, @@ -1305,8 +1315,12 @@ pub fn get_latest_deployed_hash_for_path<'e>( ) -> impl Future>> + Send + 'e { async move { let cache_key = (w_id.to_string(), script_path.to_string()); + let use_cache = !DEPLOYED_SCRIPT_CACHE_DISABLED.load(std::sync::atomic::Ordering::Relaxed); let mut computed_hash = None; - let hash = match DEPLOYED_SCRIPT_HASH_CACHE.get(&cache_key) { + let hash = match DEPLOYED_SCRIPT_HASH_CACHE + .get(&cache_key) + .filter(|_| use_cache) + { Some(cached_hash) if cached_hash.expires_at > std::time::Instant::now() && db.as_ref().is_none_or(|x| { @@ -1349,13 +1363,15 @@ pub fn get_latest_deployed_hash_for_path<'e>( }; let hash = utils::not_found_if_none(hash, "script", script_path)?; - DEPLOYED_SCRIPT_HASH_CACHE.insert( - cache_key, - ExpiringLatestVersionId { - id: hash, - expires_at: std::time::Instant::now() + LATEST_VERSION_ID_CACHE_TTL, - }, - ); + if use_cache { + DEPLOYED_SCRIPT_HASH_CACHE.insert( + cache_key, + ExpiringLatestVersionId { + id: hash, + expires_at: std::time::Instant::now() + LATEST_VERSION_ID_CACHE_TTL, + }, + ); + } hash } @@ -1387,9 +1403,10 @@ pub async fn get_script_info_for_hash<'e, E: sqlx::PgExecutor<'e>>( hash: i64, ) -> error::Result> { let key = (w_id.to_string(), hash); + let use_cache = !DEPLOYED_SCRIPT_CACHE_DISABLED.load(std::sync::atomic::Ordering::Relaxed); let mut computed_hash = None; - match DEPLOYED_SCRIPT_INFO_CACHE.get(&key) { + match DEPLOYED_SCRIPT_INFO_CACHE.get(&key).filter(|_| use_cache) { Some(info) if db_authed.as_ref().is_none_or(|x| { let r = HASH_PERMS_CACHE.check_perms_in_cache(x.authed, scripts::ScriptHash(hash)); @@ -1418,7 +1435,9 @@ pub async fn get_script_info_for_hash<'e, E: sqlx::PgExecutor<'e>>( let info = utils::not_found_if_none(info, "script", &hash.to_string())?; - DEPLOYED_SCRIPT_INFO_CACHE.insert(key, info.clone()); + if use_cache { + DEPLOYED_SCRIPT_INFO_CACHE.insert(key, info.clone()); + } Ok(info) }