On Friday, June 17, 2011 03:21:48 PM Tony Arcieri wrote:
> Celluloid is a concurrent object framework for Ruby inspired by Erlang
> and the Actor Model:
> 
> * Github: http://github.com/tarcieri/celluloid
> * RDoc: http://celluloid.github.com/
> 
> Celluloid provides thread-backed objects that run concurrently,
> allowing the familiarity of plain old Ruby objects for the
> most common use cases, but also the ability to call methods
> asynchronously. Asynchronous method calls allow the receiver
> to do things in the background while the caller carries on with its
> business.

That's pretty awesome. I was working on something like this, but ended up 
abandoning it after I had it deadlocking for awhile, and I couldn't get the 
semantics right.



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

 - How do you handle cycles?

An actor can only process one method at a time, which makes sense. One thing I 
wanted to do was give two actors references to each other, so they can send 
messages back and forth. Futures seem like a good solution to avoid a lot of 
annoying asynchronous callbacks. Two problems:

class Foo
  include Celluloid::Actor
  attr_reader :bar, :value
  def initialize
    @bar = Bar.spawn self
    @value = 3
  end

  def first
    bar.second!
    bar.result * 2
  end
end

class Bar
  include Celluloid::Actor
  attr_reader :foo
  def initialize parent
    @foo = parent
  end

  def second
    @result = foo.value + 3
  end

  def result
    @result
  end
end


This doesn't actually run -- it seems to deadlock on 'new'. But there are two 
other problems: First, 'self' wouldn't be an actor reference, it'd be the 
object itself, right? But more importantly, what happens when I call 'first'? 
It looks like we deadlock again, but it seems reasonable that since Foo is 
waiting for Bar, that maybe Bar can now call another method on Foo, acting as 
though the two actors were just plain objects and we're just building a call-
stack.

That's the part I could never get working.


Couple other annoyances:

 - Why spawn instead of new? It seems like if I've decided to make something 
an actor, it's going to expect to be an actor most of the time -- it's hard to 
imagine a case where I want the original 'new' instead.

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

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.