use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use super::fibra::FibraId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum Prioritas {
Infima = 0,
#[default]
Normalis = 1,
Alta = 2,
Critica = 3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum IndiciumExecutionis {
#[default]
Computatio,
IoOperatio,
Obstruens,
}
pub struct MunusFibrae {
pub fibra_id: FibraId,
pub prioritas: Prioritas,
pub indicium: IndiciumExecutionis,
task: Box<dyn FnOnce() + Send + 'static>,
}
impl MunusFibrae {
#[inline]
pub fn new<F>(fibra_id: FibraId, f: F) -> Self
where
F: FnOnce() + Send + 'static,
{
MunusFibrae {
fibra_id,
prioritas: Prioritas::default(),
indicium: IndiciumExecutionis::default(),
task: Box::new(f),
}
}
#[inline]
pub fn with_priority<F>(fibra_id: FibraId, prioritas: Prioritas, f: F) -> Self
where
F: FnOnce() + Send + 'static,
{
MunusFibrae {
fibra_id,
prioritas,
indicium: IndiciumExecutionis::default(),
task: Box::new(f),
}
}
#[inline]
pub fn with_config<F>(
fibra_id: FibraId,
prioritas: Prioritas,
indicium: IndiciumExecutionis,
f: F,
) -> Self
where
F: FnOnce() + Send + 'static,
{
MunusFibrae {
fibra_id,
prioritas,
indicium,
task: Box::new(f),
}
}
#[inline]
pub fn execute(self) {
(self.task)();
}
}
#[cfg(feature = "std")]
pub struct OrdoLocalis {
tasks: std::sync::Mutex<VecDeque<MunusFibrae>>,
count: AtomicUsize,
}
#[cfg(feature = "std")]
impl OrdoLocalis {
pub fn new() -> Self {
OrdoLocalis {
tasks: std::sync::Mutex::new(VecDeque::with_capacity(DEFAULT_LOCAL_QUEUE_CAPACITY)),
count: AtomicUsize::new(0),
}
}
#[inline]
pub fn push(&self, task: MunusFibrae) {
let mut guard = self.tasks.lock().unwrap();
guard.push_back(task);
self.count.fetch_add(1, Ordering::Release);
}
#[inline]
pub fn pop(&self) -> Option<MunusFibrae> {
let mut guard = self.tasks.lock().unwrap();
let task = guard.pop_back();
if task.is_some() {
self.count.fetch_sub(1, Ordering::Release);
}
task
}
#[inline]
pub fn steal(&self) -> Option<MunusFibrae> {
let mut guard = self.tasks.lock().unwrap();
let task = guard.pop_front();
if task.is_some() {
self.count.fetch_sub(1, Ordering::Release);
}
task
}
pub fn steal_batch(&self, max: usize) -> Vec<MunusFibrae> {
let mut guard = self.tasks.lock().unwrap();
let steal_count = core::cmp::min(max, guard.len() / 2);
let mut stolen = Vec::with_capacity(steal_count);
for _ in 0..steal_count {
if let Some(task) = guard.pop_front() {
stolen.push(task);
}
}
self.count.fetch_sub(stolen.len(), Ordering::Release);
stolen
}
#[inline]
pub fn is_empty(&self) -> bool {
self.count.load(Ordering::Acquire) == 0
}
#[inline]
pub fn len(&self) -> usize {
self.count.load(Ordering::Acquire)
}
}
#[cfg(feature = "std")]
impl Default for OrdoLocalis {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "std")]
pub struct OrdoGlobalis {
tasks: std::sync::Mutex<VecDeque<MunusFibrae>>,
not_empty: std::sync::Condvar,
shutdown: AtomicBool,
}
#[cfg(feature = "std")]
impl OrdoGlobalis {
pub fn new() -> Self {
OrdoGlobalis {
tasks: std::sync::Mutex::new(VecDeque::with_capacity(DEFAULT_LOCAL_QUEUE_CAPACITY)),
not_empty: std::sync::Condvar::new(),
shutdown: AtomicBool::new(false),
}
}
#[inline]
pub fn push(&self, task: MunusFibrae) {
let mut guard = self.tasks.lock().unwrap();
guard.push_back(task);
self.not_empty.notify_one();
}
#[inline]
pub fn push_batch(&self, tasks: Vec<MunusFibrae>) {
let mut guard = self.tasks.lock().unwrap();
for task in tasks {
guard.push_back(task);
}
self.not_empty.notify_all();
}
#[inline]
pub fn try_pop(&self) -> Option<MunusFibrae> {
let mut guard = self.tasks.lock().unwrap();
guard.pop_front()
}
#[inline]
pub fn pop_blocking(&self) -> Option<MunusFibrae> {
let mut guard = self.tasks.lock().unwrap();
while guard.is_empty() && !self.shutdown.load(Ordering::Acquire) {
guard = self.not_empty.wait(guard).unwrap();
}
guard.pop_front()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.tasks.lock().unwrap().is_empty()
}
#[inline]
pub fn len(&self) -> usize {
self.tasks.lock().unwrap().len()
}
#[inline]
pub fn shutdown(&self) {
self.shutdown.store(true, Ordering::Release);
self.not_empty.notify_all();
}
#[inline]
pub fn is_shutdown(&self) -> bool {
self.shutdown.load(Ordering::Acquire)
}
}
#[cfg(feature = "std")]
impl Default for OrdoGlobalis {
fn default() -> Self {
Self::new()
}
}
pub trait PolitiaFurti: Send + Sync {
fn select_victim(&self, thief_id: usize, num_workers: usize) -> Option<usize>;
fn steal_count(&self, available: usize) -> usize;
}
pub struct PolitiaFortuita {
counter: AtomicU64,
}
impl PolitiaFortuita {
pub fn new() -> Self {
PolitiaFortuita {
counter: AtomicU64::new(0),
}
}
}
impl Default for PolitiaFortuita {
fn default() -> Self {
Self::new()
}
}
impl PolitiaFurti for PolitiaFortuita {
#[inline]
fn select_victim(&self, thief_id: usize, num_workers: usize) -> Option<usize> {
if num_workers <= 1 {
return None;
}
let count = self.counter.fetch_add(1, Ordering::Relaxed);
let mut victim = (count as usize) % num_workers;
if victim == thief_id {
victim = (victim + 1) % num_workers;
}
Some(victim)
}
#[inline]
fn steal_count(&self, available: usize) -> usize {
core::cmp::max(1, available / 2)
}
}
pub struct PolitiaCircularis {
next: AtomicUsize,
}
impl PolitiaCircularis {
pub fn new() -> Self {
PolitiaCircularis {
next: AtomicUsize::new(0),
}
}
}
impl Default for PolitiaCircularis {
fn default() -> Self {
Self::new()
}
}
impl PolitiaFurti for PolitiaCircularis {
#[inline]
fn select_victim(&self, thief_id: usize, num_workers: usize) -> Option<usize> {
if num_workers <= 1 {
return None;
}
let mut victim = self.next.fetch_add(1, Ordering::Relaxed) % num_workers;
if victim == thief_id {
victim = (victim + 1) % num_workers;
}
Some(victim)
}
#[inline]
fn steal_count(&self, available: usize) -> usize {
core::cmp::max(1, available / 2)
}
}
#[derive(Debug, Clone)]
pub struct OrdinariusConfig {
pub num_workers: usize,
pub local_queue_capacity: usize,
pub work_stealing: bool,
pub steal_batch_size: usize,
}
pub const DEFAULT_NUM_WORKERS: usize = 4;
pub const DEFAULT_LOCAL_QUEUE_CAPACITY: usize = 256;
pub const DEFAULT_STEAL_BATCH_SIZE: usize = 32;
impl Default for OrdinariusConfig {
fn default() -> Self {
OrdinariusConfig {
num_workers: DEFAULT_NUM_WORKERS,
local_queue_capacity: DEFAULT_LOCAL_QUEUE_CAPACITY,
work_stealing: true,
steal_batch_size: DEFAULT_STEAL_BATCH_SIZE,
}
}
}
impl OrdinariusConfig {
#[inline]
pub fn with_workers(num_workers: usize) -> Self {
OrdinariusConfig {
num_workers,
..Default::default()
}
}
}
#[derive(Debug, Default)]
pub struct Statisticae {
pub tasks_scheduled: AtomicU64,
pub tasks_executed: AtomicU64,
pub steal_attempts: AtomicU64,
pub steal_successes: AtomicU64,
pub tasks_stolen: AtomicU64,
}
impl Statisticae {
#[inline]
pub fn new() -> Self {
Statisticae::default()
}
#[inline]
pub fn record_scheduled(&self) {
self.tasks_scheduled.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_executed(&self) {
self.tasks_executed.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_steal_attempt(&self, success: bool, count: usize) {
self.steal_attempts.fetch_add(1, Ordering::Relaxed);
if success {
self.steal_successes.fetch_add(1, Ordering::Relaxed);
self.tasks_stolen.fetch_add(count as u64, Ordering::Relaxed);
}
}
#[inline]
pub fn steal_success_rate(&self) -> f64 {
let attempts = self.steal_attempts.load(Ordering::Relaxed);
if attempts == 0 {
0.0
} else {
let successes = self.steal_successes.load(Ordering::Relaxed);
successes as f64 / attempts as f64
}
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use alloc::sync::Arc;
fn test_fibra_id() -> FibraId {
FibraId::new()
}
#[test]
fn test_prioritas_ordering() {
assert!(Prioritas::Critica > Prioritas::Alta);
assert!(Prioritas::Alta > Prioritas::Normalis);
assert!(Prioritas::Normalis > Prioritas::Infima);
}
#[test]
fn test_munus_fibrae_new() {
let id = test_fibra_id();
let executed = Arc::new(AtomicBool::new(false));
let executed_clone = executed.clone();
let task = MunusFibrae::new(id, move || {
executed_clone.store(true, Ordering::SeqCst);
});
task.execute();
assert!(executed.load(Ordering::SeqCst));
}
#[test]
fn test_ordo_localis_push_pop() {
let queue = OrdoLocalis::new();
let id = test_fibra_id();
queue.push(MunusFibrae::new(id, || {}));
assert_eq!(queue.len(), 1);
let task = queue.pop();
assert!(task.is_some());
assert!(queue.is_empty());
}
#[test]
fn test_ordo_localis_steal() {
let queue = OrdoLocalis::new();
for _ in 0..4 {
queue.push(MunusFibrae::new(test_fibra_id(), || {}));
}
let stolen = queue.steal();
assert!(stolen.is_some());
assert_eq!(queue.len(), 3);
}
#[test]
fn test_ordo_localis_steal_batch() {
let queue = OrdoLocalis::new();
for _ in 0..10 {
queue.push(MunusFibrae::new(test_fibra_id(), || {}));
}
let stolen = queue.steal_batch(10);
assert_eq!(stolen.len(), 5); assert_eq!(queue.len(), 5);
}
#[test]
fn test_ordo_globalis_push_try_pop() {
let queue = OrdoGlobalis::new();
queue.push(MunusFibrae::new(test_fibra_id(), || {}));
assert!(!queue.is_empty());
let task = queue.try_pop();
assert!(task.is_some());
assert!(queue.is_empty());
}
#[test]
fn test_politia_fortuita() {
let policy = PolitiaFortuita::new();
let victim = policy.select_victim(0, 4);
assert!(victim.is_some());
assert_ne!(
victim.expect("select_victim should return Some when there are valid targets"),
0
); }
#[test]
fn test_politia_circularis() {
let policy = PolitiaCircularis::new();
let v1 = policy.select_victim(0, 4);
let v2 = policy.select_victim(0, 4);
assert!(v1.is_some());
assert!(v2.is_some());
}
#[test]
fn test_statisticae() {
let stats = Statisticae::new();
stats.record_scheduled();
stats.record_scheduled();
stats.record_executed();
stats.record_steal_attempt(true, 3);
stats.record_steal_attempt(false, 0);
assert_eq!(stats.tasks_scheduled.load(Ordering::Relaxed), 2);
assert_eq!(stats.tasks_executed.load(Ordering::Relaxed), 1);
assert_eq!(stats.steal_attempts.load(Ordering::Relaxed), 2);
assert_eq!(stats.steal_successes.load(Ordering::Relaxed), 1);
assert_eq!(stats.tasks_stolen.load(Ordering::Relaxed), 3);
assert!((stats.steal_success_rate() - 0.5).abs() < 0.001);
}
#[test]
fn test_ordinarius_config_default() {
let config = OrdinariusConfig::default();
assert_eq!(config.num_workers, 4);
assert!(config.work_stealing);
}
}