tribe 0.3.0 → 0.3.1

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: 7425172d0fe26f65ff963c4f0b2742c3e21373fd
4
- data.tar.gz: c117f5e53b430eb17cd75b804cbabb804028cbdc
3
+ metadata.gz: c286bf55aac4e1e85558bb56a3b49b2e84dcabc3
4
+ data.tar.gz: 1b0743b0e63db87ae6d0b0f81bacdd12c520ba46
5
5
  SHA512:
6
- metadata.gz: 5e233c30e21178435eb07e4b4fcf687d9dabb4b1dfe3832f056c2e25b120d26b25f4f3e835b76be4e1cfad13ad1402626900cc0b96855a9bca2e698eb43a3847
7
- data.tar.gz: da9e75d8da2798be63f340ca80d2231d08f94be59618b4a5d8edc9597a76e22e5d60d678016faa366fe08e4ec2c7476794877e34cea2d43aa0ecb381da78985c
6
+ metadata.gz: a87e8def58e14e31d464783f10c30c421ef0c8343b1a0abb8bc9075c73208ba32e28cc3d195dda7ffee0daa80072e16d2397392e5997810a51f57b35058a2673
7
+ data.tar.gz: bb336feb72e68638ad1d92b63826724ea07eb768f377eaa53a01639ba801a876adf5f80588067246922490482572913f5fd0e8478fe5ecca4406e476db00b364
data/README.md CHANGED
@@ -29,29 +29,27 @@ Actors are light-weight objects that use asynchronous message passing for commun
29
29
  There are two types of methods that you create in your actors:
30
30
 
31
31
  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. It is important that you call the super method since their default behavior is used by the actor system.
32
+ 2. *System handlers* are postfixed with "_handler" and are built into the actor system. These are used for exception, shutdown, and cleanup handling.
33
33
 
34
34
  To send a message you use the Actable#message! method and specify a command with an optional data parameter.
35
35
  The return value will always be nil since messaging is asynchronous.
36
36
 
37
+ Note that the actors have a number of methods that end in ! (exclamation point or “bang”).
38
+ All of these mthods are asynchronous and designed to be thread safe.
39
+ More information on them will be provided throughout this readme.
40
+
37
41
  # Create your custom actor class.
38
42
  class MyActor < Tribe::Actor
39
43
  private
40
- def initialize(options = {})
41
- super
42
- end
43
-
44
44
  def on_my_custom(event)
45
45
  puts "Received a custom event (#{event.inspect})."
46
46
  end
47
47
 
48
48
  def exception_handler(e)
49
- super
50
49
  puts concat_e("MyActor (#{identifier}) died.", e)
51
50
  end
52
51
 
53
52
  def shutdown_handler(event)
54
- super
55
53
  puts "MyActor (#{identifier}) is shutting down. Put cleanup code here."
56
54
  end
57
55
  end
@@ -83,7 +81,6 @@ Actors that block for long periods of time should use a dedicated thread (:dedic
83
81
  :logger => nil, # Ruby logger instance.
84
82
  :dedicated => false, # If true, the actor runs with a worker pool that has one thread.
85
83
  :pool => Workers.pool, # The workers pool used to execute events.
86
- :mailbox => Tribe::Mailbox.new, # The mailbox used to receive events.
87
84
  :registry => Tribe.registry, # The registry used to store a reference to the actor if it has a name.
88
85
  :name => nil # The name of the actor (must be unique in the registry).
89
86
  )
@@ -108,7 +105,6 @@ Both one-shot and periodic timers are provided.
108
105
  private
109
106
  def initialize(options = {})
110
107
  super
111
-
112
108
  timer(1, :timer, Time.now)
113
109
  periodic_timer(1, :periodic_timer, Time.now)
114
110
  end
@@ -151,7 +147,6 @@ No waiting for a result is involved and the actor will continue to process other
151
147
  class ActorA < Tribe::Actor
152
148
  private
153
149
  def exception_handler(e)
154
- super
155
150
  puts concat_e("ActorA (#{identifier}) died.", e)
156
151
  end
157
152
 
@@ -176,7 +171,6 @@ No waiting for a result is involved and the actor will continue to process other
176
171
 
177
172
  class ActorB < Tribe::Actor
178
173
  def exception_handler(e)
179
- super
180
174
  puts concat_e("ActorB (#{identifier}) died.", e)
181
175
  end
182
176
 
@@ -210,7 +204,6 @@ The actor won't process any other events until the future has a result.
210
204
  class ActorA < Tribe::Actor
211
205
  private
212
206
  def exception_handler(e)
213
- super
214
207
  puts concat_e("ActorA (#{identifier}) died.", e)
215
208
  end
216
209
 
@@ -231,7 +224,6 @@ The actor won't process any other events until the future has a result.
231
224
 
232
225
  class ActorB < Tribe::Actor
233
226
  def exception_handler(e)
234
- super
235
227
  puts concat_e("ActorB (#{identifier}) died.", e)
236
228
  end
237
229
 
@@ -264,7 +256,7 @@ Below you will find a summary of performance recommendations regarding the use o
264
256
  ## Forwarding
265
257
 
266
258
  Messages and futures can be forwarded to other actors.
267
- This lets you build actors that delegate work to other actors.
259
+ This lets you build routers that delegate work to other actors.
268
260
 
269
261
  # Create your router class.
270
262
  class MyRouter < Tribe::Actor
@@ -275,16 +267,14 @@ This lets you build actors that delegate work to other actors.
275
267
  end
276
268
 
277
269
  def on_process(event)
278
- @processors[rand(100)].forward!(event)
270
+ forward!(@processors[rand(100)])
279
271
  end
280
272
 
281
273
  def exception_handler(e)
282
- super
283
274
  puts concat_e("MyRouter (#{identifier}) died.", e)
284
275
  end
285
276
 
286
277
  def shutdown_handler(event)
287
- super
288
278
  puts "MyRouter (#{identifier}) is shutting down. Put cleanup code here."
289
279
  @processors.each { |p| p.shutdown! }
290
280
  end
@@ -293,21 +283,15 @@ This lets you build actors that delegate work to other actors.
293
283
  # Create your processor class.
294
284
  class MyProcessor < Tribe::Actor
295
285
  private
296
- def initialize(options = {})
297
- super
298
- end
299
-
300
286
  def on_process(event)
301
287
  puts "MyProcessor (#{identifier}) received a process event (#{event.inspect})."
302
288
  end
303
289
 
304
290
  def exception_handler(e)
305
- super
306
291
  puts concat_e("MyProcessor (#{identifier}) died.", e)
307
292
  end
308
293
 
309
294
  def shutdown_handler(event)
310
- super
311
295
  puts "MyProcessor (#{identifier}) is shutting down. Put cleanup code here."
312
296
  end
313
297
  end
@@ -323,6 +307,10 @@ This lets you build actors that delegate work to other actors.
323
307
  # Shutdown the router.
324
308
  router.shutdown!
325
309
 
310
+ ## Benchmarks
311
+
312
+ Please see the [performance] (https://github.com/chadrem/tribe/wiki/Performance "performance") wiki page for more information.
313
+
326
314
  ## TODO - missing features
327
315
 
328
316
  - Supervisors.
data/Rakefile CHANGED
@@ -5,6 +5,7 @@ task :console do
5
5
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
6
6
 
7
7
  require 'tribe'
8
+ require 'tribe/benchmark'
8
9
  require 'irb'
9
10
 
10
11
  ARGV.clear
@@ -4,6 +4,7 @@ require 'workers'
4
4
 
5
5
  require 'tribe/safe_set'
6
6
  require 'tribe/exceptions'
7
+ require 'tribe/event'
7
8
  require 'tribe/mailbox'
8
9
  require 'tribe/actor_state'
9
10
  require 'tribe/actable'
@@ -18,12 +18,11 @@ module Tribe
18
18
  @logger = Workers::LogProxy.new(options[:logger])
19
19
  @_as = Tribe::ActorState.new
20
20
  @_as.dedicated = options[:dedicated] || false
21
- @_as.mailbox = options[:mailbox] || Tribe::Mailbox.new
21
+ @_as.pool = @_as.dedicated ? Workers::Pool.new(:size => 1) : (options[:pool] || Workers.pool)
22
+ @_as.mailbox = Tribe::Mailbox.new(@_as.pool)
22
23
  @_as.registry = options[:registry] || Tribe.registry
23
24
  @_as.scheduler = options[:scheduler]
24
25
  @_as.name = options[:name]
25
- @_as.pool = @_as.dedicated ? Workers::Pool.new(:size => 1) : (options[:pool] || Workers.pool)
26
- @_as.alive = true
27
26
 
28
27
  @_as.registry.register(self)
29
28
  end
@@ -37,37 +36,25 @@ module Tribe
37
36
 
38
37
  public
39
38
 
40
- def message!(command, data = nil)
41
- return forward!(Workers::Event.new(command, data))
39
+ def event!(event)
40
+ push_event(event)
41
+
42
+ return nil
42
43
  end
43
44
 
44
- def forward!(event)
45
- return nil unless alive?
45
+ def message!(command, data = nil, source = nil)
46
+ event = Tribe::Event.new(command, data, source)
46
47
 
47
- @_as.mailbox.push(event) do
48
- @_as.pool.perform { process_events }
49
- end
48
+ push_event(event)
50
49
 
51
50
  return nil
52
51
  end
53
52
 
54
- def future!(command, data = nil)
55
- @_as.futures ||= Tribe::SafeSet.new # Lazy instantiation for performance.
53
+ def future!(command, data = nil, source = nil)
54
+ event = Tribe::Event.new(command, data, source)
55
+ event.future = future = Tribe::Future.new
56
56
 
57
- future = Tribe::Future.new
58
- @_as.futures.add(future)
59
-
60
- perform! do
61
- begin
62
- result = event_handler(Workers::Event.new(command, data))
63
- future.result = result
64
- rescue Exception => exception
65
- future.result = exception
66
- raise
67
- ensure
68
- @_as.futures.delete(future)
69
- end
70
- end
57
+ push_event(event)
71
58
 
72
59
  return future
73
60
  end
@@ -81,7 +68,7 @@ module Tribe
81
68
  end
82
69
 
83
70
  def alive?
84
- @_as.mailbox.synchronize { return @_as.alive }
71
+ @_as.mailbox.alive?
85
72
  end
86
73
 
87
74
  def name
@@ -99,9 +86,23 @@ module Tribe
99
86
 
100
87
  private
101
88
 
102
- # The return value is used as the result of a future.
103
89
  def event_handler(event)
104
- return send("on_#{event.command}", event)
90
+ result = nil
91
+ @_as.active_event = event
92
+
93
+ begin
94
+ result = send("on_#{event.command}", event)
95
+ rescue Exception => e
96
+ result = e
97
+ raise
98
+ ensure
99
+ if event.future && @_as.active_event
100
+ event.future.result = result
101
+ end
102
+ @_as.active_event = nil
103
+ end
104
+
105
+ return nil
105
106
  end
106
107
 
107
108
  def exception_handler(exception)
@@ -120,10 +121,9 @@ module Tribe
120
121
 
121
122
  def cleanup_handler(exception = nil)
122
123
  @_as.pool.shutdown if @_as.dedicated
123
- @_as.mailbox.synchronize { @_as.alive = false }
124
+ @_as.mailbox.kill
124
125
  @_as.registry.unregister(self)
125
126
  @_as.timers.each { |t| t.cancel } if @_as.timers
126
- @_as.futures.each { |f| f.result = exception || Tribe::ActorShutdownError.new } if @_as.futures
127
127
 
128
128
  return nil
129
129
  end
@@ -181,8 +181,21 @@ module Tribe
181
181
  # Notes: These are used by the actor system and you should never call them directly.
182
182
  #
183
183
 
184
+ def forward!(dest)
185
+ dest.event!(@_as.active_event)
186
+ @_as.active_event = nil
187
+
188
+ return nil
189
+ end
190
+
191
+ def push_event(event)
192
+ @_as.mailbox.push(event) do
193
+ process_events
194
+ end
195
+ end
196
+
184
197
  def process_events
185
- while (event = @_as.mailbox.shift)
198
+ while (event = @_as.mailbox.obtain_and_shift)
186
199
  case event.command
187
200
  when :shutdown
188
201
  cleanup_handler
@@ -199,7 +212,7 @@ module Tribe
199
212
  exception_handler(exception)
200
213
  ensure
201
214
  @_as.mailbox.release do
202
- @_as.pool.perform { process_events if @_as.alive }
215
+ process_events
203
216
  end
204
217
 
205
218
  return nil
@@ -7,7 +7,6 @@ module Tribe
7
7
  attr_accessor :timers
8
8
  attr_accessor :name
9
9
  attr_accessor :pool
10
- attr_accessor :alive
11
- attr_accessor :futures
10
+ attr_accessor :active_event
12
11
  end
13
12
  end
@@ -0,0 +1 @@
1
+ require 'tribe/benchmark/throughput'
@@ -0,0 +1,74 @@
1
+ module Tribe
2
+ module Benchmark
3
+ module Throughput
4
+ MAX_INCR = 100000
5
+ ACTOR_COUNT = 100000
6
+ COUNTERS = 20
7
+
8
+ def self.run
9
+ ACTOR_COUNT.times do |i|
10
+ actor = Tribe.registry["actor_#{i}"]
11
+ actor.shutdown! if actor
12
+ end
13
+
14
+ $start_time = Time.now.utc
15
+ $finished = 0
16
+ $lock = Mutex.new
17
+
18
+ ACTOR_COUNT.times do |i|
19
+ MyActor.new(:name => "actor_#{i}")
20
+ end
21
+
22
+ COUNTERS.times do |i|
23
+ Tribe.registry["actor_#{rand(ACTOR_COUNT)}"].message!(:do_stuff, MyData.new("data_#{i}"))
24
+ end
25
+
26
+ $lock.synchronize do
27
+ puts 'Please wait...'
28
+ end
29
+ end
30
+
31
+ def self.stop
32
+ end
33
+
34
+ class MyData
35
+ def initialize(name)
36
+ @name = name
37
+ @counter = 0
38
+ @start_time = Time.now
39
+ end
40
+
41
+ def increment
42
+ @counter += 1
43
+
44
+ if @counter >= MAX_INCR
45
+ $lock.synchronize do
46
+ $finished += 1
47
+
48
+ if $finished == COUNTERS
49
+ puts "\nFinished! Rate=#{(COUNTERS * MAX_INCR).to_f / (Time.now.utc - $start_time).to_f } msgs/sec\n"
50
+ end
51
+ end
52
+
53
+ return false
54
+ end
55
+
56
+ return true
57
+ end
58
+ end
59
+
60
+ class MyActor < Tribe::Actor
61
+ private
62
+ def on_do_stuff(event)
63
+ if event.data.increment
64
+ Tribe.registry["actor_#{rand(ACTOR_COUNT)}"].message!(:do_stuff, event.data)
65
+ end
66
+ end
67
+
68
+ def exception_handler(e)
69
+ puts concat_e("MyActor (#{identifier}) died.", e)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,15 @@
1
+ module Tribe
2
+ class Event
3
+
4
+ attr_accessor :command
5
+ attr_accessor :data
6
+ attr_accessor :source
7
+ attr_accessor :future
8
+
9
+ def initialize(command, data, source = nil)
10
+ @command = command
11
+ @data = data
12
+ @source = source
13
+ end
14
+ end
15
+ end
@@ -1,44 +1,64 @@
1
1
  module Tribe
2
2
  class Mailbox
3
- def initialize(options = {})
3
+ def initialize(pool)
4
+ @pool = pool
4
5
  @messages = []
6
+ @alive = true
5
7
  @mutex = Mutex.new
6
8
  end
7
9
 
8
10
  def push(event, &block)
9
11
  @mutex.synchronize do
12
+ return nil unless @alive
13
+
10
14
  @messages.push(event)
11
- block.call unless @current_thread
15
+ @pool.perform { block.call } unless @owner_thread
12
16
  end
13
17
 
14
18
  return nil
15
19
  end
16
20
 
17
- def shift
21
+ def obtain_and_shift
18
22
  @mutex.synchronize do
19
- return nil if @current_thread && @current_thread != Thread.current
20
-
21
- @current_thread = Thread.current unless @current_thread
23
+ return nil unless @alive
22
24
 
23
- return @messages.shift
25
+ if @owner_thread
26
+ if @owner_thread == Thread.current
27
+ return @messages.shift
28
+ else
29
+ return nil
30
+ end
31
+ else
32
+ @owner_thread = Thread.current
33
+ return @messages.shift
34
+ end
24
35
  end
25
36
  end
26
37
 
27
38
  def release(&block)
28
39
  @mutex.synchronize do
29
- @current_thread = nil
30
- block.call if block && @messages.length > 0
40
+ return nil unless @owner_thread == Thread.current
41
+
42
+ @owner_thread = nil
43
+ @pool.perform { block.call } if @alive && @messages.length > 0
31
44
  end
32
45
 
33
46
  return nil
34
47
  end
35
48
 
36
- def synchronize(&block)
49
+ def kill
37
50
  @mutex.synchronize do
38
- block.call
51
+ @alive = false
52
+ @messages.clear
39
53
  end
40
54
 
41
55
  return nil
42
56
  end
57
+
58
+ def alive?
59
+ @mutex.synchronize do
60
+ return @alive
61
+ end
62
+ end
43
63
  end
44
64
  end
@@ -1,3 +1,3 @@
1
1
  module Tribe
2
- VERSION = '0.3.0'
2
+ VERSION = '0.3.1'
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.0
4
+ version: 0.3.1
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-08 00:00:00.000000000 Z
11
+ date: 2013-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: workers
@@ -41,7 +41,10 @@ files:
41
41
  - lib/tribe/actable.rb
42
42
  - lib/tribe/actor.rb
43
43
  - lib/tribe/actor_state.rb
44
+ - lib/tribe/benchmark.rb
45
+ - lib/tribe/benchmark/throughput.rb
44
46
  - lib/tribe/dedicated_actor.rb
47
+ - lib/tribe/event.rb
45
48
  - lib/tribe/exceptions.rb
46
49
  - lib/tribe/future.rb
47
50
  - lib/tribe/mailbox.rb