1use std::cell::UnsafeCell;
22use std::sync::atomic::{fence, AtomicIsize, AtomicUsize, Ordering};
23use std::sync::{Arc, Condvar, Mutex};
24use std::thread;
25
26use crate::error::{CoreError, CoreResult, ErrorContext, ErrorLocation};
27
28const INITIAL_CAPACITY: usize = 64;
32
33struct CircularBuf<T> {
37 cap: usize,
38 data: Box<[UnsafeCell<Option<T>>]>,
39}
40
41impl<T> CircularBuf<T> {
42 fn new(cap: usize) -> Self {
43 let data = (0..cap)
44 .map(|_| UnsafeCell::new(None))
45 .collect::<Vec<_>>()
46 .into_boxed_slice();
47 Self { cap, data }
48 }
49
50 fn mask(&self) -> usize {
51 self.cap - 1
52 }
53
54 unsafe fn write(&self, i: usize, val: T) {
59 let slot = self.data[i & self.mask()].get();
60 unsafe { (*slot) = Some(val) };
63 }
64
65 unsafe fn read(&self, i: usize) -> Option<T> {
71 let slot = self.data[i & self.mask()].get();
72 unsafe { (*slot).take() }
74 }
75}
76
77unsafe impl<T: Send> Send for CircularBuf<T> {}
80unsafe impl<T: Send> Sync for CircularBuf<T> {}
81
82pub struct WorkStealingDeque<T: Send + 'static> {
93 bottom: AtomicIsize,
94 top: AtomicIsize,
95 buf: Mutex<Arc<CircularBuf<T>>>,
96}
97
98#[derive(Debug)]
100pub enum StealResult<T> {
101 Success(T),
103 Empty,
105 Retry,
107}
108
109impl<T: Send + 'static> WorkStealingDeque<T> {
110 pub fn new() -> Self {
112 Self {
113 bottom: AtomicIsize::new(0),
114 top: AtomicIsize::new(0),
115 buf: Mutex::new(Arc::new(CircularBuf::new(INITIAL_CAPACITY))),
116 }
117 }
118
119 pub fn len(&self) -> usize {
121 let b = self.bottom.load(Ordering::Relaxed);
122 let t = self.top.load(Ordering::Relaxed);
123 (b - t).max(0) as usize
124 }
125
126 pub fn is_empty(&self) -> bool {
128 self.len() == 0
129 }
130
131 pub fn push(&self, task: T) -> CoreResult<()> {
135 let b = self.bottom.load(Ordering::Relaxed);
136 let t = self.top.load(Ordering::Acquire);
137 let size = (b - t) as usize;
138
139 let buf: Arc<CircularBuf<T>> = {
140 let guard = self.buf.lock().map_err(|e| {
141 CoreError::SchedulerError(
142 ErrorContext::new(format!("deque buf lock poisoned: {e}"))
143 .with_location(ErrorLocation::new(file!(), line!())),
144 )
145 })?;
146 Arc::clone(&*guard)
147 };
148
149 let buf: Arc<CircularBuf<T>> = if size >= buf.cap - 1 {
151 let new_cap = buf.cap * 2;
152 let new_buf = Arc::new(CircularBuf::new(new_cap));
153 for i in t..b {
155 unsafe {
157 let val = buf.read(i as usize);
158 if let Some(v) = val {
159 new_buf.write(i as usize, v);
160 }
161 }
162 }
163 let mut guard = self.buf.lock().map_err(|e| {
164 CoreError::SchedulerError(
165 ErrorContext::new(format!("deque buf lock poisoned during grow: {e}"))
166 .with_location(ErrorLocation::new(file!(), line!())),
167 )
168 })?;
169 *guard = Arc::clone(&new_buf);
170 new_buf
171 } else {
172 buf
173 };
174
175 unsafe { buf.write(b as usize, task) };
177 fence(Ordering::Release);
178 self.bottom.store(b + 1, Ordering::Relaxed);
179 Ok(())
180 }
181
182 pub fn pop(&self) -> CoreResult<Option<T>> {
186 let b = self.bottom.load(Ordering::Relaxed) - 1;
187 let buf: Arc<CircularBuf<T>> = {
188 let guard = self.buf.lock().map_err(|e| {
189 CoreError::SchedulerError(
190 ErrorContext::new(format!("deque buf lock poisoned on pop: {e}"))
191 .with_location(ErrorLocation::new(file!(), line!())),
192 )
193 })?;
194 Arc::clone(&*guard)
195 };
196 self.bottom.store(b, Ordering::Relaxed);
197 fence(Ordering::SeqCst);
198 let t = self.top.load(Ordering::Relaxed);
199
200 if t > b {
201 self.bottom.store(b + 1, Ordering::Relaxed);
203 return Ok(None);
204 }
205
206 let task = unsafe { buf.read(b as usize) };
208
209 if t == b {
210 let stolen = self
212 .top
213 .compare_exchange(t, t + 1, Ordering::SeqCst, Ordering::Relaxed)
214 .is_err();
215 self.bottom.store(b + 1, Ordering::Relaxed);
216 if stolen {
217 return Ok(None);
218 }
219 }
220
221 Ok(task)
222 }
223
224 pub fn steal(&self) -> StealResult<T> {
226 let t = self.top.load(Ordering::Acquire);
227 fence(Ordering::SeqCst);
228 let b = self.bottom.load(Ordering::Acquire);
229
230 if t >= b {
231 return StealResult::Empty;
232 }
233
234 let buf = match self.buf.lock() {
235 Ok(g) => Arc::clone(&*g),
236 Err(_) => return StealResult::Retry,
237 };
238
239 let task = unsafe { buf.read(t as usize) };
241
242 match self
243 .top
244 .compare_exchange(t, t + 1, Ordering::SeqCst, Ordering::Relaxed)
245 {
246 Ok(_) => match task {
247 Some(v) => StealResult::Success(v),
248 None => StealResult::Retry,
249 },
250 Err(_) => StealResult::Retry,
251 }
252 }
253}
254
255impl<T: Send + 'static> Default for WorkStealingDeque<T> {
256 fn default() -> Self {
257 Self::new()
258 }
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
265pub enum Priority {
266 High = 2,
268 Normal = 1,
270 Low = 0,
272}
273
274type BoxTask = Box<dyn FnOnce() + Send + 'static>;
275
276struct PriorityItem {
277 priority: Priority,
278 seq: u64, task: BoxTask,
280}
281
282impl PartialEq for PriorityItem {
283 fn eq(&self, other: &Self) -> bool {
284 self.priority == other.priority && self.seq == other.seq
285 }
286}
287impl Eq for PriorityItem {}
288
289impl PartialOrd for PriorityItem {
290 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
291 Some(self.cmp(other))
292 }
293}
294
295impl Ord for PriorityItem {
296 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
297 self.priority
301 .cmp(&other.priority)
302 .then_with(|| other.seq.cmp(&self.seq))
303 }
304}
305
306pub struct PriorityTaskQueue {
312 inner: Mutex<PriorityQueueInner>,
313 not_empty: Condvar,
314 not_full: Condvar,
315 capacity: usize,
316 seq: AtomicUsize,
317}
318
319struct PriorityQueueInner {
320 heap: std::collections::BinaryHeap<PriorityItem>,
321 closed: bool,
322}
323
324impl PriorityTaskQueue {
325 pub fn new(capacity: usize) -> Self {
327 let cap = capacity.max(1);
328 Self {
329 inner: Mutex::new(PriorityQueueInner {
330 heap: std::collections::BinaryHeap::with_capacity(cap),
331 closed: false,
332 }),
333 not_empty: Condvar::new(),
334 not_full: Condvar::new(),
335 capacity: cap,
336 seq: AtomicUsize::new(0),
337 }
338 }
339
340 pub fn submit<F>(&self, priority: Priority, f: F) -> CoreResult<()>
345 where
346 F: FnOnce() + Send + 'static,
347 {
348 let seq = self.seq.fetch_add(1, Ordering::Relaxed) as u64;
349 let item = PriorityItem {
350 priority,
351 seq,
352 task: Box::new(f),
353 };
354 let mut guard = self.inner.lock().map_err(|e| {
355 CoreError::SchedulerError(
356 ErrorContext::new(format!("priority queue lock poisoned on submit: {e}"))
357 .with_location(ErrorLocation::new(file!(), line!())),
358 )
359 })?;
360 loop {
361 if guard.closed {
362 return Err(CoreError::InvalidInput(ErrorContext::new(
363 "PriorityTaskQueue: queue is closed",
364 )));
365 }
366 if guard.heap.len() < self.capacity {
367 break;
368 }
369 guard = self.not_full.wait(guard).map_err(|e| {
370 CoreError::SchedulerError(
371 ErrorContext::new(format!("condvar wait poisoned: {e}"))
372 .with_location(ErrorLocation::new(file!(), line!())),
373 )
374 })?;
375 }
376 guard.heap.push(item);
377 self.not_empty.notify_one();
378 Ok(())
379 }
380
381 pub fn try_submit<F>(&self, priority: Priority, f: F) -> CoreResult<()>
383 where
384 F: FnOnce() + Send + 'static,
385 {
386 let seq = self.seq.fetch_add(1, Ordering::Relaxed) as u64;
387 let item = PriorityItem {
388 priority,
389 seq,
390 task: Box::new(f),
391 };
392 let mut guard = self.inner.lock().map_err(|e| {
393 CoreError::SchedulerError(
394 ErrorContext::new(format!("priority queue lock poisoned on try_submit: {e}"))
395 .with_location(ErrorLocation::new(file!(), line!())),
396 )
397 })?;
398 if guard.closed {
399 return Err(CoreError::InvalidInput(ErrorContext::new(
400 "PriorityTaskQueue: queue is closed",
401 )));
402 }
403 if guard.heap.len() >= self.capacity {
404 return Err(CoreError::InvalidInput(ErrorContext::new(
405 "PriorityTaskQueue: queue is full",
406 )));
407 }
408 guard.heap.push(item);
409 self.not_empty.notify_one();
410 Ok(())
411 }
412
413 pub fn dequeue(&self) -> CoreResult<Option<BoxTask>> {
417 let mut guard = self.inner.lock().map_err(|e| {
418 CoreError::SchedulerError(
419 ErrorContext::new(format!("priority queue lock poisoned on dequeue: {e}"))
420 .with_location(ErrorLocation::new(file!(), line!())),
421 )
422 })?;
423 loop {
424 if let Some(item) = guard.heap.pop() {
425 self.not_full.notify_one();
426 return Ok(Some(item.task));
427 }
428 if guard.closed {
429 return Ok(None);
430 }
431 guard = self.not_empty.wait(guard).map_err(|e| {
432 CoreError::SchedulerError(
433 ErrorContext::new(format!("condvar wait poisoned on dequeue: {e}"))
434 .with_location(ErrorLocation::new(file!(), line!())),
435 )
436 })?;
437 }
438 }
439
440 pub fn try_dequeue(&self) -> CoreResult<Option<BoxTask>> {
442 let mut guard = self.inner.lock().map_err(|e| {
443 CoreError::SchedulerError(
444 ErrorContext::new(format!("priority queue lock poisoned on try_dequeue: {e}"))
445 .with_location(ErrorLocation::new(file!(), line!())),
446 )
447 })?;
448 match guard.heap.pop() {
449 Some(item) => {
450 self.not_full.notify_one();
451 Ok(Some(item.task))
452 }
453 None => Ok(None),
454 }
455 }
456
457 pub fn close(&self) {
459 if let Ok(mut g) = self.inner.lock() {
460 g.closed = true;
461 }
462 self.not_empty.notify_all();
463 self.not_full.notify_all();
464 }
465
466 pub fn pending(&self) -> usize {
468 self.inner.lock().map(|g| g.heap.len()).unwrap_or(0)
469 }
470}
471
472#[derive(Debug, Clone)]
476pub struct SchedulerConfig {
477 pub num_workers: usize,
479 pub steal_attempts: usize,
481 pub idle_sleep_us: u64,
483}
484
485impl Default for SchedulerConfig {
486 fn default() -> Self {
487 Self {
488 num_workers: 0,
489 steal_attempts: 32,
490 idle_sleep_us: 100,
491 }
492 }
493}
494
495#[derive(Debug, Default, Clone)]
497pub struct SchedulerStats {
498 pub tasks_completed: u64,
500 pub steal_successes: u64,
502 pub steal_failures: u64,
504}
505
506type StatsCell = Arc<Mutex<SchedulerStats>>;
507
508pub struct WorkStealingScheduler {
513 deques: Arc<Vec<Arc<WorkStealingDeque<BoxTask>>>>,
514 handles: Vec<thread::JoinHandle<()>>,
515 stop: Arc<std::sync::atomic::AtomicBool>,
516 stats: StatsCell,
517 next_push: AtomicUsize,
518}
519
520impl WorkStealingScheduler {
521 pub fn new(cfg: SchedulerConfig) -> CoreResult<Self> {
523 let n = if cfg.num_workers == 0 {
524 thread::available_parallelism()
525 .map(|p| p.get())
526 .unwrap_or(4)
527 } else {
528 cfg.num_workers
529 };
530 if n == 0 {
531 return Err(CoreError::InvalidInput(ErrorContext::new(
532 "WorkStealingScheduler: num_workers must be >= 1",
533 )));
534 }
535
536 let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
537 let stats: StatsCell = Arc::new(Mutex::new(SchedulerStats::default()));
538 let deques: Arc<Vec<Arc<WorkStealingDeque<BoxTask>>>> =
539 Arc::new((0..n).map(|_| Arc::new(WorkStealingDeque::new())).collect());
540
541 let mut handles = Vec::with_capacity(n);
542 for id in 0..n {
543 let deques2 = Arc::clone(&deques);
544 let stop2 = Arc::clone(&stop);
545 let stats2 = Arc::clone(&stats);
546 let steal_attempts = cfg.steal_attempts;
547 let idle_sleep_us = cfg.idle_sleep_us;
548
549 let handle = thread::Builder::new()
550 .name(format!("ws-worker-{id}"))
551 .spawn(move || {
552 worker_loop(id, n, deques2, stop2, stats2, steal_attempts, idle_sleep_us);
553 })
554 .map_err(|e| {
555 CoreError::SchedulerError(
556 ErrorContext::new(format!("failed to spawn worker {id}: {e}"))
557 .with_location(ErrorLocation::new(file!(), line!())),
558 )
559 })?;
560 handles.push(handle);
561 }
562
563 Ok(Self {
564 deques,
565 handles,
566 stop,
567 stats,
568 next_push: AtomicUsize::new(0),
569 })
570 }
571
572 pub fn submit<F>(&self, f: F) -> CoreResult<()>
574 where
575 F: FnOnce() + Send + 'static,
576 {
577 let idx = self.next_push.fetch_add(1, Ordering::Relaxed) % self.deques.len();
578 self.deques[idx].push(Box::new(f))
579 }
580
581 pub fn num_workers(&self) -> usize {
583 self.deques.len()
584 }
585
586 pub fn stats(&self) -> SchedulerStats {
588 self.stats.lock().map(|g| g.clone()).unwrap_or_default()
589 }
590
591 pub fn shutdown(self) -> CoreResult<()> {
593 self.stop.store(true, Ordering::SeqCst);
594 for h in self.handles {
595 h.join().map_err(|_| {
596 CoreError::SchedulerError(
597 ErrorContext::new("worker thread panicked during shutdown")
598 .with_location(ErrorLocation::new(file!(), line!())),
599 )
600 })?;
601 }
602 Ok(())
603 }
604}
605
606fn worker_loop(
608 id: usize,
609 n: usize,
610 deques: Arc<Vec<Arc<WorkStealingDeque<BoxTask>>>>,
611 stop: Arc<std::sync::atomic::AtomicBool>,
612 stats: StatsCell,
613 steal_attempts: usize,
614 idle_sleep_us: u64,
615) {
616 let mut local_completed = 0u64;
617 let mut local_steals = 0u64;
618 let mut local_failures = 0u64;
619
620 loop {
621 let own = match deques[id].steal() {
627 StealResult::Success(task) => {
628 task();
629 local_completed += 1;
630 true
631 }
632 _ => false,
633 };
634
635 if own {
636 continue;
637 }
638
639 let mut stole = false;
641 'steal: for attempt in 0..steal_attempts {
642 let victim = (id + 1 + attempt) % n;
643 if victim == id {
644 continue;
645 }
646 match deques[victim].steal() {
647 StealResult::Success(task) => {
648 task();
649 local_completed += 1;
650 local_steals += 1;
651 stole = true;
652 break 'steal;
653 }
654 StealResult::Empty => {}
655 StealResult::Retry => {
656 local_failures += 1;
657 }
658 }
659 }
660
661 if stole {
662 continue;
663 }
664
665 if let StealResult::Success(task) = deques[id].steal() {
667 task();
668 local_completed += 1;
669 continue;
670 }
671
672 if stop.load(Ordering::Relaxed) {
674 break;
675 }
676
677 thread::sleep(std::time::Duration::from_micros(idle_sleep_us));
679 }
680
681 if let Ok(mut g) = stats.lock() {
683 g.tasks_completed += local_completed;
684 g.steal_successes += local_steals;
685 g.steal_failures += local_failures;
686 }
687}
688
689#[cfg(test)]
692mod tests {
693 use super::*;
694 use std::sync::atomic::AtomicU64;
695
696 #[test]
697 fn deque_push_pop_single_thread() {
698 let dq: WorkStealingDeque<i32> = WorkStealingDeque::new();
699 assert!(dq.is_empty());
700 dq.push(1).expect("push 1");
701 dq.push(2).expect("push 2");
702 dq.push(3).expect("push 3");
703 assert_eq!(dq.len(), 3);
704 assert_eq!(dq.pop().expect("pop"), Some(3));
705 assert_eq!(dq.pop().expect("pop"), Some(2));
706 assert_eq!(dq.pop().expect("pop"), Some(1));
707 assert_eq!(dq.pop().expect("pop"), None);
708 }
709
710 #[test]
711 fn deque_steal_basic() {
712 let dq = Arc::new(WorkStealingDeque::<i32>::new());
713 dq.push(10).expect("push");
714 dq.push(20).expect("push");
715
716 let dq2 = Arc::clone(&dq);
717 let stealer = thread::spawn(move || loop {
718 match dq2.steal() {
719 StealResult::Success(v) => return v,
720 StealResult::Empty => return -1,
721 StealResult::Retry => {}
722 }
723 });
724 let stolen = stealer.join().expect("stealer thread");
725 assert!(stolen == 10 || stolen == 20 || stolen == -1);
726 }
727
728 #[test]
729 fn deque_grows_automatically() {
730 let dq: WorkStealingDeque<usize> = WorkStealingDeque::new();
731 for i in 0..200 {
732 dq.push(i).expect("push");
733 }
734 let mut collected = Vec::new();
735 while let Ok(Some(v)) = dq.pop() {
736 collected.push(v);
737 }
738 assert_eq!(collected.len(), 200);
739 }
740
741 #[test]
742 fn priority_queue_ordering() {
743 let q = Arc::new(PriorityTaskQueue::new(16));
744 let results = Arc::new(Mutex::new(Vec::new()));
745
746 let r1 = Arc::clone(&results);
747 q.submit(Priority::Low, move || {
748 r1.lock().expect("lock").push("low");
749 })
750 .expect("submit low");
751
752 let r2 = Arc::clone(&results);
753 q.submit(Priority::High, move || {
754 r2.lock().expect("lock").push("high");
755 })
756 .expect("submit high");
757
758 let r3 = Arc::clone(&results);
759 q.submit(Priority::Normal, move || {
760 r3.lock().expect("lock").push("normal");
761 })
762 .expect("submit normal");
763
764 q.close();
765
766 while let Ok(Some(task)) = q.dequeue() {
768 task();
769 }
770
771 let res = results.lock().expect("lock");
772 assert_eq!(*res, vec!["high", "normal", "low"]);
773 }
774
775 #[test]
776 fn priority_queue_fifo_within_level() {
777 let q = Arc::new(PriorityTaskQueue::new(32));
778 let results = Arc::new(Mutex::new(Vec::new()));
779
780 for i in 0..5u32 {
781 let r = Arc::clone(&results);
782 q.submit(Priority::Normal, move || {
783 r.lock().expect("lock").push(i);
784 })
785 .expect("submit");
786 }
787 q.close();
788
789 while let Ok(Some(task)) = q.dequeue() {
790 task();
791 }
792
793 let res = results.lock().expect("lock");
794 assert_eq!(*res, vec![0, 1, 2, 3, 4]);
795 }
796
797 #[test]
798 fn scheduler_runs_tasks() {
799 let cfg = SchedulerConfig {
800 num_workers: 4,
801 steal_attempts: 16,
802 idle_sleep_us: 100,
803 };
804 let sched = WorkStealingScheduler::new(cfg).expect("new scheduler");
805 let counter = Arc::new(AtomicU64::new(0));
806 let n_tasks = 100usize;
807
808 for _ in 0..n_tasks {
809 let c = Arc::clone(&counter);
810 sched
811 .submit(move || {
812 c.fetch_add(1, Ordering::Relaxed);
813 })
814 .expect("submit");
815 }
816
817 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
819 while counter.load(Ordering::Relaxed) < n_tasks as u64 {
820 if std::time::Instant::now() > deadline {
821 break;
822 }
823 thread::sleep(std::time::Duration::from_millis(1));
824 }
825
826 assert_eq!(counter.load(Ordering::Relaxed), n_tasks as u64);
827 sched.shutdown().expect("shutdown");
828 }
829}