Class: Phronomy::Actor

Inherits:
Object
  • Object
show all
Defined in:
lib/phronomy/actor.rb

Overview

Lightweight synchronous actor backed by a dedicated +Thread+ and a +Queue+.

A caller submits work via #call, which blocks until the actor's thread finishes executing the block and then returns the result (or re-raises any exception that occurred inside the actor).

=== Reentrant safety

If #call is invoked from within the actor's own thread (i.e. from inside a block that is already executing on this actor), the block is executed directly in the current thread instead of being pushed onto the queue. This prevents deadlocks in deeply nested call paths without requiring callers to track whether they are already "inside" the actor.

=== Usage

actor = Phronomy::Actor.new result = actor.call { expensive_operation() } # blocks caller; runs on actor thread actor.stop # graceful shutdown

Instance Method Summary collapse

Constructor Details

#initializeActor

Returns a new instance of Actor.



24
25
26
27
28
29
30
31
32
33
# File 'lib/phronomy/actor.rb', line 24

def initialize
  @queue = Queue.new
  @thread = Thread.new do
    loop do
      task = @queue.pop
      break if task == :stop
      task.call
    end
  end
end

Instance Method Details

#call { ... } ⇒ Object

Run +block+ on the actor's thread and return its result.

If the current thread is already the actor's thread (reentrant call), the block is executed inline to prevent deadlocks.

Any exception raised inside the block is captured and re-raised in the calling thread.

Yields:

  • block to execute on the actor's thread

Returns:

  • the return value of the block



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/phronomy/actor.rb', line 45

def call(&block)
  return block.call if Thread.current == @thread

  done = Queue.new
  @queue.push(-> {
    begin
      done.push([true, block.call])
    rescue => e
      done.push([false, e])
    end
  })
  success, value = done.pop
  raise value unless success
  value
end

#stopObject

Send a +:stop+ sentinel to gracefully terminate the actor's thread. Pending tasks already in the queue will still be processed before the thread exits.



64
65
66
# File 'lib/phronomy/actor.rb', line 64

def stop
  @queue.push(:stop)
end