module Celluloid # Supervise collections of actors as a group class SupervisionGroup include Celluloid trap_exit :restart_actor class << self # Actors or sub-applications to be supervised def blocks @blocks ||= [] end # Start this application (and watch it with a supervisor) def run!(registry = nil) group = new(registry) do |_group| blocks.each do |block| block.call(_group) end end group end # Run the application in the foreground with a simple watchdog def run(registry = nil) loop do supervisor = run!(registry) # Take five, toplevel supervisor sleep 5 while supervisor.alive? Logger.error "!!! Celluloid::SupervisionGroup #{self} crashed. Restarting..." end end # Register an actor class or a sub-group to be launched and supervised # Available options are: # # * as: register this application in the Celluloid::Actor[] directory # * args: start the actor with the given arguments def supervise(klass, options = {}) blocks << lambda do |group| group.add klass, options end end # Register a pool of actors to be launched on group startup # Available options are: # # * as: register this application in the Celluloid::Actor[] directory # * args: start the actor pool with the given arguments def pool(klass, options = {}) blocks << lambda do |group| group.pool klass, options end end end finalizer :finalize # Start the group def initialize(registry = nil) @members = [] @registry = registry || Celluloid.actor_system.registry yield current_actor if block_given? end execute_block_on_receiver :initialize, :supervise, :supervise_as def supervise(klass, *args, &block) add(klass, :args => args, :block => block) end def supervise_as(name, klass, *args, &block) add(klass, :args => args, :block => block, :as => name) end def pool(klass, options = {}) options[:method] = 'pool_link' add(klass, options) end def add(klass, options) member = Member.new(@registry, klass, options) @members << member member.actor end def actors @members.map(&:actor) end def [](actor_name) @registry[actor_name] end # Restart a crashed actor def restart_actor(actor, reason) member = @members.find do |_member| _member.actor == actor end raise "a group member went missing. This shouldn't be!" unless member if reason member.restart else member.cleanup @members.delete(member) end end # A member of the group class Member def initialize(registry, klass, options = {}) @registry = registry @klass = klass # Stringify keys :/ options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h } @name = options['as'] @block = options['block'] @args = options['args'] ? Array(options['args']) : [] @method = options['method'] || 'new_link' @pool = @method == 'pool_link' @pool_size = options['size'] if @pool start end attr_reader :name, :actor def start # when it is a pool, then we don't splat the args # and we need to extract the pool size if set if @pool options = {:args => @args} options[:size] = @pool_size if @pool_size @args = [options] end @actor = @klass.send(@method, *@args, &@block) @registry[@name] = @actor if @name end def restart @actor = nil cleanup start end def terminate cleanup @actor.terminate if @actor rescue DeadActorError end def cleanup @registry.delete(@name) if @name end end private def finalize @members.reverse_each(&:terminate) if @members end end end