Actors in Common Lisp

January 3, 2008. Filed under actors 3 common-lisp 2

Recently I have been working at implementing Actors in Common Lisp. I have been looking at the Scala source for their Actors library as well.

Vision

My vision is that an effective Actors library would allow clean and simple interation between threads, programs, and external computers as well.

Implementation

There are a number of implementation questions to be considered here. But I my current approach involves two components: an actor-environment and actors.

The actor-environment will contain a number of local-actors, and can keep track of external actor-environments and the local-actors contained within those other environments. On setup an actor-environment will have to be taught how to find any other actor-environments it interacts with, but the actors inside different actor-environments (once they have been introduced) should interact with the same syntax as actors in the same actor-environment.

Each actor-environment will have one primary dispatch thread, which will be used to either (single-threaded model) run relevant actor code when they recieve a new message, or (multi-threaded model) will allocate a worker thread to run the relevant actor code when they recieve a new message. The dispatch thread will also be used to sending and recieving new messages between actor-environments and actors. It will also be used to dynamically search for other actor-environments in certain situations (zeroconf enabled actor-environments, for example).

In addition, each actor-environment will have 0+ interfaces (this is how actor-environments will be introduced to each other). These interfaces can be input, output, or bidirectional (an input socket, an output socket, a two way socket, etc). They can also be active or passive (zeroconf will have to dynamically scan for new actor-environments, but data about a specific server you want to communicate with won't fluxuate).

Syntax

I am still working on the syntax, and I don't particularly care for what I have right now. But its somewhat servicable:

(with-env (env)
    (let ((ping (make-instance 'local-actor :env env :id 'ping))
	      (pong (make-instance 'local-actor :env env :id 'pong))
	      (output nil))
      (setf (action ping)
	    (lambda (actor msg)
	      (if (equal (second msg) "pong")
		  (setf output (append output (list "ping"))))
	      (send actor 'pong "ping")))
      (setf (action pong)
	    (lambda (actor msg)
	      (declare (ignore actor))
	      (if (equal (second msg) "ping")
		  (setf output (append output (list "pong"))))))
      (send pong 'ping "pong")))

Each actor has an action associated with it, and that action is called each time it recieves a new message. The action receives the Actor itself as the first parameter (akin to explicitly passing self to a Python method), and then a msg as its second parameters. The msg is a tuple containing the sending actor, and the data sent as well (typically this will be a string, perhaps it will always be a string... there are some security considerations to be had there).

At the moment things are mostly thread-safe because I am only using the single-thread dispatcher. This wouldn't be thread safe in the multiple-thread dispatcher. Then again, you probably shouldn't be modifying shared mutable objects within the actors if at all possible.

Definitely requires some more syntactic sugar before it becomes pleasantly usable. Any thoughts?