use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Condvar, Mutex, MutexGuard, PoisonError};
use ::mpi::request::RequestCollection;
use crate::engine::transport::ChunkWait;
pub(super) const SLOT_SEND: usize = usize::MAX;
pub(super) const SLOT_EARLY: usize = usize::MAX - 1;
pub(super) fn note_slot(slot_of: &mut Vec<usize>, i: usize, slot: usize) {
if slot_of.len() <= i {
slot_of.resize(i + 1, SLOT_SEND);
}
slot_of[i] = slot;
}
pub(super) struct ChunkPipeline<'a, 'c> {
inner: Mutex<PipelineInner<'a, 'c>>,
remaining: Vec<AtomicUsize>,
driving: Mutex<bool>,
wake: Condvar,
}
struct PipelineInner<'a, 'c> {
requests: &'c mut RequestCollection<'a, [u8]>,
slot_of: Vec<usize>,
done: Vec<(usize, ::mpi::point_to_point::Status, &'a [u8])>,
}
unsafe impl Send for ChunkPipeline<'_, '_> {}
unsafe impl Sync for ChunkPipeline<'_, '_> {}
struct DriverRole<'p, 'a, 'c>(&'p ChunkPipeline<'a, 'c>);
impl Drop for DriverRole<'_, '_, '_> {
fn drop(&mut self) {
*self.0.lock_driving() = false;
self.0.wake.notify_all();
}
}
impl<'a, 'c> ChunkPipeline<'a, 'c> {
pub(super) fn new(
requests: &'c mut RequestCollection<'a, [u8]>,
slot_of: Vec<usize>,
chunks: usize,
) -> Self {
let remaining: Vec<_> = (0..chunks).map(|_| AtomicUsize::new(0)).collect();
for &slot in &slot_of {
if slot < chunks {
remaining[slot].fetch_add(1, Ordering::Relaxed);
}
}
Self {
inner: Mutex::new(PipelineInner { requests, slot_of, done: Vec::new() }),
remaining,
driving: Mutex::new(false),
wake: Condvar::new(),
}
}
fn lock_driving(&self) -> MutexGuard<'_, bool> {
self.driving.lock().unwrap_or_else(PoisonError::into_inner)
}
fn complete(&self, k: usize) -> bool {
self.remaining[k].load(Ordering::Acquire) == 0
}
fn drive_until(&self, k: usize) {
let mut inner = self.inner.lock().unwrap_or_else(PoisonError::into_inner);
let PipelineInner { requests, slot_of, done } = &mut *inner;
while !self.complete(k) && requests.incomplete() > 0 {
requests.wait_some(done);
let mut completed = false;
for &(index, _, _) in done.iter() {
let slot = slot_of[index];
if slot < self.remaining.len() {
completed |= self.remaining[slot].fetch_sub(1, Ordering::Release) == 1;
}
}
if completed {
drop(self.lock_driving());
self.wake.notify_all();
}
}
}
fn wait_slot(&self, k: usize) {
if self.complete(k) {
return;
}
let mut driving = self.lock_driving();
while !self.complete(k) {
if *driving {
driving = self.wake.wait(driving).unwrap_or_else(PoisonError::into_inner);
continue;
}
*driving = true;
drop(driving);
let role = DriverRole(self);
self.drive_until(k);
drop(role);
return;
}
}
pub(super) fn finish(&mut self) {
let inner = self.inner.get_mut().unwrap_or_else(PoisonError::into_inner);
inner.requests.wait_all(&mut inner.done);
for slot in &self.remaining {
slot.store(0, Ordering::Release);
}
}
}
impl ChunkWait for ChunkPipeline<'_, '_> {
fn wait_chunk(&self, k: usize) {
self.wait_slot(k);
}
}
#[cfg(test)]
mod tests;