Simple actor model implementation for local and remote actors
myriam is a simple attempt at implementing the actor model for secure, distributed programming. Feedback is very welcome!
This is a Common Lisp port of my
myriam Chicken Scheme egg. It's already at feature parity with its Scheme sister, aside from the ability to send functions/closures (and obviously, no continuations either), and uses ZMQ instead of NNG for messaging, which means we can make use of CurveZMQ for easier encryption and (some) authentication.
The API is very much unstable at the moment.
Myriam stands on the shoulders of giants.
cl-myriam is on Quicklisp, so you can load it with
Otherwise, you can load the
:cl-myriam system directly and require the
:myriam package. Everything you need is in there.
Make sure to check the tests folder for examples.
(spawn &rest actions) -> (values string thread)
Spawn an actor and return its address and thread. If no actions are specified, the actor will only react to predefined actions.
(action name task &optional context) -> action
Create an action for an actor definition.
name should be a keyword.
task should be a function with any number of arguments (or no arguments).
context should be either
:any and defaults to
:async; it specifies the context in which a task can be executed, e.g.
:sync tasks will be executed synchronously and it's what you'll want if you expect to get a value out of them.
(msg head &rest body) -> message
Create a message to be sent to an actor.
head should be a keyword corresponding to the name of a task defined in the target actor. Arguments in
body, if any, will be passed to the task to be executed.
(send address msg)
(send* address msg) -> (or * (values nil nil))
Send a message to an actor to be executed either in an asynchronous (
send) or synchronous (
msg should correspond in context, task name and number of arguments with the target task.
Identities encapsulate encryption keys. A
self-identity contains both a public and secret key, while a
public-identity contains only a public key.
Actors are spawned with the
*current-self-identity* (which means that many actors can share the same identity, if you want). Outgoing messages need
*target-public-identity* to be set with a
public-identity for encryption.
(make-self-identity) -> self-identity
self-identity with a keypair.
(self->public-identity self-id) -> public-identity
public-identity from the public key in
(with-self-identity id &body body)
*current-self-identity* set with
(with-target-identity id &body body)
*target-public-identity* set with
(send address (msg :ping))
Check if an actor is alive.
(send address (msg :stop))
Stop an actor.
(send address (msg :store key datum))
datum into an actor's internal storage.
key should be a keyword.
(send address (msg :fetch key))
Fetch a value from an actor's internal storage, or
nil of there is no such value.
key should be a keyword.
Dynamic variable useful for referring to an actor's self address.
Time to wait for a reply before killing the connection.
By default, actors accept all incoming messages. You need to spawn an authenticator to filter out connections based on host and identities.
(spawn-authenticator accept-p &optional name) -> (values bt:thread name)
Spawn an authenticator (a ZAP server).
accept-p should be a predicate which takes an IP address and the public key of the authenticating client (as a byte vector); an incoming connection will be either accepted or rejected based on the result of
accept-p. Name shoule be a unique string and it can be used for terminating the authenticator. You can only spawn a single authenticator per context, see below.
Kill the given authenticator.
(authenticator-alive-p name) -> boolean
Test if the authenticator with the given name is alive.
Contexts allow you to separate actor groups with different authentication parameters. Without contexts, you're limited to one authenticator per application.
(defparameter a (myr:with-new-context (let ((authenticator (myr:spawn-authenticator (lambda (ip key) (declare (ignore key)) (string= ip "127.0.0.1"))))) (declare (ignore authenticator)) (myr:spawn))))
In this example,
a will only accept connections from
(with-new-context &body forms)
forms in a fresh context. Mostly used to spawn actors under an authenticator with specific parameters.
(change-host address new-host) -> valid-address
Replace the host in
(all-actors) -> list
Get a list of all local actors.
Stop all local actors.
- Sending your own classes inside a message requires you to define the methods
decode-object. See https://github.com/conspack/cl-conspack#clos-and-general-objects for more details, it's easier than it sounds.
- Synchronous messages block execution of an actor for the duration of the task.
- No assumptions are made about identity distribution and service (actor) discovery mechanisms.