Kill-Safe Actors
This package provides a macro and runtime support for writing kill-safe actors using the techniques described in the “Kill-Safe Synchronization Abstractions”[Flatt04] paper.
syntax
(define-actor (id arg ...) maybe-option ... method-definition ...)
maybe-option =
| #:state state-expr | #:event event-proc-expr | #:receive? receive?-proc-expr | #:stopped? stopped?-proc-expr | #:on-stop on-stop-proc-expr method-definition =
(define (method-id state-arg-id arg-id ...) method-body ...+)
state-expr : state
event-proc-expr : (-> state (evt/c state))
receive?-proc-expr : (-> state boolean?)
stopped?-proc-expr : (-> state boolean?)
on-stop-proc-expr : (-> state any)
Each method takes as a first argument the current state, followed by any arguments sent by the sender, and must return two values: the next state and a value to return to the sender.
Each actor runs in its own thread/suspend-to-kill and sending an actor a message will resume its thread if it has been killed. Any one actor is guaranteed to only be processing one message at a time.
The #:state argument accepts an expression that produces the initial state of the actor. If not provided, the initial state of an actor is #f.
> (define-actor (counter start) #:state start (define (incr state) (values (add1 state) state))) > (define c (counter 5)) > (sync (incr-evt c)) 5
> (incr c) 6
The #:event argument accepts a procedure that takes the current state and produces an additional event that the actor should handle (which could be a choice-evt). If selected for synchronization, the synchronization result of the event will be used as the next state of the actor.
> (define (make-token) (gensym)) > (define (make-deadline) (+ (current-inexact-milliseconds) 1000)) > (struct state (token deadline))
> (define-actor (token-cache) #:state (state (make-token) (make-deadline)) #:event (lambda (st) (handle-evt (alarm-evt (state-deadline st)) (lambda (_) (state (make-token) (make-deadline))))) (define (get-token state) (values state (state-token state)))) > (define c (token-cache)) > (get-token c) 'g6407
> (get-token c) 'g6407
> (sleep 1) > (get-token c) 'g6407
The #:receive? argument accepts a procedure that takes a state and returns a boolean value representing whether or not the actor should receive new messages. The default value of receive?-proc-expr is a procedure that ignores its argument and always returns #t.
> (define end-work-sema (make-semaphore))
> (define-actor (backpressure limit) #:state 0 #:event (lambda (in-progress) (handle-evt end-work-sema (lambda (_) (sub1 in-progress)))) #:receive? (lambda (in-progress) (< in-progress limit)) (define (start-work in-progress) (values (add1 in-progress) #t))) > (define bp (backpressure 2)) > (start-work bp) #t
> (start-work bp) #t
> (sync/timeout 0.5 (start-work-evt bp)) #f
> (semaphore-post end-work-sema) > (sync/timeout 0.5 (start-work-evt bp)) #t
The #:stopped? argument accepts a procedure that takes a state and returns a boolean value representing whether or not the actor should stop running its internal event loop. When this procedure returns #t, the actor stops receiving new messages and drains any pending responses to senders before finally applying the on-stop-proc-expr with the final state.
> (define-actor (stoppable) #:state #f #:on-stop (λ (_) (eprintf "actor stopped!~n")) #:stopped? values (define (stop _) (values #t #t))) > (define s (stoppable)) > (stop s) actor stopped!
#t
The #:on-stop argument accepts a procedure that is called with the final state when the actor stops running its event loop. The default value of the on-stop-proc-expr is void.
1 Reference
procedure
(actor-dead-evt a) → evt?
a : actor?
Bibliography
[Flatt04] | “Kill-Safe Synchronization Abstractions.” https://www.cs.tufts.edu/~nr/cs257/archive/matthew-flatt/kill-safe.pdf |