erl
1 Generic Server
gen-server%
init
handle-call
handle-cast
handle-info
terminate
ok
reply
noreply
gen-server:  start
gen-server:  call
gen-server:  cast!
gen-server:  stop
2 Process Registration
register
unregister
whereis
2.1 Gen-Server Example
3 Supervisor
supervisor%
supervisor:  start
supervisor:  stop
supervisor:  which-children
supervisor:  start-child
child-spec
3.1 Supervisor Example
9.0

erl🔗ℹ

dannypsnl

 (require erl) package: erl

Erlang-style genserver abstractions for Racket.

1 Generic Server🔗ℹ

The generic server (gen-server) pattern provides a standardized way to implement server processes with synchronous and asynchronous messaging.

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.

class

gen-server% : class?

  superclass: object%

Base class for implementing genservers. Subclasses must override init and typically override handle-call and handle-cast to handle messages.

method

(send a-gen-server init args)  any/c

  args : any/c
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.

method

(send a-gen-server terminate reason state)  void?

  reason : any/c
  state : any/c
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.

method

(send a-gen-server ok state)  any/c

  state : any/c
Helper method to initialize or continue the server loop with the given state. Used in init to return the initial state.

method

(send a-gen-server reply response state)  any/c

  response : any/c
  state : any/c
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.

method

(send a-gen-server noreply state)  any/c

  state : any/c
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
Starts a generic server instance.

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
Sends a synchronous message to the server and waits for a response.

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
Sends an asynchronous message to the server without waiting for a response.

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
Stops a running server.

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

(register id server)  void?

  id : symbol?
  server : (is-a?/c gen-server%)
Registers a server with the given name in the global registry.

id is the symbolic name to register. server is the server instance to associate with the name.

procedure

(unregister id)  void?

  id : symbol?
Removes a server from the global registry.

id is the symbolic name to unregister.

procedure

(whereis id)  (or/c (is-a?/c gen-server%) #f)

  id : symbol?
Looks up a registered server by name.

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.

class

supervisor% : class?

  superclass: gen-server%

Base class for implementing supervisors. Extends gen-server% to provide child process management with automatic restart capabilities.
The supervisor monitors all children and restarts them based on their restart policy.

procedure

(supervisor:start impl child-specs)  (is-a?/c supervisor%)

  impl : (is-a?/c supervisor%)
  child-specs : (listof child-spec?)
Starts a supervisor with the given child specifications.

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
Stops a supervisor and all its children.

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%)
Returns a list of child IDs managed by the 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?
Dynamically starts a new child under the supervisor.

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
Creates a child specification for use with a supervisor.

id is a unique symbolic identifier for the child. start-thunk is a thunk that starts and returns a running server. restart specifies the restart strategy:
  • '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)