tribe 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,14 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rake/testtask"
2
3
 
3
- desc 'Start an IRB console with Workers loaded'
4
- task :console do
5
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
6
-
7
- require 'tribe'
8
- require 'tribe/benchmark'
9
- require 'irb'
10
-
11
- ARGV.clear
12
-
13
- IRB.start
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
14
8
  end
9
+
10
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "tribe"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -2,6 +2,7 @@ require 'set'
2
2
 
3
3
  require 'workers'
4
4
 
5
+ require 'tribe/version'
5
6
  require 'tribe/safe_set'
6
7
  require 'tribe/exceptions'
7
8
  require 'tribe/event'
@@ -19,16 +20,7 @@ module Tribe
19
20
  return @registry ||= Tribe::Registry.new
20
21
  end
21
22
 
22
- def self.registry=(val)
23
- @registry.dispose if @registry
24
- @registry = val
25
- end
26
-
27
23
  def self.root
28
24
  @root ||= Tribe::Root.new(:name => 'root', :permit_root => true)
29
25
  end
30
26
  end
31
-
32
- # Force initialization.
33
- Tribe.registry
34
- Tribe.root
@@ -16,16 +16,20 @@ module Tribe
16
16
  end
17
17
 
18
18
  @logger = Workers::LogProxy.new(options[:logger])
19
- @_as = Tribe::ActorState.new
20
- @_as.dedicated = options[:dedicated] || false
21
- @_as.pool = @_as.dedicated ? Workers::Pool.new(:size => 1) : (options[:pool] || Workers.pool)
22
- @_as.mailbox = Tribe::Mailbox.new(@_as.pool)
23
- @_as.registry = options[:registry] || Tribe.registry
24
- @_as.scheduler = options[:scheduler]
25
- @_as.name = options[:name]
26
- @_as.parent = options[:parent]
27
-
28
- @_as.registry.register(self)
19
+ @_actable = Tribe::ActorState.new
20
+ @_actable.dedicated = options[:dedicated] || false
21
+ @_actable.pool = @_actable.dedicated ? Workers::Pool.new(:size => 1) : (options[:pool] || Workers.pool)
22
+ @_actable.mailbox = Tribe::Mailbox.new(@_actable.pool)
23
+ @_actable.registry = options[:registry] || Tribe.registry
24
+ @_actable.scheduler = options[:scheduler]
25
+ @_actable.name = options[:name]
26
+ @_actable.parent = options[:parent]
27
+ @_actable.children = Tribe::SafeSet.new
28
+ @_actable.supervisees = Tribe::SafeSet.new
29
+
30
+ @_actable.registry.register(self)
31
+
32
+ direct_message!(:__initialize__)
29
33
  end
30
34
 
31
35
  #
@@ -38,14 +42,14 @@ module Tribe
38
42
  public
39
43
 
40
44
  def deliver_event!(event)
41
- @_as.mailbox.push(event) do
45
+ @_actable.mailbox.push(event) do
42
46
  process_events
43
47
  end
44
48
 
45
49
  return nil
46
50
  end
47
51
 
48
- def deliver_message!(command, data = nil, src = nil)
52
+ def direct_message!(command, data = nil, src = nil)
49
53
  deliver_event!(Tribe::Event.new(command, data, src))
50
54
 
51
55
  return nil
@@ -69,17 +73,16 @@ module Tribe
69
73
  end
70
74
 
71
75
  def shutdown!
72
- return deliver_message!(:_shutdown)
76
+ return direct_message!(:__shutdown__)
73
77
  end
74
78
 
75
79
  def perform!(&block)
76
- return deliver_message!(:_perform, block)
80
+ return direct_message!(:__perform__, block)
77
81
  end
78
82
 
79
- def spawn(klass, actor_options = {}, spawn_options = {})
83
+ def spawn!(klass, actor_options = {}, spawn_options = {})
80
84
  actor_options[:parent] = self
81
85
 
82
- @_as.children ||= []
83
86
  child = nil
84
87
 
85
88
  if spawn_options[:no_raise_on_failure]
@@ -92,33 +95,117 @@ module Tribe
92
95
  child = klass.new(actor_options)
93
96
  end
94
97
 
95
- @_as.children << child
98
+ @_actable.children.add(child)
99
+
100
+ if spawn_options[:supervise]
101
+ @_actable.supervisees.add(child)
102
+ end
96
103
 
97
104
  return child
98
105
  end
99
106
 
100
107
  def alive?
101
- @_as.mailbox.alive?
108
+ @_actable.mailbox.alive?
109
+ end
110
+
111
+ def dead?
112
+ !alive?
102
113
  end
103
114
 
104
115
  def name
105
- return @_as.name
116
+ return @_actable.name
106
117
  end
107
118
 
108
119
  def identifier
109
- return @_as.name ? "#{object_id}:#{@_as.name}" : object_id
120
+ return @_actable.name ? "#{object_id}:#{@_actable.name}" : object_id
121
+ end
122
+
123
+ def exception
124
+ return @_actable.exception
125
+ end
126
+
127
+ def registry
128
+ return @_actable.registry
129
+ end
130
+
131
+ def pool
132
+ return @_actable.pool
110
133
  end
111
134
 
112
135
  #
113
- # Private event handlers.
114
- # Notes: These methods are designed to be overriden (make sure you call super).
136
+ # Private command handlers.
137
+ # Notes: These methods are designed to be overriden in order to respond to actor system events.
115
138
  #
116
139
 
117
140
  private
118
141
 
142
+ def on_initialize(event)
143
+ end
144
+
145
+ def on_exception(event)
146
+ end
147
+
148
+ def on_shutdown(event)
149
+ end
150
+
151
+ def on_child_died(event)
152
+ end
153
+
154
+ def on_child_shutdown(event)
155
+ end
156
+
157
+ def on_parent_died(event)
158
+ end
159
+
160
+ #
161
+ # Private event system methods.
162
+ # Notes: These methods are designed to be overriden by advanced users only. Overriding is very rare!
163
+ #
164
+
165
+ private
166
+
167
+ # All system commands are prefixed with an underscore.
168
+ def process_events
169
+ while (event = @_actable.mailbox.obtain_and_shift)
170
+ event_handler(event)
171
+ end
172
+
173
+ rescue Exception => exception
174
+ cleanup_handler(exception)
175
+ exception_handler(exception)
176
+ ensure
177
+ @_actable.mailbox.release do
178
+ process_events
179
+ end
180
+
181
+ return nil
182
+ end
183
+
119
184
  def event_handler(event)
185
+ case event.command
186
+ when :__initialize__
187
+ initialize_handler(event)
188
+ when :__shutdown__
189
+ cleanup_handler
190
+ shutdown_handler(event)
191
+ when :__perform__
192
+ perform_handler(event)
193
+ when :__child_died__
194
+ child_died_handler(event.data[0], event.data[1])
195
+ when :__child_shutdown__
196
+ child_shutdown_handler(event.data)
197
+ when :__parent_died__
198
+ parent_died_handler(event.data[0], event.data[1])
199
+ when :initialize, :shutdown, :perform, :child_died, :child_shutdown, :parent_died
200
+ raise ActorReservedCommand.new("Reserved commands are not allowed (command=#{event.command}).")
201
+ else
202
+ custom_event_handler(event)
203
+ end
204
+ end
205
+
206
+ def custom_event_handler(event)
120
207
  result = nil
121
- @_as.active_event = event
208
+ @_actable.active_event = event
122
209
 
123
210
  begin
124
211
  result = send("on_#{event.command}", event)
@@ -126,30 +213,44 @@ module Tribe
126
213
  result = e
127
214
  raise
128
215
  ensure
129
- if event.future && @_as.active_event
216
+ if event.future && @_actable.active_event
130
217
  event.future.result = result
131
218
  end
132
- @_as.active_event = nil
219
+ @_actable.active_event = nil
133
220
  end
134
221
 
135
222
  return nil
136
223
  end
137
224
 
225
+ def initialize_handler(event)
226
+ on_initialize(event)
227
+ end
228
+
138
229
  def exception_handler(exception)
139
- if @_as.parent
140
- @_as.parent.deliver_message!(:_child_died, [self, exception])
230
+ if @_actable.parent
231
+ @_actable.parent.direct_message!(:__child_died__, [self, exception])
141
232
  end
142
233
 
143
- if @_as.children
144
- @_as.children.each { |c| c.deliver_message!(:_parent_died, [self, exception]) }
145
- @_as.children.clear
146
- @_as.children = nil
147
- end
234
+ @_actable.children.each { |c| c.direct_message!(:__parent_died__, [self, exception]) }
235
+ @_actable.children.clear
236
+ @_actable.supervisees.clear
237
+
238
+ on_exception(Event.new(:exception, {:exception => exception}))
148
239
 
149
240
  return nil
150
241
  end
151
242
 
152
243
  def shutdown_handler(event)
244
+ if @_actable.parent
245
+ @_actable.parent.direct_message!(:__child_shutdown__, self)
246
+ end
247
+
248
+ @_actable.children.each { |c| c.shutdown! }
249
+ @_actable.children.clear
250
+ @_actable.supervisees.clear
251
+
252
+ on_shutdown(Event.new(:shutdown, {}))
253
+
153
254
  return nil
154
255
  end
155
256
 
@@ -160,27 +261,42 @@ module Tribe
160
261
  end
161
262
 
162
263
  def cleanup_handler(exception = nil)
163
- @_as.exception = exception
164
- @_as.pool.shutdown if @_as.dedicated
165
- @_as.mailbox.kill
166
- @_as.registry.unregister(self)
167
- @_as.timers.each { |t| t.cancel } if @_as.timers
168
-
169
- if @_as.children && exception.nil?
170
- @_as.children.each { |c| c.shutdown! }
171
- @_as.children.clear
172
- @_as.children = nil
173
- end
264
+ @_actable.exception = exception
265
+ @_actable.pool.shutdown if @_actable.dedicated
266
+ @_actable.mailbox.kill
267
+ @_actable.registry.unregister(self)
268
+ @_actable.timers.each { |t| t.cancel } if @_actable.timers
174
269
 
175
270
  return nil
176
271
  end
177
272
 
178
273
  def child_died_handler(child, exception)
179
- raise Tribe::ActorChildDied.new("#{child.identifier} died.")
274
+ @_actable.children.delete(child)
275
+ supervising = !!@_actable.supervisees.delete?(child)
276
+
277
+ on_child_died(Event.new(:child_died, {:child => child, :exception => exception}))
278
+
279
+ if !supervising
280
+ raise Tribe::ActorChildDied.new("#{child.identifier} died.")
281
+ end
282
+
283
+ return nil
284
+ end
285
+
286
+ def child_shutdown_handler(child)
287
+ @_actable.children.delete(child)
288
+ @_actable.supervisees.delete(child)
289
+
290
+ on_child_shutdown(Event.new(:child_shutdown, {:child => child}))
291
+
292
+ return nil
180
293
  end
181
294
 
182
295
  def parent_died_handler(parent, exception)
296
+ on_parent_died(Event.new(:parent_died, {:parent => parent, :exception => exception}))
183
297
  raise Tribe::ActorParentDied.new("#{parent.identifier} died.")
298
+
299
+ return nil
184
300
  end
185
301
 
186
302
  #
@@ -190,86 +306,66 @@ module Tribe
190
306
 
191
307
  private
192
308
 
193
- def registry
194
- return @_as.registry
195
- end
196
-
197
- def pool
198
- return @_as.pool
199
- end
200
-
201
- def timer(delay, command, data = nil)
309
+ def timer!(delay, command, data = nil)
202
310
  # Lazy instantiation for performance.
203
- @_as.scheduler ||= Workers.scheduler
204
- @_as.timers ||= Tribe::SafeSet.new
311
+ @_actable.scheduler ||= Workers.scheduler
312
+ @_actable.timers ||= Tribe::SafeSet.new
205
313
 
206
- timer = Workers::Timer.new(delay, :scheduler => @_as.scheduler) do
207
- @_as.timers.delete(timer)
208
- deliver_message!(command, data)
314
+ timer = Workers::Timer.new(delay, :scheduler => @_actable.scheduler) do
315
+ @_actable.timers.delete(timer)
316
+ direct_message!(command, data)
209
317
  end
210
318
 
211
- @_as.timers.add(timer)
319
+ @_actable.timers.add(timer)
212
320
 
213
321
  return timer
214
322
  end
215
323
 
216
- def periodic_timer(delay, command, data = nil)
324
+ def periodic_timer!(delay, command, data = nil)
217
325
  # Lazy instantiation for performance.
218
- @_as.scheduler ||= Workers.scheduler
219
- @_as.timers ||= Tribe::SafeSet.new
326
+ @_actable.scheduler ||= Workers.scheduler
327
+ @_actable.timers ||= Tribe::SafeSet.new
220
328
 
221
- timer = Workers::PeriodicTimer.new(delay, :scheduler => @_as.scheduler) do
222
- deliver_message!(command, data)
329
+ timer = Workers::PeriodicTimer.new(delay, :scheduler => @_actable.scheduler) do
330
+ direct_message!(command, data)
223
331
  unless alive?
224
- @_as.timers.delete(timer)
332
+ @_actable.timers.delete(timer)
225
333
  timer.cancel
226
334
  end
227
335
  end
228
336
 
229
- @_as.timers.add(timer)
337
+ @_actable.timers.add(timer)
230
338
 
231
339
  return timer
232
340
  end
233
341
 
234
- #
235
- # Private internal methods.
236
- # Notes: These are used by the actor system and you should never call them directly.
237
- #
238
-
239
342
  def forward!(dest)
240
- dest.deliver_event!(@_as.active_event)
241
- @_as.active_event = nil
343
+ dest.deliver_event!(@_actable.active_event)
344
+ @_actable.active_event = nil
242
345
 
243
346
  return nil
244
347
  end
245
348
 
246
- # All system commands are prefixed with an underscore.
247
- def process_events
248
- while (event = @_as.mailbox.obtain_and_shift)
249
- case event.command
250
- when :_shutdown
251
- cleanup_handler
252
- shutdown_handler(event)
253
- when :_perform
254
- perform_handler(event)
255
- when :_child_died
256
- child_died_handler(event.data[0], event.data[1])
257
- when :_parent_died
258
- parent_died_handler(event.data[0], event.data[1])
259
- else
260
- event_handler(event)
349
+ # Wrap blocking code using this method to automatically expand/contract the pool.
350
+ # This way you avoid potential deadlock with blocking code.
351
+ # Not needed for dedicated actors since they already have their own thread.
352
+ def blocking!
353
+ if @_actable.dedicated
354
+ yield
355
+ else
356
+ pool.expand(1)
357
+ begin
358
+ yield
359
+ ensure
360
+ pool.contract(1)
261
361
  end
262
362
  end
363
+ end
263
364
 
264
- rescue Exception => exception
265
- cleanup_handler(exception)
266
- exception_handler(exception)
267
- ensure
268
- @_as.mailbox.release do
269
- process_events
365
+ def wait!(future)
366
+ blocking! do
367
+ future.wait
270
368
  end
271
-
272
- return nil
273
369
  end
274
370
  end
275
371
  end