use core::sync::atomic::{AtomicU64, Ordering};
use ax_std::os::arceos::task::thread::ThreadId;
pub(super) struct SchedulerIdentity {
id: AtomicU64,
#[cfg(axtest)]
bind_attempts: AtomicU64,
}
impl SchedulerIdentity {
pub(super) const fn unbound() -> Self {
Self {
id: AtomicU64::new(0),
#[cfg(axtest)]
bind_attempts: AtomicU64::new(0),
}
}
pub(super) fn get(&self) -> Option<ThreadId> {
decode(self.id.load(Ordering::Acquire))
}
pub(super) fn bind(&self, id: ThreadId) -> crate::StarryResult<()> {
#[cfg(axtest)]
self.bind_attempts.fetch_add(1, Ordering::Relaxed);
let raw = id.as_u64();
debug_assert_ne!(raw, 0, "a published scheduler identity cannot be zero");
match self
.id
.compare_exchange(0, raw, Ordering::Release, Ordering::Acquire)
{
Ok(_) => Ok(()),
Err(current) if current == raw => Ok(()),
Err(_) => Err(crate::StarryError::BadState),
}
}
pub(super) fn validate_bound(&self, id: ThreadId) -> crate::StarryResult<()> {
if self.id.load(Ordering::Acquire) == id.as_u64() {
Ok(())
} else {
Err(crate::StarryError::BadState)
}
}
}
#[cfg(axtest)]
fn published_scheduler_identity_check_is_read_only_for_test() -> bool {
let identity = SchedulerIdentity::unbound();
let id = ThreadId::from_parts(7, 3);
if identity.bind(id).is_err() {
return false;
}
let attempts_after_publication = identity.bind_attempts.load(Ordering::Relaxed);
identity.validate_bound(id).is_ok()
&& identity.bind_attempts.load(Ordering::Relaxed) == attempts_after_publication
}
#[cfg(all(test, axtest))]
mod axtests {
#[axtest::axtest]
fn published_scheduler_identity_check_is_read_only() {
assert!(super::published_scheduler_identity_check_is_read_only_for_test());
}
}
const fn decode(raw: u64) -> Option<ThreadId> {
if raw == 0 {
None
} else {
Some(ThreadId::from_parts(raw as u32, (raw >> 32) as u32))
}
}
#[cfg(all(test, not(axtest)))]
mod tests {
use super::*;
#[test]
fn preserves_slot_generation_when_bound() {
let identity = SchedulerIdentity::unbound();
let id = ThreadId::from_parts(7, 3);
identity.bind(id).unwrap();
assert_eq!(identity.get(), Some(id));
}
#[test]
fn rejects_rebinding_to_a_reused_slot() {
let identity = SchedulerIdentity::unbound();
identity.bind(ThreadId::from_parts(7, 3)).unwrap();
let result = identity.bind(ThreadId::from_parts(7, 4));
assert!(matches!(result, Err(crate::StarryError::BadState)));
assert_eq!(identity.get(), Some(ThreadId::from_parts(7, 3)));
}
}