From 708c37aed17c284b05066149c8d035d6cbe86850 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sun, 12 Apr 2026 12:36:19 -0400 Subject: [PATCH] refactor(async): prefer tokio sleeps and lint std::thread Add a Clippy disallowed-methods guardrail for std::thread sleep/spawn and convert the CLI polling paths to tokio::time::sleep so they no longer block Tokio workers. Keep the intentional OS-thread sites with narrow #[expect(...)] annotations that explain why std::thread is required there. --- Cargo.toml | 1 + clippy.toml | 5 ++++ .../fabro-cli/src/commands/run/runner.rs | 4 +++ lib/crates/fabro-cli/src/commands/run/wait.rs | 5 ++-- .../fabro-cli/src/commands/server/mod.rs | 2 +- .../fabro-cli/src/commands/server/start.rs | 30 ++++++++++--------- .../fabro-cli/src/commands/server/stop.rs | 8 ++--- .../fabro-cli/src/commands/uninstall.rs | 6 ++-- lib/crates/fabro-cli/src/server_client.rs | 2 ++ lib/crates/fabro-cli/tests/it/cmd/attach.rs | 8 +++++ lib/crates/fabro-cli/tests/it/cmd/runner.rs | 4 +++ .../fabro-cli/tests/it/cmd/server_start.rs | 4 +++ lib/crates/fabro-cli/tests/it/cmd/support.rs | 16 ++++++++++ .../fabro-cli/tests/it/scenario/recovery.rs | 8 +++++ lib/crates/fabro-server/src/server.rs | 4 +++ lib/crates/fabro-telemetry/src/buffer.rs | 4 +++ lib/crates/fabro-telemetry/src/lib.rs | 4 +++ lib/crates/fabro-telemetry/src/spawn.rs | 4 +++ lib/crates/fabro-test/src/lib.rs | 12 ++++++++ lib/crates/fabro-workflow/src/handler/mod.rs | 4 +++ .../tests/it/daytona_integration.rs | 4 +++ .../fabro-workflow/tests/it/integration.rs | 4 +++ 22 files changed, 119 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f39bac0ba..6c7992951 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,6 +108,7 @@ use_self = "warn" # Project-specific lints wildcard_imports = "warn" absolute_paths = "warn" +disallowed_methods = "warn" [profile.release] lto = "thin" diff --git a/clippy.toml b/clippy.toml index ecc515b4f..5680497c0 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,7 @@ absolute-paths-max-segments = 2 absolute-paths-allowed-crates = ["std", "core", "alloc"] +disallowed-methods = [ + { path = "std::thread::sleep", reason = "Prefer tokio::time::sleep on Tokio paths; document intentional blocking sleeps with #[expect(clippy::disallowed_methods, reason = \"...\")]", replacement = "tokio::time::sleep" }, + { path = "std::thread::spawn", reason = "Prefer Tokio task APIs on async paths; document intentional dedicated OS threads with #[expect(clippy::disallowed_methods, reason = \"...\")]" }, + { path = "std::thread::Builder::spawn", reason = "Prefer Tokio task APIs on async paths; document intentional dedicated OS threads with #[expect(clippy::disallowed_methods, reason = \"...\")]" }, +] diff --git a/lib/crates/fabro-cli/src/commands/run/runner.rs b/lib/crates/fabro-cli/src/commands/run/runner.rs index 9ffa0059d..1adef0fbb 100644 --- a/lib/crates/fabro-cli/src/commands/run/runner.rs +++ b/lib/crates/fabro-cli/src/commands/run/runner.rs @@ -114,6 +114,10 @@ enum WorkerControlStreamEvent { Eof, } +#[expect( + clippy::disallowed_methods, + reason = "Worker control reads blocking stdin on a dedicated OS thread and forwards lines into Tokio." +)] fn spawn_worker_control_stream( interviewer: Arc, cancel_token: Arc, diff --git a/lib/crates/fabro-cli/src/commands/run/wait.rs b/lib/crates/fabro-cli/src/commands/run/wait.rs index e99bc363e..7acf6ffd3 100644 --- a/lib/crates/fabro-cli/src/commands/run/wait.rs +++ b/lib/crates/fabro-cli/src/commands/run/wait.rs @@ -6,6 +6,7 @@ use fabro_util::printer::Printer; use fabro_util::terminal::Styles; use fabro_workflow::records::Conclusion; use fabro_workflow::run_status::RunStatus; +use tokio::time; use tracing::info; use crate::args::{GlobalArgs, WaitArgs}; @@ -65,9 +66,9 @@ pub(crate) async fn run( run_id ); } - std::thread::sleep(interval.min(dl - now)); + time::sleep(interval.min(dl - now)).await; } else { - std::thread::sleep(interval); + time::sleep(interval).await; } }; diff --git a/lib/crates/fabro-cli/src/commands/server/mod.rs b/lib/crates/fabro-cli/src/commands/server/mod.rs index 68ffa9fac..2e16117de 100644 --- a/lib/crates/fabro-cli/src/commands/server/mod.rs +++ b/lib/crates/fabro-cli/src/commands/server/mod.rs @@ -55,7 +55,7 @@ pub(crate) async fn dispatch( }) => { let settings = user_config::load_settings_with_storage_dir(storage_dir.as_deref())?; let storage_dir = user_config::storage_dir(&settings)?; - stop::execute(&storage_dir, Duration::from_secs(timeout), printer); + stop::execute(&storage_dir, Duration::from_secs(timeout), printer).await; Ok(()) } ServerCommand::Status(ServerStatusArgs { storage_dir, json }) => { diff --git a/lib/crates/fabro-cli/src/commands/server/start.rs b/lib/crates/fabro-cli/src/commands/server/start.rs index 24435d955..2da794809 100644 --- a/lib/crates/fabro-cli/src/commands/server/start.rs +++ b/lib/crates/fabro-cli/src/commands/server/start.rs @@ -1,5 +1,4 @@ use std::path::{Path, PathBuf}; -use std::thread; use std::time::Duration; use anyhow::{Result, bail}; @@ -12,6 +11,7 @@ use fabro_server::serve; use fabro_server::serve::{DEFAULT_TCP_PORT, ServeArgs}; use fabro_util::printer::Printer; use fabro_util::terminal::Styles; +use tokio::time; use super::record; @@ -28,11 +28,11 @@ pub(crate) async fn execute( if foreground { Box::pin(execute_foreground(bind, serve_args, storage_dir, styles)).await } else { - execute_daemon(&bind, &serve_args, &storage_dir, true, printer) + execute_daemon(&bind, &serve_args, &storage_dir, true, printer).await } } -pub(crate) fn ensure_server_running_for_storage( +pub(crate) async fn ensure_server_running_for_storage( storage_dir: &Path, config_path: &Path, ) -> Result { @@ -41,20 +41,20 @@ pub(crate) fn ensure_server_running_for_storage( } let bind = Bind::Unix(default_socket_path()); - ensure_server_running_with_bind(bind, config_path, storage_dir) + ensure_server_running_with_bind(bind, config_path, storage_dir).await } -pub(crate) fn ensure_server_running_on_socket( +pub(crate) async fn ensure_server_running_on_socket( socket_path: &Path, config_path: &Path, storage_dir: &Path, ) -> Result<()> { let bind = Bind::Unix(socket_path.to_path_buf()); - let _ = ensure_server_running_with_bind(bind, config_path, storage_dir)?; + let _ = ensure_server_running_with_bind(bind, config_path, storage_dir).await?; Ok(()) } -fn ensure_server_running_with_bind( +async fn ensure_server_running_with_bind( bind: Bind, config_path: &Path, storage_dir: &Path, @@ -93,7 +93,9 @@ fn ensure_server_running_with_bind( storage_dir, false, Printer::Silent, - ) { + ) + .await + { Ok(()) => Ok(bind), Err(err) => { if let Some(existing) = record::active_server_record(storage_dir) { @@ -122,7 +124,7 @@ async fn execute_foreground( storage_dir: PathBuf, styles: &'static Styles, ) -> Result<()> { - let lock_file = acquire_lock(&storage_dir)?; + let lock_file = acquire_lock(&storage_dir).await?; let _lock_file = lock_file; // keep alive for the duration if let Some(existing) = record::active_server_record(&storage_dir) { @@ -171,14 +173,14 @@ async fn execute_foreground( // Daemon mode // --------------------------------------------------------------------------- -fn execute_daemon( +async fn execute_daemon( bind: &BindRequest, serve_args: &ServeArgs, storage_dir: &Path, announce: bool, printer: Printer, ) -> Result<()> { - let lock_file = acquire_lock(storage_dir)?; + let lock_file = acquire_lock(storage_dir).await?; let _lock_file = lock_file; // keep alive until function returns if let Some(existing) = record::active_server_record(storage_dir) { @@ -288,7 +290,7 @@ fn execute_daemon( bail!("Server exited during startup with status {status}"); } - thread::sleep(poll_interval); + time::sleep(poll_interval).await; elapsed += poll_interval; } @@ -306,7 +308,7 @@ fn execute_daemon( // Helpers // --------------------------------------------------------------------------- -fn acquire_lock(storage_dir: &Path) -> Result { +async fn acquire_lock(storage_dir: &Path) -> Result { let lock_path = Storage::new(storage_dir).server_state().lock_path(); if let Some(parent) = lock_path.parent() { std::fs::create_dir_all(parent)?; @@ -325,7 +327,7 @@ fn acquire_lock(storage_dir: &Path) -> Result { if elapsed >= timeout { bail!("timed out waiting for server lock"); } - thread::sleep(poll_interval); + time::sleep(poll_interval).await; elapsed += poll_interval; } diff --git a/lib/crates/fabro-cli/src/commands/server/stop.rs b/lib/crates/fabro-cli/src/commands/server/stop.rs index c10dd49c5..2e53337ff 100644 --- a/lib/crates/fabro-cli/src/commands/server/stop.rs +++ b/lib/crates/fabro-cli/src/commands/server/stop.rs @@ -1,13 +1,13 @@ use std::path::Path; -use std::thread; use std::time::Duration; use fabro_server::bind::Bind; use fabro_util::printer::Printer; +use tokio::time; use super::record; -pub(crate) fn execute(storage_dir: &Path, timeout: Duration, printer: Printer) { +pub(crate) async fn execute(storage_dir: &Path, timeout: Duration, printer: Printer) { let Some(active) = record::active_server_record_details(storage_dir) else { fabro_util::printerr!(printer, "Server is not running"); std::process::exit(1); @@ -22,13 +22,13 @@ pub(crate) fn execute(storage_dir: &Path, timeout: Duration, printer: Printer) { if !fabro_proc::process_alive(record.pid) { break; } - thread::sleep(poll_interval); + time::sleep(poll_interval).await; elapsed += poll_interval; } if fabro_proc::process_alive(record.pid) { fabro_proc::sigkill(record.pid); - thread::sleep(Duration::from_millis(100)); + time::sleep(Duration::from_millis(100)).await; } record::remove_server_record(&active.record_path); diff --git a/lib/crates/fabro-cli/src/commands/uninstall.rs b/lib/crates/fabro-cli/src/commands/uninstall.rs index ffe48cffe..9cd9593b0 100644 --- a/lib/crates/fabro-cli/src/commands/uninstall.rs +++ b/lib/crates/fabro-cli/src/commands/uninstall.rs @@ -60,7 +60,7 @@ pub(crate) async fn run_uninstall( return Ok(()); } - execute_uninstall(&inventory, globals.json, printer) + execute_uninstall(&inventory, globals.json, printer).await } fn build_inventory(home_root: &Path, storage_dir: &Path) -> Inventory { @@ -226,7 +226,7 @@ struct UninstallResult { binary_hint: Option, } -fn execute_uninstall(inventory: &Inventory, json: bool, printer: Printer) -> Result<()> { +async fn execute_uninstall(inventory: &Inventory, json: bool, printer: Printer) -> Result<()> { let green = console::Style::new().green(); let dim = console::Style::new().dim(); let bold = console::Style::new().bold(); @@ -242,7 +242,7 @@ fn execute_uninstall(inventory: &Inventory, json: bool, printer: Printer) -> Res // Unit 3a: Server stop if inventory.server_running { - server::stop::execute(&inventory.storage_dir, Duration::from_secs(5), printer); + server::stop::execute(&inventory.storage_dir, Duration::from_secs(5), printer).await; result.server_stopped = true; } diff --git a/lib/crates/fabro-cli/src/server_client.rs b/lib/crates/fabro-cli/src/server_client.rs index 9cc115c46..d37f10d9f 100644 --- a/lib/crates/fabro-cli/src/server_client.rs +++ b/lib/crates/fabro-cli/src/server_client.rs @@ -113,6 +113,7 @@ pub(crate) async fn connect_server_with_settings( async fn connect_api_client_bundle(storage_dir: &Path) -> Result { let config_path = user_config::active_settings_path(None); let bind = start::ensure_server_running_for_storage(storage_dir, &config_path) + .await .with_context(|| format!("Failed to start fabro server for {}", storage_dir.display()))?; match bind { Bind::Unix(path) => connect_unix_socket_api_client_bundle(&path).await, @@ -145,6 +146,7 @@ async fn connect_target_api_client_bundle( &runtime.active_config_path, &runtime.storage_dir, ) + .await .with_context(|| format!("Failed to start fabro server for {}", path.display()))?; connect_unix_socket_api_client_bundle(path).await } diff --git a/lib/crates/fabro-cli/tests/it/cmd/attach.rs b/lib/crates/fabro-cli/tests/it/cmd/attach.rs index ee65af4f4..e56fac5dc 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/attach.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/attach.rs @@ -180,6 +180,10 @@ fn attach_replays_completed_detached_run() { } #[test] +#[expect( + clippy::disallowed_methods, + reason = "This sync integration test uses a dedicated stderr reader thread so the child process can stream output concurrently." +)] fn attach_before_completion_streams_to_finished_state() { let context = test_context!(); let gate = write_gated_workflow(&context.temp_dir.join("slow.fabro"), "slow", "Run slowly"); @@ -284,6 +288,10 @@ fn attach_before_completion_streams_to_finished_state() { } #[test] +#[expect( + clippy::disallowed_methods, + reason = "This sync integration test polls logs for a human gate without creating a Tokio runtime." +)] fn attach_json_errors_without_prompting_for_human_input() { let context = test_context!(); let workflow = context.temp_dir.join("human-gate.fabro"); diff --git a/lib/crates/fabro-cli/tests/it/cmd/runner.rs b/lib/crates/fabro-cli/tests/it/cmd/runner.rs index 0b2fea0b0..1eddedb85 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/runner.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/runner.rs @@ -66,6 +66,10 @@ fn spawn_worker_process( cmd.spawn().expect("worker should spawn") } +#[expect( + clippy::disallowed_methods, + reason = "This sync integration helper polls child exit without requiring a Tokio runtime." +)] fn wait_for_child_exit(child: &mut Child, timeout: Duration) -> ExitStatus { let deadline = Instant::now() + timeout; loop { diff --git a/lib/crates/fabro-cli/tests/it/cmd/server_start.rs b/lib/crates/fabro-cli/tests/it/cmd/server_start.rs index da04149fa..162b7f87d 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/server_start.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/server_start.rs @@ -337,6 +337,10 @@ fn isolated_server_switches_context_to_separate_daemon() { } #[test] +#[expect( + clippy::disallowed_methods, + reason = "This sync integration test uses OS threads to exercise concurrent CLI auto-start behavior across separate processes." +)] fn concurrent_autostart_converges_on_one_shared_daemon_and_cleans_up() { fn run_ps_json( home_dir: &std::path::Path, diff --git a/lib/crates/fabro-cli/tests/it/cmd/support.rs b/lib/crates/fabro-cli/tests/it/cmd/support.rs index 5c78df473..8c8965965 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/support.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/support.rs @@ -219,6 +219,10 @@ fn fast_simple_workflow(context: &TestContext) -> PathBuf { workflow } +#[expect( + clippy::disallowed_methods, + reason = "This sync integration helper polls run artifacts after spawning a detached CLI process." +)] pub(crate) fn setup_detached_dry_run(context: &TestContext) -> RunSetup { let workflow = fixture("simple.fabro"); let run_id = unique_run_id(); @@ -457,6 +461,10 @@ pub(crate) fn write_gated_workflow(path: &Path, name: &str, goal: &str) -> Workf WorkflowGate { gate_path } } +#[expect( + clippy::disallowed_methods, + reason = "This sync integration helper polls stored run status without requiring a Tokio runtime." +)] pub(crate) fn wait_for_status(run_dir: &Path, expected: &[&str]) -> String { let deadline = Instant::now() + COMMAND_TIMEOUT; loop { @@ -518,6 +526,10 @@ pub(crate) fn git_filters(context: &TestContext) -> Vec<(String, String)> { filters } +#[expect( + clippy::disallowed_methods, + reason = "This sync integration helper polls for the run directory to appear without requiring a Tokio runtime." +)] pub(crate) fn resolve_run(context: &TestContext, run_id: &str) -> RunSetup { let deadline = Instant::now() + COMMAND_TIMEOUT; loop { @@ -674,6 +686,10 @@ pub(crate) fn run_events(run_dir: &Path) -> Vec { crate::support::parse_event_envelopes(&response) } +#[expect( + clippy::disallowed_methods, + reason = "This sync integration helper polls stored events without requiring a Tokio runtime." +)] pub(crate) fn wait_for_event_names(run_dir: &Path, expected: &[&str]) { let deadline = std::time::Instant::now() + COMMAND_TIMEOUT; diff --git a/lib/crates/fabro-cli/tests/it/scenario/recovery.rs b/lib/crates/fabro-cli/tests/it/scenario/recovery.rs index 12dfc57bf..7d823913e 100644 --- a/lib/crates/fabro-cli/tests/it/scenario/recovery.rs +++ b/lib/crates/fabro-cli/tests/it/scenario/recovery.rs @@ -73,6 +73,10 @@ fn timeline_node_names(repo_dir: &Path, run_id: &str) -> Vec { .collect() } +#[expect( + clippy::disallowed_methods, + reason = "This sync git integration helper polls until metadata commits become readable without requiring Tokio." +)] fn build_timeline_when_ready(repo_dir: &Path, run_id: &str) -> RunTimeline { let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); loop { @@ -91,6 +95,10 @@ fn build_timeline_when_ready(repo_dir: &Path, run_id: &str) -> RunTimeline { } } +#[expect( + clippy::disallowed_methods, + reason = "This sync git integration helper retries metadata branch deletion until libgit2 releases its lock." +)] fn delete_metadata_branch_when_ready(repo_dir: &Path, run_id: &str) { let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); loop { diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs index a1f32746d..84ac2395c 100644 --- a/lib/crates/fabro-server/src/server.rs +++ b/lib/crates/fabro-server/src/server.rs @@ -8094,6 +8094,10 @@ timeout = "30s" } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + #[expect( + clippy::disallowed_methods, + reason = "This test intentionally blocks inside a sync registry factory to simulate slow startup before cancellation." + )] async fn cancel_before_run_transitions_to_running_returns_empty_attach_stream() { let state = create_app_state_with_registry_factory(|interviewer| { std::thread::sleep(std::time::Duration::from_millis(200)); diff --git a/lib/crates/fabro-telemetry/src/buffer.rs b/lib/crates/fabro-telemetry/src/buffer.rs index e5682e0ac..5997159e9 100644 --- a/lib/crates/fabro-telemetry/src/buffer.rs +++ b/lib/crates/fabro-telemetry/src/buffer.rs @@ -148,6 +148,10 @@ mod tests { } #[test] + #[expect( + clippy::disallowed_methods, + reason = "This sync test needs a dedicated OS thread to let the blocking consumer loop flush on its own timer." + )] fn flushes_on_time_threshold() { let (tx, rx) = mpsc::channel(); let mid_flushes: Arc>>> = Arc::new(Mutex::new(Vec::new())); diff --git a/lib/crates/fabro-telemetry/src/lib.rs b/lib/crates/fabro-telemetry/src/lib.rs index 0018c8c05..0f2d8b829 100644 --- a/lib/crates/fabro-telemetry/src/lib.rs +++ b/lib/crates/fabro-telemetry/src/lib.rs @@ -71,6 +71,10 @@ pub fn init_server() { init_inner(level, anonymous_id); } +#[expect( + clippy::disallowed_methods, + reason = "Telemetry uses a long-lived dedicated OS thread for buffered blocking delivery and shutdown joins." +)] fn init_inner(level: TelemetryLevel, anonymous_id: String) { let ctx = context::build_context(); let (tx, rx) = mpsc::channel(); diff --git a/lib/crates/fabro-telemetry/src/spawn.rs b/lib/crates/fabro-telemetry/src/spawn.rs index c00e18644..aa850b82e 100644 --- a/lib/crates/fabro-telemetry/src/spawn.rs +++ b/lib/crates/fabro-telemetry/src/spawn.rs @@ -150,6 +150,10 @@ mod tests { #[cfg(unix)] #[test] + #[expect( + clippy::disallowed_methods, + reason = "This sync test polls for detached child output without creating a Tokio runtime." + )] fn spawn_detached_unix_creates_marker_file() { fn wait_for_file(path: &std::path::Path) { for _ in 0..100 { diff --git a/lib/crates/fabro-test/src/lib.rs b/lib/crates/fabro-test/src/lib.rs index 6d1ae6d70..856826962 100644 --- a/lib/crates/fabro-test/src/lib.rs +++ b/lib/crates/fabro-test/src/lib.rs @@ -255,6 +255,10 @@ fn ensure_parent_dir(path: &Path) { } } +#[expect( + clippy::disallowed_methods, + reason = "This sync test helper polls filesystem and flock state without requiring a Tokio runtime." +)] fn with_session_lock(root: &Path, f: impl FnOnce() -> T) -> T { let lock_path = session_lock_path(root); // Retry create-dir + create-file as a unit: another process's @@ -573,6 +577,10 @@ fn server_running(server: &ServerPaths) -> bool { server_record_pid(&server.storage_dir).is_some_and(fabro_proc::process_alive) } +#[expect( + clippy::disallowed_methods, + reason = "This sync test helper polls a child server process without requiring a Tokio runtime." +)] fn wait_for_server_running(server: &ServerPaths) { let poll = std::time::Duration::from_millis(50); let timeout = std::time::Duration::from_secs(5); @@ -629,6 +637,10 @@ fn ensure_server_running(fabro_bin: &Path, server: &ServerPaths, config_path: &P wait_for_server_running(server); } +#[expect( + clippy::disallowed_methods, + reason = "This sync test helper polls child shutdown during cleanup without requiring a Tokio runtime." +)] fn stop_test_server(server: &ServerPaths) { let record_path = server_record_path(&server.storage_dir); let Some(pid) = server_record_pid(&server.storage_dir) else { diff --git a/lib/crates/fabro-workflow/src/handler/mod.rs b/lib/crates/fabro-workflow/src/handler/mod.rs index 1e97b81b0..2c4811ac9 100644 --- a/lib/crates/fabro-workflow/src/handler/mod.rs +++ b/lib/crates/fabro-workflow/src/handler/mod.rs @@ -93,6 +93,10 @@ impl EngineServices { /// Test-only default: empty registry, no hooks, local sandbox at cwd. #[cfg(test)] + #[expect( + clippy::disallowed_methods, + reason = "This test helper must initialize a current-thread runtime safely from both sync tests and #[tokio::test]." + )] pub fn test_default() -> Self { let store = Arc::new(Database::new( Arc::new(InMemory::new()), diff --git a/lib/crates/fabro-workflow/tests/it/daytona_integration.rs b/lib/crates/fabro-workflow/tests/it/daytona_integration.rs index 0efbd7910..0ac9c3421 100644 --- a/lib/crates/fabro-workflow/tests/it/daytona_integration.rs +++ b/lib/crates/fabro-workflow/tests/it/daytona_integration.rs @@ -45,6 +45,10 @@ fn test_run_id(label: &str) -> RunId { RunId::from(Ulid(u128::from(hasher.finish()))) } +#[expect( + clippy::disallowed_methods, + reason = "This helper spins up a dedicated current-thread runtime when called from inside an existing Tokio runtime." +)] fn load_run_checkpoint(run_dir: &Path) -> Result> { let run_dir = run_dir.to_path_buf(); let uses_shared_store = run_dir diff --git a/lib/crates/fabro-workflow/tests/it/integration.rs b/lib/crates/fabro-workflow/tests/it/integration.rs index efa258767..0483050d2 100644 --- a/lib/crates/fabro-workflow/tests/it/integration.rs +++ b/lib/crates/fabro-workflow/tests/it/integration.rs @@ -80,6 +80,10 @@ fn load_checkpoint(path: &Path) -> Result Ok(serde_json::from_str(&data)?) } +#[expect( + clippy::disallowed_methods, + reason = "This helper spins up a dedicated current-thread runtime when called from inside an existing Tokio runtime." +)] fn load_run_checkpoint(run_dir: &Path) -> Result> { let run_dir = run_dir.to_path_buf(); let uses_shared_store = run_dir