On Friday, June 17, 2011 05:54:35 PM Tony A. wrote:
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:
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 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.
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.