On Fri, Jun 17, 2011 at 6:25 PM, David Masover <ninja / slaphack.com> wrote:

> Still, I shouldn't have to create an entire new actor, link it to your
> actor,
> and have it trap errors in order to find the actual exception I caused
> which
> lead to the actor's death. Maybe it's appropriate for bang methods to
> return
> some object which can be used to retrieve an exception?
>

If you want that sort of behavior, you can use the built-in
Celluloid::Future functionality. It does exactly what you describe, calling
a block asynchronously, then letting you retrieve the exception (or value)
later. If an exception was raised in the block given to the future
originally, it will be re-raised when the value is requested every single
time.

If something goes wrong in an async call, you can either handle the error
within that method directly, or rely on the supervisor to restart the object
in a clean state. Really I think supervisors are going to be the de facto
way to handle errors in asynchronous calls. I don't think there's a lot of
good use cases for having callers handle errors in asynchronous calls that
aren't already covered by Celluloid::Future.

That's what I was trying to do, except I wasn't planning to deadlock. I was
> planning to allow the call... somehow. Basically, if you had any sort of
> pattern where two objects call methods on each other, it should work the
> way
> it does synchronously.
>
> I think this makes sense, semantically. After all, if an actor calls a
> method
> on itself, we don't get any sort of deadlock. If an actor calls a method on
> another object running in the same thread, which then calls a method on the
> actor, at least with my implementation, this also doesn't deadlock -- and
> in
> yours, if I pass 'self' around, we get the same result. Why should it be
> different if I call a method on another _actor_ which then calls a method
> on
> me?
>
> Still, it's tricky to come up with an efficient way to do this, and I never
> managed to get anything to work, no matter how inefficient.
>

Hmmmmmmmmmmm!

I think the best approach would be to wrap the dispatching of incoming calls
in a fiber. Whenever that fiber makes an outgoing call to another actor, it
defers back to the central receive loop which processes the mailbox. This
would let an actor continue processing incoming calls while waiting for a
response to a call.

You're actually the second person I've talked to who's proposed this in
regard to handling circular call chains, the other person was Steven Parkes
who created the Dramatis actor framework. At the time I had my head in
Reia/Erlang, where gen_server state is pure functional and immutable and
there would really be no way to implement this sort of approach. In a
language like Ruby, though, it's possible, and would actually be quite
similar to what you could do with plain old Ruby objects.

So, there is a way, but you probably won't like it...
>

You're right, I don't like that at all :)


> I was much more interested in getting the semantics right, to show that it
> can
> be done, rather than making it performant and immediately useful. Like you,
> I
> wanted to use this to sort of prototype those semantics, with the hope that
> they would get into something like Reia eventually. (I started this before
> I
> heard of Reia, and probably before Reia was in any way practical, so I
> wasn't
> deliberately reinventing the wheel.)
>

Well, now you definitely have me thinking. If I do allow an actor to process
multiple calls using fibers, I've definitely left the realm of what could be
done in a language like Reia. That sort of approach relies directly on
concurrent objects having mutable hidden state.

While this approach couldn't apply to Reia, I really like it's semantics,
and I think it solves the long-standing problem of circular calls. My answer
to this question for the past two years has been "circular calls are an
error", when really there should be a way to make them work.

Looking again, maybe the supervisor already does this?
>
>  supervisor = Sheen.supervise "Charlie Sheen"
>  charlie = supervisor.actor
>
> This would solve both problems, right? (Assuming the supervisor is itself
> threadsafe.) It could use some sugar, but I'm not entirely sure how.
>

The easiest way to add some sugar would be to have the supervisor create a
thread safe proxy object that always refers to the latest version of a given
actor. That way you could just use that object directly rather than always
having to call supervisor.actor to get to it.

-- 
Tony Arcieri