#![allow(clippy::unwrap_used, clippy::expect_used)]
use actor_kit::{
ActorBuilder, ActorScheduler, MailboxConfig, Message, MessagePayload, Priority, SchedulerConfig,
};
use std::sync::Arc;
use std::time::{Duration, Instant};
const ACTORS: u64 = 4;
const MSGS_PER_ACTOR: u64 = 12_500; const MAILBOX_CAP: usize = 64;
const DRAIN_TIMEOUT: Duration = Duration::from_secs(30);
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn sustained_delivery_across_50k_messages_does_not_stall() {
for round in 0..10 {
run_one_round(round).await;
}
}
async fn run_one_round(round: u64) {
let mut cfg = SchedulerConfig::new().workers(2);
cfg.mailbox_config = MailboxConfig::new(MAILBOX_CAP);
let scheduler = Arc::new(ActorScheduler::new(cfg));
scheduler.start().unwrap();
let mut handles = Vec::new();
for i in 0..ACTORS {
let handle = ActorBuilder::new()
.name(format!("stall-reg-{round}-{i}"))
.spawn(&scheduler)
.unwrap();
scheduler.set_actor_running(&handle.id()).unwrap();
handle.start().await.unwrap();
handles.push(handle);
}
let total = ACTORS * MSGS_PER_ACTOR;
let drained = tokio::time::timeout(DRAIN_TIMEOUT, async {
for (k, handle) in handles.iter().enumerate() {
for i in 0..MSGS_PER_ACTOR {
scheduler
.send(
handle.id(),
Message {
sender: None,
payload: MessagePayload::Custom(vec![((k as u64 + i) % 256) as u8]),
priority: Priority::Normal,
},
)
.await
.unwrap_or_else(|e| panic!("round {round}: send failed: {e}"));
}
}
let deadline = Instant::now() + DRAIN_TIMEOUT;
loop {
let processed: u64 = handles
.iter()
.map(|h| scheduler.registry().get_processed_count(&h.id()))
.sum();
if processed >= total + ACTORS {
break processed;
}
assert!(
Instant::now() < deadline,
"round {round}: stall — processed {} / {} within timeout",
processed,
total + ACTORS
);
tokio::time::sleep(Duration::from_millis(2)).await;
}
})
.await;
match drained {
Ok(processed) => assert!(processed >= total + ACTORS),
Err(_) => {
let processed: u64 = handles
.iter()
.map(|h| scheduler.registry().get_processed_count(&h.id()))
.sum();
let mailbox_lens: Vec<usize> = handles.iter().map(|h| h.mailbox_size()).collect();
panic!(
"round {round}: TIMED OUT after {DRAIN_TIMEOUT:?} — stall reproduced \
(processed {processed}/{}; mailbox sizes {mailbox_lens:?})",
total + ACTORS
);
}
}
scheduler.stop();
}