From 2d26358602fc1cd0a026f634b38c34e7b4618cc9 Mon Sep 17 00:00:00 2001
From: mo8it <mo8it@proton.me>
Date: Fri, 6 Sep 2024 15:40:25 +0200
Subject: [PATCH] Use the thread builder and handle the spawn error

---
 clippy.toml      |  3 +++
 src/app_state.rs | 11 +++++++++--
 src/dev/check.rs | 27 ++++++++++++++++++---------
 src/watch.rs     |  6 ++++--
 4 files changed, 34 insertions(+), 13 deletions(-)

diff --git a/clippy.toml b/clippy.toml
index 81e372a..4a5dd06 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 ed723c2..ecb4689 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::<Vec<_>>();
 
-            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 a6db3c2..b7e23dc 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::<Vec<_>>();
+        .collect::<Result<Vec<_>, _>>()
+        .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::<Vec<_>>();
+        .collect::<Result<Vec<_>, _>>()
+        .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 900eba7..a44b565 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 {