From: David Masover Date: 2011-06-18T07:25:40+09:00 Subject: Re: [ANN] celluloid 0.0.3: a concurrent object framework for Ruby 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.