feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
use crate::{WATERLEVEL, channel::Receiver, get_random, runtime::Runtime};
|
2026-01-25 13:38:34 +00:00
|
|
|
|
|
|
|
|
/// The primary trait defining data that can be passed to and from actor processes
|
|
|
|
|
pub trait Message: 'static + Sized + Clone + Send + Sync {}
|
|
|
|
|
impl<T: 'static + Sized + Clone + Send + Sync> Message for T {}
|
|
|
|
|
|
|
|
|
|
/// The trait that needs to be implemented in order to run a process as an `Actor`
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
///
|
2026-01-25 13:38:34 +00:00
|
|
|
/// The `Incoming` type represents `Messages` that can be delivered to the `Actor`.
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
///
|
2026-01-25 13:38:34 +00:00
|
|
|
/// The `Response` type represents possible `Messages` the actor may attempt to reply with.
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
///
|
2026-01-25 13:38:34 +00:00
|
|
|
/// The `fn handle(..)` is where you implement the logic for handling `Incoming` messages
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
///
|
2026-01-25 13:38:34 +00:00
|
|
|
/// # Example
|
|
|
|
|
/// ```
|
|
|
|
|
/// use swactor::{actor::{ActorAddress, ActorInterface}, runtime::Runtime};
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
///
|
2026-01-25 13:38:34 +00:00
|
|
|
/// struct Greeter {
|
|
|
|
|
/// num_greeted: usize,
|
|
|
|
|
/// }
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
///
|
2026-01-25 13:38:34 +00:00
|
|
|
/// #[derive(Clone)] // required to auto implement `Message`
|
|
|
|
|
/// struct GreetMessage {
|
|
|
|
|
/// who: String,
|
|
|
|
|
/// return_addr: ActorAddress,
|
|
|
|
|
/// }
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
///
|
2026-01-25 13:38:34 +00:00
|
|
|
/// #[derive(Clone)]
|
|
|
|
|
/// struct GreetResponse(String);
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
///
|
2026-01-25 13:38:34 +00:00
|
|
|
/// impl ActorInterface for Greeter {
|
|
|
|
|
/// type Incoming = GreetMessage;
|
|
|
|
|
/// type Response = GreetResponse;
|
|
|
|
|
///
|
|
|
|
|
/// fn handle(&mut self, ctx: &Runtime, msg: Self::Incoming) {
|
|
|
|
|
/// let response = GreetResponse(format!("Hello, {}!", msg.who).to_string());
|
|
|
|
|
/// if let Ok(_) = ctx.send_to(msg.return_addr, response) {
|
|
|
|
|
/// self.num_greeted += 1;
|
|
|
|
|
/// }
|
|
|
|
|
/// }
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
pub trait ActorInterface: 'static + Send {
|
|
|
|
|
type Incoming: Message;
|
|
|
|
|
type Response: Message;
|
|
|
|
|
fn handle(&mut self, ctx: &Runtime, msg: Self::Incoming);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A unique address for this actor. 32 bytes is overkill for a small application,
|
|
|
|
|
/// but most systems are powerful, and this allows us to create a global map of
|
|
|
|
|
/// actor processes in the future, without worrying about collision.
|
|
|
|
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
|
pub struct ActorAddress(pub [u8; 32]);
|
|
|
|
|
impl ActorAddress {
|
|
|
|
|
pub fn new_random() -> Self {
|
|
|
|
|
let mut bytes = [0u8; 32];
|
|
|
|
|
get_random(&mut bytes);
|
|
|
|
|
Self(bytes)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The actor process as represented in the Runtime, with the actor state stored with it's inbox.
|
|
|
|
|
pub(crate) struct Actor<A>
|
|
|
|
|
where
|
|
|
|
|
A: ActorInterface,
|
|
|
|
|
{
|
|
|
|
|
inbox: Receiver<A::Incoming>,
|
|
|
|
|
inner: A,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<A: ActorInterface> Actor<A> {
|
|
|
|
|
pub(crate) fn new(inbox: Receiver<A::Incoming>, inner: A) -> Self {
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
Self { inbox, inner }
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Trait for type-erased actors
|
|
|
|
|
pub(crate) trait AnyActor: Send {
|
|
|
|
|
fn tick(&mut self, ctx: &Runtime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<A> AnyActor for Actor<A>
|
|
|
|
|
where
|
|
|
|
|
A: ActorInterface,
|
|
|
|
|
{
|
|
|
|
|
fn tick(&mut self, ctx: &Runtime) {
|
|
|
|
|
// TODO: WATERLEVEL is hard coded, and so is this message handling scheme. We should
|
|
|
|
|
// make it so both are more flexible, with sane defaults.
|
|
|
|
|
let total_messages = self.inbox.len();
|
|
|
|
|
let messages_to_process = if total_messages < WATERLEVEL {
|
|
|
|
|
total_messages
|
|
|
|
|
} else {
|
|
|
|
|
total_messages >> 1
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for _ in 0..messages_to_process {
|
|
|
|
|
match self.inbox.try_recv() {
|
|
|
|
|
Some(msg) => self.inner.handle(ctx, msg),
|
|
|
|
|
None => unreachable!(
|
|
|
|
|
"We checked number of unprocessed messages in the queue ahead of processing"
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|