Skip to content

Commit 6a25394

Browse files
committed
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 theseus-os#1088 one does. Signed-off-by: Klim Tsoutsman <[email protected]>
1 parent ffb5e8b commit 6a25394

File tree

6 files changed

+133
-0
lines changed

6 files changed

+133
-0
lines changed

Cargo.lock

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ exclude = [
8888
"applications/test_mlx5",
8989
"applications/test_panic",
9090
"applications/test_preemption_counter",
91+
"applications/test_task_priority",
9192
"applications/test_restartable",
9293
"applications/test_scheduler",
9394
"applications/test_std_fs",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "test_task_priority"
3+
version = "0.1.0"
4+
authors = ["Klim Tsoutsman <[email protected]>"]
5+
description = "Test for priority schedulers"
6+
edition = "2021"
7+
8+
[dependencies]
9+
cpu = { path = "../../kernel/cpu" }
10+
fastrand = { version = "2.0.1", default-features = false }
11+
log = "0.4.8"
12+
preemption = { path = "../../kernel/preemption" }
13+
random = { path = "../../kernel/random" }
14+
spawn = { path = "../../kernel/spawn" }
15+
task = { path = "../../kernel/task" }
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//! Test for schedulers supporting priorities i.e. epoch and priority
2+
//! schedulers.
3+
//!
4+
//! The test ensures that tasks are run in order of priority for at least one
5+
//! time slice.
6+
7+
#![no_std]
8+
9+
extern crate alloc;
10+
11+
use alloc::{string::String, vec::Vec};
12+
use core::sync::atomic::{AtomicUsize, Ordering};
13+
14+
static CURRENT_PRIORITY: AtomicUsize = AtomicUsize::new(MAX_PRIORITY);
15+
16+
const MAX_PRIORITY: usize = 63;
17+
18+
fn worker(priority: usize) {
19+
// Add a bit of chaos.
20+
//
21+
// NOTE: When using the epoch scheduler, the test relies on the fact that the
22+
// worker runs in less than one time slice, and so we can't yield.
23+
#[cfg(priority_scheduler)]
24+
task::schedule();
25+
26+
let previous = CURRENT_PRIORITY.fetch_sub(1, Ordering::Relaxed);
27+
assert_eq!(previous, priority);
28+
}
29+
30+
fn spawner(_: ()) {
31+
if !task::scheduler::supports_priority() {
32+
log::warn!("scheduler does not support priorities");
33+
return;
34+
}
35+
36+
let current_cpu = cpu::current_cpu();
37+
38+
let priorities = priorities();
39+
let mut tasks = Vec::with_capacity(MAX_PRIORITY);
40+
41+
// We hold preemption here so that when the scheduler next runs, all the worker
42+
// tasks are unblocked and in a random order on the run queue. Holding
43+
// preemption is sufficient as we pin the worker threads to the same core as
44+
// the spawner thread.
45+
let guard = preemption::hold_preemption();
46+
47+
for priority in priorities {
48+
let task = spawn::new_task_builder(worker, priority)
49+
.pin_on_cpu(current_cpu)
50+
.block()
51+
.spawn()
52+
.unwrap();
53+
assert!(task::scheduler::set_priority(
54+
&task,
55+
priority.try_into().unwrap()
56+
));
57+
tasks.push(task);
58+
}
59+
60+
for task in tasks.iter() {
61+
task.unblock().unwrap();
62+
}
63+
64+
drop(guard);
65+
66+
for task in tasks {
67+
matches!(task.join().unwrap(), task::ExitValue::Completed(_));
68+
}
69+
}
70+
71+
/// Returns a shuffled list of priorities.
72+
fn priorities() -> Vec<usize> {
73+
let mut priorities = (0..=MAX_PRIORITY).collect::<Vec<_>>();
74+
75+
let mut rng = fastrand::Rng::with_seed(random::next_u64());
76+
rng.shuffle(&mut priorities);
77+
78+
priorities
79+
}
80+
81+
pub fn main(_: Vec<String>) -> isize {
82+
let current_cpu = cpu::current_cpu();
83+
// The spawning thread must be pinned to the same CPU as the worker threads.
84+
let task = spawn::new_task_builder(spawner, ())
85+
.pin_on_cpu(current_cpu)
86+
.spawn()
87+
.unwrap();
88+
matches!(task.join().unwrap(), task::ExitValue::Completed(_));
89+
0
90+
}

kernel/task/src/scheduler.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,11 @@ pub fn inherit_priority(task: &TaskRef) -> PriorityInheritanceGuard<'_> {
267267
}
268268
}
269269

270+
/// Returns whether the current scheduler supports priorities.
271+
pub fn supports_priority() -> bool {
272+
SCHEDULER.update(|scheduler| scheduler.as_ref().unwrap().lock().as_priority_scheduler().is_some())
273+
}
274+
270275
/// A guard that lowers a task's priority back to its previous value when dropped.
271276
pub struct PriorityInheritanceGuard<'a> {
272277
inner: Option<(&'a TaskRef, u8)>,

theseus_features/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ test_scheduler = { path = "../applications/test_scheduler", optional = true }
6767
test_std_fs = { path = "../applications/test_std_fs", optional = true }
6868
test_sync_block = { path = "../applications/test_sync_block", optional = true }
6969
test_task_cancel = { path = "../applications/test_task_cancel", optional = true }
70+
test_task_priority = { path = "../applications/test_task_priority", optional = true }
7071
test_tls = { path = "../applications/test_tls", optional = true }
7172
test_wait_queue = { path = "../applications/test_wait_queue", optional = true }
7273
test_wasmtime = { path = "../applications/test_wasmtime", optional = true }
@@ -164,6 +165,7 @@ theseus_tests = [
164165
"test_std_fs",
165166
"test_sync_block",
166167
"test_task_cancel",
168+
"test_task_priority",
167169
"test_tls",
168170
"test_wait_queue",
169171
"test_wasmtime",

0 commit comments

Comments
 (0)