-
-
Notifications
You must be signed in to change notification settings - Fork 272
Linking
Whenever any unhandled exceptions occur in any of the methods of an actor, that actor crashes and dies. Let's start with an example:
class JamesDean
include Celluloid
class CarInMyLaneError < StandardError; end
def drive_little_bastard
raise CarInMyLaneError, "that guy's gotta stop. he'll see us"
end
end
Now, let's have James drive Little Bastard and see what happens:
>> james = JamesDean.new
=> #<Celluloid::Actor(JamesDean:0x1068)>
>> james.async.drive_little_bastard
=> nil
>> james
=> #<Celluloid::Actor(JamesDean:0x1068) dead>
When we told james asynchronously to drive Little Bastard, it killed him! If we were Elizabeth Taylor, co-star in James' latest film at the time of his death, we'd certainly want to know when he died. So how can we do that?
Actors can link to other actors they're interested in and want to receive crash notifications from. In order to receive these events, we need to use the trap_exit method to be notified of them. Let's look at how a hypothetical Elizabeth Taylor object could be notified that James Dean has crashed:
class ElizabethTaylor
include Celluloid
trap_exit :actor_died
def actor_died(actor, reason)
p "Oh no! #{actor.inspect} has died because of a #{reason.class}"
end
end
We've now used the trap_exit method to configure a callback which is invoked whenever any linked actors crashed. Now we need to link Elizabeth to James so James' crash notifications get sent to her:
>> james = JamesDean.new
=> #<Celluloid::Actor(JamesDean:0x11b8)>
>> elizabeth = ElizabethTaylor.new
=> #<Celluloid::Actor(ElizabethTaylor:0x11f0)>
>> elizabeth.link james
=> #<Celluloid::Actor(JamesDean:0x11b8)>
>> james.async.drive_little_bastard
=> nil
"Oh no! #<Celluloid::Actor(JamesDean:0x11b8) dead> has died because of a JamesDean::CarInMyLaneError"
Elizabeth called the link method to receive crash events from James. Because Elizabeth was linked to James, when James crashed, James' exit message was sent to her. Because Elizabeth was trapping the exit messages she received using the trap_exit method, the callback she specified was invoked, allowing her to take action (in this case, printing the error). But what would happen if she weren't trapping exits? Let's break James apart into two separate objects, one for James himself and one for Little Bastard, his car:
class PorscheSpider
include Celluloid
class CarInMyLaneError < StandardError; end
def drive_on_route_466
raise CarInMyLaneError, "head on collision :("
end
end
class JamesDean
include Celluloid
def initialize
@little_bastard = PorscheSpider.new_link
end
def drive_little_bastard
@little_bastard.drive_on_route_466
end
end
If you take a look in JamesDean#initialize, you'll notice that to create an instance of PorcheSpider, James is calling the new_link method.
This method works similarly to new, except it combines new and link into a single call.
Now what happens if we repeat the same scenario with Elizabeth Taylor watching for James Dean's crash?
>> james = JamesDean.new
=> #<Celluloid::Actor(JamesDean:0x1108) @little_bastard=#<Celluloid::Actor(PorscheSpider:0x10ec)>>
>> elizabeth = ElizabethTaylor.new
=> #<Celluloid::Actor(ElizabethTaylor:0x1144)>
>> elizabeth.link james
=> #<Celluloid::Actor(JamesDean:0x1108) @little_bastard=#<Celluloid::Actor(PorscheSpider:0x10ec)>>
>> james.async.drive_little_bastard
=> nil
"Oh no! #<Celluloid::Actor(JamesDean:0x1108) dead> has died because of a PorscheSpider::CarInMyLaneError"
When Little Bastard crashed, it killed James as well. Little Bastard killed James, and because Elizabeth was trapping James' exit events, she received the notification of James' death.
Actors that are linked together propagate their error messages to all other actors that they're linked to. Unless those actors are trapping exit events, those actors too will die, like James did in this case. If you have many, many actors linked together in a large object graph, killing one will kill them all unless they are trapping exits.
This allows you to factor your problem into several actors. If an error occurs in any of them, it will kill off all actors used in a particular system. In general, you'll probably want to have a supervisor start a single actor which is in charge of a particular part of your system, and have that actor new_link to other actors which are part of the same system. If any error occurs in any of these actors, all of them will be killed off and the entire subsystem will be restarted by the supervisor in a clean state.
If for any reason you've linked to an actor and want to sever the link, there's a corresponding unlink method to remove links between actors.
Always feel free to:
- Visit the
#celluloid
channel on freenode. - Post a bug report or feature request.
- Ask questions on our mailing list.