Rakka:   Actor Model for Racket
1 Quick Start
1.1 Proof of Life
1.2 Your First Gen  Server
2 Gen  Server In Depth
2.1 The Callbacks
2.2 Calls vs Casts
2.3 The handle-info Callback
2.4 Priority Message Handling
3 Tutorial:   Key-Value Store
4 Naming and Discovery
5 When Servers Crash
5.1 Links:   Shared Fate
5.2 Monitors:   One-Way Watching
5.3 When to Use Which
6 Supervisors:   Automatic Recovery
6.1 Basic Supervision
6.2 Restart Strategies
6.3 Restart Types
6.4 Child Specs
6.5 Dynamic Children with simple-one-for-one
6.6 Managing Children Dynamically
7 Applications:   Structuring Real Programs
8 Process Dictionary
9 Under the Hood:   Raw Actors
9.1 Spawn and Messages
9.2 The Actor Loop Pattern
9.3 When to Use Raw Actors
10 Green Threads and Scheduling
10.1 Cooperative vs Preemptive
10.2 Threaded Mode
10.3 Performance Comparison
11 Runtime Lifecycle
11.1 Starting and Stopping
11.2 Convenient Wrapper
11.3 Checking Runtime State
12 Gen  Statem:   State Machines
12.1 Postponing Events
13 Distribution:   Actors Across Machines
13.1 Starting Nodes
13.2 Connecting and Messaging
13.3 Global Registry
13.4 Remote Spawning
14 Hot Code Reload
14.1 Basic Reload
14.2 State Migration
14.3 Tree-Wide Upgrades
15 Operational Features
15.1 Logging
15.2 Metrics
15.3 Process Introspection
15.4 Graceful Shutdown
16 Process Groups
17 Pub  Sub:   Topic-Based Messaging
18 Tables:   Shared State with Isolation
18.1 Your First Table
18.2 Why Tables Exist
18.3 Copy Semantics:   The Key Insight
18.4 Named Tables:   Global Access
18.5 Ownership and Lifecycle
18.6 Common Patterns
19 Process Introspection
processes
process-count
process-info
20 API Reference
20.1 Gen  Server
gen-server-start
gen-server-start-link
gen-server-call
gen-server-cast!
gen-server-stop
ok
stop-init
reply
noreply
stop
20.2 Supervisor
supervisor-start-link
child-spec*
strategy?
supervisor-which-children
supervisor-count-children
supervisor-get-childspec
supervisor-start-child
supervisor-terminate-child
supervisor-restart-child
supervisor-delete-child
20.3 Process Primitives
spawn
spawn-link
self
send!
receive
receive/  timeout
receive-match
process?
process-alive?
20.4 Registry
register!
unregister!
whereis
registered
20.5 Links and Monitors
link!
unlink!
monitor!
demonitor!
set-trap-exit!
exit-signal
down-signal
20.6 Process Dictionary
put
get
erase
get-keys
20.7 Runtime
start-runtime!
stop-runtime!
with-runtime
runtime-started?
scheduler-count
runtime-mode
runtime-mode-green?
scheduler-start!
scheduler-stop!
scheduler-run-until-done!
scheduler-running?
20.8 Gen  Statem
gen-statem-start
gen-statem-start-link
gen-statem-call
gen-statem-cast!
gen-statem-stop
postpone-action
postpone
20.9 Distribution
start-epmd!
stop-epmd!
start-node!
stop-node!
connect-node!
nodes
global-register!
global-whereis
global-send!
send-remote!
dist-link!
dist-monitor!
20.10 Hot Reload
version-info
hot-reload!
trigger-code-change!
upgrade-process!
upgrade-supervisor-tree!
20.11 Testing Utilities
wait-until
check-process-exits
with-fresh-registry
20.12 Process Groups
pg-join
pg-leave
pg-members
pg-which-groups
pg-monitor
pg-demonitor
pg-monitor-ref?
20.13 Pub  Sub
pubsub-start
pubsub-stop
pubsub-subscribe
pubsub-unsubscribe
pubsub-broadcast
pubsub-broadcast-from
pubsub-subscribers
20.14 Tables
table-new
table?
table-alive?
table-whereis
table-insert!
table-lookup
table-member?
table-remove!
table-insert-new!
table-update!
table-delete!
table-keys
table-count
table-to-list
table-clear!
table-info
21 Tracing
21.1 Trace a Process in 30 Seconds
21.2 Understanding Trace Flags
21.3 Propagating Traces Through the System
21.4 Tracer Backends
21.5 Call Tracing for Gen  Server
21.6 API Reference
21.6.1 Tracer Control
tracer
stop-tracer
get-tracer
21.6.2 Setting Trace Flags
trace
trace-flags
clear-trace
21.6.3 Call Patterns
trace-pattern
clear-trace-pattern
clear-trace-patterns
21.6.4 Tracer Backends
make-tty-tracer
make-channel-tracer
make-logger-tracer
make-file-tracer
21.6.5 Trace Events
trace-event
22 Pattern-Matching Behavior Languages
22.1 #lang rakka/  gen-server
22.2 #lang rakka/  gen-statem
23 License
9.1.0.1

Rakka: Actor Model for Racket🔗ℹ

Aldric Giacomoni

 (require rakka)

Erlang/OTP-style actors for Racket 9. Isolated processes, message passing, supervision trees, and hot code reload.

For you if: You want fault-tolerant concurrency without shared-state threading.

    1 Quick Start

      1.1 Proof of Life

      1.2 Your First GenServer

    2 GenServer In Depth

      2.1 The Callbacks

      2.2 Calls vs Casts

      2.3 The handle-info Callback

      2.4 Priority Message Handling

    3 Tutorial: Key-Value Store

    4 Naming and Discovery

    5 When Servers Crash

      5.1 Links: Shared Fate

      5.2 Monitors: One-Way Watching

      5.3 When to Use Which

    6 Supervisors: Automatic Recovery

      6.1 Basic Supervision

      6.2 Restart Strategies

      6.3 Restart Types

      6.4 Child Specs

      6.5 Dynamic Children with simple-one-for-one

      6.6 Managing Children Dynamically

    7 Applications: Structuring Real Programs

    8 Process Dictionary

    9 Under the Hood: Raw Actors

      9.1 Spawn and Messages

      9.2 The Actor Loop Pattern

      9.3 When to Use Raw Actors

    10 Green Threads and Scheduling

      10.1 Cooperative vs Preemptive

      10.2 Threaded Mode

      10.3 Performance Comparison

    11 Runtime Lifecycle

      11.1 Starting and Stopping

      11.2 Convenient Wrapper

      11.3 Checking Runtime State

    12 GenStatem: State Machines

      12.1 Postponing Events

    13 Distribution: Actors Across Machines

      13.1 Starting Nodes

      13.2 Connecting and Messaging

      13.3 Global Registry

      13.4 Remote Spawning

    14 Hot Code Reload

      14.1 Basic Reload

      14.2 State Migration

      14.3 Tree-Wide Upgrades

    15 Operational Features

      15.1 Logging

      15.2 Metrics

      15.3 Process Introspection

      15.4 Graceful Shutdown

    16 Process Groups

    17 PubSub: Topic-Based Messaging

    18 Tables: Shared State with Isolation

      18.1 Your First Table

      18.2 Why Tables Exist

      18.3 Copy Semantics: The Key Insight

      18.4 Named Tables: Global Access

      18.5 Ownership and Lifecycle

      18.6 Common Patterns

    19 Process Introspection

    20 API Reference

      20.1 GenServer

      20.2 Supervisor

      20.3 Process Primitives

      20.4 Registry

      20.5 Links and Monitors

      20.6 Process Dictionary

      20.7 Runtime

      20.8 GenStatem

      20.9 Distribution

      20.10 Hot Reload

      20.11 Testing Utilities

      20.12 Process Groups

      20.13 PubSub

      20.14 Tables

    21 Tracing

      21.1 Trace a Process in 30 Seconds

      21.2 Understanding Trace Flags

      21.3 Propagating Traces Through the System

      21.4 Tracer Backends

      21.5 Call Tracing for GenServer

      21.6 API Reference

        21.6.1 Tracer Control

        21.6.2 Setting Trace Flags

        21.6.3 Call Patterns

        21.6.4 Tracer Backends

        21.6.5 Trace Events

    22 Pattern-Matching Behavior Languages

      22.1 #lang rakka/gen-server

      22.2 #lang rakka/gen-statem

    23 License

1 Quick Start🔗ℹ

1.1 Proof of Life🔗ℹ

Run an actor in 3 lines:

#lang rakka
(require rakka)
 
(spawn (lambda () (displayln "I'm an actor!")))

You just ran an isolated process. Now let’s build something useful.

1.2 Your First GenServer🔗ℹ

One-shot actors aren’t practical. For stateful services, use GenServer:

#lang racket/base
(require rakka racket/match)
 
(struct counter ()
  #:methods gen:server
  [(define (init self args)
     (ok 0))  ; start at 0
 
   (define (handle-call self msg state from)
     (match msg
       ['get (reply state state)]
       ['inc (reply 'ok (add1 state))]))
 
   (define (handle-cast self msg state)
     (noreply state))
 
   (define (handle-info self msg state)
     (noreply state))
 
   (define (terminate self reason state)
     (void))])
 
;; Use it
(define c (gen-server-start (counter) #f))
(gen-server-call c 'inc)  ; => 'ok
(gen-server-call c 'inc)  ; => 'ok
(gen-server-call c 'get)  ; => 2
(gen-server-stop c)

What you get for free:
  • State management (no manual loop)

  • Request/reply handled for you

  • Timeouts, shutdown, error handling built in

2 GenServer In Depth🔗ℹ

GenServer is the workhorse of Rakka. Most actors you write will be GenServers.

2.1 The Callbacks🔗ℹ

You implement these, Rakka calls them:

Callback

  

When Called

  

You Return

init

  

Server starts

  

(ok state) or (stop-init reason)

handle-call

  

Sync request (caller waits)

  

(reply value new-state)

handle-cast

  

Async message (fire-and-forget)

  

(noreply new-state)

handle-info

  

Any other message

  

(noreply new-state)

terminate

  

Server stopping

  

(void)

2.2 Calls vs Casts🔗ℹ

Calls are synchronous—the caller blocks until you reply:

;; Client side
(gen-server-call my-server 'get-data)  ; blocks
 
;; Server side (handle-call)
(reply the-data new-state)  ; unblocks the caller

Casts are asynchronous—fire and forget:

;; Client side
(gen-server-cast! my-server 'reset)  ; returns immediately
 
;; Server side (handle-cast)
(noreply new-state)  ; no reply expected

Use calls when you need a response. Use casts when you don’t.

2.3 The handle-info Callback🔗ℹ

Messages that aren’t calls or casts go to handle-info. This includes:
  • Messages sent with plain send!

  • Exit signals from linked processes (if trapping exits)

  • Monitor down signals

  • Timer messages

2.4 Priority Message Handling🔗ℹ

Rakka processes system messages (exit signals, down signals, timeout signals) with priority over regular user messages. This means:

  • Supervisor signals are never starved by message floods

  • Exit signals from linked processes are processed promptly

  • Monitor notifications arrive reliably even under load

This matches Erlang’s behavior and is transparent to your code—you don’t need to do anything special. System messages simply get handled first.

3 Tutorial: Key-Value Store🔗ℹ

Let’s build a practical GenServer—an in-memory key-value store.

#lang racket/base
(require rakka racket/match)
 
(struct kv-store ()
  #:methods gen:server
  [(define (init self args)
     (ok (make-hash)))  ; state is a hash table
 
   (define (handle-call self msg state from)
     (match msg
       [(list 'get key)
        (reply (hash-ref state key #f) state)]
       [(list 'keys)
        (reply (hash-keys state) state)]))
 
   (define (handle-cast self msg state)
     (match msg
       [(list 'put key value)
        (hash-set! state key value)
        (noreply state)]
       [(list 'delete key)
        (hash-remove! state key)
        (noreply state)]
       ['clear
        (noreply (make-hash))]))
 
   (define (handle-info self msg state)
     (noreply state))
 
   (define (terminate self reason state)
     (void))])
 
;; Try it
(define store (gen-server-start (kv-store) #f))
 
(gen-server-cast! store '(put name "Alice"))
(gen-server-cast! store '(put age 30))
 
(gen-server-call store '(get name))   ; => "Alice"
(gen-server-call store '(keys))       ; => '(name age)
 
(gen-server-cast! store '(delete age))
(gen-server-call store '(keys))       ; => '(name)
 
(gen-server-stop store)

Notice the pattern:
  • Reads use handle-call (caller needs the data)

  • Writes use handle-cast (no response needed)

4 Naming and Discovery🔗ℹ

Instead of passing PIDs around, register names:

;; Start with a name
(gen-server-start (kv-store) #f #:name 'cache)
 
;; Now anyone can find it
(gen-server-call 'cache '(get user-123))

Or register after the fact:

(define pid (gen-server-start (kv-store) #f))
(register! 'cache pid)
 
;; Look up by name
(define cache-pid (whereis 'cache))

whereis returns #f if the name isn’t registered.

5 When Servers Crash🔗ℹ

Actors crash. Rakka gives you tools to respond.

5.1 Links: Shared Fate🔗ℹ

Linked processes die together. If one crashes, the other gets an exit signal:

;; Link when starting
(gen-server-start-link (my-server) args)
 
;; Or link explicitly
(link! some-pid)

By default, exit signals kill the receiver. To catch them instead:

(set-trap-exit! (self) #t)
 
;; Now exits arrive in handle-info
(define (handle-info self msg state)
  (match msg
    [(exit-signal from reason)
     (printf "~a died: ~a\n" from reason)
     (noreply state)]
    [_ (noreply state)]))

5.2 Monitors: One-Way Watching🔗ℹ

Monitors let you watch without shared fate. If the target dies, you get notified but don’t die:

(define ref (monitor! some-pid))
 
;; In handle-info:
(match msg
  [(down-signal ref pid reason)
   (printf "~a died: ~a\n" pid reason)
   (noreply state)])

Use demonitor! to stop watching.

5.3 When to Use Which🔗ℹ

Mechanism

  

Use When

Link

  

Processes should live and die together (e.g., worker and its coordinator)

Monitor

  

You need to know about death but survive it (e.g., connection pool tracking clients)

6 Supervisors: Automatic Recovery🔗ℹ

Instead of handling every possible error, let processes crash and restart clean. This is the "let it crash" philosophy.

6.1 Basic Supervision🔗ℹ

(define sup-pid
  (supervisor-start-link
   'one-for-one    ; restart strategy
   3               ; max 3 restarts
   5               ; in 5 seconds
   (list
    (child-spec* #:id 'cache
                 #:start (lambda () (gen-server-start-link (kv-store) #f))
                 #:restart 'permanent)
    (child-spec* #:id 'worker
                 #:start (lambda () (gen-server-start-link (worker) #f))
                 #:restart 'permanent))))

If a child crashes, the supervisor restarts it. If it crashes too often (3 times in 5 seconds), the supervisor gives up and terminates.

6.2 Restart Strategies🔗ℹ

Strategy

  

Behavior

'one-for-one

  

Restart only the crashed child

'one-for-all

  

Restart all children if one crashes

'rest-for-one

  

Restart crashed child and those started after it

'simple-one-for-one

  

Dynamic children, all same spec

6.3 Restart Types🔗ℹ

Type

  

Behavior

'permanent

  

Always restart

'temporary

  

Never restart

'transient

  

Restart only on abnormal exit

6.4 Child Specs🔗ℹ

(child-spec* #:id 'my-worker           ; unique identifier
             #:start start-fn          ; (-> pid?) returns linked pid
             #:restart 'permanent      ; restart policy
             #:shutdown 5000)          ; ms to wait for graceful stop

6.5 Dynamic Children with simple-one-for-one🔗ℹ

The 'simple-one-for-one strategy is for dynamic worker pools where all children use the same specification. Unlike other strategies:

  • No children at startup: The supervisor starts empty

  • Single template spec: The child-spec list must have exactly one entry

  • Dynamic creation: Children are added via supervisor-start-child

  • Arguments passed through: The start-child argument goes to the start function

;; Worker that receives its name at startup
(define (make-worker name)
  (lambda ()
    (spawn-link (lambda ()
      (printf "Worker ~a started\n" name)
      (let loop ()
        (receive)
        (loop))))))
 
;; Start supervisor with template spec
(define sup
  (supervisor-start-link
   'simple-one-for-one  ; strategy
   3 5                   ; max restarts
   (list
    (child-spec* #:id 'worker-template
                 #:start (lambda (name) ((make-worker name)))
                 #:restart 'transient))))
 
;; Dynamically add workers
(supervisor-start-child sup '(worker-1))
(supervisor-start-child sup '(worker-2))
(supervisor-start-child sup '(worker-3))
 
;; Check children
(supervisor-count-children sup)
;; => ((specs . 1) (active . 3) (supervisors . 0) (workers . 3))

6.6 Managing Children Dynamically🔗ℹ

Beyond 'simple-one-for-one, any supervisor can manage children dynamically:

;; Terminate a running child (by id)
(supervisor-terminate-child sup 'my-worker)
 
;; Restart a terminated child
(supervisor-restart-child sup 'my-worker)  ; must be terminated first
 
;; Permanently remove a child spec (must terminate first)
(supervisor-terminate-child sup 'my-worker)
(supervisor-delete-child sup 'my-worker)

7 Applications: Structuring Real Programs🔗ℹ

An Application is your top-level entry point. It starts your supervision tree.

(struct my-app ()
  #:methods gen:application
  [(define (start self args)
     (define sup (supervisor-start-link
                   'one-for-one 3 5
                   (list
                     (child-spec* #:id 'cache
                                  #:start cache-start
                                  #:restart 'permanent)
                     (child-spec* #:id 'web
                                  #:start web-start
                                  #:restart 'permanent))))
     (app-ok sup))
 
   (define (stop self state)
     (void))])
 
;; Start the application
(application-start (my-app) '())

This gives you:
  • A supervised tree of processes

  • Automatic restart on failures

  • Clean shutdown when the app stops

8 Process Dictionary🔗ℹ

Each process has a private mutable dictionary for storing per-process state. Like Erlang’s process dictionary, this provides fast key-value storage without message passing.

When to use: Caching, counters, or state that doesn’t belong in your GenServer/GenStatem state. Use sparingly—explicit state is usually clearer.

(spawn (lambda ()
  ;; Store a value
  (put 'counter 0)
 
  ;; Get a value (returns 'undefined if not found)
  (define count (get 'counter))  ; => 0
 
  ;; Update and get old value
  (define old (put 'counter (add1 count)))  ; => 0
 
  ;; List all keys
  (get-keys)  ; => '(counter)
 
  ;; Remove a key
  (erase 'counter)  ; => 1 (returns old value)
  (get 'counter)))  ; => 'undefined

Note: The process dictionary is local to each process. It cannot be accessed from other processes.

9 Under the Hood: Raw Actors🔗ℹ

GenServer is built on lower-level primitives. You usually don’t need these, but understanding them helps.

9.1 Spawn and Messages🔗ℹ

;; Spawn a process
(define pid (spawn (lambda ()
                     (displayln "Started!")
                     (define msg (receive))  ; block for message
                     (printf "Got: ~a\n" msg))))
 
;; Send it a message
(send! pid 'hello)

9.2 The Actor Loop Pattern🔗ℹ

Raw actors typically loop, handling messages:

(define (counter-actor initial)
  (let loop ([count initial])
    (match (receive)
      ['inc (loop (add1 count))]
      [(list 'get from)
       (send! from count)
       (loop count)]
      ['stop (void)])))

This is exactly what GenServer abstracts. Compare:
  • Raw: You write the loop, handle replies manually

  • GenServer: You implement callbacks, infrastructure handles the rest

9.3 When to Use Raw Actors🔗ℹ

Almost never. Use raw actors only when:
  • You need custom message scheduling (e.g., priority queues)

  • You’re building infrastructure on top of Rakka

  • You’re learning how actors work internally

For application code, use GenServer.

10 Green Threads and Scheduling🔗ℹ

Rakka uses green threads by default—lightweight cooperative scheduling that runs thousands of actors on a single OS thread.

10.1 Cooperative vs Preemptive🔗ℹ

Green threads yield at receive points. But what if an actor computes without receiving? It starves other actors.

Solution: Use #lang rakka for fair scheduling.

#lang rakka  ; <-- enables preemptive yielding
(require rakka)
 
(define (fib n)
  (if (<= n 1) n
      (+ (fib (- n 1)) (fib (- n 2)))))
 
;; Even fib(30) won't starve other actors
(spawn (lambda () (printf "fib(30) = ~a\n" (fib 30))))
(spawn (lambda () (displayln "I run too!")))

#lang rakka counts function calls and yields after ~4000, like Erlang’s reduction counting.

10.2 Threaded Mode🔗ℹ

For CPU-bound parallelism, switch to OS threads:

(runtime-mode 'threaded)
 
(spawn (lambda ()
  ;; This runs on a real OS thread
  (heavy-computation)))

10.3 Performance Comparison🔗ℹ

Operation

  

Green

  

Threaded

Spawn 1000 actors

  

2ms

  

45ms

50k messages

  

32ms

  

5,300ms

Use green mode unless you need CPU parallelism.

11 Runtime Lifecycle🔗ℹ

The Rakka runtime manages schedulers that execute your actors. Before spawning any processes, you must start the runtime.

11.1 Starting and Stopping🔗ℹ

#lang racket/base
(require rakka)
 
;; Start the runtime with default number of schedulers (one per CPU core)
(start-runtime!)
 
;; Spawn actors and do work...
(define pid (spawn (lambda () (displayln "Hello!"))))
 
;; When done, clean up
(stop-runtime!)

By default, start-runtime! creates one scheduler per CPU core. You can override this:

;; Start with exactly 4 schedulers
(start-runtime! #:schedulers 4)

11.2 Convenient Wrapper🔗ℹ

For tests and scripts, with-runtime handles the lifecycle for you:

(with-runtime #:schedulers 2
  (define pid (spawn (lambda () (displayln "Running!"))))
  (sleep 0.1))
;; Runtime automatically stops here

11.3 Checking Runtime State🔗ℹ

(runtime-started?)  ; => #t if runtime is running
(scheduler-count)   ; => number of active schedulers

12 GenStatem: State Machines🔗ℹ

When behavior depends on state, use GenStatem (generic state machine).

(struct traffic-light ()
  #:methods gen:statem
  [(define (statem-init self args)
     (statem-ok 'red 0))  ; state='red, data=cycle-count
 
   (define (callback-mode self) 'handle-event)
 
   (define (handle-event self event-type event state data)
     (match* (event-type (call-event-request event))
       [('call 'next)
        (define new-state
          (match state ['red 'green] ['green 'yellow] ['yellow 'red]))
        (define new-data
          (if (eq? new-state 'red) (add1 data) data))
        (next-state new-state new-data (statem-reply event new-state))]
       [('call 'cycles)
        (keep-state-and-data (statem-reply event data))]))
 
   (define (statem-terminate self reason state data)
     (void))])
 
(define light (gen-statem-start (traffic-light) #f))
(gen-statem-call light 'next)   ; => 'green
(gen-statem-call light 'next)   ; => 'yellow
(gen-statem-call light 'next)   ; => 'red
(gen-statem-call light 'cycles) ; => 1

Use GenStatem when:
  • Valid operations depend on current state

  • You have explicit state transitions

  • State machine is the natural model

For simple stateful services, GenServer is usually clearer.

12.1 Postponing Events🔗ℹ

Sometimes an event arrives in a state that can’t handle it yet. Instead of dropping or erroring, use postpone to queue it for later:

(define (handle-event self event-type event state data)
  (match* (state (call-event-request event))
    ;; In 'waiting state, postpone 'process-me until ready
    [('waiting 'process-me)
     (keep-state-and-data (list postpone))]
 
    ;; Handle 'become-ready to transition
    [('waiting 'become-ready)
     (next-state 'ready data '())]
 
    ;; In 'ready state, actually process it
    [('ready 'process-me)
     (keep-state-and-data (list (statem-reply event 'processed)))]))

When you include postpone in the actions list:
  • The current event is saved in a queue

  • When the state machine receives its next event, postponed events are tried first

  • Postponed events stay queued until the machine enters a state that handles them

This is useful for connection state machines, protocol handlers, and any situation where events may arrive out of order.

13 Distribution: Actors Across Machines🔗ℹ

Rakka supports Erlang-style distribution with EPMD protocol compatibility.

13.1 Starting Nodes🔗ℹ

;; Start EPMD (once per machine)
(define epmd (start-epmd! 4369))
 
;; Start this node
(start-node! "mynode@localhost" 9001
             #:cookie "secret-cookie")

The cookie must match between nodes.

13.2 Connecting and Messaging🔗ℹ

;; On node2
(start-node! "node2@localhost" 9002
             #:cookie "secret-cookie")
 
(connect-node! "mynode@localhost")
 
;; Send to a globally registered process
(global-send! 'calculator '(add 1 2))

13.3 Global Registry🔗ℹ

;; Register globally (visible on all nodes)
(global-register! 'calculator my-pid)
 
;; Look up from any node
(define calc (global-whereis 'calculator))

13.4 Remote Spawning🔗ℹ

;; On remote node, register a spawn handler
(register-spawn-handler! 'my-worker
  (lambda (args) (spawn (lambda () (worker-loop args)))))
 
;; From any node, spawn remotely
(define remote-pid
  (request-spawn! "node2@localhost" 'my-worker '(args)))

14 Hot Code Reload🔗ℹ

Update running code without stopping your system.

14.1 Basic Reload🔗ℹ

(hot-reload! "my-server.rkt")

14.2 State Migration🔗ℹ

When code changes, the code-change callback transforms old state to new:

(struct my-server ()
  #:methods gen:server
  [;; ... other callbacks ...
 
   (define (code-change self version-info state)
     (match (version-info-new-version version-info)
       ["2.0"
        ;; Migrate state from v1 to v2
        (ok (upgrade-state state))]
       [_ (ok state)]))])
 
;; Trigger the upgrade
(trigger-code-change! pid (version-info "1.0" "2.0" #f))

14.3 Tree-Wide Upgrades🔗ℹ

Upgrade an entire supervision tree (children first, then supervisor):

(upgrade-supervisor-tree! sup-pid (version-info "1.0" "2.0" #f))

15 Operational Features🔗ℹ

15.1 Logging🔗ℹ

(current-rakka-log-level 'debug)
(set-process-log-level! some-pid 'error)
 
(rakka-log-info "Starting on port ~a" port)
(rakka-log-error "Connection failed: ~a" reason)

Lifecycle events (SPAWN, EXIT, CRASH, RESTART) are logged automatically.

15.2 Metrics🔗ℹ

;; Built-in metrics
(counter-value metrics-process-spawns)
(gauge-value metrics-active-processes)
 
;; Custom metrics
(define reqs (make-counter 'http-requests))
(counter-inc! reqs)
 
(define conns (make-gauge 'connections))
(gauge-inc! conns)
(gauge-dec! conns)

15.3 Process Introspection🔗ℹ

(sys-get-state some-pid)   ; get current state
(sys-suspend some-pid)     ; pause processing
(sys-resume some-pid)      ; resume
(sys-trace some-pid #t)    ; enable tracing

15.4 Graceful Shutdown🔗ℹ

(on-shutdown
 (lambda (ctx) (close-connections))
 #:priority 10
 #:name 'cleanup)
 
(graceful-shutdown #:timeout 30000)

16 Process Groups🔗ℹ

Group processes together for easy lookup and messaging. Inspired by Erlang/OTP’s pg module.

;; Add a process to a group
(pg-join 'workers (self))
 
;; Get all processes in a group
(define workers (pg-members 'workers))
(for ([w workers]) (send! w 'do-work))
 
;; Leave a group
(pg-leave 'workers (self))
 
;; See all groups
(pg-which-groups)  ; => '(workers ...)

Groups auto-create on first join. Processes are automatically removed when they die. A process can join the same group multiple times (refcounted)—it must leave the same number of times.

Note: This is local-only. Erlang’s pg module synchronizes across cluster nodes; ours does not yet.

17 PubSub: Topic-Based Messaging🔗ℹ

Publish-subscribe for decoupled communication, inspired by Phoenix.PubSub.

;; Start a named pubsub
(pubsub-start 'events)
 
;; Subscribe to a topic (in an actor)
(pubsub-subscribe 'events "user:123")
 
;; Broadcast to all subscribers
(pubsub-broadcast 'events "user:123" '(new-message "hello"))
 
;; Broadcast excluding yourself
(pubsub-broadcast-from 'events "user:123" '(typing-indicator))
 
;; Unsubscribe
(pubsub-unsubscribe 'events "user:123")
 
;; Stop when done
(pubsub-stop 'events)

Subscribers are automatically removed when they die (uses process groups under the hood).

Note: This is local-only. Phoenix.PubSub broadcasts across cluster nodes; ours does not yet.

18 Tables: Shared State with Isolation🔗ℹ

Mutable key-value storage that multiple actors can access safely. Values are copied in and out, so mutations never leak between actors.

For you if: You need shared state (caches, counters, lookup tables) but want the safety of message passing.

18.1 Your First Table🔗ℹ

#lang racket/base
(require rakka)
 
;; Create a table (must be inside an actor)
(spawn (lambda ()
  (define cache (table-new 'my-cache #:type 'set))
 
  (table-insert! cache 'user-123 '((name . "Alice") (score . 42)))
  (table-lookup cache 'user-123)  ; => '((name . "Alice") (score . 42))
 
  (table-update! cache 'user-123
    (lambda (user) (cons '(vip . #t) user))
    '())
 
  (displayln (table-lookup cache 'user-123))))

That’s it. You have isolated shared state.

18.2 Why Tables Exist🔗ℹ

Rakka actors don’t share memory—they communicate by message passing. But sometimes you genuinely need shared state:

  • Caches: Multiple actors checking the same cached data

  • Counters: Incrementing a shared metric

  • Lookup tables: A routing table that many actors consult

You could create a GenServer that wraps a hash table, but:
  • Every read requires a message round-trip

  • The GenServer becomes a bottleneck

  • Simple lookups feel heavy

Tables solve this with a different tradeoff: direct access, but values are copied on every insert and lookup. This preserves isolation—mutating a looked-up value doesn’t affect the table.

18.3 Copy Semantics: The Key Insight🔗ℹ

Tables copy values in and out. This is crucial:

(spawn (lambda ()
  (define t (table-new 'isolation-demo #:type 'set))
 
  ;; Insert a mutable hash
  (define h (make-hash '((count . 0))))
  (table-insert! t 'data h)
 
  ;; Mutate the original
  (hash-set! h 'count 999)
 
  ;; Table still has the old value!
  (hash-ref (table-lookup t 'data) 'count)  ; => 0
 
  ;; Lookup returns a copy too
  (define result (table-lookup t 'data))
  (hash-set! result 'count 777)
  (hash-ref (table-lookup t 'data) 'count)  ; => still 0
))

Rule: What goes in stays in. What comes out is a copy.

This uses Racket’s place-message-allowed? constraint—only serializable values can be stored. No procedures, no opaque structs.

18.4 Named Tables: Global Access🔗ℹ

Anonymous tables require passing the reference. Named tables can be looked up by symbol:

;; Create a named table
(spawn (lambda ()
  (table-new 'sessions #:type 'set #:named #t)
  (table-insert! 'sessions 'sess-abc '((user . "alice")))
  ;; ...
))
 
;; From another actor, find it by name
(spawn (lambda ()
  (define sessions (table-whereis 'sessions))
  (when sessions
    (table-lookup sessions 'sess-abc))))
 
;; Or use the name directly in operations
(spawn (lambda ()
  (table-insert! 'sessions 'sess-xyz '((user . "bob")))))

table-whereis returns #f if the name doesn’t exist.

18.5 Ownership and Lifecycle🔗ℹ

Every table has an ownerthe actor that created it. When the owner dies, the table dies too:

(define owner
  (spawn (lambda ()
    (table-new 'will-die #:type 'set #:named #t)
    (receive))))  ; wait forever
 
(table-whereis 'will-die)  ; => the table
 
(send! owner 'goodbye)     ; owner exits
(sleep 0.1)                ; let death propagate
 
(table-whereis 'will-die)  ; => #f (table is gone)

This automatic cleanup prevents orphaned tables. If you need a table to outlive its creator, have a long-lived actor (like a supervisor’s child) own it.

18.6 Common Patterns🔗ℹ

Counter with atomic increment:
(table-update! counters 'page-views add1 0)  ; returns new value

Cache with conditional insert:
(unless (table-member? cache key)
  (table-insert! cache key (expensive-computation)))
(table-lookup cache key)

Or atomically:
(when (table-insert-new! cache key computed-value)
  (log-info "Cache miss for ~a" key))

Iterate all entries:
(for ([entry (table-to-list my-table)])
  (printf "~a: ~a\n" (car entry) (cdr entry)))

19 Process Introspection🔗ℹ

Rakka provides functions to inspect running processes, similar to Erlang’s erlang:processes/0 and erlang:process_info/2.

procedure

(processes)  (listof pid?)

Returns a list of all currently running process identifiers.

Returns the number of currently running processes. More efficient than (length (processes)) as it doesn’t allocate a list.

procedure

(process-info pid-or-name [key])  (or/c hash? any/c #f)

  pid-or-name : (or/c pid? symbol?)
  key : symbol? = #f
Returns information about a process.

If key is not provided, returns a hash containing all available information:
  • 'pid — the process identifier

  • 'registered-name — registered name or #f

  • 'status'running, 'waiting, or 'suspended

  • 'message-queue-len — number of pending messages

  • 'links — list of linked process identifiers

  • 'monitors — list of active monitor references

  • 'monitored-by — list of processes monitoring this one

  • 'trap-exit — whether exit signals are trapped

If key is provided, returns only that specific value.

Returns #f if the process is dead or does not exist.

20 API Reference🔗ℹ

20.1 GenServer🔗ℹ

procedure

(gen-server-start impl args [#:name name])  process?

  impl : server?
  args : any/c
  name : (or/c symbol? #f) = #f
Starts a GenServer. Optionally registers under name.

procedure

(gen-server-start-link impl    
  args    
  [#:name name])  process?
  impl : server?
  args : any/c
  name : (or/c symbol? #f) = #f
Like gen-server-start, but links to the caller.

procedure

(gen-server-call server    
  request    
  [#:timeout timeout])  any/c
  server : (or/c process? symbol?)
  request : any/c
  timeout : exact-positive-integer? = 5000
Sends request and waits for reply. Raises error on timeout.

procedure

(gen-server-cast! server request)  void?

  server : (or/c process? symbol?)
  request : any/c
Sends request asynchronously. No reply.

procedure

(gen-server-stop server [reason])  void?

  server : (or/c process? symbol?)
  reason : any/c = 'normal
Stops the server.

struct

(struct ok (state)
    #:extra-constructor-name make-ok)
  state : any/c
Return from init to start with state.

struct

(struct stop-init (reason)
    #:extra-constructor-name make-stop-init)
  reason : any/c
Return from init to abort startup.

struct

(struct reply (value new-state)
    #:extra-constructor-name make-reply)
  value : any/c
  new-state : any/c
Return from handle-call to reply with value.

struct

(struct noreply (new-state)
    #:extra-constructor-name make-noreply)
  new-state : any/c
Return from handle-cast or handle-info.

struct

(struct stop (reason reply-value new-state)
    #:extra-constructor-name make-stop)
  reason : any/c
  reply-value : any/c
  new-state : any/c
Return from any callback to stop the server.

20.2 Supervisor🔗ℹ

procedure

(supervisor-start-link strategy    
  max-restarts    
  max-seconds    
  child-specs)  pid?
  strategy : strategy?
  max-restarts : exact-nonnegative-integer?
  max-seconds : exact-positive-integer?
  child-specs : (listof child-spec?)
Starts a supervisor linked to the caller.

procedure

(child-spec* #:id id    
  #:start start    
  [#:restart restart    
  #:shutdown shutdown])  child-spec?
  id : any/c
  start : (-> pid?)
  restart : (or/c 'permanent 'temporary 'transient) = 'permanent
  shutdown : (or/c 'brutal 'infinity exact-nonnegative-integer?)
   = 5000
Creates a child specification.

procedure

(strategy? v)  boolean?

  v : any/c
Returns #t if v is one of: 'one-for-one, 'one-for-all, 'rest-for-one, 'simple-one-for-one.

procedure

(supervisor-which-children sup)

  (listof (list/c symbol? (or/c pid? 'undefined) (or/c 'worker 'supervisor) symbol?))
  sup : pid?
Returns information about all child specifications. Each entry is a list of:
  • id - child specification identifier (symbol)

  • child-status - pid if running, 'undefined if not started or terminated

  • type - 'worker or 'supervisor

  • modules - always 'dynamic (Racket does not track modules like Erlang)

procedure

(supervisor-count-children sup)

  (listof (cons/c symbol? exact-nonnegative-integer?))
  sup : pid?
Returns child counts as an association list with keys:
  • 'specs - total child specifications

  • 'active - currently running children

  • 'supervisors - children of type 'supervisor

  • 'workers - children of type 'worker

procedure

(supervisor-get-childspec sup child-id)

  (cons/c (or/c 'ok 'error) (or/c child-spec? 'not-found))
  sup : pid?
  child-id : symbol?
Retrieves the full child specification for the given child id. Returns (cons 'ok spec) on success, (cons 'error 'not-found) if the child id does not exist.

procedure

(supervisor-start-child sup spec-or-args)

  (cons/c (or/c 'ok 'error) any/c)
  sup : pid?
  spec-or-args : any/c
Dynamically starts a new child.

For 'simple-one-for-one supervisors, spec-or-args is passed to the template’s start function. For other strategies, spec-or-args should be a full child-spec.

Returns (cons 'ok pid) on success.

procedure

(supervisor-terminate-child sup child-id)

  (or/c 'ok (cons/c 'error any/c))
  sup : pid?
  child-id : symbol?
Terminates a running child by its id. The child spec remains, allowing restart. Returns 'ok on success.

procedure

(supervisor-restart-child sup child-id)

  (cons/c (or/c 'ok 'error) any/c)
  sup : pid?
  child-id : symbol?
Restarts a previously terminated child. The child must be terminated (not running) for this to succeed. Returns (cons 'ok pid) on success, (cons 'error 'running) if already running, or (cons 'error 'not-found) if unknown.

procedure

(supervisor-delete-child sup child-id)

  (or/c 'ok (cons/c 'error any/c))
  sup : pid?
  child-id : symbol?
Permanently removes a child specification from the supervisor. The child must be terminated first—use supervisor-terminate-child. Returns 'ok on success, (cons 'error 'running) if still running, or (cons 'error 'not-found) if unknown.

20.3 Process Primitives🔗ℹ

procedure

(spawn thunk)  process?

  thunk : (-> any)
Spawns a new process running thunk.

procedure

(spawn-link thunk)  process?

  thunk : (-> any)
Like spawn, but links to the caller.

procedure

(self)  process?

Returns the current process’s PID.

procedure

(send! proc msg)  any/c

  proc : process?
  msg : any/c
Sends msg to pid’s mailbox.

procedure

(receive)  any/c

Blocks until a message arrives.

procedure

(receive/timeout timeout-ms)  (or/c any/c #f)

  timeout-ms : exact-nonnegative-integer?
Like receive, but returns #f after timeout.

procedure

(receive-match predicate [timeout-ms])  (or/c any/c #f)

  predicate : (-> any/c boolean?)
  timeout-ms : (or/c #f exact-nonnegative-integer?) = #f
Returns first message matching predicate.

procedure

(process? v)  boolean?

  v : any/c
Returns #t if v is a process.

procedure

(process-alive? proc)  boolean?

  proc : process?
Returns #t if process is still running.

20.4 Registry🔗ℹ

procedure

(register! name proc)  void?

  name : symbol?
  proc : process?
Registers pid under name.

procedure

(unregister! name)  void?

  name : symbol?
Removes the registration.

procedure

(whereis name)  (or/c process? #f)

  name : symbol?
Returns the PID registered as name, or #f.

procedure

(registered)  (listof symbol?)

Returns all registered names.

20.5 Links and Monitors🔗ℹ

procedure

(link! proc)  void?

  proc : process?
Creates a bidirectional link.

procedure

(unlink! proc)  void?

  proc : process?
Removes the link.

procedure

(monitor! proc)  monitor-ref?

  proc : process?
Starts monitoring pid.

procedure

(demonitor! ref)  void?

  ref : monitor-ref?
Stops monitoring.

procedure

(set-trap-exit! proc trap?)  void?

  proc : process?
  trap? : boolean?
When #t, exit signals become messages.

struct

(struct exit-signal (from reason)
    #:extra-constructor-name make-exit-signal)
  from : pid?
  reason : any/c
Received when a linked process exits (if trapping).

struct

(struct down-signal (ref proc reason)
    #:extra-constructor-name make-down-signal)
  ref : monitor-ref?
  proc : process?
  reason : any/c
Received when a monitored process exits.

20.6 Process Dictionary🔗ℹ

procedure

(put key value)  (or/c any/c 'undefined)

  key : any/c
  value : any/c
Stores value under key in the current process’s dictionary. Returns the previous value, or 'undefined if the key was not set. Must be called from within a process.

procedure

(get key)  (or/c any/c 'undefined)

  key : any/c
Returns the value stored under key, or 'undefined if not found. Must be called from within a process.

procedure

(erase key)  (or/c any/c 'undefined)

  key : any/c
Removes key from the process dictionary. Returns the previous value, or 'undefined if the key was not set. Must be called from within a process.

procedure

(get-keys)  (listof any/c)

Returns a list of all keys in the current process’s dictionary. Must be called from within a process.

20.7 Runtime🔗ℹ

procedure

(start-runtime! [#:schedulers n])  exact-positive-integer?

  n : exact-positive-integer? = (processor-count)
Starts the Rakka runtime with n schedulers. Must be called before spawning any processes. Returns the number of schedulers started.

procedure

(stop-runtime!)  void?

Stops all schedulers and cleans up the runtime.

syntax

(with-runtime #:schedulers n body ...)

Starts the runtime, executes body ..., then stops the runtime. Convenient for tests and scripts.

procedure

(runtime-started?)  boolean?

Returns #t if the runtime is currently running, #f otherwise.

Returns the number of schedulers currently running.

procedure

(runtime-mode mode)  void?

  mode : (or/c 'green 'threaded)
Sets runtime mode. Call before spawning actors.

procedure

(runtime-mode-green?)  boolean?

Returns #t if in green thread mode.

procedure

(scheduler-start!)  void?

Starts the green thread scheduler.

procedure

(scheduler-stop!)  void?

Stops the scheduler.

procedure

(scheduler-run-until-done!)  void?

Runs until all actors complete.

procedure

(scheduler-running?)  boolean?

Returns #t if scheduler is active.

20.8 GenStatem🔗ℹ

procedure

(gen-statem-start impl args [#:name name])  process?

  impl : statem?
  args : any/c
  name : (or/c symbol? #f) = #f
Starts a state machine.

procedure

(gen-statem-start-link impl    
  args    
  [#:name name])  process?
  impl : statem?
  args : any/c
  name : (or/c symbol? #f) = #f
Like gen-statem-start, but links to caller.

procedure

(gen-statem-call statem    
  request    
  [#:timeout timeout])  any/c
  statem : (or/c process? symbol?)
  request : any/c
  timeout : exact-positive-integer? = 5000
Synchronous call to state machine.

procedure

(gen-statem-cast! statem request)  void?

  statem : (or/c process? symbol?)
  request : any/c
Async message to state machine.

procedure

(gen-statem-stop statem [reason])  void?

  statem : (or/c process? symbol?)
  reason : any/c = 'normal
Stops the state machine.

struct

(struct postpone-action ()
    #:extra-constructor-name make-postpone-action)
Action struct indicating the current event should be postponed. Use the postpone constant instead of constructing directly.

Convenience value for postponing events. Include in the actions list returned from handle-event to defer the current event until the state machine receives new events.

20.9 Distribution🔗ℹ

procedure

(start-epmd! [port])  epmd-server?

  port : exact-positive-integer? = 4369
Starts an EPMD server.

procedure

(stop-epmd! epmd)  void?

  epmd : epmd-server?
Stops EPMD.

procedure

(start-node! name port [#:cookie cookie])  void?

  name : string?
  port : exact-positive-integer?
  cookie : string? = "nocookie"
Starts the local node.

procedure

(stop-node!)  void?

Stops the local node.

procedure

(connect-node! name)  boolean?

  name : string?
Connects to a remote node.

procedure

(nodes)  (listof string?)

Returns connected node names.

procedure

(global-register! name pid)  void?

  name : symbol?
  pid : (or/c process? dist-pid?)
Registers globally (all nodes).

procedure

(global-whereis name)  (or/c pid? #f)

  name : symbol?
Looks up a global name.

procedure

(global-send! name msg)  void?

  name : symbol?
  msg : any/c
Sends to a globally registered process.

procedure

(send-remote! pid msg)  void?

  pid : (or/c process? dist-pid?)
  msg : any/c
Sends directly to a remote PID.

procedure

(dist-link! pid)  void?

  pid : (or/c process? dist-pid?)
Creates a distributed link.

procedure

(dist-monitor! pid)  monitor-ref?

  pid : (or/c process? dist-pid?)
Monitors a remote process.

20.10 Hot Reload🔗ℹ

struct

(struct version-info (old-version new-version extra)
    #:extra-constructor-name make-version-info)
  old-version : any/c
  new-version : any/c
  extra : any/c
Version info for code changes.

procedure

(hot-reload! module-path)  void?

  module-path : path-string?
Reloads a module.

procedure

(trigger-code-change! proc version-info)

  (or/c 'ok (cons/c 'error any/c))
  proc : process?
  version-info : version-info?
Triggers code-change callback.

procedure

(upgrade-process! proc version-info)

  (or/c 'ok (cons/c 'error any/c))
  proc : process?
  version-info : version-info?
Suspends, triggers code-change, resumes.

procedure

(upgrade-supervisor-tree! sup-pid    
  version-info    
  [#:skip-ids skip-ids])  void?
  sup-pid : pid?
  version-info : version-info?
  skip-ids : (listof any/c) = '()
Upgrades all processes in a tree.

20.11 Testing Utilities🔗ℹ

 (require rakka/testing) package: Rakka

procedure

(wait-until pred [#:timeout timeout-ms])  boolean?

  pred : (-> boolean?)
  timeout-ms : exact-positive-integer? = 1000
Polls pred until true or timeout.

procedure

(check-process-exits proc    
  expected-reason    
  [#:timeout timeout-ms])  boolean?
  proc : process?
  expected-reason : any/c
  timeout-ms : exact-positive-integer? = 1000
Asserts that pid exits.

syntax

(with-fresh-registry body ...)

Runs body with empty registry.

20.12 Process Groups🔗ℹ

procedure

(pg-join group pid-or-pids)  symbol?

  group : symbol?
  pid-or-pids : (or/c pid? (listof pid?))
Adds pid-or-pids to group. Creates group if needed. A process can join multiple times (refcounted). Accepts a single pid or list of pids. Returns 'ok.

procedure

(pg-leave group pid-or-pids)  symbol?

  group : symbol?
  pid-or-pids : (or/c pid? (listof pid?))
Removes pid-or-pids from group. Must leave same number of times as joined. Accepts a single pid or list of pids. Returns 'ok if at least one pid was removed, 'not_joined otherwise.

procedure

(pg-members group)  (listof pid?)

  group : symbol?
Returns all pids in group, or empty list if group doesn’t exist.

procedure

(pg-which-groups)  (listof symbol?)

Returns all known group names.

procedure

(pg-monitor group)  
pg-monitor-ref? (listof pid?)
  group : symbol?
Monitors group for membership changes. Returns a reference and current members. The caller will receive messages of the form:

procedure

(pg-demonitor ref)  symbol?

  ref : pg-monitor-ref?
Stops monitoring. Returns 'ok.

procedure

(pg-monitor-ref? v)  boolean?

  v : any/c
Returns #t if v is a process group monitor reference.

20.13 PubSub🔗ℹ

procedure

(pubsub-start name)  any/c

  name : symbol?
Starts a named pubsub system. Raises error if name already exists.

procedure

(pubsub-stop name)  symbol?

  name : symbol?
Stops a pubsub system. Raises error if name doesn’t exist.

procedure

(pubsub-subscribe pubsub topic)  symbol?

  pubsub : symbol?
  topic : string?
Subscribes the calling process to topic.

procedure

(pubsub-unsubscribe pubsub topic)  symbol?

  pubsub : symbol?
  topic : string?
Unsubscribes the calling process from topic.

procedure

(pubsub-broadcast pubsub topic message)  symbol?

  pubsub : symbol?
  topic : string?
  message : any/c
Sends message to all subscribers of topic.

procedure

(pubsub-broadcast-from pubsub topic message)  symbol?

  pubsub : symbol?
  topic : string?
  message : any/c
Like pubsub-broadcast, but excludes the calling process.

procedure

(pubsub-subscribers pubsub topic)  (listof pid?)

  pubsub : symbol?
  topic : string?
Returns all subscribers to topic. For debugging/monitoring.

20.14 Tables🔗ℹ

procedure

(table-new name #:type type [#:named named?])  table?

  name : symbol?
  type : (or/c 'set)
  named? : boolean? = #f
Creates a new table owned by the calling actor. The name is for identification (and registration if named? is #t). Currently only 'set type is supported. When named? is #t, the table is registered globally and can be looked up by name.

procedure

(table? v)  boolean?

  v : any/c
Returns #t if v is a table.

procedure

(table-alive? t)  boolean?

  t : table?
Returns #t if the table is still active (owner hasn’t died).

procedure

(table-whereis name)  (or/c table? #f)

  name : symbol?
Looks up a named table. Returns #f if no table is registered under name.

procedure

(table-insert! t key value)  void?

  t : (or/c table? symbol?)
  key : any/c
  value : any/c
Inserts key-value pair. The value is copied into the table. Accepts table reference or name. Raises error if value is not place-message-allowed?.

procedure

(table-lookup t key [default])  any/c

  t : (or/c table? symbol?)
  key : any/c
  default : any/c = #f
Returns the value for key, or default if not found. The returned value is a copy—mutations don’t affect the table.

procedure

(table-member? t key)  boolean?

  t : (or/c table? symbol?)
  key : any/c
Returns #t if key exists in the table.

procedure

(table-remove! t key)  void?

  t : (or/c table? symbol?)
  key : any/c
Removes key from the table. No error if key doesn’t exist.

procedure

(table-insert-new! t key value)  boolean?

  t : (or/c table? symbol?)
  key : any/c
  value : any/c
Inserts only if key doesn’t exist. Returns #t if inserted, #f if key already existed.

procedure

(table-update! t key proc default)  any/c

  t : (or/c table? symbol?)
  key : any/c
  proc : (-> any/c any/c)
  default : any/c
Atomic read-modify-write. Calls (proc old-value) where old-value is the existing value (or default if key doesn’t exist). Returns the new value.

procedure

(table-delete! t)  void?

  t : (or/c table? symbol?)
Destroys the table. Unregisters if named. All future operations will error.

procedure

(table-keys t)  (listof any/c)

  t : (or/c table? symbol?)
Returns all keys in the table.

procedure

(table-count t)  exact-nonnegative-integer?

  t : (or/c table? symbol?)
Returns the number of entries in the table.

procedure

(table-to-list t)  (listof (cons/c any/c any/c))

  t : (or/c table? symbol?)
Returns all entries as an association list. Values are copies.

procedure

(table-clear! t)  void?

  t : (or/c table? symbol?)
Removes all entries from the table (but keeps the table alive).

procedure

(table-info t)  hash?

  t : (or/c table? symbol?)
Returns metadata: 'name, 'type, 'size, 'named?, 'owner.

21 Tracing🔗ℹ

Erlang-style tracing for debugging and observability. Watch messages flow between processes, see spawns and exits, trace function calls—all without modifying your code.

For you if: You’re debugging process interactions, tracking down message ordering issues, or building production observability.

21.1 Trace a Process in 30 Seconds🔗ℹ

See all messages a process sends and receives:

(require rakka)
 
;; 1. Set up a tracer (where events go)
(tracer (make-tty-tracer))
 
;; 2. Start your server and trace it
(define pid (gen-server-start (my-server) #f))
(trace pid 'messages)  ; trace send/receive
 
;; 3. Interact normally - trace events appear
(gen-server-call pid 'get)
;; Output: [TRACE 1234567] send <pid> (list ...)
;; Output: [TRACE 1234568] receive <pid> ...
 
;; 4. Stop when done
(stop-tracer)

21.2 Understanding Trace Flags🔗ℹ

Trace flags control what gets traced. You can combine them:

Flag

  

What It Traces

'send

  

outgoing messages from this process

'receive

  

incoming messages to this process

'messages

  

both send and receive (shorthand)

'procs

  

spawn, exit, link, unlink, register

'calls

  

function calls matching trace patterns

'sos

  

set flags on spawned children

'sol

  

set flags on linked processes

'all

  

everything

Example: Trace everything a process does:

(trace pid 'all)

Example: Trace lifecycle events only:

(trace pid 'procs)
;; Now you'll see when this process spawns children,
;; exits, links/unlinks, or registers names

21.3 Propagating Traces Through the System🔗ℹ

The 'sos (set on spawn) and 'sol (set on link) flags let you trace entire subsystems without touching each process:

;; Parent traces messages and passes to children
(trace parent-pid 'messages 'sos)
 
;; Now any process spawned by parent-pid also traces messages
(define child (spawn ...))  ; automatically inherits 'messages flag

This is powerful for debugging supervisor trees—trace the supervisor, and all its children automatically trace too.

21.4 Tracer Backends🔗ℹ

Trace events need somewhere to go. Choose a backend:

;; Print to terminal (good for debugging)
(tracer (make-tty-tracer))
 
;; Collect events programmatically
(define-values (handler ch) (make-channel-tracer))
(tracer handler)
;; Later: (sync ch) returns trace events
 
;; Write to file (for production)
(tracer (make-file-tracer "/tmp/trace.log"))
 
;; Use Racket's logging system
(tracer (make-logger-tracer))

21.5 Call Tracing for GenServer🔗ℹ

Trace specific callback invocations:

;; Trace all handle-call invocations
(trace-pattern 'gen-server 'handle-call #t)
(trace pid 'calls)
 
;; Now you'll see 'call and 'return events for handle-call
(gen-server-call pid 'get)
;; Output: [TRACE ...] call (handle-call (get ...))
;; Output: [TRACE ...] return (handle-call (reply ...))

Filter by argument pattern:

;; Only trace calls where first arg is 'get
(trace-pattern 'gen-server 'handle-call #t
  #:match '(get _ _))

21.6 API Reference🔗ℹ

21.6.1 Tracer Control🔗ℹ

procedure

(tracer handler)  void?

  handler : (-> trace-event? any)
Sets the trace handler. All trace events pass through this function.

procedure

(stop-tracer)  void?

Disables tracing by removing the handler.

procedure

(get-tracer)  (or/c (-> trace-event? any) #f)

Returns the current trace handler, or #f if none.

21.6.2 Setting Trace Flags🔗ℹ

procedure

(trace proc flag ...)  void?

  proc : process?
  flag : symbol?
Enables trace flags on a process. Use 'clear to remove all flags.

procedure

(trace-flags proc)  (setof symbol?)

  proc : process?
Returns the set of trace flags currently enabled on pid.

procedure

(clear-trace proc)  void?

  proc : process?
Removes all trace flags from pid and cleans up resources.

21.6.3 Call Patterns🔗ℹ

procedure

(trace-pattern module    
  fn    
  matcher    
  [#:match pattern    
  #:guard guard])  void?
  module : symbol?
  fn : symbol?
  matcher : any/c
  pattern : any/c = #t
  guard : (or/c #f (-> list? boolean?)) = #f
Registers a call trace pattern. Calls matching the pattern are traced when the process has the 'calls flag. Use '_ as wildcard in patterns.

procedure

(clear-trace-pattern module fn)  void?

  module : symbol?
  fn : symbol?
Removes all trace patterns for the given module/function pair.

procedure

(clear-trace-patterns)  void?

Removes all registered trace patterns.

21.6.4 Tracer Backends🔗ℹ

procedure

(make-tty-tracer)  (-> trace-event? any)

Returns a tracer that prints events to current-output-port.

procedure

(make-channel-tracer)  
(-> trace-event? any) async-channel?
Returns a tracer and a channel. Events are put on the channel for consumption.

procedure

(make-logger-tracer)  (-> trace-event? any)

Returns a tracer that sends events to Racket’s logging system.

procedure

(make-file-tracer path)  (-> trace-event? any)

  path : path-string?
Returns a tracer that appends events to the file at path.

21.6.5 Trace Events🔗ℹ

struct

(struct trace-event (type pid timestamp data)
    #:transparent)
  type : symbol?
  pid : (or/c pid? #f)
  timestamp : real?
  data : any/c
A trace event. The type indicates what happened ('send, 'receive, 'spawn, 'exit, 'link, 'unlink, 'register, 'unregister, 'call, 'return). The data varies by type.

22 Pattern-Matching Behavior Languages🔗ℹ

Rakka provides specialized languages for defining gen-server and gen-statem behaviors with Erlang-style pattern matching.

22.1 #lang rakka/gen-server🔗ℹ

A language for defining gen-server behaviors with pattern-matched callbacks.

#lang rakka/gen-server
 
(init '()
  (ok (hash)))
 
(init `(,filename)
  (ok (load-from-file filename)))
 
(handle-call `(get ,key) _from state
  (reply (hash-ref state key #f) state))
 
(handle-call `(set ,key ,value) _from state
  (reply 'ok (hash-set state key value)))
 
(handle-cast `(log ,msg) state
  (displayln msg)
  (noreply state))
 
(handle-info (exit-signal pid reason) state
  (noreply (handle-exit state pid)))
 
(terminate 'normal state
  (log-info "clean shutdown"))

The server is automatically exported with a name derived from the filename. Use it with gen-server-start:

(require "kv-store.rkt")
(define pid (gen-server-start kv-store '()))

22.2 #lang rakka/gen-statem🔗ℹ

A language for defining gen-statem state machines with pattern-matched state functions.

#lang rakka/gen-statem
 
(init '()
  (statem-ok 'idle (hash)))
 
(state idle
  (call `(connect ,host) from data
    (next-state 'connected (hash-set data 'host host)
                (list (statem-reply from 'ok))))
  (cast 'ping data
    (keep-state data '())))
 
(state connected
  (call 'disconnect from data
    (next-state 'idle (hash-remove data 'host)
                (list (statem-reply from 'ok))))
  (timeout 'heartbeat data
    (keep-state data '())))

23 License🔗ℹ

Rakka is released under the MIT License.

MIT License

 

Copyright (c) 2024 Aldric Giacomoni

 

Permission is hereby granted, free of charge, to any person obtaining a copy

of this software and associated documentation files (the "Software"), to deal

in the Software without restriction, including without limitation the rights

to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

copies of the Software, and to permit persons to whom the Software is

furnished to do so, subject to the following conditions:

 

The above copyright notice and this permission notice shall be included in all

copies or substantial portions of the Software.

 

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

SOFTWARE.