From 5c17abd1bf52d1222f6574d38a56a29ca0e7696f Mon Sep 17 00:00:00 2001 From: Nahor Date: Wed, 2 Oct 2024 14:10:26 -0700 Subject: [PATCH] Use a channel to update the check_all progress The previous code was checking the threads in the order they were created. So the progress update would be blocked on an earlier thread even if later thread were already done. Add to that that multiple instances of `cargo build` cannot run in parallel, they will be serialized instead. So if the exercises needs to be recompiled, depending on the order those `cargo build` are run, the first update can be a long time coming. So instead of relying on the thread terminating, use a channel to get notified when an exercise check is done, regardless of the order they finish in. --- src/app_state.rs | 55 ++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/app_state.rs b/src/app_state.rs index 99772b7b..28226d64 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -5,6 +5,7 @@ use std::{ io::{self, Read, Seek, StdoutLock, Write}, path::{Path, MAIN_SEPARATOR_STR}, process::{Command, Stdio}, + sync::mpsc, thread, }; @@ -409,35 +410,43 @@ impl AppState { let n_exercises = self.exercises.len(); let (mut checked_count, mut results) = thread::scope(|s| { - let handles = self - .exercises + let (tx, rx) = mpsc::channel(); + + self.exercises .iter() - .map(|exercise| { - thread::Builder::new() - .spawn_scoped(s, || exercise.run_exercise(None, &self.cmd_runner)) - }) - .collect::>(); + .enumerate() + .for_each(|(index, exercise)| { + let tx = tx.clone(); + let cmd_runner = &self.cmd_runner; + let _ = thread::Builder::new().spawn_scoped(s, move || { + tx.send((index, exercise.run_exercise(None, cmd_runner))) + }); + }); + + // Drop this `tx`, since the `rx` loop will not stop while there is + // at least one tx alive (i.e. we want the loop to block only while + // there are `tx` clones, i.e. threads) + drop(tx); let mut results = vec![AllExercisesResult::Pending; n_exercises]; let mut checked_count = 0; - for (exercise_ind, spawn_res) in handles.into_iter().enumerate() { + write!(stdout, "\rProgress: {checked_count}/{n_exercises}")?; + stdout.flush()?; + while let Ok((exercise_ind, result)) = rx.recv() { + results[exercise_ind] = result.map_or_else( + |_| AllExercisesResult::Error, + |success| { + checked_count += 1; + if success { + AllExercisesResult::Success + } else { + AllExercisesResult::Failed + } + }, + ); + write!(stdout, "\rProgress: {checked_count}/{n_exercises}")?; stdout.flush()?; - - results[exercise_ind] = spawn_res - .context("Spawn error") - .and_then(|handle| handle.join().unwrap()) - .map_or_else( - |_| AllExercisesResult::Error, - |success| { - checked_count += 1; - if success { - AllExercisesResult::Success - } else { - AllExercisesResult::Failed - } - }, - ); } Ok::<_, io::Error>((checked_count, results))