Class: Thespian::Actor

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

Constant Summary collapse

STATE_INITIALIZED =

The state an actor is in after it has been created, but before it enters the message processing loop.

:initialized
STATE_RUNNING =

The state for when an actor is in the message processing loop.

:running
STATE_FINISHED =

The state for when an actor has exited the message processing loop (either by error or intentially).

:finished
DEFAULT_OPTIONS =
{
  :strict    => true,
  :trap_exit => false,
  :object    => nil
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}, &block) ⇒ Actor

call-seq:

new(options = {}){ |message| ... }

Create a new actor using the given block to handles messages. The actor will be in the :initialized state, meaning the message processing loop hasn’t started yet (see #start).

options are the same as specified by #options.



38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/thespian/actor.rb', line 38

def initialize(options = {}, &block)
  self.options(DEFAULT_OPTIONS.merge(options))

  @state         = :initialized
  @receive_block = block
  @linked_actors = Set.new

  @mailbox = []
  @mailbox_lock = Monitor.new
  @mailbox_cond = @mailbox_lock.new_cond

end

Instance Attribute Details

#exceptionObject (readonly)

If an actor died due to an exception, it is stored here.



11
12
13
# File 'lib/thespian/actor.rb', line 11

def exception
  @exception
end

#stateObject (readonly)

Returns the actor’s state.



14
15
16
# File 'lib/thespian/actor.rb', line 14

def state
  @state
end

Instance Method Details

#<<(message) ⇒ Object

Add a message to the actor’s mailbox. May raise an exception according to #check_alive!



132
133
134
135
136
137
138
139
140
# File 'lib/thespian/actor.rb', line 132

def <<(message)
  check_alive! if options(:strict)
  message = message.new if message == Stop
  @mailbox_lock.synchronize do
    @mailbox << message
    @mailbox_cond.signal
  end
  self
end

#error?Boolean

Returns true if an error occurred that caused the actor to enter the :finished state.

Returns:

  • (Boolean)


168
169
170
# File 'lib/thespian/actor.rb', line 168

def error?
  !!@exception
end

#finished?Boolean

#state == :finished

Returns:

  • (Boolean)


163
164
165
# File 'lib/thespian/actor.rb', line 163

def finished?
  state == :finished
end

#initialized?Boolean

#state == :initialized

Returns:

  • (Boolean)


153
154
155
# File 'lib/thespian/actor.rb', line 153

def initialized?
  state == :initialized
end

call-seq:

link(actor)

Exceptions in actors will be propogated to actors that are linked to them. How they are propogated is determined by the :trap_exit option (see #options).

actor can either be an Actor instance or an instance of a class that included Thespian.



87
88
89
90
91
# File 'lib/thespian/actor.rb', line 87

def link(object)
  actor = object.kind_of?(Actor) ? object : object.actor
  actor.send(:_link, self)
  object
end

#options(arg = nil) ⇒ Object

call-seq:

options -> Hash
options(hash) -> Hash
options(symbol) -> value

Get or set options. Valid options are:

:strict (default true)

Require an actor to be running in order to put messages into its mailbox.

:trap_exit (default false)

If true, the actor will get a DeadActorError message in its mailbox when a linked actor raises an unhandled exception.

If given no arguments, returns a hash of options.

If given a hash, sets the options specified in the hash.

If given a symbol, returns that option’s value.



67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/thespian/actor.rb', line 67

def options(arg = nil)
  @options ||= {}

  case arg
  when Hash
    @options.merge!(arg)
  when Symbol
    @options[arg]
  when nil
    @options
  end
end

#running?Boolean

#state == :running

Returns:

  • (Boolean)


158
159
160
# File 'lib/thespian/actor.rb', line 158

def running?
  state == :running
end

#salvage_mailboxObject

Salvage mailbox contents from a dead actor (including the message it died on). Useful for restarting a dead actor while preserving its mailbox.



174
175
176
177
178
179
# File 'lib/thespian/actor.rb', line 174

def salvage_mailbox
  raise "cannot salvage mailbox from an actor that isn't finished" unless state == :finished
  @mailbox.dup.tap do |mailbox|
    mailbox.unshift(@last_message) if @last_message
  end
end

#startObject

Start the actor’s message processing loop. The thread that the loop is run on is guaranteed to have started by the time this method returns.

Raises:



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/thespian/actor.rb', line 95

def start

  # Don't let them start an already started actor.
  raise "already running" if running?

  # Can't raise an actor from the dead.
  raise @exception if @exception

  # IMPORTANT - Race condition!
  # This method and the thread both set @state. We don't want this method to
  # possibly overwrite how the thread sets @state, so we set the @state
  # before staring the thread.
  @state = :running

  # Declare local synchronization vars.
  lock = Monitor.new
  cond = lock.new_cond
  wait = true

   # Start the thread and have it signal when it's running.
  @thread = Thread.new do
    Thread.current[:actor] = options(:object).to_s
    lock.synchronize do
      wait = false
      cond.signal
    end
    run
  end

  # Block until the thread has signaled that it's running.
  lock.synchronize do
    cond.wait_while{ wait }
  end
end

#stopObject

Stop the actor. All pending messages will be processed before stopping the actor. Raises an exception if the actor is not #running? and :strict (see #options) is true.

Raises:



145
146
147
148
149
150
# File 'lib/thespian/actor.rb', line 145

def stop
  check_alive! if options(:strict)
  self << Stop.new
  @thread.join
  raise @exception if @exception
end