Kill-Safe Actors
define-actor
1 Reference
actor?
actor-dead-evt
define/  private
Bibliography
8.17

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 ...
  definition ...)
 
maybe-option = 
  | #:state state-expr
  | #:event event-proc-expr
  | #:receive? receive?-proc-expr
  | #:stopped? stopped?-proc-expr
  | #:on-stop on-stop-proc-expr
     
definition = private-definition
  | method-definition
     
private-definition = (define/private id expr)
  | (define/private (id . args) body ...+)
     
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, defines a procedure named method-id-evt 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, for each method, defines a method-id procedure that composes sync with its associated method-id-evt procedure, for convenience. Finally, defines a procedure named id? that recognizes instances of the actor.

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, under the custody of the calling thread, if it has been killed. An instance of an 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))
> (counter? c)

#t

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

'g11931

> (get-token c)

'g11931

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

'g12388

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.

Within an define-actor form, define/private may be used to define private bindings and procedures that can be used from within the body of an actor. Methods may call one-another. Calling a method from another method is a regular procedure call that does not go through the synchronization mechanism.

Changed in version 0.2 of package actor-lib: Added support for predicate procedures.
Changed in version 0.2: Added support calling methods from other methods.

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.

syntax

(define/private (id . args)
  body ...+)
Defines a private procedure that can be used from within the body of an actor.

Added in version 0.2 of package actor-lib.

Bibliography🔗ℹ

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