4 releases (2 stable)
1.0.1 | Nov 10, 2024 |
---|---|
1.0.0 | Jun 10, 2024 |
0.0.2 | Jun 5, 2024 |
0.0.1 | Jun 5, 2024 |
#249 in Asynchronous
103 downloads per month
20KB
355 lines
A synchronous and simple Maelstrom crate
nebkor-maelstreom
is a lean and simple synchronous library for writing
Maelstrom-compatible
distributed actors. It has three dependencies:
- serde
- serde_json
- serde_repr
For a simple example, see the echo example:
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);
}
For a slightly more complicated example, check out the broadcast example, which passes the single-node 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 that use the RPC
mechanism to provide
read
,write
, andcas
operations, and returnResult<Option<serde_json::Value>, ErrorCode>
s to the caller
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
Runner. The run()
method takes an optional callback that will be run when the init
Message is
received; see the
broadcast
example, where it spawns a thread from the callback to send periodic messages to the node.
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.
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.
Acknowledgments
I straight-up stole the design of the IO/network system from Maelbreaker, which allowed me to get a working RPC call. Thanks! And thanks to Nicole for nudging me to publish this.
Dependencies
~0.7–1.6MB
~34K SLoC