1use parking_lot::{Mutex, RwLock};
6use std::panic::AssertUnwindSafe;
7use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
8use std::sync::Arc;
9use std::thread::{self, JoinHandle};
10
11#[cfg(test)]
12use crate::executor::NullExecutor;
13use crate::executor::{ActorExecutor, ExecutionResult};
14use crate::policy::ResourcePolicy;
15use crate::queue::{create_local_queue, PriorityQueue, Task, WorkQueue, WorkStealer};
16#[cfg(feature = "serde")]
17use crate::rpc::RpcClient;
18use crate::supervisor::ResourceMonitor;
19use crate::Error;
20use crate::{ActorId, ActorRegistry, ActorState, MailboxConfig, Message, MessagePayload, Priority};
21
22type WorkerStealer = (usize, crossbeam_deque::Stealer<Task>);
24
25#[derive(Clone)]
27pub struct StealerRegistry {
28 stealers: Arc<RwLock<Vec<WorkerStealer>>>,
29 version: Arc<AtomicU64>,
30}
31
32impl StealerRegistry {
33 pub fn new() -> Self {
35 Self {
36 stealers: Arc::new(RwLock::new(Vec::new())),
37 version: Arc::new(AtomicU64::new(0)),
38 }
39 }
40
41 pub fn add_stealer(&self, worker_id: usize, stealer: crossbeam_deque::Stealer<Task>) {
43 let mut stealers = self.stealers.write();
44 stealers.push((worker_id, stealer));
45 self.version.fetch_add(1, Ordering::Release);
46 }
47
48 pub fn get_stealers(&self, exclude_worker_id: usize) -> Vec<crossbeam_deque::Stealer<Task>> {
50 let stealers = self.stealers.read();
51 stealers
52 .iter()
53 .filter(|(id, _)| *id != exclude_worker_id)
54 .map(|(_, s)| s.clone())
55 .collect()
56 }
57
58 pub fn version(&self) -> u64 {
60 self.version.load(Ordering::Acquire)
61 }
62}
63
64impl Default for StealerRegistry {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70#[derive(Debug, Clone)]
72pub struct SchedulerConfig {
73 pub workers: usize,
75 pub mailbox_config: MailboxConfig,
77 pub priority_scheduling: bool,
79 pub max_steal_batch: usize,
81 pub idle_sleep_us: u64,
83 pub stealer_refresh_interval: u32,
85}
86
87impl Default for SchedulerConfig {
88 fn default() -> Self {
89 Self {
90 workers: 0,
91 mailbox_config: MailboxConfig::default(),
92 priority_scheduling: true,
93 max_steal_batch: 32,
94 idle_sleep_us: 100,
95 stealer_refresh_interval: 1000,
96 }
97 }
98}
99
100impl SchedulerConfig {
101 pub fn new() -> Self {
103 Self::default()
104 }
105
106 pub fn workers(mut self, workers: usize) -> Self {
108 self.workers = workers;
109 self
110 }
111
112 pub fn effective_workers(&self) -> usize {
114 if self.workers == 0 {
115 thread::available_parallelism()
116 .map(|p| p.get())
117 .unwrap_or(4)
118 } else {
119 self.workers
120 }
121 }
122}
123
124#[derive(Debug, Default)]
138#[repr(align(64))]
139struct WorkerStats {
140 processed: AtomicU64,
142 stolen: AtomicU64,
144 batches_processed: AtomicU64,
146}
147
148pub struct ActorScheduler {
150 config: SchedulerConfig,
152 global_queue: Arc<WorkQueue>,
154 priority_queue: Arc<PriorityQueue>,
156 registry: Arc<ActorRegistry>,
158 stealer_registry: StealerRegistry,
160 worker_handles: Mutex<Vec<JoinHandle<()>>>,
162 running: Arc<AtomicBool>,
164 total_actors: AtomicU64,
166 worker_stats: Vec<Arc<WorkerStats>>,
168 executor: Option<Arc<dyn ActorExecutor>>,
170 resource_policy: Option<Arc<dyn ResourcePolicy>>,
177 degradation: Option<(Arc<dyn ResourceMonitor>, f64)>,
181}
182
183impl ActorScheduler {
184 pub fn new(config: SchedulerConfig) -> Self {
186 Self::with_options(config, None, None)
187 }
188
189 pub fn with_executor(config: SchedulerConfig, executor: Arc<dyn ActorExecutor>) -> Self {
191 Self::with_options(config, Some(executor), None)
192 }
193
194 pub fn with_resource_policy(
200 config: SchedulerConfig,
201 executor: Arc<dyn ActorExecutor>,
202 resource_policy: Arc<dyn ResourcePolicy>,
203 ) -> Self {
204 Self::with_options(config, Some(executor), Some(resource_policy))
205 }
206
207 fn with_options(
209 config: SchedulerConfig,
210 executor: Option<Arc<dyn ActorExecutor>>,
211 resource_policy: Option<Arc<dyn ResourcePolicy>>,
212 ) -> Self {
213 let worker_count = config.effective_workers();
214 let worker_stats: Vec<_> = (0..worker_count)
215 .map(|_| Arc::new(WorkerStats::default()))
216 .collect();
217
218 let registry = Arc::new(ActorRegistry::with_mailbox_config(
223 config.mailbox_config.clone(),
224 ));
225
226 Self {
227 config,
228 global_queue: Arc::new(WorkQueue::new()),
229 priority_queue: Arc::new(PriorityQueue::new()),
230 registry,
231 stealer_registry: StealerRegistry::new(),
232 worker_handles: Mutex::new(Vec::new()),
233 running: Arc::new(AtomicBool::new(false)),
234 total_actors: AtomicU64::new(0),
235 worker_stats,
236 executor,
237 resource_policy,
238 degradation: None,
239 }
240 }
241
242 pub fn set_executor(&mut self, executor: Arc<dyn ActorExecutor>) {
244 self.executor = Some(executor);
245 }
246
247 pub fn set_degradation_with_monitor(
253 &mut self,
254 monitor: Arc<dyn ResourceMonitor>,
255 elevated_batch_factor: f64,
256 ) {
257 self.degradation = Some((monitor, elevated_batch_factor));
258 }
259
260 pub fn start(&self) -> Result<(), Error> {
262 if self.running.swap(true, Ordering::SeqCst) {
263 return Ok(());
264 }
265
266 let mut handles = self.worker_handles.lock();
267
268 for (id, stats) in self.worker_stats.iter().enumerate() {
269 let (worker, stealer) = create_local_queue();
270 self.stealer_registry.add_stealer(id, stealer);
271
272 let global_queue = self.global_queue.clone();
273 let priority_queue = self.priority_queue.clone();
274 let registry = self.registry.clone();
275 let config = self.config.clone();
276 let running_flag = self.running.clone();
277 let stats = stats.clone();
278 let stealer_registry = self.stealer_registry.clone();
279 let executor = self.executor.clone();
280
281 let handle = thread::Builder::new()
282 .name(format!("actor-kit-worker-{}", id))
283 .spawn(move || {
284 Self::worker_loop(
285 id,
286 worker,
287 global_queue,
288 priority_queue,
289 registry,
290 config,
291 running_flag,
292 stats,
293 stealer_registry,
294 executor.as_ref(),
295 );
296 });
297
298 match handle {
299 Ok(h) => handles.push(h),
300 Err(e) => {
301 tracing::error!("Failed to spawn worker thread {}: {}", id, e);
302 return Err(Error::internal(format!(
303 "Failed to spawn worker thread {}: {}",
304 id, e
305 )));
306 }
307 }
308 }
309
310 Ok(())
311 }
312
313 pub fn stop(&self) {
315 self.running.store(false, Ordering::SeqCst);
316
317 let mut handles = self.worker_handles.lock();
318 for handle in handles.drain(..) {
319 let _ = handle.join();
320 }
321 }
322
323 pub fn spawn(&self) -> crate::Result<ActorId> {
325 self.spawn_named(None)
326 }
327
328 pub fn spawn_named(&self, name: Option<String>) -> crate::Result<ActorId> {
330 self.check_degradation()?;
331
332 if let Some(ref policy) = self.resource_policy {
333 policy.admit_actor()?;
334 }
335 let id = ActorId::new();
336 self.registry.register_named(id, name)?;
337 self.total_actors.fetch_add(1, Ordering::Relaxed);
338 Ok(id)
339 }
340
341 fn check_degradation(&self) -> crate::Result<()> {
345 let (monitor, _) = match &self.degradation {
346 Some(d) => d,
347 None => return Ok(()),
348 };
349
350 match monitor.current_pressure() {
351 crate::supervisor::PressureLevel::Normal => Ok(()),
352 crate::supervisor::PressureLevel::Elevated => {
353 tracing::debug!("actor spawn throttled due to elevated resource pressure");
354 Ok(())
355 }
356 crate::supervisor::PressureLevel::Critical => {
357 tracing::warn!("actor spawn rejected due to critical resource pressure");
358 Err(Error::resource_exhausted(
359 "system resources critically low; actor spawn rejected",
360 ))
361 }
362 }
363 }
364
365 pub fn kill(&self, id: &ActorId) -> crate::Result<()> {
367 self.registry.set_state(id, ActorState::Stopped)?;
368 if let Some(m) = self.registry.get_mailbox(id) {
369 m.clear()
370 }
371 self.registry.unregister(id)?;
372 if let Some(ref policy) = self.resource_policy {
373 policy.release_actor();
374 }
375 Ok(())
376 }
377
378 pub fn set_actor_running(&self, id: &ActorId) -> crate::Result<()> {
380 self.registry.set_state(id, ActorState::Running)
381 }
382
383 pub fn set_actor_state(&self, id: &ActorId, state: ActorState) -> crate::Result<()> {
385 self.registry.set_state(id, state)
386 }
387
388 pub async fn send(&self, target: ActorId, message: Message) -> crate::Result<()> {
390 if let Some(ref policy) = self.resource_policy {
391 policy.admit_message(1)?;
392 }
393
394 let mailbox = self
395 .registry
396 .get_mailbox(&target)
397 .ok_or_else(|| Error::actor(format!("actor {:?} not found", target)))?;
398
399 let state = self.registry.get_state(&target);
400 match state {
401 Some(ActorState::Stopped) | Some(ActorState::Failed) => {
402 return Err(Error::actor(format!("actor {:?} is not running", target)));
403 }
404 Some(ActorState::Suspended) => {
405 }
407 _ => {}
408 }
409
410 let priority = message.priority;
411
412 mailbox.send(message.clone()).await?;
415
416 let task = Task {
417 actor_id: target,
418 message, priority,
420 additional_messages: Vec::new(),
421 };
422
423 if self.config.priority_scheduling && task.priority >= Priority::High {
424 self.priority_queue.push(task);
425 } else {
426 self.global_queue.push(task);
427 }
428
429 Ok(())
430 }
431
432 pub async fn send_batch(&self, target: ActorId, messages: Vec<Message>) -> crate::Result<()> {
438 if messages.is_empty() {
439 return Ok(());
440 }
441
442 if let Some(ref policy) = self.resource_policy {
443 policy.admit_message(messages.len())?;
444 }
445
446 let mailbox = self
447 .registry
448 .get_mailbox(&target)
449 .ok_or_else(|| Error::actor(format!("actor {:?} not found", target)))?;
450
451 let state = self.registry.get_state(&target);
452 match state {
453 Some(ActorState::Stopped) | Some(ActorState::Failed) => {
454 return Err(Error::actor(format!("actor {:?} is not running", target)));
455 }
456 Some(ActorState::Suspended) => {}
457 _ => {}
458 }
459
460 let max_priority = messages
461 .iter()
462 .map(|m| m.priority)
463 .max()
464 .unwrap_or(Priority::Normal);
465
466 for msg in &messages {
467 mailbox.send(msg.clone()).await?;
468 }
469
470 let mut messages_into_iter = messages.into_iter();
471 let first = match messages_into_iter.next() {
472 Some(msg) => msg,
473 None => return Ok(()),
474 };
475 let additional: Vec<Message> = messages_into_iter.collect();
476
477 let task = Task {
478 actor_id: target,
479 message: first,
480 priority: max_priority,
481 additional_messages: additional,
482 };
483
484 if self.config.priority_scheduling && task.priority >= Priority::High {
485 self.priority_queue.push(task);
486 } else {
487 self.global_queue.push(task);
488 }
489
490 Ok(())
491 }
492
493 pub fn try_send(&self, target: ActorId, message: Message) -> crate::Result<()> {
495 let mailbox = self
496 .registry
497 .get_mailbox(&target)
498 .ok_or_else(|| Error::actor(format!("actor {:?} not found", target)))?;
499
500 let priority = message.priority;
501
502 mailbox.try_send(message.clone()).map_err(|(_, e)| e)?;
504
505 let task = Task {
506 actor_id: target,
507 message, priority,
509 additional_messages: Vec::new(),
510 };
511
512 if self.config.priority_scheduling && task.priority >= Priority::High {
513 self.priority_queue.push(task);
514 } else {
515 self.global_queue.push(task);
516 }
517
518 Ok(())
519 }
520
521 pub fn registry(&self) -> &Arc<ActorRegistry> {
523 &self.registry
524 }
525
526 pub fn stats(&self) -> SchedulerStats {
528 let mut worker_stats = Vec::new();
529 let mut total_processed = 0u64;
530 let mut total_stolen = 0u64;
531 let mut total_batches = 0u64;
532
533 for (id, stats) in self.worker_stats.iter().enumerate() {
534 let processed = stats.processed.load(Ordering::Relaxed);
535 let stolen = stats.stolen.load(Ordering::Relaxed);
536 let batches = stats.batches_processed.load(Ordering::Relaxed);
537
538 total_processed += processed;
539 total_stolen += stolen;
540 total_batches += batches;
541
542 worker_stats.push(WorkerStatsInfo {
543 id,
544 processed,
545 stolen,
546 batches_processed: batches,
547 });
548 }
549
550 SchedulerStats {
551 running: self.running.load(Ordering::Relaxed),
552 total_actors: self.total_actors.load(Ordering::Relaxed),
553 active_actors: self.registry.stats().running,
554 total_messages_processed: total_processed,
555 total_batches_processed: total_batches,
556 total_stolen,
557 worker_count: self.worker_stats.len(),
558 workers: worker_stats,
559 }
560 }
561
562 #[cfg(feature = "serde")]
564 pub fn rpc_client(self: &Arc<Self>) -> RpcClient {
565 RpcClient::new(self.clone())
566 }
567
568 #[allow(clippy::too_many_arguments)]
569 fn worker_loop(
570 worker_id: usize,
571 worker: crossbeam_deque::Worker<Task>,
572 global_queue: Arc<WorkQueue>,
573 priority_queue: Arc<PriorityQueue>,
574 registry: Arc<ActorRegistry>,
575 config: SchedulerConfig,
576 running: Arc<AtomicBool>,
577 stats: Arc<WorkerStats>,
578 stealer_registry: StealerRegistry,
579 executor: Option<&Arc<dyn ActorExecutor>>,
580 ) {
581 let mut stealer = WorkStealer::new(Vec::new());
582 let mut last_version = 0u64;
583 let mut iteration = 0u32;
584
585 let mut consecutive_empty = 0u32;
586
587 while running.load(Ordering::Acquire) {
588 iteration = iteration.wrapping_add(1);
589
590 if iteration % config.stealer_refresh_interval.max(1) == 0 {
593 let current_version = stealer_registry.version();
594 if current_version != last_version {
595 let stealers = stealer_registry.get_stealers(worker_id);
596 stealer = WorkStealer::new(stealers);
597 last_version = current_version;
598 }
599 }
600
601 if let Some(task) = priority_queue.pop() {
603 Self::process_task_safe(®istry, task, &stats, executor);
604 consecutive_empty = 0;
605 continue;
606 }
607
608 if let Some(task) = worker.pop() {
610 Self::process_task_safe(®istry, task, &stats, executor);
611 consecutive_empty = 0;
612 continue;
613 }
614
615 if let Some(task) = global_queue.steal_global() {
617 Self::process_task_safe(®istry, task, &stats, executor);
618 consecutive_empty = 0;
619 continue;
620 }
621
622 if let Some(task) = stealer.steal() {
624 stats.stolen.fetch_add(1, Ordering::Relaxed);
625 Self::process_task_safe(®istry, task, &stats, executor);
626 consecutive_empty = 0;
627 continue;
628 }
629
630 let stolen = stealer.steal_batch(&worker, config.max_steal_batch);
632 if stolen > 0 {
633 stats.stolen.fetch_add(stolen as u64, Ordering::Relaxed);
634 continue;
635 }
636
637 consecutive_empty += 1;
639 if consecutive_empty > 100 {
640 std::thread::sleep(std::time::Duration::from_micros(config.idle_sleep_us));
641 } else if consecutive_empty > 10 {
642 std::hint::spin_loop();
643 }
644 }
645 }
646
647 fn process_task_safe(
654 registry: &ActorRegistry,
655 task: Task,
656 stats: &WorkerStats,
657 executor: Option<&Arc<dyn ActorExecutor>>,
658 ) {
659 let actor_id = task.actor_id;
660 let batch_size = 1 + task.additional_messages.len();
661 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
662 Self::process_task(registry, task, stats, executor);
663 }));
664
665 if let Err(panic_payload) = result {
666 if batch_size > 1 {
667 stats.batches_processed.fetch_add(1, Ordering::Relaxed);
668 }
669 tracing::error!(
670 actor_id = ?actor_id,
671 batch_size,
672 "worker panicked while processing task; marking actor as Failed and draining mailbox"
673 );
674 let _ = registry.set_state(&actor_id, ActorState::Failed);
675 if let Some(mailbox) = registry.get_mailbox(&actor_id) {
676 mailbox.clear();
677 }
678 if let Some(s) = panic_payload.downcast_ref::<&str>() {
679 tracing::error!(panic_message = %s, "panic details");
680 } else if let Some(s) = panic_payload.downcast_ref::<String>() {
681 tracing::error!(panic_message = %s, "panic details");
682 }
683 }
684 }
685
686 #[cfg(feature = "sim")]
697 pub(crate) fn sim_process_task(
698 registry: &ActorRegistry,
699 task: Task,
700 executor: Option<&Arc<dyn ActorExecutor>>,
701 ) -> u64 {
702 let stats = WorkerStats::default();
703 Self::process_task_safe(registry, task, &stats, executor);
704 stats.processed.load(Ordering::Relaxed)
705 }
706
707 fn process_task(
708 registry: &ActorRegistry,
709 mut task: Task,
710 stats: &WorkerStats,
711 executor: Option<&Arc<dyn ActorExecutor>>,
712 ) {
713 if task.additional_messages.is_empty() {
714 Self::process_single_message(registry, task.actor_id, &task.message, stats, executor);
716 } else {
717 stats.batches_processed.fetch_add(1, Ordering::Relaxed);
719
720 Self::process_single_message(registry, task.actor_id, &task.message, stats, executor);
721
722 if matches!(
724 registry.get_state(&task.actor_id),
725 Some(ActorState::Failed) | Some(ActorState::Stopped)
726 ) {
727 return;
728 }
729
730 for message in task.additional_messages.drain(..) {
731 Self::process_single_message(registry, task.actor_id, &message, stats, executor);
732
733 if matches!(
735 registry.get_state(&task.actor_id),
736 Some(ActorState::Failed) | Some(ActorState::Stopped)
737 ) {
738 break;
739 }
740 }
741 }
742 }
743
744 fn process_single_message(
745 registry: &ActorRegistry,
746 actor_id: ActorId,
747 message: &Message,
748 stats: &WorkerStats,
749 executor: Option<&Arc<dyn ActorExecutor>>,
750 ) {
751 let actor_state = registry.get_state(&actor_id);
752
753 match actor_state {
754 Some(ActorState::Running) | Some(ActorState::Creating) => {
755 let should_count = if let Some(exec) = executor {
756 match exec.execute(&actor_id, message) {
757 ExecutionResult::Success { .. } => {
758 if matches!(message.payload, MessagePayload::Start) {
759 let _ = registry.set_state(&actor_id, ActorState::Running);
760 } else if matches!(message.payload, MessagePayload::Stop) {
761 let _ = registry.set_state(&actor_id, ActorState::Stopped);
762 }
763 true
764 }
765 ExecutionResult::FuelExhausted { .. } => {
766 let _ = registry.set_state(&actor_id, ActorState::Failed);
767 if let Some(mailbox) = registry.get_mailbox(&actor_id) {
768 mailbox.clear();
769 }
770 false
771 }
772 ExecutionResult::Failed { error } => {
773 tracing::warn!("Actor execution failed: {}", error);
774 let _ = registry.set_state(&actor_id, ActorState::Failed);
775 if let Some(mailbox) = registry.get_mailbox(&actor_id) {
776 mailbox.clear();
777 }
778 false
779 }
780 ExecutionResult::NotReady => {
781 Self::handle_state_change_for(actor_id, message, registry);
782 true
783 }
784 }
785 } else {
786 Self::handle_state_change_for(actor_id, message, registry);
787 true
788 };
789
790 if should_count {
791 stats.processed.fetch_add(1, Ordering::Relaxed);
792 registry.record_processed(&actor_id);
793 if let Some(mailbox) = registry.get_mailbox(&actor_id) {
802 mailbox.try_recv();
803 }
804 }
805 }
806 Some(ActorState::Suspended) => {
807 if matches!(
814 message.payload,
815 MessagePayload::Signal(_) | MessagePayload::Start | MessagePayload::Stop
816 ) {
817 Self::handle_state_change_for(actor_id, message, registry);
818 if let Some(mailbox) = registry.get_mailbox(&actor_id) {
819 mailbox.try_recv();
820 }
821 if matches!(
835 registry.get_state(&actor_id),
836 Some(ActorState::Running) | Some(ActorState::Creating)
837 ) {
838 while let Some(mailbox) = registry.get_mailbox(&actor_id) {
839 if mailbox.is_empty() {
840 break;
841 }
842 match mailbox.try_recv() {
843 Some(queued) => Self::process_single_message(
844 registry, actor_id, &queued, stats, executor,
845 ),
846 None => break,
847 }
848 }
849 }
850 } else if let Some(mailbox) = registry.get_mailbox(&actor_id) {
851 let _ = mailbox.try_send(message.clone());
854 }
855 }
856 _ => {}
857 }
858 }
859
860 fn handle_state_change_for(actor_id: ActorId, message: &Message, registry: &ActorRegistry) {
861 if matches!(message.payload, MessagePayload::Start) {
862 let _ = registry.set_state(&actor_id, ActorState::Running);
863 } else if matches!(message.payload, MessagePayload::Stop) {
864 let _ = registry.set_state(&actor_id, ActorState::Stopped);
865 } else if let Some(MessagePayload::Signal(signal)) = Some(&message.payload) {
866 match signal {
867 crate::Signal::Pause => {
868 let _ = registry.set_state(&actor_id, ActorState::Suspended);
869 }
870 crate::Signal::Resume => {
871 let _ = registry.set_state(&actor_id, ActorState::Running);
872 }
873 crate::Signal::Restart => {
874 let _ = registry.set_state(&actor_id, ActorState::Creating);
875 }
876 }
877 }
878 }
879}
880
881impl Drop for ActorScheduler {
882 fn drop(&mut self) {
883 self.stop();
884 }
885}
886
887#[derive(Debug)]
889pub struct SchedulerStats {
890 pub running: bool,
892 pub total_actors: u64,
894 pub active_actors: usize,
896 pub total_messages_processed: u64,
898 pub total_batches_processed: u64,
900 pub total_stolen: u64,
902 pub worker_count: usize,
904 pub workers: Vec<WorkerStatsInfo>,
906}
907
908#[derive(Debug)]
910pub struct WorkerStatsInfo {
911 pub id: usize,
913 pub processed: u64,
915 pub stolen: u64,
917 pub batches_processed: u64,
919}
920
921#[cfg(test)]
922mod tests {
923 use super::*;
924 use crate::policy::{NoopPolicy, ResourcePolicy};
925
926 struct CountingPolicy {
929 max_actors: usize,
930 live: std::sync::atomic::AtomicUsize,
931 }
932
933 impl CountingPolicy {
934 fn new(max_actors: usize) -> Self {
935 Self {
936 max_actors,
937 live: std::sync::atomic::AtomicUsize::new(0),
938 }
939 }
940 }
941
942 impl ResourcePolicy for CountingPolicy {
943 fn admit_actor(&self) -> crate::Result<()> {
944 let prev = self.live.fetch_add(1, Ordering::SeqCst);
945 if prev >= self.max_actors {
946 self.live.fetch_sub(1, Ordering::SeqCst);
947 return Err(crate::Error::ResourceExhausted(format!(
948 "actor limit exceeded ({}/{})",
949 prev, self.max_actors
950 )));
951 }
952 Ok(())
953 }
954
955 fn release_actor(&self) {
956 self.live.fetch_sub(1, Ordering::SeqCst);
957 }
958 }
959
960 #[test]
961 fn test_scheduler_creation() {
962 let config = SchedulerConfig::new().workers(2);
963 let scheduler = ActorScheduler::new(config);
964
965 assert!(!scheduler.running.load(Ordering::Relaxed));
966 assert_eq!(scheduler.worker_stats.len(), 2);
967 }
968
969 #[tokio::test]
970 async fn test_scheduler_spawn_actor() {
971 let scheduler = ActorScheduler::new(SchedulerConfig::new().workers(1));
972 scheduler.start().unwrap();
973
974 let id = scheduler.spawn().unwrap();
975 assert!(scheduler.registry().get_state(&id).is_some());
976
977 scheduler.stop();
978 }
979
980 #[tokio::test]
981 async fn test_scheduler_send_message() {
982 let scheduler = ActorScheduler::new(SchedulerConfig::new().workers(1));
983 scheduler.start().unwrap();
984
985 let id = scheduler.spawn().unwrap();
986
987 scheduler.set_actor_running(&id).unwrap();
988
989 let msg = Message {
990 sender: None,
991 payload: MessagePayload::Custom(vec![1, 2, 3]),
992 priority: Priority::Normal,
993 };
994
995 scheduler.send(id, msg).await.unwrap();
996
997 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
998
999 scheduler.stop();
1000 }
1001
1002 #[test]
1003 fn test_config_effective_workers() {
1004 let config = SchedulerConfig::new().workers(0);
1005 assert!(config.effective_workers() > 0);
1006
1007 let config = SchedulerConfig::new().workers(8);
1008 assert_eq!(config.effective_workers(), 8);
1009 }
1010
1011 #[test]
1012 fn test_stealer_registry() {
1013 let registry = StealerRegistry::new();
1014 let (worker1, stealer1) = create_local_queue();
1015 let (worker2, stealer2) = create_local_queue();
1016
1017 registry.add_stealer(0, stealer1);
1018 registry.add_stealer(1, stealer2);
1019
1020 let stealers_for_0 = registry.get_stealers(0);
1021 assert_eq!(stealers_for_0.len(), 1);
1022
1023 let stealers_for_1 = registry.get_stealers(1);
1024 assert_eq!(stealers_for_1.len(), 1);
1025
1026 drop(worker1);
1027 drop(worker2);
1028 }
1029
1030 #[tokio::test]
1031 async fn test_scheduler_with_null_executor() {
1032 let executor = Arc::new(NullExecutor::new());
1033 let scheduler =
1034 ActorScheduler::with_executor(SchedulerConfig::new().workers(2), executor.clone());
1035 scheduler.start().unwrap();
1036
1037 let id = scheduler.spawn().unwrap();
1038 scheduler.set_actor_running(&id).unwrap();
1039
1040 let msg = Message {
1041 sender: None,
1042 payload: MessagePayload::Custom(vec![1, 2, 3]),
1043 priority: Priority::Normal,
1044 };
1045
1046 scheduler.send(id, msg).await.unwrap();
1047
1048 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1049
1050 let stats = scheduler.stats();
1051 assert!(stats.total_messages_processed > 0 || stats.total_stolen > 0);
1052
1053 scheduler.stop();
1054 }
1055
1056 #[tokio::test]
1057 async fn test_work_stealing_between_workers() {
1058 let scheduler = ActorScheduler::new(SchedulerConfig::new().workers(4));
1059 scheduler.start().unwrap();
1060
1061 let mut actors = Vec::new();
1062 for _ in 0..10 {
1063 let id = scheduler.spawn().unwrap();
1064 scheduler.set_actor_running(&id).unwrap();
1065 actors.push(id);
1066 }
1067
1068 for actor_id in &actors {
1069 let msg = Message {
1070 sender: None,
1071 payload: MessagePayload::Custom(vec![1, 2, 3]),
1072 priority: Priority::Normal,
1073 };
1074 scheduler.send(*actor_id, msg).await.unwrap();
1075 }
1076
1077 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
1078
1079 let stats = scheduler.stats();
1080 assert!(stats.total_messages_processed >= 5);
1081 assert_eq!(stats.worker_count, 4);
1082
1083 scheduler.stop();
1084 }
1085
1086 #[tokio::test]
1087 async fn test_scheduler_resource_policy_rejects_over_limit() {
1088 let policy = Arc::new(CountingPolicy::new(1));
1089 let scheduler = ActorScheduler::with_resource_policy(
1090 SchedulerConfig::new().workers(1),
1091 Arc::new(NullExecutor::new()),
1092 policy,
1093 );
1094
1095 let first = scheduler.spawn().unwrap();
1096 assert!(scheduler.registry().get_state(&first).is_some());
1097
1098 let result = scheduler.spawn();
1099 assert!(result.is_err());
1100 let err_msg = result.unwrap_err().to_string();
1101 assert!(err_msg.contains("actor limit exceeded"), "got: {err_msg}");
1102
1103 scheduler.kill(&first).unwrap();
1105 let second = scheduler.spawn().unwrap();
1106 assert!(scheduler.registry().get_state(&second).is_some());
1107
1108 scheduler.stop();
1109 }
1110
1111 #[test]
1112 fn test_scheduler_noop_policy_admits_all() {
1113 let policy = NoopPolicy;
1114 assert!(policy.admit_actor().is_ok());
1115 assert!(policy.admit_message(100).is_ok());
1116 policy.release_actor();
1117 }
1118
1119 #[test]
1127 fn a_suspended_actor_accepts_control_signals_and_resumes() {
1128 let rt = crate::rt();
1129 rt.block_on(async {
1130 let scheduler =
1131 std::sync::Arc::new(ActorScheduler::new(SchedulerConfig::new().workers(2)));
1132 scheduler.start().expect("workers start");
1133 let handle = crate::ActorBuilder::new()
1134 .name("suspending")
1135 .spawn(&scheduler)
1136 .expect("actor spawns");
1137
1138 macro_rules! wait {
1139 ($cond:expr) => {{
1140 let mut ok = false;
1141 for _ in 0..200 {
1142 if $cond {
1143 ok = true;
1144 break;
1145 }
1146 tokio::time::sleep(std::time::Duration::from_millis(5)).await;
1147 }
1148 ok
1149 }};
1150 }
1151
1152 handle.start().await.expect("start accepted");
1153 assert!(wait!(handle.is_running()), "actor runs");
1154
1155 handle.pause().await.expect("pause accepted");
1156 assert!(wait!(handle.is_suspended()), "actor suspends");
1157
1158 let processed_before_resume = handle.processed_count();
1162 handle
1163 .send(MessagePayload::Custom(vec![7]))
1164 .await
1165 .expect("a suspended actor accepts messages");
1166 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1167 assert!(handle.is_suspended(), "queued work does not resume it");
1168
1169 handle.resume().await.expect("resume accepted");
1171 assert!(
1172 wait!(handle.is_running()),
1173 "a suspended actor must be resumable — it used to stay \
1174 Suspended forever because Resume was re-queued"
1175 );
1176
1177 assert!(
1184 wait!(handle.processed_count() > processed_before_resume),
1185 "the message queued during the suspension must be processed \
1186 after the resume, not stranded in the mailbox"
1187 );
1188
1189 handle.stop().await.expect("stop accepted");
1191 assert!(wait!(handle.is_stopped()), "and stoppable");
1192
1193 scheduler.stop();
1194 });
1195 }
1196
1197 #[test]
1201 fn work_queued_during_suspension_runs_after_the_resume() {
1202 let rt = crate::rt();
1203 rt.block_on(async {
1204 let scheduler =
1205 std::sync::Arc::new(ActorScheduler::new(SchedulerConfig::new().workers(2)));
1206 scheduler.start().expect("workers start");
1207 let handle = crate::ActorBuilder::new()
1208 .name("stranded")
1209 .spawn(&scheduler)
1210 .expect("actor spawns");
1211
1212 macro_rules! wait {
1213 ($cond:expr) => {{
1214 let mut ok = false;
1215 for _ in 0..200 {
1216 if $cond {
1217 ok = true;
1218 break;
1219 }
1220 tokio::time::sleep(std::time::Duration::from_millis(5)).await;
1221 }
1222 ok
1223 }};
1224 }
1225
1226 handle.start().await.expect("start accepted");
1227 assert!(wait!(handle.is_running()), "actor runs");
1228
1229 handle.pause().await.expect("pause accepted");
1230 assert!(wait!(handle.is_suspended()), "actor suspends");
1231
1232 let before = handle.processed_count();
1233 for payload in 0..3u8 {
1234 handle
1235 .send(MessagePayload::Custom(vec![payload]))
1236 .await
1237 .expect("a suspended actor accepts messages");
1238 }
1239 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1240 assert!(
1241 handle.processed_count() == before,
1242 "nothing runs while suspended"
1243 );
1244
1245 handle.resume().await.expect("resume accepted");
1246 assert!(
1247 wait!(handle.processed_count() >= before + 3),
1248 "all three queued messages run after the resume: {} of 3",
1249 handle.processed_count() - before
1250 );
1251
1252 handle.stop().await.expect("stop accepted");
1253 assert!(wait!(handle.is_stopped()), "stoppable");
1254 scheduler.stop();
1255 });
1256 }
1257}