Rakka: Actor Model for Racket
| (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
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)
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 |
| |
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—
;; 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—
;; 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 sent with plain send!
Exit signals from linked processes (if trapping exits)
Monitor down signals
Timer messages
3 Tutorial: Key-Value Store
Let’s build a practical GenServer—
#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)
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
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) '())
A supervised tree of processes
Automatic restart on failures
Clean shutdown when the app stops
8 Under the Hood: Raw Actors
GenServer is built on lower-level primitives. You usually don’t need these, but understanding them helps.
8.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)
8.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)])))
Raw: You write the loop, handle replies manually
GenServer: You implement callbacks, infrastructure handles the rest
8.3 When to Use Raw Actors
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.
9 Green Threads and Scheduling
Rakka uses green threads by default—
9.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.
9.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)))
9.3 Performance Comparison
Operation |
| Green |
| Threaded |
Spawn 1000 actors |
| 2ms |
| 45ms |
50k messages |
| 32ms |
| 5,300ms |
Use green mode unless you need CPU parallelism.
10 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
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.
11 Distribution: Actors Across Machines
Rakka supports Erlang-style distribution with EPMD protocol compatibility.
11.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.
11.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))
11.3 Global Registry
;; Register globally (visible on all nodes) (global-register! 'calculator my-pid) ;; Look up from any node (define calc (global-whereis 'calculator))
11.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)))
12 Hot Code Reload
Update running code without stopping your system.
12.1 Basic Reload
(hot-reload! "my-server.rkt")
12.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))
12.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))
13 Operational Features
13.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.
13.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)
13.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
13.4 Graceful Shutdown
(on-shutdown (lambda (ctx) (close-connections)) #:priority 10 #:name 'cleanup) (graceful-shutdown #:timeout 30000)
14 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)—
Note: This is local-only. Erlang’s pg module synchronizes across cluster nodes; ours does not yet.
15 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.
16 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.
16.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.
16.2 Why Tables Exist
Rakka actors don’t share memory—
Caches: Multiple actors checking the same cached data
Counters: Incrementing a shared metric
Lookup tables: A routing table that many actors consult
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—
16.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—
16.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.
16.5 Ownership and Lifecycle
Every table has an owner—
(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.
16.6 Common Patterns
(table-update! counters 'page-views add1 0) ; returns new value
(unless (table-member? cache key) (table-insert! cache key (expensive-computation))) (table-lookup cache key)
(when (table-insert-new! cache key computed-value) (log-info "Cache miss for ~a" key))
(for ([entry (table-to-list my-table)]) (printf "~a: ~a\n" (car entry) (cdr entry)))
17 API Reference
17.1 GenServer
procedure
(gen-server-start impl args [#:name name]) → pid?
impl : server? args : any/c name : (or/c symbol? #f) = #f
procedure
(gen-server-start-link impl args [ #:name name]) → pid? impl : server? args : any/c name : (or/c symbol? #f) = #f
procedure
(gen-server-call server request [ #:timeout timeout]) → any/c server : (or/c pid? symbol?) request : any/c timeout : exact-positive-integer? = 5000
procedure
(gen-server-stop server [reason]) → void?
server : (or/c pid? symbol?) reason : any/c = 'normal
struct
(struct reply (value new-state) #:extra-constructor-name make-reply) value : any/c new-state : any/c
struct
(struct stop (reason reply-value new-state) #:extra-constructor-name make-stop) reason : any/c reply-value : any/c new-state : any/c
17.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?)
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
17.3 Process Primitives
procedure
(spawn-link thunk) → pid?
thunk : (-> any)
procedure
(receive/timeout timeout-ms) → (or/c any/c #f)
timeout-ms : exact-nonnegative-integer?
procedure
(receive-match predicate [timeout-ms]) → (or/c any/c #f)
predicate : (-> any/c boolean?) timeout-ms : (or/c #f exact-nonnegative-integer?) = #f
procedure
(pid-alive? pid) → boolean?
pid : pid?
17.4 Registry
procedure
(unregister! name) → void?
name : symbol?
procedure
(registered) → (listof symbol?)
17.5 Links and Monitors
procedure
(demonitor! ref) → void?
ref : monitor-ref?
procedure
(set-trap-exit! pid trap?) → void?
pid : pid? trap? : boolean?
struct
(struct exit-signal (from reason) #:extra-constructor-name make-exit-signal) from : pid? reason : any/c
struct
(struct down-signal (ref pid reason) #:extra-constructor-name make-down-signal) ref : monitor-ref? pid : pid? reason : any/c
17.6 Runtime Mode
procedure
(runtime-mode-green?) → boolean?
procedure
(scheduler-start!) → void?
procedure
(scheduler-stop!) → void?
procedure
(scheduler-run-until-done!) → void?
procedure
(scheduler-running?) → boolean?
17.7 GenStatem
procedure
(gen-statem-start impl args [#:name name]) → pid?
impl : statem? args : any/c name : (or/c symbol? #f) = #f
procedure
(gen-statem-start-link impl args [ #:name name]) → pid? impl : statem? args : any/c name : (or/c symbol? #f) = #f
procedure
(gen-statem-call statem request [ #:timeout timeout]) → any/c statem : (or/c pid? symbol?) request : any/c timeout : exact-positive-integer? = 5000
17.8 Distribution
procedure
(start-epmd! [port]) → epmd-server?
port : exact-positive-integer? = 4369
procedure
(stop-epmd! epmd) → void?
epmd : epmd-server?
procedure
(start-node! name port [#:cookie cookie]) → void?
name : string? port : exact-positive-integer? cookie : string? = "nocookie"
procedure
(stop-node!) → void?
procedure
(connect-node! name) → boolean?
name : string?
procedure
(global-register! name pid) → void?
name : symbol? pid : pid?
procedure
(global-whereis name) → (or/c pid? #f)
name : symbol?
procedure
(global-send! name msg) → void?
name : symbol? msg : any/c
procedure
(send-remote! pid msg) → void?
pid : pid? msg : any/c
procedure
(dist-link! pid) → void?
pid : pid?
procedure
(dist-monitor! pid) → monitor-ref?
pid : pid?
17.9 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
procedure
(hot-reload! module-path) → void?
module-path : path-string?
procedure
(trigger-code-change! pid version-info)
→ (or/c 'ok (cons/c 'error any/c)) pid : pid? version-info : version-info?
procedure
(upgrade-process! pid version-info)
→ (or/c 'ok (cons/c 'error any/c)) pid : pid? version-info : version-info?
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) = '()
17.10 Testing Utilities
| (require rakka/testing) | package: Rakka |
procedure
(wait-until pred [#:timeout timeout-ms]) → boolean?
pred : (-> boolean?) timeout-ms : exact-positive-integer? = 1000
procedure
(check-process-exits pid expected-reason [ #:timeout timeout-ms]) → boolean? pid : pid? expected-reason : any/c timeout-ms : exact-positive-integer? = 1000
17.11 Process Groups
procedure
(pg-members group) → (listof pid?)
group : symbol?
procedure
(pg-which-groups) → (listof symbol?)
procedure
(pg-monitor group) →
pg-monitor-ref? (listof pid?) group : symbol?
procedure
(pg-demonitor ref) → symbol?
ref : pg-monitor-ref?
procedure
(pg-monitor-ref? v) → boolean?
v : any/c
17.12 PubSub
procedure
(pubsub-start name) → any/c
name : symbol?
procedure
(pubsub-stop name) → symbol?
name : symbol?
procedure
(pubsub-subscribe pubsub topic) → symbol?
pubsub : symbol? topic : string?
procedure
(pubsub-unsubscribe pubsub topic) → symbol?
pubsub : symbol? topic : string?
procedure
(pubsub-broadcast pubsub topic message) → symbol?
pubsub : symbol? topic : string? message : any/c
procedure
(pubsub-broadcast-from pubsub topic message) → symbol?
pubsub : symbol? topic : string? message : any/c
procedure
(pubsub-subscribers pubsub topic) → (listof pid?)
pubsub : symbol? topic : string?
17.13 Tables
procedure
(table-alive? t) → boolean?
t : table?
procedure
(table-whereis name) → (or/c table? #f)
name : symbol?
procedure
(table-lookup t key [default]) → any/c
t : (or/c table? symbol?) key : any/c default : any/c = #f
procedure
(table-insert-new! t key value) → boolean?
t : (or/c table? symbol?) key : any/c value : any/c
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
procedure
(table-delete! t) → void?
t : (or/c table? symbol?)
procedure
(table-clear! t) → void?
t : (or/c table? symbol?)
procedure
(table-info t) → hash?
t : (or/c table? symbol?)
18 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. |