Compare commits
3 commits
3c5e3d2ece
...
3e504d3969
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e504d3969 | ||
|
|
c56a05433f | ||
| b422605f6b |
5 changed files with 59 additions and 27 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
/target
|
||||
.vscode/
|
||||
32
DESIGN.md
32
DESIGN.md
|
|
@ -6,6 +6,11 @@ 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
|
||||
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
|
||||
|
||||
An actor has:
|
||||
|
|
@ -48,30 +53,3 @@ The router is the engine for message delivery. It posesses:
|
|||
- 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.
|
||||
|
||||
### 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
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
44
TODOs.md
Normal file
44
TODOs.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
### 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.
|
||||
|
|
@ -87,6 +87,7 @@ where
|
|||
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.
|
||||
// FIXME: lots of indirection just to get length on a hot path
|
||||
let total_messages = self.inbox.len();
|
||||
let messages_to_process = if total_messages < WATERLEVEL {
|
||||
total_messages
|
||||
|
|
|
|||
|
|
@ -19,6 +19,14 @@ pub(crate) trait SenderT: Send + Sync {
|
|||
impl<M: Message> SenderT for Sender<M> {
|
||||
fn try_send(&self, envelope: Envelope) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue