tribe 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c286bf55aac4e1e85558bb56a3b49b2e84dcabc3
4
- data.tar.gz: 1b0743b0e63db87ae6d0b0f81bacdd12c520ba46
3
+ metadata.gz: 50de6f25aa1a85a300729b25e432f661619f2468
4
+ data.tar.gz: d8f1c9329ceefad25909e6db346288a6aea39ab6
5
5
  SHA512:
6
- metadata.gz: a87e8def58e14e31d464783f10c30c421ef0c8343b1a0abb8bc9075c73208ba32e28cc3d195dda7ffee0daa80072e16d2397392e5997810a51f57b35058a2673
7
- data.tar.gz: bb336feb72e68638ad1d92b63826724ea07eb768f377eaa53a01639ba801a876adf5f80588067246922490482572913f5fd0e8478fe5ecca4406e476db00b364
6
+ metadata.gz: 9f58370c1c6b6b9ba42b7858bcac28016f4a5dc170432d7882163573330c12b949ad037daa36424db3839d8dad7f642294302be4d91903ea80f431ee4a9a721a
7
+ data.tar.gz: d894eb6eca95825d89ab91d4823545fa737a6cc588320ffb80b3f4239e5a3c946e13191f3941210b5f2f0436e2dc22692c2532d3646563452790a4edf6588f9a
data/README.md CHANGED
@@ -9,6 +9,23 @@ It is built on top of the [Workers] (https://github.com/chadrem/workers "Workers
9
9
 
10
10
  Event-driven servers can be built using [Tribe EM] (https://github.com/chadrem/tribe_em "Tribe EM").
11
11
 
12
+ ## Contents
13
+
14
+ - [Installation](#installation)
15
+ - [Actors](#actors)
16
+ - [Registries](#registries)
17
+ - [Timers](#timers)
18
+ - [Futures](#futures)
19
+ - [Non-blocking](#non-blocking)
20
+ - [Blocking](#blocking)
21
+ - [Timeouts](#timeouts)
22
+ - [Performance](#performance-summary)
23
+ - [Forwarding](#forwarding)
24
+ - [Linking](#linking)
25
+ - [Supervisors](#supervisors)
26
+ - [Benchmarks](#benchmarks)
27
+ - [Contributing](#contributing)
28
+
12
29
  ## Installation
13
30
 
14
31
  Add this line to your application's Gemfile:
@@ -29,7 +46,7 @@ Actors are light-weight objects that use asynchronous message passing for commun
29
46
  There are two types of methods that you create in your actors:
30
47
 
31
48
  1. *Command handlers* are prefixed with "on_" and define the types of commands your actor will process.
32
- 2. *System handlers* are postfixed with "_handler" and are built into the actor system. These are used for exception, shutdown, and cleanup handling.
49
+ 2. *System handlers* are postfixed with "_handler" and are built into the actor system. These are hooks into the Tribe's actor system.
33
50
 
34
51
  To send a message you use the Actable#message! method and specify a command with an optional data parameter.
35
52
  The return value will always be nil since messaging is asynchronous.
@@ -46,10 +63,12 @@ More information on them will be provided throughout this readme.
46
63
  end
47
64
 
48
65
  def exception_handler(e)
66
+ super
49
67
  puts concat_e("MyActor (#{identifier}) died.", e)
50
68
  end
51
69
 
52
70
  def shutdown_handler(event)
71
+ super
53
72
  puts "MyActor (#{identifier}) is shutting down. Put cleanup code here."
54
73
  end
55
74
  end
@@ -82,7 +101,8 @@ Actors that block for long periods of time should use a dedicated thread (:dedic
82
101
  :dedicated => false, # If true, the actor runs with a worker pool that has one thread.
83
102
  :pool => Workers.pool, # The workers pool used to execute events.
84
103
  :registry => Tribe.registry, # The registry used to store a reference to the actor if it has a name.
85
- :name => nil # The name of the actor (must be unique in the registry).
104
+ :name => nil, # The name of the actor (must be unique in the registry).
105
+ :parent => nil # Set the parent actor (used by the supervision feature).
86
106
  )
87
107
 
88
108
  ## Registries
@@ -124,7 +144,7 @@ Both one-shot and periodic timers are provided.
124
144
  end
125
145
 
126
146
  # Sleep in order to observe the timers.
127
- sleep 10
147
+ sleep(10)
128
148
 
129
149
  # Shutdown the actors.
130
150
  10.times do |i|
@@ -147,6 +167,7 @@ No waiting for a result is involved and the actor will continue to process other
147
167
  class ActorA < Tribe::Actor
148
168
  private
149
169
  def exception_handler(e)
170
+ super
150
171
  puts concat_e("ActorA (#{identifier}) died.", e)
151
172
  end
152
173
 
@@ -171,6 +192,7 @@ No waiting for a result is involved and the actor will continue to process other
171
192
 
172
193
  class ActorB < Tribe::Actor
173
194
  def exception_handler(e)
195
+ super
174
196
  puts concat_e("ActorB (#{identifier}) died.", e)
175
197
  end
176
198
 
@@ -204,6 +226,7 @@ The actor won't process any other events until the future has a result.
204
226
  class ActorA < Tribe::Actor
205
227
  private
206
228
  def exception_handler(e)
229
+ super
207
230
  puts concat_e("ActorA (#{identifier}) died.", e)
208
231
  end
209
232
 
@@ -224,6 +247,7 @@ The actor won't process any other events until the future has a result.
224
247
 
225
248
  class ActorB < Tribe::Actor
226
249
  def exception_handler(e)
250
+ super
227
251
  puts concat_e("ActorB (#{identifier}) died.", e)
228
252
  end
229
253
 
@@ -245,6 +269,23 @@ The actor won't process any other events until the future has a result.
245
269
  actor_a.shutdown!
246
270
  actor_b.shutdown!
247
271
 
272
+ #### Timeouts
273
+
274
+ Futures can be confgured to timeout after a specified number of seconds.
275
+ When a timeout occurs, the result of the future will be a Tribe::FutureTimeout exception.
276
+
277
+ # Manually create a future (Use Actable#future! in your actors).
278
+ future = Tribe::Future.new
279
+
280
+ # Set a timeout (in seconds).
281
+ future.timeout = 2
282
+
283
+ # Wait for the timeout.
284
+ sleep(3)
285
+
286
+ # The result of the future is a timeout exception.
287
+ puts "Result: #{future.result}"
288
+
248
289
  #### Performance Summary
249
290
 
250
291
  Below you will find a summary of performance recommendations regarding the use of futures:
@@ -271,10 +312,12 @@ This lets you build routers that delegate work to other actors.
271
312
  end
272
313
 
273
314
  def exception_handler(e)
315
+ super
274
316
  puts concat_e("MyRouter (#{identifier}) died.", e)
275
317
  end
276
318
 
277
319
  def shutdown_handler(event)
320
+ super
278
321
  puts "MyRouter (#{identifier}) is shutting down. Put cleanup code here."
279
322
  @processors.each { |p| p.shutdown! }
280
323
  end
@@ -288,10 +331,12 @@ This lets you build routers that delegate work to other actors.
288
331
  end
289
332
 
290
333
  def exception_handler(e)
334
+ super
291
335
  puts concat_e("MyProcessor (#{identifier}) died.", e)
292
336
  end
293
337
 
294
338
  def shutdown_handler(event)
339
+ super
295
340
  puts "MyProcessor (#{identifier}) is shutting down. Put cleanup code here."
296
341
  end
297
342
  end
@@ -307,16 +352,110 @@ This lets you build routers that delegate work to other actors.
307
352
  # Shutdown the router.
308
353
  router.shutdown!
309
354
 
310
- ## Benchmarks
355
+ ## Linking
311
356
 
312
- Please see the [performance] (https://github.com/chadrem/tribe/wiki/Performance "performance") wiki page for more information.
357
+ Linking allows actors to group together into a tree structure such that they all live or die as one group.
358
+ Such linking is useful for breaking complex problems into smaller (and easier to manage) components.
359
+ To create a linked actor you use the Actable#spawn method to create it.
360
+ If any actor in a tree of linked actors dies, it will cause all actors above and below it to die too.
361
+
362
+ # Create the root level actor class.
363
+ class Level1 < Tribe::Actor
364
+ private
365
+ def on_spawn(event)
366
+ 5.times do |i|
367
+ name = "level2_#{i}"
368
+ puts name
369
+ actor = spawn(Level2, :name => name)
370
+ actor.message!(:spawn, i)
371
+ end
372
+ end
373
+ end
374
+
375
+ # Create the mid-level actor class.
376
+ class Level2 < Tribe::Actor
377
+ private
378
+ def on_spawn(event)
379
+ 5.times do |i|
380
+ name = "level3_#{event.data}_#{i}"
381
+ actor = spawn(Level3, :name => name)
382
+ actor.message!(:spawn)
383
+ end
384
+ end
385
+ end
386
+
387
+ # Create the bottom level actor class.
388
+ class Level3 < Tribe::Actor
389
+ private
390
+ def on_spawn(event)
391
+ puts "#{identifier} hello world!"
392
+ end
393
+ end
394
+
395
+ # Create the root actor.
396
+ root = Level1.new(:name => 'level1')
397
+
398
+ # Tell the root actor to create the tree of children.
399
+ root.message!(:spawn)
400
+
401
+ ## Supervisors
402
+
403
+ As mentioned above, a failure in a linked actor will cause all associated actors (parent and children) to die.
404
+ Supervisors can be used to block the failure from propogating and allow you to restart the failed section of the tree.
405
+
406
+ # Create the root level actor class.
407
+ class Level1 < Tribe::Actor
408
+ private
409
+ def on_spawn(event)
410
+ 5.times do |i|
411
+ create_subtree
412
+ end
413
+ end
414
+
415
+ def create_subtree
416
+ actor = spawn(Level2)
417
+ actor.message!(:spawn)
418
+ end
419
+
420
+ def child_died_handler(actor, exception)
421
+ puts "My child (#{actor.identifier}) died. Restarting it."
422
+ create_subtree
423
+ end
424
+ end
313
425
 
314
- ## TODO - missing features
426
+ # Create the mid-level actor class.
427
+ class Level2 < Tribe::Actor
428
+ private
429
+ def on_spawn(event)
430
+ 5.times do |i|
431
+ actor = spawn(Level3)
432
+ actor.message!(:spawn)
433
+ end
434
+ end
435
+ end
315
436
 
316
- - Supervisors.
317
- - Linking.
318
- - Future timeouts.
319
- - Remote actors (Tribe clustering).
437
+ # Create the bottom level actor class.
438
+ class Level3 < Tribe::Actor
439
+ private
440
+ def on_spawn(event)
441
+ puts "#{identifier} says hello world!"
442
+ raise 'Sometimes I like to die.' if rand < 0.5
443
+ end
444
+ end
445
+
446
+ # Create the root actor.
447
+ root = Level1.new(:name => 'root')
448
+
449
+ # Tell the root actor to create the tree of children.
450
+ root.message!(:spawn)
451
+
452
+ #### Important!
453
+ Restarting named actors is NOT currently supported, but will be in a future update.
454
+ Attempting to do so may result in Tribe::RegistryError exceptions when trying to spawn a replacement child.
455
+
456
+ ## Benchmarks
457
+
458
+ Please see the [performance] (https://github.com/chadrem/tribe/wiki/Performance "performance") wiki page for more information.
320
459
 
321
460
  ## Contributing
322
461
 
data/lib/tribe/actable.rb CHANGED
@@ -23,6 +23,7 @@ module Tribe
23
23
  @_as.registry = options[:registry] || Tribe.registry
24
24
  @_as.scheduler = options[:scheduler]
25
25
  @_as.name = options[:name]
26
+ @_as.parent = options[:parent]
26
27
 
27
28
  @_as.registry.register(self)
28
29
  end
@@ -60,11 +61,11 @@ module Tribe
60
61
  end
61
62
 
62
63
  def shutdown!
63
- return message!(:shutdown)
64
+ return message!(:_shutdown)
64
65
  end
65
66
 
66
67
  def perform!(&block)
67
- return message!(:perform, block)
68
+ return message!(:_perform, block)
68
69
  end
69
70
 
70
71
  def alive?
@@ -106,6 +107,16 @@ module Tribe
106
107
  end
107
108
 
108
109
  def exception_handler(exception)
110
+ if @_as.parent
111
+ @_as.parent.message!(:_child_died, [self, exception])
112
+ end
113
+
114
+ if @_as.children
115
+ @_as.children.each { |c| c.message!(:_parent_died, [self, exception]) }
116
+ @_as.children.clear
117
+ @_as.children = nil
118
+ end
119
+
109
120
  return nil
110
121
  end
111
122
 
@@ -120,14 +131,29 @@ module Tribe
120
131
  end
121
132
 
122
133
  def cleanup_handler(exception = nil)
134
+ @_as.exception = exception
123
135
  @_as.pool.shutdown if @_as.dedicated
124
136
  @_as.mailbox.kill
125
137
  @_as.registry.unregister(self)
126
138
  @_as.timers.each { |t| t.cancel } if @_as.timers
127
139
 
140
+ if @_as.children && exception.nil?
141
+ @_as.children.each { |c| c.shutdown! }
142
+ @_as.children.clear
143
+ @_as.children = nil
144
+ end
145
+
128
146
  return nil
129
147
  end
130
148
 
149
+ def child_died_handler(child, exception)
150
+ raise Tribe::ActorChildDied.new("#{child.identifier} died.")
151
+ end
152
+
153
+ def parent_died_handler(parent, exception)
154
+ raise Tribe::ActorParentDied.new("#{parent.identifier} died.")
155
+ end
156
+
131
157
  #
132
158
  # Private API methods.
133
159
  # Notes: Use these methods internally in your actor.
@@ -188,20 +214,34 @@ module Tribe
188
214
  return nil
189
215
  end
190
216
 
217
+ def spawn(klass, options = {})
218
+ options[:parent] = self
219
+
220
+ @_as.children ||= []
221
+ @_as.children << (actor = klass.new(options))
222
+
223
+ return actor
224
+ end
225
+
191
226
  def push_event(event)
192
227
  @_as.mailbox.push(event) do
193
228
  process_events
194
229
  end
195
230
  end
196
231
 
232
+ # All system commands are prefixed with an underscore.
197
233
  def process_events
198
234
  while (event = @_as.mailbox.obtain_and_shift)
199
235
  case event.command
200
- when :shutdown
236
+ when :_shutdown
201
237
  cleanup_handler
202
238
  shutdown_handler(event)
203
- when :perform
239
+ when :_perform
204
240
  perform_handler(event)
241
+ when :_child_died
242
+ child_died_handler(event.data[0], event.data[1])
243
+ when :_parent_died
244
+ parent_died_handler(event.data[0], event.data[1])
205
245
  else
206
246
  event_handler(event)
207
247
  end
@@ -8,5 +8,8 @@ module Tribe
8
8
  attr_accessor :name
9
9
  attr_accessor :pool
10
10
  attr_accessor :active_event
11
+ attr_accessor :parent
12
+ attr_accessor :children
13
+ attr_accessor :exception
11
14
  end
12
15
  end
@@ -1,8 +1,14 @@
1
1
  module Tribe
2
- class ActorShutdownError < RuntimeError; end
3
- class ActorNameError < RuntimeError; end
2
+ class TribeError < RuntimeError; end
4
3
 
5
- class FutureError < RuntimeError; end
4
+ class ActorShutdownError < TribeError; end
5
+ class ActorNameError < TribeError; end
6
+ class ActorChildDied < TribeError; end
7
+ class ActorParentDied < TribeError; end
6
8
 
7
- class RegistryError < RuntimeError; end
9
+ class FutureError < TribeError; end
10
+ class FutureNoResult < TribeError; end
11
+ class FutureTimeout < TribeError; end
12
+
13
+ class RegistryError < TribeError; end
8
14
  end
data/lib/tribe/future.rb CHANGED
@@ -17,9 +17,17 @@ module Tribe
17
17
  end
18
18
  end
19
19
 
20
+ def timeout?
21
+ @mutex.synchronnize do
22
+ return @state == :finished && @result.is_a?(Tribe::FutureTimeout)
23
+ end
24
+ end
25
+
20
26
  def result=(val)
21
27
  @mutex.synchronize do
22
- raise Tribe::FutureError.new('Result must only be set once.') unless @state == :initialized
28
+ return unless @state == :initialized
29
+
30
+ @timer.cancel if @timer
23
31
 
24
32
  @result = val
25
33
  @state = :finished
@@ -37,7 +45,7 @@ module Tribe
37
45
 
38
46
  def result
39
47
  @mutex.synchronize do
40
- raise Tribe::FutureError.new('Result must be set first.') unless @state == :finished
48
+ raise Tribe::FutureNoResult.new('Result must be set first.') unless @state == :finished
41
49
 
42
50
  return @result
43
51
  end
@@ -55,7 +63,7 @@ module Tribe
55
63
 
56
64
  def success?
57
65
  @mutex.synchronize do
58
- raise Tribe::FutureError.new('Result must be set first.') unless @state == :finished
66
+ raise Tribe::FutureNoResult.new('Result must be set first.') unless @state == :finished
59
67
 
60
68
  return !@result.is_a?(Exception)
61
69
  end
@@ -94,5 +102,24 @@ module Tribe
94
102
  return nil
95
103
  end
96
104
  end
105
+
106
+ def timeout
107
+ @mutex.synchronize do
108
+ end
109
+ end
110
+
111
+ def timeout=(val)
112
+ raise Tribe::FutureError.new('Timeout may only be set once.') if @timeout
113
+
114
+ @timeout = val
115
+
116
+ @timer = Workers::Timer.new(val) do
117
+ begin
118
+ raise Tribe::FutureTimeout.new("Timeout after #{@timeout} seconds.")
119
+ rescue Tribe::FutureTimeout => e
120
+ self.result = e
121
+ end
122
+ end
123
+ end
97
124
  end
98
125
  end
data/lib/tribe/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Tribe
2
- VERSION = '0.3.1'
2
+ VERSION = '0.3.2'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tribe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chad Remesch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-06-09 00:00:00.000000000 Z
11
+ date: 2013-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: workers