Kill-Safe Actors
define-actor
1 Reference
actor?
actor-dead-evt
Bibliography
8.15.0.2

Kill-Safe Actors🔗ℹ

Bogdan Popa <bogdan@defn.io>

 (require actor) package: actor-lib

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)
Defines a procedure named id that returns an instance of an actor when applied. For each method-definition, a procedure named method-id-evt is defined that sends the actor a message to be handled by the body of that method and returns a synchronizable event representing the result of executing the body. Additionally, a method-id procedure is defined that composes sync with its associated method-id-evt procedure, for convenience.

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.

Examples:
> (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.

Examples:
> (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)

'g6413

> (get-token c)

'g6413

> (sleep 1)
> (get-token c)

'g6415

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.

Examples:
> (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.

Examples:
> (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? v)  boolean?

  v : any/c
Returns #t when v is an actor.

procedure

(actor-dead-evt a)  evt?

  a : actor?
Returns an event that is ready for synchronization iff a has terminated. The synchronization result of an actor-dead event is the actor-dead event itself.

Bibliography🔗ℹ

[Flatt04] “Kill-Safe Synchronization Abstractions.” https://www.cs.tufts.edu/~nr/cs257/archive/matthew-flatt/kill-safe.pdf