From 6a25394e49fe8dc7a189d7b4ec1db9cf033dcfe0 Mon Sep 17 00:00:00 2001 From: Klim Tsoutsman Date: Mon, 8 Jan 2024 16:17:42 +1100 Subject: [PATCH] Add `test_task_priority` Didn't name it `test_priority_scheduler` because it should also test the epoch scheduler. The current epoch scheduler doesn't pass, but the #1088 one does. Signed-off-by: Klim Tsoutsman --- Cargo.lock | 20 +++++ Cargo.toml | 1 + applications/test_task_priority/Cargo.toml | 15 ++++ applications/test_task_priority/src/lib.rs | 90 ++++++++++++++++++++++ kernel/task/src/scheduler.rs | 5 ++ theseus_features/Cargo.toml | 2 + 6 files changed, 133 insertions(+) create mode 100644 applications/test_task_priority/Cargo.toml create mode 100644 applications/test_task_priority/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 5bc9205de2..dc9170603d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1129,6 +1129,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + [[package]] name = "fatfs" version = "0.4.0" @@ -3909,6 +3915,19 @@ dependencies = [ "task", ] +[[package]] +name = "test_task_priority" +version = "0.1.0" +dependencies = [ + "cpu", + "fastrand", + "log", + "preemption", + "random", + "spawn", + "task", +] + [[package]] name = "test_thread_local" version = "0.1.0" @@ -4035,6 +4054,7 @@ dependencies = [ "test_std_fs", "test_sync_block", "test_task_cancel", + "test_task_priority", "test_thread_local", "test_tls", "test_wait_queue", diff --git a/Cargo.toml b/Cargo.toml index 6ac877dcce..6b6f7fb104 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ exclude = [ "applications/test_mlx5", "applications/test_panic", "applications/test_preemption_counter", + "applications/test_task_priority", "applications/test_restartable", "applications/test_scheduler", "applications/test_std_fs", diff --git a/applications/test_task_priority/Cargo.toml b/applications/test_task_priority/Cargo.toml new file mode 100644 index 0000000000..14d72fa7f2 --- /dev/null +++ b/applications/test_task_priority/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "test_task_priority" +version = "0.1.0" +authors = ["Klim Tsoutsman "] +description = "Test for priority schedulers" +edition = "2021" + +[dependencies] +cpu = { path = "../../kernel/cpu" } +fastrand = { version = "2.0.1", default-features = false } +log = "0.4.8" +preemption = { path = "../../kernel/preemption" } +random = { path = "../../kernel/random" } +spawn = { path = "../../kernel/spawn" } +task = { path = "../../kernel/task" } diff --git a/applications/test_task_priority/src/lib.rs b/applications/test_task_priority/src/lib.rs new file mode 100644 index 0000000000..d6bac60995 --- /dev/null +++ b/applications/test_task_priority/src/lib.rs @@ -0,0 +1,90 @@ +//! Test for schedulers supporting priorities i.e. epoch and priority +//! schedulers. +//! +//! The test ensures that tasks are run in order of priority for at least one +//! time slice. + +#![no_std] + +extern crate alloc; + +use alloc::{string::String, vec::Vec}; +use core::sync::atomic::{AtomicUsize, Ordering}; + +static CURRENT_PRIORITY: AtomicUsize = AtomicUsize::new(MAX_PRIORITY); + +const MAX_PRIORITY: usize = 63; + +fn worker(priority: usize) { + // Add a bit of chaos. + // + // NOTE: When using the epoch scheduler, the test relies on the fact that the + // worker runs in less than one time slice, and so we can't yield. + #[cfg(priority_scheduler)] + task::schedule(); + + let previous = CURRENT_PRIORITY.fetch_sub(1, Ordering::Relaxed); + assert_eq!(previous, priority); +} + +fn spawner(_: ()) { + if !task::scheduler::supports_priority() { + log::warn!("scheduler does not support priorities"); + return; + } + + let current_cpu = cpu::current_cpu(); + + let priorities = priorities(); + let mut tasks = Vec::with_capacity(MAX_PRIORITY); + + // We hold preemption here so that when the scheduler next runs, all the worker + // tasks are unblocked and in a random order on the run queue. Holding + // preemption is sufficient as we pin the worker threads to the same core as + // the spawner thread. + let guard = preemption::hold_preemption(); + + for priority in priorities { + let task = spawn::new_task_builder(worker, priority) + .pin_on_cpu(current_cpu) + .block() + .spawn() + .unwrap(); + assert!(task::scheduler::set_priority( + &task, + priority.try_into().unwrap() + )); + tasks.push(task); + } + + for task in tasks.iter() { + task.unblock().unwrap(); + } + + drop(guard); + + for task in tasks { + matches!(task.join().unwrap(), task::ExitValue::Completed(_)); + } +} + +/// Returns a shuffled list of priorities. +fn priorities() -> Vec { + let mut priorities = (0..=MAX_PRIORITY).collect::>(); + + let mut rng = fastrand::Rng::with_seed(random::next_u64()); + rng.shuffle(&mut priorities); + + priorities +} + +pub fn main(_: Vec) -> isize { + let current_cpu = cpu::current_cpu(); + // The spawning thread must be pinned to the same CPU as the worker threads. + let task = spawn::new_task_builder(spawner, ()) + .pin_on_cpu(current_cpu) + .spawn() + .unwrap(); + matches!(task.join().unwrap(), task::ExitValue::Completed(_)); + 0 +} diff --git a/kernel/task/src/scheduler.rs b/kernel/task/src/scheduler.rs index 404e86e199..39b3cc462c 100644 --- a/kernel/task/src/scheduler.rs +++ b/kernel/task/src/scheduler.rs @@ -267,6 +267,11 @@ pub fn inherit_priority(task: &TaskRef) -> PriorityInheritanceGuard<'_> { } } +/// Returns whether the current scheduler supports priorities. +pub fn supports_priority() -> bool { + SCHEDULER.update(|scheduler| scheduler.as_ref().unwrap().lock().as_priority_scheduler().is_some()) +} + /// A guard that lowers a task's priority back to its previous value when dropped. pub struct PriorityInheritanceGuard<'a> { inner: Option<(&'a TaskRef, u8)>, diff --git a/theseus_features/Cargo.toml b/theseus_features/Cargo.toml index 47bebb30aa..50663882f8 100644 --- a/theseus_features/Cargo.toml +++ b/theseus_features/Cargo.toml @@ -67,6 +67,7 @@ test_scheduler = { path = "../applications/test_scheduler", optional = true } test_std_fs = { path = "../applications/test_std_fs", optional = true } test_sync_block = { path = "../applications/test_sync_block", optional = true } test_task_cancel = { path = "../applications/test_task_cancel", optional = true } +test_task_priority = { path = "../applications/test_task_priority", optional = true } test_tls = { path = "../applications/test_tls", optional = true } test_wait_queue = { path = "../applications/test_wait_queue", optional = true } test_wasmtime = { path = "../applications/test_wasmtime", optional = true } @@ -164,6 +165,7 @@ theseus_tests = [ "test_std_fs", "test_sync_block", "test_task_cancel", + "test_task_priority", "test_tls", "test_wait_queue", "test_wasmtime",