Skip to content
Eric Wu edited this page Oct 30, 2013 · 10 revisions

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.

Clone this wiki locally