Compare commits

..

9 commits

Author SHA1 Message Date
Zachery Aaron Shores-Chmielewski
3c5e3d2ece feat: hybrid channels
Add a mutex-locked Dequeue to prevent panics and failures on overflow.
2026-01-26 14:00:25 +07:00
Zachery Aaron Shores-Chmielewski
58d3c19ff9 feat: benchmarks
Got a ton of benchmarking and stress testing code spit out by the
LLM. Did a not-so-thorough vetting, not ready for merge into master,
but it mostly makes sense. Needs to actually fail on the stress
tests, not anticipate failure and call it success. The benchmarks
also need a more thorough going over, in order to validate that they
make sense. Then we can work on improving our metrics.
2026-01-26 11:14:54 +07:00
Zachery Aaron Shores-Chmielewski
d14f99c6af feat: conditionally compile without a source of randomness 2026-01-26 09:11:35 +07:00
Zachery Aaron Shores-Chmielewski
98a2733173 feat: multithreaded runtime
Runtime is now configurable. Adds a tunable config for modifying the
size of pre-allocations for actor messaging channels, and for selecting
the number of threads the runtime will use.
2026-01-25 20:31:34 +07:00
Zachery Aaron Shores-Chmielewski
c62b20c732 feat(WIP): refactor router execution
In single threaded contexts, the Router now is treated as yet another actor
on the queue. In multithreaded contexts, it gets its own dedicated thread.
2026-01-25 12:54:01 +07:00
Zachery Aaron Shores-Chmielewski
2f91c7d1bd feat(WIP): runtime/router refactor
Simplify the implementation of the multithreaded runtime and router.
2026-01-25 11:45:34 +07:00
Zachery Aaron Shores-Chmielewski
d504377ba9 feat(WIP): multithreaded runtime
Basic framework for a multithreaded runtime has been put in place. Needs
plenty of fixes to keep the logic straightforward and performant.
2026-01-25 10:39:44 +07:00
Zachery Aaron Shores-Chmielewski
2450442566 feat: refactor: split out components into modules 2026-01-23 14:09:31 +07:00
Zachery Aaron Shores-Chmielewski
a9f7abba25 feat: mvp actor ring test 2026-01-23 13:48:26 +07:00
5 changed files with 27 additions and 59 deletions

1
.gitignore vendored
View file

@ -1,2 +1 @@
/target /target
.vscode/

View file

@ -6,11 +6,6 @@ memory allocator and threading provided by the rust standard library.
We are not building a new erlang/BEAM. Minimal feature set means spawning actor processes, not having supervisiors, lots of process We are not building a new erlang/BEAM. Minimal feature set means spawning actor processes, not having supervisiors, lots of process
monitoring tools, prempting, etc. monitoring tools, prempting, etc.
# FIXME
This is slightly obsolete. Was necessary in order to concentrate on getting the basic skeleton up, now it's a distraction and unreliable.
After getting the benches/etc finished, move this into an `ARCHITECTURE.md` file, have it make sense.
## Actor model ## Actor model
An actor has: An actor has:
@ -53,3 +48,30 @@ The router is the engine for message delivery. It posesses:
- 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 directly into the recipient's inbox buffer. 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.
### 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
}
}
}
```

View file

@ -1,44 +0,0 @@
### Profiling and Benchmarking
- Research as to modern art on benching and profiling
- Implement an MVP here.
- Bench/profile against a suite of tests selected for generality across actor framework usecases
- Identify hot paths and bottlenecks
- e.g. pretty sure the router is a major bottleneck, what else
- follow through the entire message cycle:
Parent process -> Convert to swactor::Message/Envelope -> Router -> Delivery -> Processing -> etc.
Identify every small detail on which you may be able to improve, any unneeded processing or branching
- (optional) Visualization tools:
- make some pretty stuff for tracing messages, actor activity, router activity, etc.
### Usage
- After benching and profiling, cleaning up the most egregious wrongdoings we will:
- actually implement our own projects in the framework, ones I actually find useful personally
- Optimization pipeline:
- Once we have well-established benches and profiles for general cases, build a set of tools that can auto-optimize
for given use cases. Tuning, for example, the channel buffers, router behavior, message consumption behavior, etc.
### Chores
- go over all the FIXMEs littered about. Add comments.
- add misc features as they come up. Prefer tools for understanding execution flows, visualizing flows, and adding
robustness, over ergonomics. Better to be slightly clunky but fast and optimized, than vice versa.
### Far future
- make language bindings. e.g., an npm package, python bindings, etc.
### Optimization
- Localize actors and inboxes:
- Because the entire runtime is message driven, the happy path must be fast. Even lock free, when we have to go through
several calls of an atomic ring buffer in order to process a single message, its unnecessary.
Parent process -> router -> actor -> router -> inbox -> parent process; every transfer going through an atomic buffer.
- to do this, design heavily around a localized worker thread. Actors on a working thread should have their inbox localized, they
should be 'sticky' to that thread (FILO queue?), and we should route messages based on core locality. Future optimizations can include
a tunable algorithm that puts actors that frequently communicate together on the same thread.

View file

@ -87,7 +87,6 @@ where
fn tick(&mut self, ctx: &Runtime) { fn tick(&mut self, ctx: &Runtime) {
// TODO: WATERLEVEL is hard coded, and so is this message handling scheme. We should // TODO: WATERLEVEL is hard coded, and so is this message handling scheme. We should
// make it so both are more flexible, with sane defaults. // make it so both are more flexible, with sane defaults.
// FIXME: lots of indirection just to get length on a hot path
let total_messages = self.inbox.len(); let total_messages = self.inbox.len();
let messages_to_process = if total_messages < WATERLEVEL { let messages_to_process = if total_messages < WATERLEVEL {
total_messages total_messages

View file

@ -19,14 +19,6 @@ pub(crate) trait SenderT: Send + Sync {
impl<M: Message> SenderT for Sender<M> { impl<M: Message> SenderT for Sender<M> {
fn try_send(&self, envelope: Envelope) { fn try_send(&self, envelope: Envelope) {
if let Some(msg) = envelope.downcast_ref::<M>() { if let Some(msg) = envelope.downcast_ref::<M>() {
// FIXME: we are directly cloning the contents of the Arc pointer here
// Do we want to? Should we provide another way?
//
// The standard concept of an actor has message and state
// isolation, so we should leave this as is. However, we should
// make it clear and obvious this pathway is the heavy, contained
// pathway, and include a shared memory pathway for logic that
// may need it.
let _ = Sender::try_send(self, msg.clone()); let _ = Sender::try_send(self, msg.clone());
} }
} }