use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc;
use std::thread;
use henad_core::explore::outcome::RunOutcome;
use super::{BatchEnd, ExecutionError, Executor, Placement, ReorderBuffer, RunRequest, RunSink, SweepControl};
pub(super) fn run_in_lanes(
executor: &Executor<'_>,
pools: &[rayon::ThreadPool],
requests: &[RunRequest<'_>],
sink: &mut dyn RunSink,
) -> Result<BatchEnd, ExecutionError> {
let next_request = AtomicUsize::new(0);
let (sender, receiver) = mpsc::channel::<(usize, RunOutcome)>();
thread::scope(|scope| {
let mut lanes = Vec::with_capacity(pools.len());
for (lane, pool) in pools.iter().enumerate() {
let sender = sender.clone();
let next_request = &next_request;
let spawned = thread::Builder::new()
.name(format!("henad-lane-{lane}"))
.spawn_scoped(scope, move || {
let _abort_on_panic = AbortOnPanic(&executor.control);
while executor.control.proceed() {
let index = next_request.fetch_add(1, Ordering::Relaxed);
let Some(request) = requests.get(index) else {
break;
};
let Some(outcome) = executor.drive_in(Placement::LanePool(pool), request) else {
break;
};
if sender.send((index, outcome)).is_err() {
break;
}
}
});
match spawned {
Ok(handle) => lanes.push(handle),
Err(error) => {
executor.control.abort();
return Err(ExecutionError::Spawn(error));
}
}
}
drop(sender);
let _abort_on_panic = AbortOnPanic(&executor.control);
let mut reorder = ReorderBuffer::default();
let mut refused = None;
for (index, outcome) in &receiver {
sink.finished(&outcome);
reorder.insert(index, outcome);
while let Some(ready) = reorder.pop_ready() {
if let Err(error) = executor.commit(sink, ready) {
refused = Some(error);
break;
}
}
if refused.is_some() {
break;
}
}
let mut lost_lane = false;
for lane in lanes {
lost_lane |= lane.join().is_err();
}
if let Some(error) = refused {
Err(error)
} else if lost_lane {
Err(ExecutionError::LanePanicked)
} else if reorder.handed_out() == requests.len() {
Ok(BatchEnd::Complete)
} else {
Ok(BatchEnd::Aborted)
}
})
}
struct AbortOnPanic<'c>(&'c SweepControl);
impl Drop for AbortOnPanic<'_> {
fn drop(&mut self) {
if thread::panicking() {
self.0.abort();
}
}
}