erl
| (require erl) | package: erl |
Erlang-style genserver abstractions for Racket.
1 Generic Server
Warning: Don’t call init, handle-call, handle-cast, handle-info, and terminate directly. Override them in your implementation and use gen-server:start, gen-server:call, etc. to interact with servers. Don’t override ok, reply, and noreply; inherit them using inherit instead.
|
superclass: object% |
Initialization callback that must be overridden. Called when the server starts.args is the initialization argument passed to gen-server:start.
Should return the initial state by calling (ok state).
method
(send a-gen-server handle-call msg from state) → any/c msg : any/c from : async-channel? state : any/c Synchronous message handler that must be overridden. Handles messages sent via gen-server:call.msg is the message received. from is a reply channel for sending the response. state is the current server state.
Should return by calling (reply response new-state) to send a response and continue with updated state.
method
(send a-gen-server handle-cast msg state) → any/c
msg : any/c state : any/c Asynchronous message handler that must be overridden. Handles messages sent via gen-server:cast!.msg is the message received. state is the current server state.
Should return by calling (noreply new-state) to continue with updated state.
method
(send a-gen-server handle-info msg state) → any/c
msg : any/c state : any/c Handler for info messages. Default implementation calls (noreply state).msg is the info message received. state is the current server state.
Can be overridden to handle internal messages.
Cleanup callback invoked when the server is stopping. Default implementation does nothing.reason is the termination reason (e.g., 'normal). state is the final server state.
Override to perform cleanup operations.
Helper method to initialize or continue the server loop with the given state. Used in init to return the initial state.Helper method to send a reply to a synchronous call and continue the server loop.response is the value to send back to the caller. state is the new state to continue with.
Helper method to continue the server loop without sending a reply (used in asynchronous handlers).state is the new state to continue with.
procedure
(gen-server:start impl init-args) → (is-a?/c gen-server%)
impl : (is-a?/c gen-server%) init-args : any/c
impl is an instance of a class derived from gen-server%. init-args is passed to the server’s init method.
Returns the running server instance.
procedure
(gen-server:call server msg) → any/c
server : (or/c (is-a?/c gen-server%) symbol?) msg : any/c
server is the server instance or registered name. msg is the message to send.
Returns the response from the server’s handle-call method.
procedure
(gen-server:cast! server msg) → void?
server : (or/c (is-a?/c gen-server%) symbol?) msg : any/c
server is the server instance or registered name. msg is the message to send.
The message is handled by the server’s handle-cast method.
procedure
(gen-server:stop server [reason]) → void?
server : (or/c (is-a?/c gen-server%) symbol?) reason : any/c = 'normal
server is the server instance or registered name. reason is the termination reason; defaults to 'normal.
Triggers the server’s terminate callback before shutting down.
2 Process Registration
The process registry allows servers to be registered with symbolic names, enabling location-transparent messaging.
procedure
id : symbol? server : (is-a?/c gen-server%)
id is the symbolic name to register. server is the server instance to associate with the name.
procedure
(unregister id) → void?
id : symbol?
id is the symbolic name to unregister.
procedure
(whereis id) → (or/c (is-a?/c gen-server%) #f)
id : symbol?
id is the symbolic name to look up.
Returns the server instance if found, or #f if not registered.
Once a server is registered, gen-server:call, gen-server:cast!, and gen-server:stop can accept either the server instance or its registered name:
(define counter (gen-server:start (new my-counter%) '(0))) (register 'my-counter counter) ; Both of these work: (gen-server:call counter 'get) (gen-server:call 'my-counter 'get)
2.1 Gen-Server Example
Here’s a complete example of implementing a counter server:
(struct counter-state (value)) (define my-counter% (class gen-server% (super-new) (inherit ok noreply reply) (define/override (init args) (match-define (list n) args) (ok (counter-state n))) (define/override (handle-call msg from state) (match msg ['get (reply from (counter-state-value state) state)] [(list 'add n) (define new-val (+ (counter-state-value state) n)) (reply from new-val (counter-state new-val))])) (define/override (handle-cast msg state) (match msg ['inc (define new-val (add1 (counter-state-value state))) (noreply (counter-state new-val))] [_ (noreply state)])))) ; Start the counter with initial value 1 (define counter (gen-server:start (new my-counter%) '(1))) ; Synchronous calls that wait for response (gen-server:call counter 'get) ; => 1 (gen-server:call counter '(add 5)) ; => 6 ; Asynchronous cast that doesn't wait (gen-server:cast! counter 'inc) ; Stop the server (gen-server:stop counter)
3 Supervisor
Supervisors manage a set of child processes, automatically restarting them according to a specified restart strategy when they crash.
|
superclass: gen-server% |
procedure
(supervisor:start impl child-specs) → (is-a?/c supervisor%)
impl : (is-a?/c supervisor%) child-specs : (listof child-spec?)
impl is an instance of supervisor%. child-specs is a list of child specifications created with child-spec.
Returns the running supervisor instance.
procedure
(supervisor:stop supervisor [reason]) → void?
supervisor : (is-a?/c supervisor%) reason : any/c = 'normal
supervisor is the supervisor instance to stop. reason is the termination reason; defaults to 'normal.
All children are stopped with reason 'shutdown before the supervisor terminates.
procedure
(supervisor:which-children supervisor) → (listof symbol?)
supervisor : (is-a?/c supervisor%)
supervisor is the supervisor instance.
procedure
(supervisor:start-child supervisor spec)
→ (or/c (list/c 'ok symbol?) (list/c 'error 'already-started)) supervisor : (is-a?/c supervisor%) spec : child-spec?
supervisor is the supervisor instance. spec is a child specification created with child-spec.
Returns '(ok id) on success where id is the child’s identifier, or '(error already-started) if a child with the same ID already exists.
The newly started child is automatically registered and monitored by the supervisor.
procedure
(child-spec #:id id #:start start-thunk [ #:restart restart]) → child-spec? id : symbol? start-thunk : (-> (is-a?/c gen-server%)) restart : (or/c 'permanent 'temporary 'transient) = 'permanent
'permanent — Always restart the child when it terminates (default)
'temporary — Never restart the child
'transient — Restart only on abnormal termination
3.1 Supervisor Example
Here’s an example of using a supervisor to manage counter servers:
(define sup (supervisor:start (new supervisor%) (list (child-spec #:id 'counter1 #:start (lambda () (gen-server:start (new my-counter%) '(10)))) (child-spec #:id 'counter2 #:start (lambda () (gen-server:start (new my-counter%) '(20))) #:restart 'transient)))) ; Children are automatically registered by their ID (gen-server:call 'counter1 'get) ; => 10 (gen-server:call 'counter2 'get) ; => 20 ; List managed children (supervisor:which-children sup) ; => '(counter1 counter2) ; If counter1 crashes, it will be automatically restarted ; Dynamically add a new child at runtime (supervisor:start-child sup (child-spec #:id 'counter3 #:start (lambda () (gen-server:start (new my-counter%) '(30))))) ; => '(ok counter3) (gen-server:call 'counter3 'get) ; => 30 ; Stop the supervisor and all children (supervisor:stop sup)