Dramatis Actor FAQ
Okay. It's a little much to call this a FAQ. (I mean, for one thing, no one asked me any of these questions once, let alone frequently.) It's some cross between a FAQ and a tutorial, with a bit of backstory and editorial thrown in. I fall on the mercy of "progress, not perfection". Needless to say, if you find any inaccuracies, feel free to let me know ... or better yet, fix 'em. It's a wiki ...
What are Actors?
The terse answer:
Actors are objects with concurrent semantics. They can only execute one method at a time and they are uninterruptible.
The long answer:
Most programming languages don't have a first-class representation for concurrency or threads. The programming model they provide is a single thread of control that executes statements one at a time. Concurrency is often added to these languages through libraries that create new threads of control and provide tools for managing synchronization. In particular, there is either no or limited interaction between the object-oriented aspects of the language and the concurrency provided by the language libraries.
In actors, concurrency is intimately tied to the object nature of the language. Rather than creating independent threads that run through multiple objects, each actor has, abstractly, its own thread. Since a thread can only do one thing at a time, the implication is that the actor can only do one thing at a time. Multiple actors can be running at the same time, but each actor only has one thread of control running thorough it.
Actors also can't have their thread of control yanked away from them to do something else. For example, if an actor is executing some bit of code, something else can't interrupt it and cause it to execute another bit of code. The actor gets to run its method to completion.
Are all object in Dramatis actors?
No. Only objects that are identified as actors are actors. All other objects have their native semantics. So standard library objects behave like they always have. If multiple actors access the same object, chaos can reign: Dramatis actors help you manage concurrency, and hopefully they do it in a pretty way. But they don't solve all the world's (or all your) problems.
Are Dramatis actors normal objects? Can they be used like normal objects?
Note: this discussion tries to avoid bringing up behaviors, which makes it slightly inaccurate, but if you don't care about behaviors, an advanced feature mentioned below, you won't care about the slight inaccuracies.
Yes. Dramatis actors are normal objects and will act like normal objects when accessed through normal references. Dramatis programs access an actor object's "actor-ness" by using actor names. These are reference-like proxy objects that cause what look like normal method calls to have concurrent semantics; they are the primary entree to the Dramatis runtime.
How do I create an actor?
You mixin the dramatis Actor class. In Ruby, this looks like
1 class MyClass
2 include Dramatis::Actor
3 ...
4 end
In Python, it looks like
1 class MyClass ( dramatis.Actor ):
2 ...
Then you just create an object of that type like you always would:
MyClass.new in Ruby and MyClass() in Python.
What if I want actors for existing classes?
Something like this works, too (I think), but (currently) has holes; Ruby:
1 my_concurrent_hash = Dramatis::Actor.new Hash.new
Python:
1 my_concurrent_hash = dramatis.Actor( dict() )
What does that buy me?
It works (if it does?) if the existing class isn't thread safe or, eventually, if the object lives in a different process.
What holes do you know about?
If the object accepts or passes back mutable data, things can go funny. The library takes care of converting between actor names and references for the object itself but won't, for example, make a copy of the actor an actor (it'll be a plain object.) Of course, you're not supposed to copy actors in the actor model, so ...
How do I call a method on an actor?
If you have an actor name, let's call it my_actor, you call the method like you would normally, e.g.,
1 my_actor.my_function( arg1, arg2, arg3 )
This works the same in Ruby or Python.
What do I have to do create a method on an actor?
Nothing, except define the method in the class of the object as you normally would. There is no difference in the way that the method is defined or what it does internally.
If I have a native reference to an object (like self) can I call methods normally?
Sure. These calls have the native language semantics; that is, they aren't scheduled by the dramatis runtime and run within the current thread of control. If you have shared state, all the normal shared-state bugaboos apply. So if the object will be accessed by multiple threads, for example with dramatis, by multiple actors, the object should itself be an actor and those other actors should access it using its actor name.
In most (but not all) cases, objects that are not shared do not need to be actors.
The approach represented by dramatis is to allow you to use all your normal serial constructs and then give you actor tools to manage concurrency.
Can actor method calls return a value?
Sure. The most common call form, the one shown above, returns the value the normal method call would.
Can actor methods calls raise exceptions?
Yes. Different kinds of calls (see below) process exceptions in different ways, but the normal case raises the exception just as it would if the object were called via a normal reference.
Where is the "send" function I see in other actor languages?
The dramatis way of "sending a message to an actor" is to make a method call via an actor name.
It looks like a method call? Does that mean it returns a value?
Yup. I said that.
So it blocks? It waits for the method to execute?
Yup.
Doesn't that make it exactly the same as a normal method call?
Nope. (You thought I was gonna say yup. Admit it.)
There are a number of differences. First and foremost, if the callee actor is in the midst of executing another method call, the current call is queued and the caller blocks. (With a normal reference, the call will simply go ahead, with multiple threads running in the object.) When the callee actor finishes its current method, the next method call in its queue is scheduled. When our call finally makes it the front of the queue, it gets executed and the return value is computed and returned to us. When we get our value, we are unblocked and we can continue.
So it's more like an RPC?
Yup.
This doesn't sound very concurrent.
You're right, it doesn't. This is just one kind of dramatis method call. Dramatis has a few other kinds, as found in other actor libraries. Those other calls are more explicitly concurrent but before considering them, we wanted to emphasize this case because- it's easiest to understand
- you'll use it a lot
In many cases, the difficulty of concurrency comes from managing, that is, limiting, concurrency. Blocking calls are a nice simple way to manage concurrency when that's appropriate (more on that later).
I thought actors didn't block.
Blocking is in the eye of the beholder. In actually, the actor isn't really blocked. If you were to look under the covers, you'd see that the actor was sending your method to the callee and asking the receiver to send a response message back when it had a result value. The response message causes us, the calling actor, to pick up where we left off. (If you know what continuations and continuation passing style (CPS) are, this should sound familiar. If that makes you think about Ruby continuations, don't go there; they're conceptually related, but dramatis doesn't use them.)
And as an aside, let's just say that anyone that tells you that actor programs don't have deadlocks or races is playing a bit fast and loose with their explanations. Data and control dependence issues don't manifest the same way and, perhaps, with the same frequency as they can in thread-model programs, but they can arise in any concurrent program. However, they should be less common and easier to manage with actors.
Another thing that is hopefully true is that we can build better, higher level, tools for development and debugging in actor systems (given that it's a higher level model).
If my blocking call is actually waiting for messages, can't other messages (that is, calls from other actors) come in?
Yes, they can. However, dramatis has gating logic that will, by default, only allow through the unique response continuation it is looking for. All other call requests are queued and will only be delivered once the result value is returned and the blocked method resumed and completed. This is one use of what is often called selective receive.
What if I want to let other messages through while waiting on the RPC?
Dramatis has a gating API that integrates with the continuation gating. Currently, methods can be identified as always allowed or never allowed, overriding the continuation gate. This can be used, for example, to always allow status requests to be responded to.
So if I make a blocking actor call, but I have an always gated method, it will get executed? Isn't that "interrupting" my method?
Yes to the first part and, I would say, no to the second. You're making the call to the other actor, so, to some extent, you're voluntarily relinquishing control. Moreover, default actor gating will block all other messages until your method is rescheduled and completed. When you explicitly identify always methods, you're making yourself responsible for the ramifications.
To reiterate: with blocking calls and always gates:
- It's possible for other methods to be executed while waiting on the results of a blocking call
- The actor is still single threaded: only one method is ever executed at a time so there are still no internal races. What can happen, though, is that if you look at, for example, a member value before a blocking call and then make a blocking call, if you have @alway@s-enabled methods that change that member state, they could run and the member state of the object may be different on return from the blocking call
Enough about blocking calls. So how do I send a message / call an actor method without waiting for the result?
Where a normal blocking call might look like an_actor.method_a( arg1 ) you make a non-blocking call with
1 Dramatis.release( an_actor ).method_a( arg1 )
in Ruby and
1 dramatis.release( an_actor ).method_a( arg1 )
in Python. (In Ruby, if you
include Dramatis, you don't need to to put the Dramatis. on every call.)
So that doesn't wait?
Nope. The method might start immediately, if the actor isn't busy and the scheduler decides to do that, but the calling actor doesn't wait around to find out. Importantly, there's no "blocking" here so the calling actor cannot be interrupted: no other methods can get scheduled on it until it either finishes this method or makes a blocking call.
So, I take it that, in this case, the call doesn't return a value?
Well, everything in dynamic languages returns a value. Casts return nil/None immediately.
What does release do, exactly?
release takes a name and returns a new name that has non-blocking semantics; you may also see this referred to as a null continuation. You can use that name as you would any other actor name or object reference, but it will always have non-blocking semantics.
Can I reuse that name?
Sure:
1 release_actor = Dramatis.release( an_actor )
2 release_actor.method_a( "once" )
3 release_actor.method_a( "twice" )
same in both languages (big D for Ruby, little d for Python).
Where does the result from a release call go?
To the big return value bit bucket in the sky. I.e., nowhere.
What about exceptions?
I'm glad you asked that. The caller hasn't waited around, so there's no way to raise the exception to it via the call. One might be tempted to say, hey, if you don't care about the return value, you don't care about the exception either, but this is a Bad Idea. Swallowing exceptions is a Very Very Bad Idea. Don't ever work on an actor system that does this. (I've written a few.)
That we all know. Nobody disagrees about that. (Well, probably somebody does, but let's pretend they don't.) Unfortunately, the next step gets a little fuzzy.
Imagine this: you're the callee actor and you're in the middle of some middle layer of code somewhere when some exception gets raised. Right off, it would seem like you could partition most exceptions into one of two sets: (1) those that are the result of something you were asked to do and (2) those that arise because something internal to you is in an unanticipated state. An example of the first case might arise if you were given two numbers to divide and the denominator was zero. If the caller gave you the zero, it's pretty much the callers fault: it should be informed and the callee actor can go along its merry way.
An example of the second case might be an actor that provides some kind of service to clients. Let's say it keeps a few references to other objects but at some point, because of bugs in the code and/or the phase of the moon, one of those references is null. As part of the method call, it tries to call a method and raises an exception. In this case, it isn't the caller's fault at all. It sent a prefectly valid request. The fault lies with the callee, so at the very least, the callee should probably react to the exception. (The caller should perhaps get an exception, too, though if the callee could somehow restart, maybe it could fulfill the request after the restart).
So handling exceptions is gonna take some thought. Our Erlang friends (well, we like them; they probably don't know who we are) have some methods for this and we're frantically copying/adapting their methods for dramatis.
In the case of null continuations, dramatis will currently attempt to call a method dramatis_exception on the caller actor at some point in the future. This call is queued for the caller like all methods but in this case, it (currently) can't be gated.
If the caller doesn't have this function, it's passed to the runtime which records it.
This is all definitely incomplete but does provide the basis for current debugging.
This is a pretty interesting area for more work in dramatis ... really looking for use cases and experience here. Proper exception handling, both during development and production, will be crucial for dramatis.
Where is the receive I see in other actor libraries?
Dramatis does not have an explicit receive. Many of our dynamic languages, including Ruby and Python, have "send" without receive: why should actors make things different? They don't (in dramatis). The language runtime already has a mechanism for method dispatch. Dramatis integrates with this.
But my other (functional) languages have receive loop(s) ...
Mostly the functions they provide aren't necessary with dramatis. For those cases where they are necessary, or just helpful, dramatis provides gates and behaviors.
Can an actor be executing more than one method at a time?
An actor will not have more than one method scheduled by the dramatis runtime at a time. However, if a method is called via a normal reference, that call will execute in the normal fashion, using the caller's thread of control, wherever that may have come from. So, to manage concurrency, the developer manages the type of reference that is used by client code: if you only give out the actor name, then all calls to that actor will always use actor semantics.
Does Dramatis have advanced features?
What, I'm gonna say, no? Okay, yes, these topics are a bit more advanced (actually, I think that's just a nice way of saying my explanation of them is weak.) Two advanced features are gates and behaviors.
What are dramatis gates?
At the highest level, dramatis gates are similar to the pattern matching and guards in a language like Erlang. They affect when particular methods become schedulable. In Erlang, the list of all possibly receivable patterns is given in the receive statement. If a received message does not match this list, it is deferred. In Erlang, this receive block also specifies the code that processes messages when they are accepted.
In dramatis, the gating function (what can be done) and the response behavior (what to do) are separate. The response behavior to a message is always whatever the callee does when it executes the given method. Gates, however, affect whether a method will be executed or not.
Conceptually, Dramatis needs to provide the ability to
refusemethodsacceptmethodsalwaysmethods
The refuse and accept methods are fairly self-explanatory: a refused method won't be scheduled until it is accepted. An accepted method will be allowed. All methods are accepted by default when the actor is created.
The always method is required to support advanced features like blocking continuations.
By default, when a blocking call is made, only that unique continuation is allowed (this is a bit of a lie; but it's mostly true). This really is necessary: otherwise managing concurrency gets pretty hard, pretty quickly. So, when making a blocking call, the gate state is kind of "pushed" and only that continuation allowed. When the result continuation is received from the callee, the gate state is "popped" and the method resumes, more or less the way it was.
This is all great, except that another common use case is status methods which are designed to be always safe. We don't want the blocking call to block these. So actors can identify always methods which don't interact with continuation gating (and, in fact, override all refuse specifications).
One observation here is that while the concept of delaying methods is similar to that in Erlang, the way it manifests is very different. This is primarily a result of the functional nature of Erlang and the imperative nature of our non-functional dynamic languages. The functional nature of Erlang means that the entire matching structure must be specified in one declaration in the Erlang receive statement. The gate features in dramatis are not functional: they are mutable and change over time. There are some use cases (like continuations) where this proves very convenient, but it's a new concept and will certainly evolve with experience.
A few notes:
- Gate behavior is private to an actor. Only the actor (well, if it's well behaved) can change its gate behavior and it can only do it during method execution.
- Gating happens whenever the scheduler determines that an actor might be available to run: that is, when it's idle and receives a new message or when it finishes processing a message.
- When a call is deferred, it is put onto a queue. Deferring a call does not affect acceptance or deferral of calls that come after it.
- Once a call has completed, the queue is examined again from the beginning since changes that occurred to the state of the actor may have now enabled calls that had been refused earlier.
- The actual matching to received calls incorporates some of the ideas from Erlang, Rubinius, and Revactor.
This is an area of active work. The existing code and API supports the existing use cases but quite a lot of fleshing out can be done. Use cases and ideas very welcome.
What are dramatis behaviors?
This is advanced stuff. You don't need to understand this at first. In fact, you may not need to know it at all. It'll be interesting to see how much it's used.
In reality, in dramatis, actors and their behaviors are separate concepts. While we normally think of "an actor" as being an instance of a class with an Actor class mixin, this isn't exactly what happens in dramatis.
In dramatis, an actor is, by definition, an object that has an actor name and queue. An actor doesn't have any intrinsic behavior: it gets that, from, well, the behavior. Your class is the behavior part of the actor. The behavior is the part that actually responds to method calls. It's your instance: its data and code and whatnot.
So in dramatis, an actor is something that has a name, has a queue for holding method calls, has a gate, and has a behavior. Your code is the last part.
How do I use behaviors?
Well, right now you don't. (But this could change pretty soon.) When it is allowed, you can become a new instance. See the auction example below.
Why did you do this?
For a few reasons. One is related to user functionality (see the next question). But another reason is to allow objects to have both actor and normal object semantics. This gives a lot more flexibility in managing concurrency.
What do behaviors buy me?
Some seriously cool, maybe seriously useful, and perhaps seriously twisted capabilities.
Gul Agha's book talks a lot about behaviors, which are fairly close to dramatis behaviors.
In functional languages, behaviors are the receive statements: they're the code to call and all the state you're going to pass. So in Erlang, you can think of each receive statement as defining a behavior.
We tend to not think of behaviors, or behavior-like objects, in imperative languages because we have mutable state. Every time we change a member variable, we're kind of "becoming" a new behavior, at least from the functional language point of view.
So, mostly we can ignore behaviors in imperative languages and dramatis.
But some use cases are cropping up that seem kind of interesting. The auction example (which originally came from Scala) provides an interesting use case. If you look at the original Scala code, the auction sever has two receive loops: one that it uses when the auction is ongoing and another that is used when the auction has closed. When the auction first starts, the first loop receives and responds to all bid requests. Once the auction has closed, its behavior has to change to no longer allow new bids. In fact, most messages should just get an "auction over" response.
There are many ways one could implement this is an imperative language. One could keep a member state variable and have every method check that state variable, responding accordingly. People do this all the time. You're effectively implementing a little ad hoc state machine.
But a nice, clean way to do this is to become an EndedAuction. That object has a different set of methods that respond differently.
There's really nothing inherently parallel about this ... at least as far as you've heard so far.
If I could do all this stuff with my underlying language, why provide it in dramatis?
Two answers:
First: the feeling is that because actor programs tend to be event/data driven applications, the need for this kind of state evolution is more prevalent than in other contexts. To the extent that this is true, making it easy is worth the effort.
Second: there is a concurrent issue here that goes beyond the serial case. It revolves around the become method.
When you become a new behavior, that behavior, by definition, isn't executing anything, so the actor it represents is immediately free to schedule the next task in its queue. Your current method, any code after the become, can still execute, but it has no effect on the actor in the future. This gives you concurrency that might be useful. It's sort of like returning a value in the middle of a function, and then finishing some other tasks (for purposes of evaluating side effects, presumably) (which should also be possible in dramatis but isn't currently implemented.)
Gul talks about this a lot. It's cool but I don't have a compelling use case for it ... yet.
Does Dramatis have Futures?
Yeah, it does, but they're pretty new and while I really like the start, there's some things that need to be figured out.
What is a Future?
A future is kind of like an in-between of an RPC and a null continuation: it gets a result, like the RPC, but it doesn't wait for it, as a release call doesn't.
How do I use Futures?
You create a future name, much like you do a cast:
1 future_name = dramatis.future( an_actor )
(Big D Ruby, little d Python).
Then you call it (you could, of course, do this all in one step):
1 my_value = future_name.some_method( some_arg )
Then, sometime later, you use the value:
1 my_string = "I got %s" % my_value
How is that not an RPC?
The difference is that the object you got back, my_value, is not the actual result, but a proxy for the result. It is returned immediately and subsequent code executed. There is no blocking at this point, as there would be in the RPC case. Only when you try to do something that depends on the value in some way might a block occur.
When the value is required, the future internally examines itself. Hopefully, over on the callee, the method got executed and the result calculated and sent back to us. In this case, the value is just returned where we need it (this is a bit of a fib ... but this is experimental, so ...)
If the callee hasn't sent us the value, trying to access the value of the future blocks, as if it were an RPC continuation.
How might I use this?
A very simple but pretty useful example (my first use case) was needing to fetch a bunch of web pages. Say I have an actor that represents a web site and I want to get a few pages:
1 root = web_site.fetch( "/" )
2 index = web_site.fetch( "/index.html" )
3 robots = web_site.fetch( "/robots.txt" )
4 result = root + " " + index + " " + robots
If web_site is a normal actor name, these are RPCs, the implication being that I won't even try to fetch the subsequent pages until the earlier ones have been completely returned. Since this is all network I/O, this takes a while and I'm mostly sitting around doing nothing useful. I'm generating latency for no good reason.
I could use release names ... but then how would I get the result? There are answers to this. It's certainly possible to do this without futures, but the futures code is just so very pretty:
1 future_web_site = Dramatis.future( web_site )
2 root = future_web_site.fetch( "/" )
3 index = future_web_site.fetch( "/index.html" )
4 robots = future_web_site.fetch( "/robots.txt" )
5 result = root + " " + index + " " + robots
In this case, all the fetches are started. When the final string concatenation tries to convert the futures to strings, the futures go see if their value has been returned and wait if they aren't.
If you're a parallel-type person, that is a simple fork-join without fork or join.
Is this experimental?
Yup. I don't know exactly how well this works. It's unclear when the proxy gets evaluated and how good a proxy it can be.
Are there limitations?
Yes. Right now, the the futures have to be evaluated within the same actor that made the original call. This almost certainly isn't a good idea.
What happens to exceptions in futures?
I don't know. See "experimental" above.
Do these limitations make Dramatis futures useless?
I don't think so. I think they probably can't be naively used willy-nilly, but I think used with care, they can be very useful. (The only reason I actually make of point of this is that futures are so pretty, it's very tempting to use them willy-nilly.)
Are there any continuations you haven't talked about?
Yes, the function/block/proc call. The idea is that you specify a block of code to receive the value returned from an actor call. Since the way blocks and functions are represented is pretty different between Ruby and Python, the implementation is fairly different.
In Ruby, you can ask for a block of code to be called as the result of an actor method call:
1 ( interface( actor_name ).continue { |result| do_something( result ) } ).a_method
In Python, you should be able to specify a method reference (not sure if this works; let me know if you need it):
def a_block( result ):
do_something( result )
( Dramatis.interface( actor_name ).continuation( { result: a_block } ) ).a_method()
Doesn't that syntax suck?
Yes. Well, maybe.
Aren't there race conditions if the code in the block changes state in the call stack or the object?
No. The block is effectively an unnamed method of the actor and goes through execution scheduling like all other actor methods.