Nikhil Marathe <racket-packages@me.nikhilism.com>
Maelstrom is a workbench for learning distributed systems by writing your own. This is a library to allow implementing a Maelstrom server node in Racket, similar to existing implementations in other languages.
Familiarity with Maelstrom terminology is assumed. The following resources are handy:
The typical implementation of a node will involve:
Creating a node
Adding handler functions
In a main submodule, running the node.
For example, Challenge #1 Echo would be written as:
maelstrom/message)"echo"
built into a binary using:
raco exe echo.rkt
and then run as:
maelstrom test -w echo --bin echo --node-count 1 --time-limit 10
The handler receives a message (see the The Message API). Its return value is ignored. Each handler invocation runs in its own thread to not block other handlers. Errors are logged to the maelstrom logger.
run will only return once current-input-port is closed and all handlers have returned. To forcibly shut it down, you can spawn it in a separate thread and use kill-thread .
It is undefined behavior to call run on the same node more than once!
This library uses Racket’s parameters and dynamic scoping to make responding to messages more convenient. The handler code does not need to refer to the node? . Instead handlers are invoked with the dynamic context suitably modified such that all these functions no which node to act on.
Any sub-threads spawned by handlers will inherit the correct bindings automatically. See this solution to challenge #3d that spawns a spawn-minder thread within the topology handler, and the spawn-minder thread is still able to use rpc .
request will usually be the message received by the handler procedure. Any additional body parameters can be supplied in the additional-body hash. See the example in Introduction.
Similar to add-handler handlers, proc is called in a new thread .
Note that proc is stored until a response is received. This means if peers never respond to messages, memory leaks are possible. There is no solution to this right now, as Maelstrom is not for building production services.
procedure
( known-peers n)→(listof string? )
n:node?
Messages are simply jsexpr? s with some additional semantics required by the Maelstrom protocol. Because of this, all keys are always Racket symbols.
procedure
( message-ref msgkey)→jsexpr?
msg:message?key:symbol?
procedure
( message-body msg)→jsexpr?
msg:message?
procedure
( message-sender msg)→jsexpr?
msg:message?
procedure
( message-id msg)→jsexpr?
msg:message?
procedure
( message-type msg)→jsexpr?
msg:message?
procedure
( make-message body)→(hash/c symbol? jsexpr? )
body:jsexpr?
node"my-message"'field158
Maelstrom provides a few different key-value (KV) stores that your nodes can use. These are exposed via the kv module.
;Read my-key from the sequentially consistent store.;Return 0 if the key does not exist in the store.(kv-readseq-kv"my-key"0)
If create-if-missing? is #t, then the key is created if it does not exist. If create-if-missing? is #f, then an error will be raised.
The library logs to the maelstrom logger. As an example, to see debug messages, you can run the program with the PLTSTDERR="debug@maelstrom" environment variable.