diff --git a/clippy.toml b/clippy.toml index 81e372a7..4a5dd06a 100644 --- a/clippy.toml +++ b/clippy.toml @@ -10,4 +10,7 @@ disallowed-methods = [ "std::collections::HashSet::with_capacity", # Inefficient. Use `.queue(…)` instead. "crossterm::style::style", + # Use `thread::Builder::spawn` instead and handle the error. + "std::thread::spawn", + "std::thread::Scope::spawn", ] diff --git a/src/app_state.rs b/src/app_state.rs index ed723c22..ecb46898 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -388,13 +388,20 @@ impl AppState { let handles = self .exercises .iter() - .map(|exercise| s.spawn(|| exercise.run_exercise(None, &self.cmd_runner))) + .map(|exercise| { + thread::Builder::new() + .spawn_scoped(s, || exercise.run_exercise(None, &self.cmd_runner)) + }) .collect::>(); - for (exercise_ind, handle) in handles.into_iter().enumerate() { + for (exercise_ind, spawn_res) in handles.into_iter().enumerate() { write!(stdout, "\rProgress: {exercise_ind}/{n_exercises}")?; stdout.flush()?; + let Ok(handle) = spawn_res else { + return Ok(AllExercisesCheck::CheckedUntil(exercise_ind)); + }; + let Ok(success) = handle.join().unwrap() else { return Ok(AllExercisesCheck::CheckedUntil(exercise_ind)); }; diff --git a/src/dev/check.rs b/src/dev/check.rs index a6db3c21..b7e23dca 100644 --- a/src/dev/check.rs +++ b/src/dev/check.rs @@ -185,12 +185,14 @@ fn check_exercises_unsolved( return None; } - Some(( - exercise_info.name.as_str(), - thread::spawn(|| exercise_info.run_exercise(None, cmd_runner)), - )) + Some( + thread::Builder::new() + .spawn(|| exercise_info.run_exercise(None, cmd_runner)) + .map(|handle| (exercise_info.name.as_str(), handle)), + ) }) - .collect::>(); + .collect::, _>>() + .context("Failed to spawn a thread to check if an exercise is already solved")?; let n_handles = handles.len(); write!(stdout, "Progress: 0/{n_handles}")?; @@ -226,7 +228,9 @@ fn check_exercises(info_file: &'static InfoFile, cmd_runner: &'static CmdRunner) Ordering::Equal => (), } - let handle = thread::spawn(move || check_exercises_unsolved(info_file, cmd_runner)); + let handle = thread::Builder::new() + .spawn(move || check_exercises_unsolved(info_file, cmd_runner)) + .context("Failed to spawn a thread to check if any exercise is already solved")?; let info_file_paths = check_info_file_exercises(info_file)?; check_unexpected_files("exercises", &info_file_paths)?; @@ -253,7 +257,7 @@ fn check_solutions( .exercises .iter() .map(|exercise_info| { - thread::spawn(move || { + thread::Builder::new().spawn(move || { let sol_path = exercise_info.sol_path(); if !Path::new(&sol_path).exists() { if require_solutions { @@ -274,7 +278,8 @@ fn check_solutions( } }) }) - .collect::>(); + .collect::, _>>() + .context("Failed to spawn a thread to check a solution")?; let mut sol_paths = hash_set_with_capacity(info_file.exercises.len()); let mut fmt_cmd = Command::new("rustfmt"); @@ -322,7 +327,11 @@ fn check_solutions( } stdout.write_all(b"\n")?; - let handle = thread::spawn(move || check_unexpected_files("solutions", &sol_paths)); + let handle = thread::Builder::new() + .spawn(move || check_unexpected_files("solutions", &sol_paths)) + .context( + "Failed to spawn a thread to check for unexpected files in the solutions directory", + )?; if !fmt_cmd .status() diff --git a/src/watch.rs b/src/watch.rs index 900eba7c..a44b5656 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -1,4 +1,4 @@ -use anyhow::{Error, Result}; +use anyhow::{Context, Error, Result}; use notify_debouncer_mini::{ new_debouncer, notify::{self, RecursiveMode}, @@ -77,7 +77,9 @@ fn run_watch( let mut stdout = io::stdout().lock(); watch_state.run_current_exercise(&mut stdout)?; - thread::spawn(move || terminal_event_handler(tx, manual_run)); + thread::Builder::new() + .spawn(move || terminal_event_handler(tx, manual_run)) + .context("Failed to spawn a thread to handle terminal events")?; while let Ok(event) = rx.recv() { match event {