ax-task 0.8.2

OS-independent IRQ-safe SMP task scheduling core
Documentation
#[path = "../src/sync/mutex/entry.rs"]
mod entry;
#[path = "../src/sched/system/task_system/pi/transition.rs"]
mod transition;

use core::{
    cell::Cell,
    sync::atomic::{AtomicU64, Ordering},
};

use entry::{
    FastLockAttempt, FastReleaseAttempt, LockEntry, capture_current_and_prepare_slow,
    owner_spin_eligible, owner_spin_progress_gates, try_release_current_owner_word,
};
use transition::{
    PiOwnerRqAccountingPath, owner_rq_needs_current_settlement, publish_owner_after_waiter_detach,
};

#[test]
fn uncontended_entry_does_not_validate_a_blocking_context() {
    let blocking_validations = Cell::new(0);

    let entry = capture_current_and_prepare_slow(
        || 1_u64,
        |_| FastLockAttempt::Acquired,
        || blocking_validations.set(blocking_validations.get() + 1),
    );

    assert!(matches!(entry, LockEntry::Acquired));
    assert_eq!(blocking_validations.get(), 0);
}

#[test]
fn contended_entry_attempts_owner_fastpath_once_before_slowpath() {
    let fast_attempts = Cell::new(0);
    let current_captures = Cell::new(0);
    let blocking_validations = Cell::new(0);

    let entry = capture_current_and_prepare_slow(
        || {
            current_captures.set(current_captures.get() + 1);
            1_u64
        },
        |_| {
            fast_attempts.set(fast_attempts.get() + 1);
            FastLockAttempt::Contended
        },
        || blocking_validations.set(blocking_validations.get() + 1),
    );

    assert!(matches!(entry, LockEntry::Contended(1)));
    assert_eq!(
        fast_attempts.get(),
        1,
        "Linux rtmutex performs one owner-word fast attempt before the wait-locked slowpath",
    );
    assert_eq!(
        current_captures.get(),
        1,
        "the move-only current token must be reused from fast attempt through slow entry",
    );
    assert_eq!(blocking_validations.get(), 1);
}

#[test]
fn fast_release_rejects_a_different_executing_identity() {
    let owner = AtomicU64::new(7);

    assert_eq!(
        try_release_current_owner_word(&owner, 8, u64::MAX >> 1),
        FastReleaseAttempt::InvalidOwner,
    );
    assert_eq!(owner.load(Ordering::Relaxed), 7);
}

#[test]
fn single_cpu_spin_gate_skips_owner_progress_observations() {
    let progress_observations = Cell::new(0);

    let eligible = owner_spin_eligible(1, || {
        progress_observations.set(progress_observations.get() + 1);
        true
    });

    assert!(!eligible);
    assert_eq!(
        progress_observations.get(),
        0,
        "Linux compiles owner spinning out on non-SMP and performs no owner-progress observations",
    );
}

#[test]
fn owner_spin_requires_every_linux_progress_gate() {
    assert!(owner_spin_eligible(2, || owner_spin_progress_gates(
        true, true, true, false,
    )));
    assert!(!owner_spin_eligible(2, || owner_spin_progress_gates(
        false, true, true, false,
    )));
    assert!(!owner_spin_eligible(2, || owner_spin_progress_gates(
        true, false, true, false,
    )));
    assert!(!owner_spin_eligible(2, || owner_spin_progress_gates(
        true, true, false, false,
    )));
    assert!(!owner_spin_eligible(2, || owner_spin_progress_gates(
        true, true, true, true,
    )));
}

#[test]
fn waiter_edge_is_detached_before_owner_update() {
    let mut events = Vec::new();

    publish_owner_after_waiter_detach(
        &mut events,
        |events| {
            events.push("detach waiter");
            Ok::<_, ()>(())
        },
        |events, _detached| {
            events.push("publish owner");
            Ok::<_, ()>(())
        },
        |_events, _detached| unreachable!("successful publication must not roll back"),
    )
    .unwrap();

    assert_eq!(events, ["detach waiter", "publish owner"]);
}

#[test]
fn failed_owner_update_restores_the_detached_waiter() {
    let mut events = Vec::new();

    let result = publish_owner_after_waiter_detach(
        &mut events,
        |events| {
            events.push("detach waiter");
            Ok::<_, &'static str>(())
        },
        |events, _detached| {
            events.push("publish owner");
            Err::<(), _>("owner update failed")
        },
        |events, _detached| events.push("restore waiter"),
    );

    assert_eq!(result, Err("owner update failed"));
    assert_eq!(events, ["detach waiter", "publish owner", "restore waiter"]);
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum TestSchedulerClass {
    Stop,
    Deadline,
    Realtime,
    Fair,
}

#[test]
fn pi_owner_update_accounts_the_linux_class_dequeue_path() {
    assert!(owner_rq_needs_current_settlement(
        PiOwnerRqAccountingPath::Running,
        Some(TestSchedulerClass::Stop),
        Some(TestSchedulerClass::Stop),
    ));

    for class in [
        TestSchedulerClass::Deadline,
        TestSchedulerClass::Realtime,
        TestSchedulerClass::Fair,
    ] {
        assert!(owner_rq_needs_current_settlement(
            PiOwnerRqAccountingPath::QueuedClassDequeue,
            Some(class),
            Some(class),
        ));
    }

    assert!(!owner_rq_needs_current_settlement(
        PiOwnerRqAccountingPath::QueuedClassDequeue,
        None,
        Some(TestSchedulerClass::Stop),
    ));
    assert!(!owner_rq_needs_current_settlement(
        PiOwnerRqAccountingPath::QueuedClassDequeue,
        Some(TestSchedulerClass::Fair),
        Some(TestSchedulerClass::Realtime),
    ));
    assert!(!owner_rq_needs_current_settlement(
        PiOwnerRqAccountingPath::Inactive,
        Some(TestSchedulerClass::Fair),
        Some(TestSchedulerClass::Fair),
    ));
    assert!(!owner_rq_needs_current_settlement(
        PiOwnerRqAccountingPath::QueuedClassDequeue,
        Some(TestSchedulerClass::Fair),
        None,
    ));
}