ruCCL 0.21.13

Ruda collective communication algorithms and orchestration.
Documentation
use super::*;

const TEST_TIMEOUT: Duration = Duration::from_secs(5);

#[test]
fn queued_work_survives_dropped_handles_and_senders_in_submission_order() {
    let queue = OrderedWorkQueue::new("ruccl-work-order-test").unwrap();
    let peer = queue.clone();
    let order = Arc::new(Mutex::new(Vec::new()));
    let resource = Arc::new(());
    let weak_resource = Arc::downgrade(&resource);
    let (started_tx, started_rx) = mpsc::channel();
    let (release_tx, release_rx) = mpsc::channel();
    let first_order = order.clone();
    let first = queue
        .submit(move || {
            started_tx.send(()).unwrap();
            release_rx.recv_timeout(TEST_TIMEOUT).unwrap();
            first_order.lock().unwrap().push(0);
            drop(resource);
            Ok::<_, WorkError>(())
        })
        .unwrap();
    started_rx.recv_timeout(TEST_TIMEOUT).unwrap();
    let second_order = order.clone();
    let second = peer
        .submit(move || {
            second_order.lock().unwrap().push(1);
            Ok::<_, WorkError>(17)
        })
        .unwrap();
    drop(first);
    drop(queue);
    drop(peer);
    assert!(weak_resource.upgrade().is_some());
    assert!(!second.is_complete());
    assert!(!second.wait_for(Duration::ZERO).unwrap());
    release_tx.send(()).unwrap();
    assert!(second.wait_for(TEST_TIMEOUT).unwrap());
    assert_eq!(second.wait().unwrap(), 17);
    assert_eq!(*order.lock().unwrap(), vec![0, 1]);
    assert!(weak_resource.upgrade().is_none());
}

#[derive(Debug)]
enum CallerError {
    Work(WorkError),
    Operation(&'static str),
}

impl From<WorkError> for CallerError {
    fn from(error: WorkError) -> Self {
        Self::Work(error)
    }
}

#[test]
fn operation_errors_and_panics_do_not_stop_later_queued_work() {
    let queue = OrderedWorkQueue::new("ruccl-work-error-test").unwrap();
    let operation = queue
        .submit(|| Err::<(), _>(CallerError::Operation("original failure")))
        .unwrap();
    let panic = queue
        .submit(|| -> Result<(), CallerError> { panic!("worker panic") })
        .unwrap();
    let success = queue.submit(|| Ok::<_, CallerError>(23)).unwrap();
    assert!(success.wait_for(TEST_TIMEOUT).unwrap());
    assert!(matches!(
        operation.wait(),
        Err(CallerError::Operation("original failure"))
    ));
    assert!(matches!(
        panic.wait(),
        Err(CallerError::Work(WorkError::WorkerPanic))
    ));
    assert_eq!(success.wait().unwrap(), 23);
}

#[test]
fn spawned_work_can_be_joined_or_detached_without_cancellation() {
    let joined = CollectiveWork::<_, WorkError>::spawn("ruccl-work-join-test", || Ok(31)).unwrap();
    assert!(joined.wait_for(TEST_TIMEOUT).unwrap());
    assert_eq!(joined.wait().unwrap(), 31);

    let (release_tx, release_rx) = mpsc::channel();
    let (finished_tx, finished_rx) = mpsc::channel();
    let detached = CollectiveWork::<_, WorkError>::spawn("ruccl-work-detach-test", move || {
        release_rx.recv_timeout(TEST_TIMEOUT).unwrap();
        finished_tx.send(37).unwrap();
        Ok(())
    })
    .unwrap();
    drop(detached);
    release_tx.send(()).unwrap();
    assert_eq!(finished_rx.recv_timeout(TEST_TIMEOUT).unwrap(), 37);
}

#[test]
fn closed_queue_reports_failure_without_running_the_task() {
    let (sender, receiver) = mpsc::channel::<OrderedTask>();
    drop(receiver);
    let queue = OrderedWorkQueue { sender };
    let resource = Arc::new(());
    let weak_resource = Arc::downgrade(&resource);
    let result = queue.submit(move || -> Result<(), CallerError> {
        drop(resource);
        panic!("closed queue must not execute the task")
    });
    assert!(matches!(
        result,
        Err(CallerError::Work(WorkError::WorkerQueueClosed))
    ));
    assert!(weak_resource.upgrade().is_none());
}

#[test]
fn poisoned_completion_keeps_original_observation_and_wait_behavior() {
    let completion = Arc::new(WorkCompletion::<(), WorkError> {
        result: Mutex::new(None),
        changed: Condvar::new(),
    });
    let poisoned = completion.clone();
    let _ = catch_unwind(move || {
        let _guard = poisoned.result.lock().unwrap();
        panic!("poison completion state")
    });
    let work = CollectiveWork {
        completion,
        worker: None,
    };
    assert!(work.is_complete());
    assert!(matches!(
        work.wait_for(Duration::ZERO),
        Err(WorkError::Poisoned)
    ));
    assert!(matches!(work.wait(), Err(WorkError::Poisoned)));
}