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.
This commit is contained in:
Nahor 2024-10-02 14:10:26 -07:00
parent c52867eb8b
commit 5c17abd1bf

View file

@ -5,6 +5,7 @@ use std::{
io::{self, Read, Seek, StdoutLock, Write}, io::{self, Read, Seek, StdoutLock, Write},
path::{Path, MAIN_SEPARATOR_STR}, path::{Path, MAIN_SEPARATOR_STR},
process::{Command, Stdio}, process::{Command, Stdio},
sync::mpsc,
thread, thread,
}; };
@ -409,35 +410,43 @@ impl AppState {
let n_exercises = self.exercises.len(); let n_exercises = self.exercises.len();
let (mut checked_count, mut results) = thread::scope(|s| { let (mut checked_count, mut results) = thread::scope(|s| {
let handles = self let (tx, rx) = mpsc::channel();
.exercises
self.exercises
.iter() .iter()
.map(|exercise| { .enumerate()
thread::Builder::new() .for_each(|(index, exercise)| {
.spawn_scoped(s, || exercise.run_exercise(None, &self.cmd_runner)) let tx = tx.clone();
}) let cmd_runner = &self.cmd_runner;
.collect::<Vec<_>>(); 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 results = vec![AllExercisesResult::Pending; n_exercises];
let mut checked_count = 0; 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}")?; write!(stdout, "\rProgress: {checked_count}/{n_exercises}")?;
stdout.flush()?; 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)) Ok::<_, io::Error>((checked_count, results))