From: David Masover Date: 2011-06-20T02:57:32+09:00 Subject: Re: [ANN] celluloid 0.0.3: a concurrent object framework for Ruby On Saturday, June 18, 2011 05:40:09 PM Tony Arcieri wrote: > On Fri, Jun 17, 2011 at 6:25 PM, David Masover 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? [...] > 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. Maybe not, other than that Future applies to a block, where I want the result of a method call. Maybe it's not a good use case, but this still seems cool: actors.map(&:some_calculation).reduce{|a,b| ...} I guess the bigger annoyance, though I didn't really have a good solution, is that adopting bang to mean "asynchronous" means that these don't quite quack like Ruby objects anymore -- they can't have bang methods of their own that mean something, and every method gets a bang whether it makes sense or not. > 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, it's been awhile since I looked at Erlang, but I don't actually see an obstacle to this in Erlang itself or in the VM. Maybe in gen_server. But there's really nothing preventing me from creating the effect of mutable state in a generic Erlang process, right? > > So, there is a way, but you probably won't like it... > > You're right, I don't like that at all :) I don't like it either, and I avoided it as much as I could. One thing I thought of was trying to filter the reference any way that it would get out of the object, since I was already wrapping things in futures and the like anyway. The problem is, there's no guarantee that a bare 'self' will cross any filter I set up. I mean, it'd be almost trivial to catch this: def get_self self end But what if they stuff it deep in some data structure? What if it's in a call to some other object? The other option was to make the blankslate-like proxy class a child class of the original, so calling method 'foo' would look like: original.instance_method(:foo).bind(self).call(*args, &block) That's a minor win in that it might be somewhat more tolerant of the parent classes being redefined. But it's not much of a win, because I have to watch the parent classes anyway to remove methods from the child -- in fact, the only sane way I could find to do that was to watch every single method created. So this doesn't really buy me much. A way to make that significantly better would be to bind those methods to a BasicObject proxy instead, but you can't do that, because binding methods is one case where Ruby is _not_ duck-typed at all -- you can only bind a method to an object which is actually an instance of that class, or something which inherits from or includes it. In the end, while the approach I went with is pretty ridiculous, I still like it for the simple reason that if I forget to call Celluloid.current_actor instead of self, I've completely broken the concurrency model by doing the normal Ruby thing. With my approach, aside from the fact that my attempt at cycles currently deadlocks, I can still more or less pretend that an actor is a normal object. > > 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. Except in this case, I'm thinking of the call to 'supervisor.actor' as being something like starting a transaction. That is, let's say someone actually convinces (or court-orders) Sheen to go to rehab before we let him back on the road. So we might have a series of calls like: charlie.rehab! charlie.give license But maybe the withdrawal kills him. If 'charlie' is a thread-safe proxy which always refers to the latest version, we end up with a situation where rehab kills him, we get a new version who hasn't been to rehab, and we give the new version a license. This is clearly an error, and worse, it's almost silent. By contrast, if we force people to call something like supervisor.actor to start something like this, we end up with the best of both worlds -- we're guaranteed he's alive before we send him to rehab, and we either fail (because we have a dead actor) or ensure that he's actually recovered before we give him the license. Or, in other words, any time we're sending more than one message to an actor and depending on those messages being processed in order, we need to know, at a _minimum_, that we're talking to the same actor. On the other hand, in a situation like this, we also have to think about what other calls might happen in between -- for example: charlie.rehab! if charlie.out_of_control? There's potentially a race condition between receiving the out_of_control? value and sending him to rehab. Still, if someone else kills charlie, he's just as dead and I still don't want to give the new version a license until he goes through rehab again. Also: There has got to be a better metaphor.