module Celluloid # Supervise actor instances in a container. module Supervision class Container include Celluloid trap_exit :restart_actor class << self def define(options) Configuration.define(top(options)) end def deploy(options) Configuration.deploy(top(options)) end def top(options) { as: options.delete(:as), type: (options.delete(:type) || self), branch: (options.delete(:branch) || :services), supervise: options.delete(:supervise) || [] } end # Actors or sub-applications to be supervised def blocks @blocks ||= [] end # Start this application (and watch it with a supervisor) def run!(options = {}) container = new(options) do |g| blocks.each do |block| block.call(g) end end container end # Run the application in the foreground with a simple watchdog def run(options = {}) loop do supervisor = run!(options) # Take five, toplevel supervisor sleep 5 while supervisor.alive? # Why 5? Internals::Logger.error "!!! Celluloid::Supervision::Container #{self} crashed. Restarting..." end end end finalizer :finalize attr_accessor :registry # Start the container. def initialize(options = {}) options = { registry: options } if options.is_a? Internals::Registry @state = :initializing @actors = [] # instances in the container @registry = options.delete(:registry) || Celluloid.actor_system.registry @branch = options.delete(:branch) || :services yield current_actor if block_given? end execute_block_on_receiver :initialize, :supervise, :supervise_as def add(configuration) Configuration.valid?(configuration, true) @actors << Instance.new(configuration.merge(registry: @registry, branch: @branch)) @state = :running add_accessors configuration Actor.current end def add_accessors(configuration) if configuration[:as] unless methods.include? configuration[:as] self.class.instance_eval do define_method(configuration[:as]) do @registry[configuration[:as]] end end end end end def remove_accessors; end def remove(actor) actor = Celluloid::Actor[actor] if actor.is_a? Symbol instance = find(actor) instance.terminate if instance end def actors @actors.map(&:actor) end def find(actor) @actors.find do |instance| instance.actor == actor end end def [](actor_name) @registry[actor_name] end # Restart a crashed actor def restart_actor(actor, reason) return if @state == :shutdown instance = find(actor) raise "a container instance went missing. This shouldn't be!" unless instance if reason exclusive { instance.restart } else instance.cleanup @actors.delete(instance) end end def shutdown @state = :shutdown finalize end private def finalize if @actors @actors.reverse_each do |instance| instance.terminate @actors.delete(instance) end end end end end end