On Friday, June 17, 2011 05:54:35 PM Tony Arcieri wrote:
> On Fri, Jun 17, 2011 at 4:25 PM, David Masover <ninja / slaphack.com> wrote:
> > Speaking of semantics, my biggest problems were:
> >  - Is there a way to handle exceptions in a Ruby-esque way?
> > 
> > It looks like I have to explicitly trap actor exceptions. But this is a
> > place
> > I have to be aware that this is an actor and not just a Ruby object.
> 
> When making synchronous calls, exceptions which occur in the context of the
> receiver are automatically reraised in the caller just like any other Ruby
> object, regardless of if you're using any actor-specific features like
> linking or trapping exits. It will also crash the receiver.

That makes sense.

But when making asynchronous calls:

> Your parallel map is a perfect example of what I'd actually want here: If
> an
> 
> > exception is raised, re-raise that exception when I try to call methods
> > on the
> > actor, rather than DeadActorException. Is there a reason not to do that?
> 
> I think reraising the original exception in the caller context gives the
> caller appropriate context to bail out of whatever they're doing and avoid
> making subsequent calls at all. Other threads may be trying to make calls,
> and if an exception entirely unrelated to the calls they're making is
> raised because the actor is dead, I think that'd be rather confusing.

That makes a lot of sense.

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?

>  - How do you handle cycles?
> 
> 
> I don't, but they can be detected if you don't mind a bit of a performance
> penalty. For that I need to track chains of synchronous calls and detect if
> the receiver of a given method exists earlier in the call chain. If so,
> Celluloid can raise an exception in the caller context indicating that a
> deadlock would occur. This is a bit of a glaring deficiency right now.

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.

> First, 'self' wouldn't be an actor reference, it'd be the object itself,
> 
> > right?
> 
> Yes. I provide Celluloid.current_actor to use in lieu of self. This feels a
> bit ugly, but I don't know of any way to redefine self (nor do I think
> that'd be a particularly good idea either)

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

One experiment I did here was:

 - Grab all methods, stuff them in a hash, and undef them.
 - When a method is called, intercept it like a proxy, and do whatever I need 
to do to get it to the right thread.
 - To actually call the method, grab the method object, bind it to self, and 
apply.

It's not really redefining self, but it accomplishes what's needed here.

However, I suspect it breaks all kinds of inheritance, unless I also absorb 
that kind of functionality -- that is, whenever something inherits from this 
class, give it a clone of the hash to start with.

One advantage to this approach is that I could very easily allow some methods 
to require the actor thread, and some methods to run in the calling thread -- 
by default, they run in the actor thread. The obvious application is when a 
method really doesn't need to involve the actor:

  class Sheen
    include Suit

    # define a new threadsafe method
    threadsafe :status do
      :winning
    end
  end

But maybe you want to anyway:

  class Sheen
    include Suit

    attr_reader :status, :sober
    def initialize
      @status = :winning
      @sober = true
    end
    def fall_off_wagon!
      @status = :WINNING
      @sober = false
    end
    def is_off_wagon?
      !sober && status == :WINNING
    end

    threadsafe :hello do
      if is_off_wagon?
        puts 'WINNING!!!'
      else
        puts 'Hi.'
      end
    end
  end

It makes sense that fall_off_wagon! and is_off_wagon? should run on the actor 
thread. It makes sense that the 'hello' method doesn't really need to run on 
the actor thread, and maybe it's a performance improvement that the Sheen 
thread doesn't actually have to talk, or ever wait for output, etc. I'm really 
reaching here, because I don't actually have a real application for this, but 
I don't think it's entirely unreasonable -- kind of like the Java 
'synchronized' keyword, except message-passing behavior is the default.

But notice that the 'threadsafe' call doesn't have to call 'self' at all. In 
fact, that syntax is actually syntactic sugar for:

  def hello
    ...
  end
  threadsafe :hello

I'm still just writing normal methods, but every method call, whether it's to 
'self' or not, is still going through the same logic to determine whether or 
not it needs to run on the Sheen thread.

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.)

> > I really don't like the registry -- one flat namespace of actors? Ew. But
> > I'm not really sure how to solve this -- some sort of super-reference,
> > which
> > points to the currently-alive actor from a given supervisor? But then I
> > might
> > send something which asynchronously kills the actor, and I'll get a fresh
> > actor for the next line, which seems like a bad thing. There needs to be
> > some
> > clean semantics for "Give me a reference to the currently-alive version
> > of this actor" which doesn't rely on a global, flat registry.
> 
> I don't know of a better solution. This is the same approach Erlang uses.
> The only evolution it's seen in recent history is systems like Ulf Wiger's
> gproc.

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.

> > And a thought: I just had every method return a future. If people wanted
> > something to run asynchronously, all they had to do is ignore the future.
> > The
> > downside is that this makes it hard to force things to be synchronous. I
> > actually thought of this as a good thing -- if I make the call up at the
> > top
> > of a method, and don't use the result till the bottom, that's some
> > surprise parallelism right there. The biggest problem is that if there's
> > an exception,
> > you don't know about it until the future is resolved.
> 
> That's an interesting approach, but a bit different than the one I'm
> shooting for in Celluloid, where I want concurrent objects to quack like
> normal Ruby objects as much as possible.

And this does quack like a normal Ruby object, unless something goes wrong and 
an exception is raised. But I was never quite satisfied with how exceptions 
were dealt with. For one thing, it's not OK that someone might ignore a future 
and never see the exception.