class Celluloid::Actor
Actors are Celluloid's concurrency primitive. They're implemented as normal Ruby objects wrapped in threads which communicate with asynchronous messages.
Attributes
Public Class Methods
Obtain all running actors in the system
# File lib/celluloid/actor.rb, line 50 def all Celluloid.actor_system.running end
Invoke a method asynchronously on an actor via its mailbox
# File lib/celluloid/actor.rb, line 38 def async(mailbox, meth, *args, &block) proxy = AsyncProxy.new(mailbox, "UnknownClass") proxy.method_missing(meth, *args, &block) end
Invoke a method on the given actor via its mailbox
# File lib/celluloid/actor.rb, line 32 def call(mailbox, meth, *args, &block) proxy = SyncProxy.new(mailbox, "UnknownClass") proxy.method_missing(meth, *args, &block) end
Obtain the current actor
# File lib/celluloid/actor.rb, line 18 def current actor = Thread.current[:celluloid_actor] raise NotActorError, "not in actor scope" unless actor actor.behavior_proxy end
Call a method asynchronously and retrieve its value later
# File lib/celluloid/actor.rb, line 44 def future(mailbox, meth, *args, &block) proxy = FutureProxy.new(mailbox, "UnknownClass") proxy.method_missing(meth, *args, &block) end
Wait for an actor to terminate
# File lib/celluloid/actor.rb, line 95 def join(actor, timeout = nil) actor.thread.join(timeout) actor end
Forcibly kill a given actor
# File lib/celluloid/actor.rb, line 89 def kill(actor) actor.thread.kill actor.mailbox.shutdown if actor.mailbox.alive? end
Link to another actor
# File lib/celluloid/actor.rb, line 67 def link(actor) monitor actor Thread.current[:celluloid_actor].links << actor end
Are we bidirectionally linked to the given actor?
# File lib/celluloid/actor.rb, line 84 def linked_to?(actor) monitoring?(actor) && Thread.current[:celluloid_actor].links.include?(actor) end
Watch for exit events from another actor
# File lib/celluloid/actor.rb, line 55 def monitor(actor) raise NotActorError, "can't link outside actor context" unless Celluloid.actor? Thread.current[:celluloid_actor].linking_request(actor, :link) end
Are we monitoring the given actor?
# File lib/celluloid/actor.rb, line 79 def monitoring?(actor) actor.links.include? Actor.current end
# File lib/celluloid/actor.rb, line 101 def initialize(behavior, options) @behavior = behavior @actor_system = options.fetch(:actor_system) @mailbox = options.fetch(:mailbox_class, Mailbox).new @mailbox.max_size = options.fetch(:mailbox_size, nil) @task_class = options[:task_class] || Celluloid.task_class @exit_handler = method(:default_exit_handler) @exclusive = options.fetch(:exclusive, false) @tasks = TaskSet.new @links = Links.new @signals = Signals.new @timers = Timers::Group.new @receivers = Receivers.new(@timers) @handlers = Handlers.new @running = false @name = nil handle(SystemEvent) do |message| handle_system_event message end end
Obtain the name of the current actor
# File lib/celluloid/actor.rb, line 25 def registered_name actor = Thread.current[:celluloid_actor] raise NotActorError, "not in actor scope" unless actor actor.name end
Unlink from another actor
# File lib/celluloid/actor.rb, line 73 def unlink(actor) unmonitor actor Thread.current[:celluloid_actor].links.delete actor end
Stop waiting for exit events from another actor
# File lib/celluloid/actor.rb, line 61 def unmonitor(actor) raise NotActorError, "can't link outside actor context" unless Celluloid.actor? Thread.current[:celluloid_actor].linking_request(actor, :unlink) end
Public Instance Methods
Schedule a block to run at the given time
# File lib/celluloid/actor.rb, line 235 def after(interval, &block) @timers.after(interval) { task(:timer, &block) } end
# File lib/celluloid/actor.rb, line 137 def behavior_proxy @behavior.proxy end
Clean up after this actor
# File lib/celluloid/actor.rb, line 336 def cleanup(exit_event) Celluloid::Probe.actor_died(self) if $CELLULOID_MONITORING @mailbox.shutdown @links.each do |actor| if actor.mailbox.alive? actor.mailbox << exit_event end end tasks.to_a.each(&:terminate) rescue => ex # TODO: metadata Logger.crash("CLEANUP CRASHED!", ex) end
# File lib/celluloid/actor.rb, line 313 def default_exit_handler(event) raise event.reason if event.reason end
Schedule a block to run at the given time
# File lib/celluloid/actor.rb, line 240 def every(interval, &block) @timers.every(interval) { task(:timer, &block) } end
# File lib/celluloid/actor.rb, line 220 def handle(*patterns, &block) @handlers.handle(*patterns, &block) end
Handle any exceptions that occur within a running actor
# File lib/celluloid/actor.rb, line 318 def handle_crash(exception) # TODO: add meta info Logger.crash("Actor crashed!", exception) shutdown ExitEvent.new(behavior_proxy, exception) rescue => ex Logger.crash("ERROR HANDLER CRASHED!", ex) end
Handle exit events received by this actor
# File lib/celluloid/actor.rb, line 307 def handle_exit_event(event) @links.delete event.actor @exit_handler.call(event) end
Handle standard low-priority messages
# File lib/celluloid/actor.rb, line 279 def handle_message(message) unless @handlers.handle_message(message) unless @receivers.handle_message(message) Logger.debug "Discarded message (unhandled): #{message}" if $CELLULOID_DEBUG end end message end
Handle high-priority system event messages
# File lib/celluloid/actor.rb, line 289 def handle_system_event(event) if event.instance_of? ExitEvent handle_exit_event(event) elsif event.instance_of? LinkingRequest event.process(links) elsif event.instance_of? NamingRequest @name = event.name Celluloid::Probe.actor_named(self) if $CELLULOID_MONITORING elsif event.instance_of? TerminationRequest terminate elsif event.instance_of? SignalConditionRequest event.call else Logger.debug "Discarded message (unhandled): #{message}" if $CELLULOID_DEBUG end end
Perform a linking request with another actor
# File lib/celluloid/actor.rb, line 176 def linking_request(receiver, type) Celluloid.exclusive do receiver.mailbox << LinkingRequest.new(Actor.current, type) system_events = [] Timers::Wait.for(LINKING_TIMEOUT) do |remaining| begin message = @mailbox.receive(remaining) do |msg| msg.is_a?(LinkingResponse) && msg.actor.mailbox.address == receiver.mailbox.address && msg.type == type end rescue TimeoutError next # IO reactor did something, no message in queue yet. end if message.instance_of? LinkingResponse Celluloid::Probe.actors_linked(self, receiver) if $CELLULOID_MONITORING # We're done! system_events.each { |ev| @mailbox << ev } return elsif message.is_a? SystemEvent # Queue up pending system events to be processed after we've successfully linked system_events << message else raise "Unexpected message type: #{message.class}. Expected LinkingResponse, NilClass, SystemEvent." end end raise TimeoutError, "linking timeout of #{LINKING_TIMEOUT} seconds exceeded" end end
Receive an asynchronous message
# File lib/celluloid/actor.rb, line 225 def receive(timeout = nil, &block) loop do message = @receivers.receive(timeout, &block) break message unless message.is_a?(SystemEvent) handle_system_event(message) end end
Run the actor loop
# File lib/celluloid/actor.rb, line 147 def run while @running begin @timers.wait do |interval| interval = 0 if interval and interval < 0 if message = @mailbox.check(interval) handle_message(message) break unless @running end end rescue MailboxShutdown @running = false end end shutdown rescue Exception => ex handle_crash(ex) raise unless ex.is_a? StandardError end
# File lib/celluloid/actor.rb, line 141 def setup_thread Thread.current[:celluloid_actor] = self Thread.current[:celluloid_mailbox] = @mailbox end
Handle cleaning up this actor after it exits
# File lib/celluloid/actor.rb, line 327 def shutdown(exit_event = ExitEvent.new(behavior_proxy)) @behavior.shutdown cleanup exit_event ensure Thread.current[:celluloid_actor] = nil Thread.current[:celluloid_mailbox] = nil end
Send a signal with the given name to all waiting methods
# File lib/celluloid/actor.rb, line 211 def signal(name, value = nil) @signals.broadcast name, value end
Sleep for the given amount of time
# File lib/celluloid/actor.rb, line 273 def sleep(interval) sleeper = Sleeper.new(@timers, interval) Celluloid.suspend(:sleeping, sleeper) end
# File lib/celluloid/actor.rb, line 126 def start @running = true @thread = ThreadHandle.new(@actor_system, :actor) do setup_thread run end @proxy = ActorProxy.new(@thread, @mailbox) Celluloid::Probe.actor_created(self) if $CELLULOID_MONITORING end
Run a method inside a task unless it's exclusive
# File lib/celluloid/actor.rb, line 352 def task(task_type, meta = nil) @task_class.new(task_type, meta) { if @exclusive Celluloid.exclusive { yield } else yield end }.resume end
Terminate this actor
# File lib/celluloid/actor.rb, line 171 def terminate @running = false end
# File lib/celluloid/actor.rb, line 244 def timeout(duration) bt = caller task = Task.current timer = @timers.after(duration) do exception = Task::TimeoutError.new("execution expired") exception.set_backtrace bt task.resume exception end yield ensure timer.cancel if timer end
Wait for the given signal
# File lib/celluloid/actor.rb, line 216 def wait(name) @signals.wait name end