nebkor-maelstrom/README.md

89 lines
3.7 KiB
Markdown
Raw Permalink Normal View History

2024-06-05 00:27:52 +00:00
# A synchronous and simple Maelstrom crate
`nebkor-maelstreom` is a lean and simple synchronous library for writing
[Maelstrom](https://github.com/jepsen-io/maelstrom/tree/0186f398f96564a0453dda97c07daeac67c3d8d7)-compatible
distributed actors. It has three dependencies:
- serde
- serde_json
- serde_repr
2024-06-05 23:36:40 +00:00
For a simple example, see the [echo](https://git.kittencollective.com/nebkor/nebkor-maelstrom/src/branch/main/examples/echo.rs) example:
2024-06-05 00:27:52 +00:00
``` rust
use nebkor_maelstrom::{Body, Message, Node, Runner};
struct Echo;
impl Node for Echo {
fn handle(&mut self, runner: &Runner, msg: Message) {
let typ = &msg.body.typ;
if typ.as_str() == "echo" {
let body = Body::from_type("echo_ok").with_payload(msg.body.payload.clone());
runner.reply(&msg, body);
}
}
}
fn main() {
let node = Echo;
let runner = Runner::new(node);
runner.run(None);
}
```
2024-06-05 23:36:40 +00:00
For a slightly more complicated example, check out the
[broadcast](https://git.kittencollective.com/nebkor/nebkor-maelstrom/src/branch/main/examples/broadcast.rs)
example, which passes the [single-node](https://fly.io/dist-sys/3a/) challenge, but utterly fails
even the friendliest (eg, no partitions or lag) multi-node challenge, so hopefully is not giving too
much away.
## Features
- no async
- minimal boilerplate
- working RPC calls (allowing the main thread to call out to other nodes and receive a reply while
already handling a message)
- proxies for the [Maelstrom KV
services](https://github.com/jepsen-io/maelstrom/blob/main/doc/services.md) that use the RPC
mechanism to provide `read`, `write`, and `cas` operations, and return
`Result<Option<serde_json::Value>, ErrorCode>`s to the caller
2024-06-05 00:27:52 +00:00
## How to use
Create a struct and implement `nebkor_maelstrom::Node` for it, which involves a single method,
`handle(&mut self, &Runner, Message)`. This method is passed a `Runner` which contains methods like
`send`, `reply`, and `rpc`.
In your main function, instantiate that struct and pass that into `Runner::new()` to get a
2024-06-05 23:36:40 +00:00
Runner. The `run()` method takes an optional callback that will be run when the `init` Message is
2024-06-05 00:27:52 +00:00
received; see the
2024-06-05 23:36:40 +00:00
[broadcast](https://git.kittencollective.com/nebkor/nebkor-maelstrom/src/commit/c45b179de45ba7e03a884d6c7cdb4c1c2625ae20/examples/broadcast.rs#L8-L20)
example, where it spawns a thread from the callback to send periodic messages to the node.
2024-06-05 00:27:52 +00:00
## Design considerations
I wanted the client code to be as simple as possible, with the least amount of boilerplate. Using
`&mut self` as the receiver for the `handle()` method lets you easily mutate state in your node if
you need to, without the ceremony of `Rc<Mutex<>>` and the like. Eschewing `async` results in an
order of magnitude fewer dependencies, and the entire workspace (crate and clients) can be compiled
in a couple seconds.
2024-06-05 23:36:40 +00:00
It also assumes that some things are infallible. For example, there's liberal `unwrap()`ing when
calling `send()` or `recv()` on MPSC channels, because those kinds of errors are not part of the
Maelstrom protocol; this crate is not a general-purpose network client crate for the real
world. Likewise `stdin` and `stdout` are always assumed to be available and reliable; those two
channels are the physical layer for connecting a node to the Maelstrom router, and failures there
are out of scope for Gossip Glomers.
A final consideration is understandability of the crate itself; you should not have a hard time
diving into its source from your IDE or browser.
2024-06-05 00:27:52 +00:00
## Acknowledgments
I straight-up stole the design of the IO/network system from
[Maelbreaker](https://github.com/rafibayer/maelbreaker/), which allowed me to get a working RPC
2024-06-05 23:36:40 +00:00
call. Thanks! And thanks to Nicole for nudging me to publish this.