use alloc::collections::BTreeMap;
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::clock::GlobalTime;
use crate::core::error::{Error, Result};
use crate::core::exec::{Exit, ExitingCore};
use crate::core::sched::Budget;
use crate::core::state::{Sink, Source};
use crate::core::sync::{self, LockRank};
use super::clock::GuestClock;
pub const DEFAULT_QUANTUM: u64 = 10_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ThreadId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThreadState {
Runnable,
Blocked {
until: Option<GlobalTime>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stop {
pub thread: ThreadId,
pub consumed: u64,
pub at: GlobalTime,
pub exit: Option<Exit>,
}
#[derive(Debug)]
struct Entry {
core: Arc<dyn ExitingCore>,
state: ThreadState,
}
#[derive(Debug)]
struct Inner {
threads: BTreeMap<ThreadId, Entry>,
next_id: u32,
cursor: ThreadId,
quantum: u64,
}
#[derive(Debug)]
pub struct ThreadSet {
clock: Arc<GuestClock>,
inner: sync::Mutex<Inner>,
}
impl ThreadSet {
#[must_use]
pub fn new(clock: Arc<GuestClock>) -> ThreadSet {
ThreadSet {
clock,
inner: sync::Mutex::with_rank(
LockRank::SCHED,
Inner {
threads: BTreeMap::new(),
next_id: 1,
cursor: ThreadId(0),
quantum: DEFAULT_QUANTUM,
},
),
}
}
#[must_use]
pub fn clock(&self) -> &Arc<GuestClock> {
&self.clock
}
#[must_use]
pub fn quantum(&self) -> u64 {
self.inner.lock().quantum
}
pub fn set_quantum(&self, ticks: u64) {
self.inner.lock().quantum = ticks.max(1);
}
pub fn insert(&self, core: Arc<dyn ExitingCore>) -> ThreadId {
let mut inner = self.inner.lock();
let id = ThreadId(inner.next_id);
inner.next_id = inner.next_id.wrapping_add(1);
inner.threads.insert(
id,
Entry {
core,
state: ThreadState::Runnable,
},
);
id
}
pub fn remove(&self, id: ThreadId) -> bool {
self.inner.lock().threads.remove(&id).is_some()
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.lock().threads.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.lock().threads.is_empty()
}
#[must_use]
pub fn ids(&self) -> Vec<ThreadId> {
self.inner.lock().threads.keys().copied().collect()
}
#[must_use]
pub fn core(&self, id: ThreadId) -> Option<Arc<dyn ExitingCore>> {
self.inner
.lock()
.threads
.get(&id)
.map(|e| Arc::clone(&e.core))
}
#[must_use]
pub fn state(&self, id: ThreadId) -> Option<ThreadState> {
self.inner.lock().threads.get(&id).map(|e| e.state)
}
pub fn block(&self, id: ThreadId, until: Option<GlobalTime>) -> bool {
let mut inner = self.inner.lock();
match inner.threads.get_mut(&id) {
Some(entry) => {
entry.state = ThreadState::Blocked { until };
true
}
None => false,
}
}
pub fn wake(&self, id: ThreadId) -> bool {
let mut inner = self.inner.lock();
match inner.threads.get_mut(&id) {
Some(entry) => {
entry.state = ThreadState::Runnable;
true
}
None => false,
}
}
pub fn run_next(&self) -> Option<Stop> {
let (id, core, quantum) = self.pick()?;
let run = core.run_to_exit(Budget::of(quantum));
self.clock.advance(run.consumed.ticks);
Some(Stop {
thread: id,
consumed: run.consumed.ticks,
at: self.clock.now(),
exit: run.exit,
})
}
fn pick(&self) -> Option<(ThreadId, Arc<dyn ExitingCore>, u64)> {
for _ in 0..2 {
if let Some(picked) = self.pick_runnable() {
return Some(picked);
}
let earliest = {
let inner = self.inner.lock();
inner
.threads
.values()
.filter_map(|e| match e.state {
ThreadState::Blocked { until } => until,
ThreadState::Runnable => None,
})
.min()?
};
self.clock.advance_to(earliest);
let now = self.clock.now();
let mut inner = self.inner.lock();
for entry in inner.threads.values_mut() {
if let ThreadState::Blocked { until: Some(when) } = entry.state
&& when <= now
{
entry.state = ThreadState::Runnable;
}
}
}
None
}
fn pick_runnable(&self) -> Option<(ThreadId, Arc<dyn ExitingCore>, u64)> {
let mut inner = self.inner.lock();
let cursor = inner.cursor;
let id = inner
.threads
.range(cursor..)
.chain(inner.threads.range(..cursor))
.find(|(_, entry)| entry.state == ThreadState::Runnable)
.map(|(id, _)| *id)?;
inner.cursor = ThreadId(id.0.wrapping_add(1));
let core = Arc::clone(&inner.threads.get(&id)?.core);
Some((id, core, inner.quantum))
}
pub fn save<S: Sink + ?Sized>(&self, sink: &mut S) -> Result<()> {
let inner = self.inner.lock();
sink.write_u32(inner.next_id)?;
sink.write_u32(inner.cursor.0)?;
sink.write_u64(inner.quantum)?;
sink.write_seq_len(inner.threads.len() as u64)?;
for (id, entry) in &inner.threads {
sink.write_u32(id.0)?;
match entry.state {
ThreadState::Runnable => sink.write_u8(0)?,
ThreadState::Blocked { until: None } => sink.write_u8(1)?,
ThreadState::Blocked { until: Some(at) } => {
sink.write_u8(2)?;
sink.write_u128(at.raw())?;
}
}
}
Ok(())
}
pub fn load<'a, S: Source<'a> + ?Sized>(&self, source: &mut S) -> Result<()> {
let next_id = source.read_u32()?;
let cursor = source.read_u32()?;
let quantum = source.read_u64()?;
let count = source.read_seq_len(5)?;
let mut states = BTreeMap::new();
for _ in 0..count {
let id = ThreadId(source.read_u32()?);
let state = match source.read_u8()? {
0 => ThreadState::Runnable,
1 => ThreadState::Blocked { until: None },
2 => ThreadState::Blocked {
until: Some(GlobalTime::from_raw(source.read_u128()?)),
},
other => {
return Err(Error::State(alloc::format!(
"unknown thread state {other} in a schedule"
)));
}
};
states.insert(id, state);
}
let mut inner = self.inner.lock();
for id in states.keys() {
if !inner.threads.contains_key(id) {
return Err(Error::State(alloc::format!(
"the schedule names thread {} but it has not been inserted",
id.0
)));
}
}
inner.threads.retain(|id, _| states.contains_key(id));
for (id, state) in states {
if let Some(entry) = inner.threads.get_mut(&id) {
entry.state = state;
}
}
inner.next_id = next_id;
inner.cursor = ThreadId(cursor);
inner.quantum = quantum;
Ok(())
}
}