Actors

Actor-oriented concurrent and distributed programming.

to use bindings from this package

(import :std/actor)

Note

This page documents the API of the actors package in Gerbil v0.18 and later; the legacy actor package is deprecated, but still available in :std/actor-v13 if you need time to port existing code.

Messaging Primitives

->

(-> dest msg
    replyto: (replyto #f)
    expiry: (expiry #f)
    reply-expected: (reply-expected? #f))

Sends a message to dest, which must be a thread or actor handle, wrapped in an envelope.

  • replyto is an optional nonce when sending a reply to a previous message.
  • expiry is an optional expiry time (as a time object), which denotes the expiry of the message. Expired messages will not be processed by the reaction macro <- below.
  • reply-expected? is a boolean indicated whether a reply is expected to this message. It is a hint for message routers to send back a control reply in case of routing errors. Returns the nonce of the message or #f if the message was not sent because the destination actor was detected as dead.

->>

(->> dest msg
     replyto: (replyto #f)
     timeout: (timeo (default-reply-timeout)))

Sends a message to dest and waits for the reply.

  • replyto is an optional nonce indicating a reply to a previous interaction.
  • timeout is the timeout for receiving the reply, which must be a relative time (a positive real) or an absolute time (a time object).

Returns the value received in the reply message.

Raises an actor error if the message could not be sent because the destination was detected as dead.

Raises a timeout error if waiting for the reply times out.

-->

(--> result)

Sends result as a reply to the source of the message in the current reaction context; it must be invoked within the lexical scope of a <- reaction.

-->?

(-->? result)

Conditionally sends result as a reply to the source of the message in the current reaction context, if a reply is expected; it must be invoked within the lexical scope of a <- reaction.

<-

(<- (pattern body ...) ...
    [,(reaction-rule-macro ...) ...]
    [,@(multiple-reaction-rules-macro ...) ...]
    [timeout: timeo]
    [(else body ...)])

Receives an enveloped message from the thread's mailbox matching one of the patterns and dispatching the body accordingly; patterns are matched with match.

If there is a timeout specified, it will raise a timeout error if no message matching any of the patterns is received before the timeout elapses.

If there is an else clause it will be dispatched immediately if there is no message matching any of the patterns in the mailbox.

Within a reaction rule body, the following syntactic variables are set:

  • @envelope is set to the message envelope; this is an instance of the evenelope struct.
  • @message is set to the envelope payload; this can be anything.
  • @dest is set to the envelope destination; this is where the message was sent, a thread or a handle.
  • @source is set to the envelope source; this is where the message came from, a thread or a handle.
  • @nonce is set to the envelope nonce; a monotonically increasing nonnegative integer.
  • @replyto is set to the envelope reply nonce; it can be #f if this message is not a reply to a previous message, or the nonce of a previously sent message.
  • @expiry is set to the envelope expiry; this is a time object, representing absolute expiration time.
  • @reply-expected? is set to the envelope reply-expected hint; this is #t if the source is expecting a reply to this message.

<<

(<< (pattern body ...) ...
    [timeout: timeo]
    [(else body ...)])

Like <- but it destructures raw messages that can be anything, not necessarily wrapped in an envelope. You don't normally have to use this unless you are expecting to receive messages sent with a raw primitive like thread-send or send-message and not with the send operator(s).

There are no syntactic variables bound in the reaction context.

envelope

(defstruct envelope (message dest source nonce replyto expiry reply-expected?)
 ...)

Envelope structure for messages; you normally shouldn't construct those by hand and let the send operators construct it for you.

The structure is provided so that you program raw reactions with <<, for instance when writing a proxy actor.

The meanings and types of the struct fields are as defined as follows:

  • message is the payload; this can be anything.
  • dest is the destination actor; this is where the message was sent, a thread or a handle.
  • source is the source of the message; this is where the message came from, a thread or a handle.
  • nonce is the source-specific message nonce; a monotonically increasing nonnegative integer.
  • replyto is the destination-specific reply nonce; it can be #f if this message is not a reply to a previous message, or the nonce of a previously sent message.
  • expiry is envelope expiry; this is a time object, representing absolute expiration time.
  • reply-expected? is hint for proxies; this is #t if the source is expecting a reply to this message.

send-message

(send-message actor msg)

Sends a message to an actor, which must be a thread or a handle. Returns #f if the message was not sent because the actor was detected as dead.

You don't normally have to invoke this, it is invoked internally by the send operators. It is provided however in case you want to send pre-constructed envelopes, for instance when writing a proxy actor.

current-thread-nonce!

(current-thread-nonce!)

Returns the current thread's numeric nonce and post increments.

You don't normally have to use this procedure, the nonce is incremented automatically by the send operators. It is provided however in case you want to construct your own envelopes, for instance when writing a proxy actor.

actor-authorized?

(actor-authorized? actor)

Returns true if the actor (a thread or a handle) are authorized for administrative actions.

All threads within the process are implicitly authorized.

Remote actors must be authorized with the actor server using admin-authorize if the actor server requires authentication. This happens by default if an administrative public key exists in the ensemble directory. If none exists, the actor server will consider all actors in the ensemble as authorized.

actor-error

(defstruct (actor-error <error>) ())

Structure for actor errors.

raise-actor-error

(raise-actor-error where what . irritants)

Raises an actor error.

default-reply-timeout

(default-reply-timeout)

The default reply timeout in seconds; initial value is 5s.

set-default-reply-timeout!

(set-default-reply-timeout! timeo)

Sets the default reply timeout.

Handles and References

make-handle

(handle proxy ref)

Creates a handle for sending messages to an actor through a proxy.

  • proxy is the actor who will receive the messages, a thread.
  • ref is a reference to the actor being proxied; in general it is something the proxy can interpret; see reference below.

handle?

(handle? obj)

Predicate for handles.

handle-proxy

(handle-proxy h)

Returns the proxy in the handle.

handle-ref

(handle-ref h)

Returns the actor reference in the handle.

reference

(defmessage reference (server id))

References for actors in the ensemble.

  • server is the server identifier where the actor resides.
  • id is the actor identifier, generally a symbol for references by name.

reference->handle

(reference->handle ref (srv (current-actor-server)))

Creates a handle from a reference, using by default the current actor server as the proxy.

Protocols

defmessage

(defmessage id (field-name ...) struct-options ...)

Macro to define message types that can be efficiently marshalled as protocol messages.

The structure is final and transparent, and it is automatically registered in the message type registry where the unmarshaller can find it.

Note

Messages must be acyclic; if you want to send cyclic data you can use a normal struct, but be aware that such structs will be serialized/deserialized with the raw gambit serializer and carry the whole type descriptor (including methods) with them.

message?

(message? obj)

Predicate for instances of messages defined with defmessage.

defcall-actor

(defcall-actor (proc arg ...)
  expr
  [error: error-msg error-irritant ...])

Macro for defining synchronous interaction entry points for actors.

The macro defines a procedure that invokes an actor with expr and unwraps the result. If it is !ok the embedded value is returned. If it is !error an actor error is raised, using the optional error message and irritants specified in the definition.

!ok

(defmessage !ok (value))

Message indicating a successful invocation of an actor.

!error

(defmessage !error (message))

Message indicating an error in an actor invocation. The message is a diagnostic string, that will be included in the error raised by defcall-actor definitions.

with-result

(with-result expr [fail!])

Evaluates expr and matches the result; if it is !ok the embedded value is returned. If it is !error an error is raised by invoking the fail! (error by default) procedure with the error message.

Actor Management Protocol

!ping

(defmessage !ping ())

Message sent to check liveness of an actor; the actor must reply with (!ok 'OK) if it is live.

@ping

(defrule (@ping)
  ((!ping) (--> (!ok 'OK))))

Reaction macro to automatically respond to !ping messages.

You can use this in reaction context (<-) with the gnostic ,(@ping) syntax.

!shutdown

(defmessage !shutdown ())

Message sent to request an actor to gracefully shutdown.

@shutdown

(defrule (@shutdown exit ...)
  ((!shutdown)
   (-->? (!ok (void)))
   exit ...))

Reaction macro to automatically (conditionally) respond to a !shutdown message.

You can use this in reaction context (<-) with the gnostic ,(@shutdown exit-actor-loop ...) syntax.

@unexpected

(defrule (@unexpected logf)
  (unexpected
   (logf "unexpected message from ~a: ~a" @source @message)
   (-->? (!error "unexpected message"))))

Reaction macro to automatically log and conditionally respond to unexpected messages.

You can use this in reaction contex (<-) with the gnostic ,(@unexpected warnf) syntax.

Tickers

ticker

(ticker peer (period 1) (tick 'tick))

Runs in a loop sending !tick messages to peer every elapsed period (in seconds).

You can spawn tickers to send heartbeat messages to an actor like this:

(spawn/name 'ticker ticker (current-thread))

ticker-after

(ticker-after peer initial-delay (period 1) (tick 'tick))

Runs ticker after sleeping for initial-delay (in seconds).

after

(after time peer (tick 'tick))

Sleeps for time and sends a tick to the specified peer.

!tick

(defmessage !tick (id seqno))

This is the message sent by ticker and related procedures to signify a temporal tick.

  • id is the identifier of the tick, which is a hint for actors to demultiplex multiple tick sources.
  • seqno is the sequence number of the tick.

Actor Monitors

actor-monitor

(actor-monitor actor peer (send ->))

Waits for actor to terminate by joining it and sends an !actor-dead message to peer when it exits. The actor must be a thread.

The send procedure is used to send the message; if you are processing raw messages with << in your actor's reaction loop, you can use send-message instead of -> to avoid wrapping the notification in an envelope.

You can spawn an actor monitor to notify you of thread exits like this:

(spawn/name 'actor-monitor actor-monitor actor (current-thread))

!actor-dead

(defmessage !actor-dead (thread))

Message sent by actor monitors to notify or a monitored thread exit.

The Actor Server

Server Addresses

Actor server addresses can be:

  • UNIX domain addresses.
  • TCP addresses.
  • TLS addresses.

Unix Addresses

A UNIX domain address is denoted like this:

[unix: hostname path]

hostname is the name of the host where the server is accessible and path is the socket path; they are both strings.

Actor servers will never try to connect to UNIX addresses in different hosts.

TCP Addresses

A TCP address is denoted like this:

[tcp: inet-addr]

inet-addr is an Internet address; normally a pair of a host address and a port or a string encoded Internet address of the form "host:port". See the :std/net/address module for more details.

Do not use bare TCP addresses in production, as you will vulnerable to Man in the Middle attacks. Gerbil actors only support them for development and debugging purposes; you should use TLS in production.

TLS Addresses

A TLS address is denoted like this:

[tls: inet-addr]

inet-addr is an Internet address; normally a pair of a host address and a port or a string encoded Internet address of the form "host:port". See the :std/net/address module for more details.

current-actor-server

(current-actor-server)

Parameter denoting the current actor server.

This parameter is set automatically by start-actor-server! and call-with-ensemble-server; you don't normally have to set it manually.

start-actor-server!

(start-actor-server! identifier:  (id (make-random-identifier))
                     tls-context: (tls-context (get-actor-tls-context id))
                     cookie:      (cookie (get-actor-server-cookie))
                     admin:       (admin (get-admin-pubkey))
                     auth:        (auth #f)
                     addresses:   (addrs [])
                     ensemble:    (known-servers (default-known-servers)))

Starts an actor server, sets the current-actor-server parameter and returns the main server thread.

  • identifier is the server identifier; if you don't specify one, a random server identifier will be generated.
  • tls-context is the server's TLS context, which can be omitted if this is a development server. By default it is looked up in GERBIL_PATH/ensemble/server/<server-id>/tls.
  • cookie is the ensemble cookie; normally resides in $GERBIL_PATH/ensemble/cookie. Note that the administrator has to explicitly create a cookie for the ensemble, it is not automatically created.
  • admin is the (optional) administrative public key; normally resindes in $GERBIL_PATH/ensemble/admin.pub.
  • auth: a hash table mapping server ids to capabilities; these are preauthorized server capabilities.
  • addresses is the list of addresses the server should listen; by default it is empty, making this a transient actor server.
  • ensemble specifies statically known hosts; it is a hash table mapping server identifiers to lists of addresses. The default known servers only contain the registry with the default registry address.

stop-actor-server!

(stop-actor-server! (srv (current-actor-server)))

Stops and joins an actor server.

actor-server-identifier

(actor-server-identifier (srv (current-actor-server)))

Returns an actor server's identifier.

register-actor!

(register-actor! name (srv (current-actor-server)))

Registers the current thread in an actor server as an actor with the name name.

connect-to-server!

(connect-to-server! id (addrs #f) (srv (current-actor-server)))

Instructs an actor server to connect to another server.

  • id is the identifier of the target server
  • addrs is an optional list of addresses; if none is specified and the server is unknown, its addresses will be resolved through the ensemble registry.

list-actors

(list-actors (srv (current-actor-server)))

Lists the actors registered with an actor server. Returns a list of references.

list-connections

(list-connections (srv (current-actor-server)))

Lists the current connections of an actor server. Returns an associative list, with the server identifier at the car and the list of addresses connected at the cdr.

default-known-servers

(default-known-servers)

Returns a hash table with the default known servers. By default it only contains the registry reachable in its default address.

set-default-known-servers!

(set-default-known-servers! servers)

Sets the default known servers; servers must be a hash table mapping server identifiers to lists of addresses.

default-registry-addresses

(default-registry-addresses)

Returns the default registry addresses, as a list of addresses. By default the registry is reachable at /tmp/ensemble/registry in the current host.

set-default-registry-addresses!

(set-default-registry-addresses! addrs)

Sets the default registry addresses.

server-address-cache-ttl

(server-address-cache-ttl)

Returns the actor's server address cache TTL in seconds (a real number); by default this is 5 minutes.

set-server-address-cache-ttl!

(set-server-address-cache-ttl! ttl)

Sets the actor server's address cache TTL (in seconds, a real number).

Ensemble Servers

call-with-ensemble-server

(call-with-ensemble-server server-id thunk
                           log-level:   (log-level 'INFO)
                           log-file:    (log-file #f)
                           listen:      (listen-addrs [])
                           announce:    (public-addrs #f)
                           registry:    (registry-addrs #f)
                           roles:       (roles [])
                           tls-context: (tls-context (get-actor-tls-context server-id))
                           cookie:      (cookie (get-actor-server-cookie))
                           admin:       (admin (get-admin-pubkey))
                           auth:        (auth #f))

This is the programmatic equivalent of gxensemble run; first it starts the logger with the appropriate options, and then it starts a new actor server with identifier server-id, starts the loader service, adds the server to the ensemble, and then invokes thunk. When the thunk exits, it shuts down the actor server and removes the server-id from the ensemble.

Options:

  • log-level: logging level to use; INFO by default
  • log-file: log file for the logger; if it is "-" then the canonical server log is used; this file is at $GERBIL_PATH/ensemble/server/<server-id>/log.
  • listen: a list of addresses for the actor server to listen to, in addition to the default unix address.
  • announce: an optional list of addresses to announce to the registry, in addition to the default unix address. If it is not specified, then the listen addresses are announced.
  • registry: an optional list of registry addresses. If it is not specified, then the default registry address is used.
  • roles: a list of roles the server fullfills in the registry.
  • tls-context is the server's TLS context, which can be omitted if this is a development server. By default it is looked up in GERBIL_PATH/ensemble/server/<server-id>/tls.
  • cookie: the cookie to use; by default it uses the ensemble cooke in $GERBIL_PATH/ensemble/cookie.
  • admin: the administrative public key, if any; by default it uses the public key in $GERBIL_PATH/ensemble/admin.pub if it exists.
  • auth: a hash table mapping server ids to capabilities; these are preauthorized server capabilities.

ensemble-base-path

(ensemble-base-path)

Returns the base directory for the ensemble; this is $GERBIL_PATH/ensemble.

ensemble-server-path

(ensemble-server-path server-id)

Returns the directory for server specific data; this is $GERBIL_PATH/ensemble/server/<server-id>.

Ensemble Control

This is programmatic functionality for operations normally performed using the gxensemble tool; see the ensemble tutorial for more information.

stop-actor!

(stop-actor! ref (srv (current-actor-server)))

Stops an actor referred by ref by sending it a !shutdown request.

remote-stop-server!

(remote-stop-server! srv-id (srv (current-actor-server)))

Stops the remote server with identifier srv-id.

remote-list-actors

(remote-list-actors srv-id (srv (current-actor-server)))

Lists registered actors at the remote server with identifier srv-id.

remote-connect-to-server!

(remote-connect-to-server! from-id to-id (addrs #f) (srv (current-actor-server)))

Asks the remote server with identifier from-id to connect to the server with identifier to-id. If the optional addresses addrs are not specified, then the to-id server will be looked up in the registry.

remote-list-connections

(remote-list-connections srv-id (srv (current-actor-server)))

Lists the connections of a remote server with identifier srv-id.

remote-load-library-module

(remote-load-library-module srv-id mod (srv (current-actor-server)))

Asks the remote server with identifier srv-id to load the library module mod.

remote-load-code

(remote-load-code srv-id object-file-path (srv (current-actor-server)))

Asks the remote server with identifier srv-id to load a code object file.

remote-eval

(remote-eval srv-id expr (srv (current-actor-server)))

Evaluates expr in the remote server with identifier srv-id.

ping-server

(ping-server srv-id (srv (current-actor-server)))

Pings the remote server with identifier srv-id.

ping-actor

(ping-actor ref (srv (current-actor-server)))

Pings the actor referred by ref.

ensemble-add-server!

(ensemble-add-server! id addrs roles (srv (current-actor-server)))

Adds a server to the ensemble registry:

  • id is the server identifier
  • addrs is the list of actor server addresses.
  • roles is a list of roles for the server; a list of symbols.

ensemble-remove-server!

(ensemble-remove-server! id (srv (current-actor-server)))

Removes the server with identifier id from the ensemble registry.

ensemble-list-servers

(ensemble-list-servers (srv (current-actor-server)))

Lists the current ensemble servers.

ensemble-lookup-server

(ensemble-lookup-server id (srv (current-actor-server)))

Looks up a server's addresses in the registry.

ensemble-lookup-servers/role

(ensemble-lookup-servers/role role (srv (current-actor-server)))

Looks up servers (and their addresses) that fulfill role in the ensemble.

admin-authorize

(admin-authorize privk srv-id authorized-server-id (srv (current-actor-server))
                 capabilities: (cap '(admin)))

Authorizes the capabilities specified by cap with administrative privileges for authorized-server-id in the remote server srv-id, using the private key privk. cap is a list of symbols denoting the capabilities of the authorized server; the admin capability implies all other capabilities.

admin-retract

(admin-retract srv-id authorized-server-id (srv (current-actor-server)))

Retracts the capabilities previously confered to authorized-server-id within the context of srv-id. Note that the in-process actor server must have admin capabilities.

get-admin-pubkey

(get-admin-pubkey (path (default-admin-pubkey-path)))

Loads the administrative public key from the specified path, if it exists

get-admin-privkey

(get-admin-privkey passphrase (path (default-admin-privkey-path)))

Loads and decrypts the administrative private key from the specified path, if it exists

default-admin-pubkey-path

(default-admin-pubkey-path)

The default path for the ensemble admin public key; defaults to $GERBIL_PATH/ensemble/admin.pub.

default-admin-privkey-path

(default-admin-privkey-path)

The default path for the encrypted ensemble admin private key; defaults to $GERBIL_PATH/ensemble/admin.priv.

The following procedures offer programmatic functionality used by actor servers and the gxensemble for managing TLS infrastructure.

ensemble-tls-base-path

(ensemble-tls-base-path)

Returns the base TLS directory for the ensemble; this is $GERBIL_PATH/ensemble/tls.

ensemble-tls-server-path

(ensemble-tls-server-path server-id)

Returns the directory for server specific data; this is $GERBIL_PATH/ensemble/server/<server-id>/tls.

ensemble-tls-cafile

(ensemble-tls-cafile)

Returns the base TLS CA file for the ensemble; this is $GERBIL_PATH/ensemble/tls/ca.pem.

get-actor-tls-context

(get-actor-tls-context server-id) -> tls-context or #f

Create an appropriate TLS context for the actor server with id server-id. It loads the server's private key from (ensemble-tls-server-path).

actor-tls-certificate-id

(actor-tls-certificate-id x509)

Returns the actor server id for an x509 certificate.

actor-tls-certificate-cap

(actor-tls-certificate-cap x509)

Returns the actor server capabilities embedded in an x509 certificate.

actor-tls-host

(actor-tls-host server-id)

Returns the host name for an actor server as it is expected to appear in its certificate, for certificate verification purposes.

generate-actor-tls-root-ca!

(generate-actor-tls-root-ca! root-ca-passphrase
                             domain: (domain "ensemble.local")
                             country-name: (country-name "UN")
                             organization-name: (organization-name "Mighty Gerbils")
                             common-name: (common-name (string-append organization-name " Root CA")))

Generates a new root CA.

generate-actor-tls-sub-ca!

(generate-actor-tls-sub-ca! root-ca-passphrase
                            sub-ca-passphrase
                            country-name: (country-name "UN")
                            organization-name: (organization-name "Mighty Gerbils")
                            common-name: (common-name (string-append organization-name " Subordinate CA")))

Generates a new subordinate CA.

generate-actor-tls-cafiles!

(generate-actor-tls-cafiles! force: (force? #f))

Generates the CA files for the actor CA.

generate-actor-tls-cert!

(generate-actor-tls-cert! sub-ca-passphrase
                          server-id: server-id
                          capabilities: (capabilities [])
                          country-name: (country-name "UN")
                          organization-name: (organization-name "Mighty Gerbils")
                          location: (location "Internet"))

Issues a new actor TLS certificate.