stash: rewrite wip
We have a basic ring buffer for dealing with concurrent message exchange. Currently in the middle of reifying the traits, types and structs needed for the `hello.rs` example of the simple `Hello, World!` greeter type actor.
This commit is contained in:
parent
87fb908ba7
commit
08b241481f
6 changed files with 150 additions and 139 deletions
64
Cargo.lock
generated
64
Cargo.lock
generated
|
|
@ -23,28 +23,19 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "crossbeam-queue"
|
||||||
version = "1.11.0"
|
version = "0.3.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "crossbeam-utils"
|
||||||
version = "0.3.31"
|
version = "0.8.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-sink"
|
|
||||||
version = "0.3.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-project-lite"
|
|
||||||
version = "0.2.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
|
|
@ -69,8 +60,7 @@ name = "swactor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"tokio",
|
"crossbeam-queue",
|
||||||
"tokio-util",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -84,40 +74,6 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio"
|
|
||||||
version = "1.48.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
|
||||||
dependencies = [
|
|
||||||
"pin-project-lite",
|
|
||||||
"tokio-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-macros"
|
|
||||||
version = "2.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-util"
|
|
||||||
version = "0.7.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
"pin-project-lite",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,4 @@ crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytemuck = { version = "1.24.0", features = ["derive"] }
|
bytemuck = { version = "1.24.0", features = ["derive"] }
|
||||||
tokio = { version = "1.48.0", features = ["rt", "macros"] }
|
crossbeam-queue = "0.3.12"
|
||||||
tokio-util = "0.7.17"
|
|
||||||
|
|
|
||||||
28
DESIGN.md
28
DESIGN.md
|
|
@ -48,3 +48,31 @@ The router is the engine for message delivery. It runs on its own thread and pos
|
||||||
- Its own inbox:
|
- Its own inbox:
|
||||||
The router possesses its own mpsc queue where references to messages are stored. The router will process this queue by dereferencing and writing
|
The router possesses its own mpsc queue where references to messages are stored. The router will process this queue by dereferencing and writing
|
||||||
directly into the recipient's inbox buffer.
|
directly into the recipient's inbox buffer.
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
A means of providing an emergency overflow without adding much more code complexity. The mutex means
|
||||||
|
this will not be `no_std` however.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct HybridChannel<T> {
|
||||||
|
// Start with lock-free ring buffer
|
||||||
|
ring: AtomicRingBuffer<T>,
|
||||||
|
|
||||||
|
// When full, spill into a Mutex<VecDeque<T>>
|
||||||
|
overflow: parking_lot::Mutex<VecDeque<T>>,
|
||||||
|
|
||||||
|
// Track overflow frequency to resize ring proactively
|
||||||
|
overflow_count: AtomicUsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> HybridChannel<T> {
|
||||||
|
fn push(&self, value: T) {
|
||||||
|
if self.ring.push(value).is_err() {
|
||||||
|
self.overflow.lock().push_back(value);
|
||||||
|
self.overflow_count.fetch_add(1, Relaxed);
|
||||||
|
// Optionally: if overflow_count > threshold, grow ring
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -1,63 +1,34 @@
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use swactor::{ActorAddress, ActorInterface, Message, Runtime};
|
||||||
|
|
||||||
// use std::sync::{Arc, atomic::AtomicBool};
|
#[repr(C)]
|
||||||
|
#[derive(Pod, Zeroable)]
|
||||||
|
struct Greeter {
|
||||||
|
pub num_greeted: usize,
|
||||||
|
}
|
||||||
|
|
||||||
// use swactor::error::*;
|
#[derive(Clone)]
|
||||||
// use tokio::task::JoinHandle;
|
struct GreetMessage {
|
||||||
|
name: String,
|
||||||
// struct ActorInbox {
|
addr: ActorAddress,
|
||||||
// _guard: AtomicBool
|
}
|
||||||
// }
|
impl Message for GreetMessage {}
|
||||||
|
|
||||||
// struct GenericGreeter {
|
|
||||||
// _guard: Arc<AtomicBool>,
|
|
||||||
// inbox: Vec<GreeterMessage>,
|
|
||||||
// outbox: Vec<GreeterResponse>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl GenericGreeter {
|
|
||||||
// pub fn new() -> Self {
|
|
||||||
// Self {
|
|
||||||
// _guard: Arc::new(AtomicBool::new(false)),
|
|
||||||
// inbox: Vec::new(),
|
|
||||||
// outbox: Vec::new(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// pub enum GreeterMessage {
|
impl ActorInterface<GreetMessage> for Greeter {
|
||||||
// Name(String),
|
fn handle(&mut self, ctx: &Runtime, msg: GreetMessage) {
|
||||||
// }
|
let res = GreetResponse(format!("Hello, {}!", msg.name));
|
||||||
|
if let Err(_) = ctx.send(res, msg.addr) {
|
||||||
|
// no error handling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// pub enum GreeterResponse {
|
#[derive(Debug, Default, Clone)]
|
||||||
// Hello(String),
|
struct GreetResponse(String);
|
||||||
// }
|
impl Message for GreetResponse {}
|
||||||
|
|
||||||
// pub struct Greeter;
|
fn main() {
|
||||||
|
let rt = Runtime::new();
|
||||||
|
|
||||||
|
}
|
||||||
// fn main() {
|
|
||||||
// let rt = tokio::runtime::Builder::new_current_thread()
|
|
||||||
// .build()
|
|
||||||
// .expect("failed to build runtime");
|
|
||||||
|
|
||||||
// let greet = GenericGreeter::spawn(&rt);
|
|
||||||
|
|
||||||
// let res = rt.block_on(async {greet.await}).expect("runtime error").expect("greeter error");
|
|
||||||
|
|
||||||
// println!("Success!");
|
|
||||||
|
|
||||||
// // let greeter = Greeter.spawn(&rt);
|
|
||||||
|
|
||||||
// // let response = rt
|
|
||||||
// // .block_on(async move {
|
|
||||||
// // greeter
|
|
||||||
// // .send(GreeterMessage::Name("world".to_string()))
|
|
||||||
// // .await
|
|
||||||
// // })
|
|
||||||
// // .expect("failed to get respose");
|
|
||||||
|
|
||||||
// // match response {
|
|
||||||
// // GreeterResponse::Hello(hello) => println!("{hello}"),
|
|
||||||
// // }
|
|
||||||
// }
|
|
||||||
|
|
|
||||||
60
src/lib.rs
60
src/lib.rs
|
|
@ -1,39 +1,47 @@
|
||||||
// mod kimi;
|
mod ring_buffer;
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use ring_buffer::{Receiver, Sender};
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
use std::{
|
pub trait Message: 'static + Sized + Clone {}
|
||||||
marker::PhantomData, mem::MaybeUninit, sync::{Mutex, atomic::AtomicUsize, mpsc::TryRecvError}
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::error::{Error, Result, convert_err};
|
pub trait ActorInterface<M: Message>: Pod + Zeroable {
|
||||||
|
fn handle(&mut self, ctx: &Runtime, msg: M);
|
||||||
|
|
||||||
use std::sync::mpsc;
|
|
||||||
pub trait Message: 'static + Sized + Copy + Default {}
|
|
||||||
|
|
||||||
pub trait Handler<M: Message> {
|
|
||||||
fn handle(&mut self, msg: M);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type ActorAddress = u64;
|
||||||
|
|
||||||
|
pub struct Actor<S, M, N>
|
||||||
|
where
|
||||||
|
M: Message,
|
||||||
use bytemuck::{Pod, Zeroable};
|
N: Message,
|
||||||
|
S: ActorInterface<M>,
|
||||||
#[repr(C)]
|
{
|
||||||
#[derive(Copy, Clone, Pod, Zeroable)]
|
inbox: Receiver<M>,
|
||||||
struct GreeterState {
|
outbox: Sender<N>,
|
||||||
pub num_greeted: usize,
|
state: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum GreetMessage {
|
pub struct Runtime {
|
||||||
Name(String),
|
router: (),
|
||||||
|
actor_queue: (),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum GreetResponse {
|
impl Runtime {
|
||||||
Greeting(String),
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
router: (),
|
||||||
|
actor_queue: (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send<M>(&self, msg: M, addr: ActorAddress) -> Result<(), M> {
|
||||||
|
Err(msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type GreeterId = u64;
|
pub struct MessageRouter<M> {
|
||||||
|
inbox: Receiver<M>,
|
||||||
|
address_book: (),
|
||||||
|
}
|
||||||
|
|
|
||||||
49
src/ring_buffer.rs
Normal file
49
src/ring_buffer.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use crossbeam_queue::ArrayQueue;
|
||||||
|
|
||||||
|
/// The receiving end of a `crossbeam_queue::ArrayQueue`, a lock-free mpsc queue.
|
||||||
|
/// The queue is constructed by the `Receiver::new()` method.
|
||||||
|
/// Responsible for creating the `Sender` ends of itself.
|
||||||
|
///
|
||||||
|
/// Notably: The `Receiver` provides no guarentees that a sending end of the channel exists.
|
||||||
|
pub(crate) struct Receiver<T> {
|
||||||
|
queue: Arc<ArrayQueue<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Receiver<T> {
|
||||||
|
/// Constructs a new `ArrayQueue` with given capacity.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Will panic if capacity is passed as 0
|
||||||
|
pub fn new(capacity: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
queue: Arc::new(ArrayQueue::new(capacity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to retrieve a value from the queue. Returns `None` if empty
|
||||||
|
pub fn try_recv(&self) -> Option<T> {
|
||||||
|
self.queue.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a new `Sender` assosciated with this queue.
|
||||||
|
pub fn new_sender(&self) -> Sender<T> {
|
||||||
|
Sender { queue: self.queue.clone() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The sending end of a `crossbeam_queue::ArrayQueue`, a lock free mpsc queue.
|
||||||
|
/// The queue is initialized via calling the corresponding `Receiver::<T>::new()` method,
|
||||||
|
/// and the sending end of the queue is constructed via calling `receiver.new_sender()`.
|
||||||
|
///
|
||||||
|
/// Notably: The `Sender` provides no guarentees that a receiving end of the channel exists.
|
||||||
|
pub(crate) struct Sender<T> {
|
||||||
|
queue: Arc<ArrayQueue<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Sender<T> {
|
||||||
|
/// Attempt to push a value to the queue. Returns Err(value) if the queue is full.
|
||||||
|
pub fn try_send(&self, value: T) -> Result<(), T> {
|
||||||
|
self.queue.push(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue