A Racket client for the Maelstrom distributed systems test harness
(require maelstrom) | package: maelstrom |
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:
1 Introduction
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:
(require maelstrom maelstrom/message) (define node (make-node)) (add-handler node "echo" (lambda (req) (respond req (hash 'echo (message-ref req 'echo))))) (module+ main (run node))
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 |
2 Creating a node
3 Adding handlers
procedure
(add-handler node type proc) → void
node : node? type : string? proc : (message? . -> . any/c)
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.
4 Running a node
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!
5 Sending messages
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.
6 Other node operations
procedure
(known-peers n) → (listof string?)
n : node?
7 The Message API
(require maelstrom/message) | package: maelstrom |
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 msg key) → 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?
(add-handler node "my-message" (lambda (req) (send "n3" (make-message (hash 'type "another-type" 'field1 58 'field2 (list "multiple" "items"))))))
8 Access to key-value stores
(require maelstrom/kv) | package: maelstrom |
Maelstrom provides a few different key-value (KV) stores that your nodes can use. These are exposed via the kv module.
(require maelstrom/kv) ; Read my-key from the sequentially consistent store. ; Return 0 if the key does not exist in the store. (kv-read seq-kv "my-key" 0)
procedure
(kv-cas kv k from to [ #:create-if-missing? create-if-missing?]) → bool? kv : kv? k : string? from : jsexpr? to : jsexpr? create-if-missing? : bool? = #f
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.
9 Logging
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.