tribe 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7425172d0fe26f65ff963c4f0b2742c3e21373fd
4
+ data.tar.gz: c117f5e53b430eb17cd75b804cbabb804028cbdc
5
+ SHA512:
6
+ metadata.gz: 5e233c30e21178435eb07e4b4fcf687d9dabb4b1dfe3832f056c2e25b120d26b25f4f3e835b76be4e1cfad13ad1402626900cc0b96855a9bca2e698eb43a3847
7
+ data.tar.gz: da9e75d8da2798be63f340ca80d2231d08f94be59618b4a5d8edc9597a76e22e5d60d678016faa366fe08e4ec2c7476794877e34cea2d43aa0ecb381da78985c
data/README.md CHANGED
@@ -25,13 +25,13 @@ Or install it yourself as:
25
25
 
26
26
  ## Actors
27
27
 
28
- Actors are light-weight objects which use asynchronous message passing for communcation.
28
+ Actors are light-weight objects that use asynchronous message passing for communcation.
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
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.
33
33
 
34
- To send a message you use the "enqueue" method and specify a command with an optional data parameter.
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
37
  # Create your custom actor class.
@@ -42,7 +42,7 @@ The return value will always be nil since messaging is asynchronous.
42
42
  end
43
43
 
44
44
  def on_my_custom(event)
45
- puts "Received a custom event (#{event.inspect})"
45
+ puts "Received a custom event (#{event.inspect})."
46
46
  end
47
47
 
48
48
  def exception_handler(e)
@@ -64,13 +64,13 @@ The return value will always be nil since messaging is asynchronous.
64
64
  # Send an event to each actor.
65
65
  100.times do |i|
66
66
  actor = Tribe.registry["my_actor_#{i}"]
67
- actor.enqueue(:my_custom, 'hello world')
67
+ actor.message!(:my_custom, 'hello world')
68
68
  end
69
69
 
70
70
  # Shutdown the actors.
71
71
  100.times do |i|
72
72
  actor = Tribe.registry["my_actor_#{i}"]
73
- actor.enqueue(:shutdown)
73
+ actor.shutdown!
74
74
  end
75
75
 
76
76
  #### Implementation
@@ -133,27 +133,20 @@ Both one-shot and periodic timers are provided.
133
133
  # Shutdown the actors.
134
134
  10.times do |i|
135
135
  actor = Tribe.registry["my_actor_#{i}"]
136
- actor.enqueue(:shutdown)
136
+ actor.shutdown!
137
137
  end
138
138
 
139
139
  ## Futures
140
140
 
141
- As mentioned above, message passing with the "enqueue" method is asynchronous and always returns nil.
141
+ As mentioned above, message passing with the Actable#message! method is asynchronous and always returns nil.
142
142
  This can be a pain since in many cases you will be interested in the result.
143
-
144
- The "enqueue_future" method helps solve this problem by returning a a Tribe::Future object instead of nil.
145
- You can then use this object to obtain the result when it becomes available sometime in the future.
146
-
147
- Tribe includes both blocking and non-blocking futures.
148
- You should prefer to use non-blocking futures for performance reasons (see details below).
149
-
150
- In situations where an actor dies, your future will receive the raised exception as the result.
143
+ The Actable#future! method solves this problem by returning a Tribe::Future object instead of nil.
144
+ You can then use this object to obtain the result when it becomes available.
151
145
 
152
146
  #### Non-blocking
153
147
 
154
148
  Non-blocking futures are asynchronous and use callbacks.
155
- No waiting for a result is involved.
156
- The actor will continue to process other events.
149
+ No waiting for a result is involved and the actor will continue to process other events.
157
150
 
158
151
  class ActorA < Tribe::Actor
159
152
  private
@@ -165,16 +158,16 @@ The actor will continue to process other events.
165
158
  def on_start(event)
166
159
  friend = registry['actor_b']
167
160
 
168
- future = friend.enqueue_future(:compute, 10)
161
+ future = friend.future!(:compute, 10)
169
162
 
170
163
  future.success do |result|
171
- perform do
164
+ perform! do
172
165
  puts "ActorA (#{identifier}) future result: #{result}"
173
166
  end
174
167
  end
175
168
 
176
169
  future.failure do |exception|
177
- perform do
170
+ perform! do
178
171
  puts "ActorA (#{identifier}) future failure: #{exception}"
179
172
  end
180
173
  end
@@ -200,14 +193,14 @@ The actor will continue to process other events.
200
193
  actor_a = ActorA.new(:name => 'actor_a')
201
194
  actor_b = ActorB.new(:name => 'actor_b')
202
195
 
203
- actor_a.enqueue(:start)
196
+ actor_a.message!(:start)
204
197
 
205
- actor_a.enqueue(:shutdown)
206
- actor_b.enqueue(:shutdown)
198
+ actor_a.shutdown!
199
+ actor_b.shutdown!
207
200
 
208
- *Important*: You must use Actor#perform inside the above callbacks.
201
+ *Important*: You must use Actable#perform! inside the above callbacks.
209
202
  This ensures that your code executes within the context of the correct actor.
210
- Failure to do so will result in many nasty things.
203
+ Failure to do so will result in unexpected behavior (thread safety will be lost)!
211
204
 
212
205
  #### Blocking
213
206
 
@@ -224,7 +217,7 @@ The actor won't process any other events until the future has a result.
224
217
  def on_start(event)
225
218
  friend = registry['actor_b']
226
219
 
227
- future = friend.enqueue_future(:compute, 10)
220
+ future = friend.future!(:compute, 10)
228
221
 
229
222
  future.wait # The current thread will sleep until a result is available.
230
223
 
@@ -255,32 +248,87 @@ The actor won't process any other events until the future has a result.
255
248
  actor_a = ActorA.new(:name => 'actor_a')
256
249
  actor_b = ActorB.new(:name => 'actor_b')
257
250
 
258
- actor_a.enqueue(:start)
251
+ actor_a.message!(:start)
252
+
253
+ actor_a.shutdown!
254
+ actor_b.shutdown!
255
+
256
+ #### Performance Summary
257
+
258
+ Below you will find a summary of performance recommendations regarding the use of futures:
259
+
260
+ - Use Actable#message! unless you really need Actable#future! since futures have overhead.
261
+ - If you use Actable#future!, prefer the non-blocking API over the blocking one.
262
+ - If you use the blocking API, the actor calling Future#wait should use a dedicated worker thread.
263
+
264
+ ## Forwarding
265
+
266
+ Messages and futures can be forwarded to other actors.
267
+ This lets you build actors that delegate work to other actors.
268
+
269
+ # Create your router class.
270
+ class MyRouter < Tribe::Actor
271
+ private
272
+ def initialize(options = {})
273
+ super
274
+ @processors = 100.times.map { MyProcessor.new }
275
+ end
276
+
277
+ def on_process(event)
278
+ @processors[rand(100)].forward!(event)
279
+ end
280
+
281
+ def exception_handler(e)
282
+ super
283
+ puts concat_e("MyRouter (#{identifier}) died.", e)
284
+ end
285
+
286
+ def shutdown_handler(event)
287
+ super
288
+ puts "MyRouter (#{identifier}) is shutting down. Put cleanup code here."
289
+ @processors.each { |p| p.shutdown! }
290
+ end
291
+ end
292
+
293
+ # Create your processor class.
294
+ class MyProcessor < Tribe::Actor
295
+ private
296
+ def initialize(options = {})
297
+ super
298
+ end
259
299
 
260
- actor_a.enqueue(:shutdown)
261
- actor_b.enqueue(:shutdown)
300
+ def on_process(event)
301
+ puts "MyProcessor (#{identifier}) received a process event (#{event.inspect})."
302
+ end
262
303
 
263
- #### Futures and Performance
304
+ def exception_handler(e)
305
+ super
306
+ puts concat_e("MyProcessor (#{identifier}) died.", e)
307
+ end
264
308
 
265
- Futures have overhead associated with them.
266
- You should avoid them unless you are actaully interested in the result.
309
+ def shutdown_handler(event)
310
+ super
311
+ puts "MyProcessor (#{identifier}) is shutting down. Put cleanup code here."
312
+ end
313
+ end
267
314
 
268
- You should also prefer non-blocking futures over blocking ones.
269
- This is because a blocking future causes the current actor (and thread) to sleep.
315
+ # Create the router.
316
+ router = MyRouter.new(:name => 'router')
270
317
 
271
- Tribe is designed specifically to support a large number of actors running on a small number of threads.
272
- Thus, you will run into performance and/or deadlock problems if too many actors are waiting at the same time.
318
+ # Send an event to the router and it will forward it to a random processor.
319
+ 100.times do |i|
320
+ router.message!(:process, i)
321
+ end
273
322
 
274
- If you choose to use blocking futures then it is highly recommended that you only use them with dedicated actors.
275
- Each dedicated actor runs in a separate thread (instead of a shared thread pool).
276
- The downside to using dedicated actors is that they consume more resources and you can't have as many of them.
323
+ # Shutdown the router.
324
+ router.shutdown!
277
325
 
278
326
  ## TODO - missing features
279
327
 
280
328
  - Supervisors.
281
329
  - Linking.
282
330
  - Future timeouts.
283
- - Clustering.
331
+ - Remote actors (Tribe clustering).
284
332
 
285
333
  ## Contributing
286
334
 
data/lib/tribe/actable.rb CHANGED
@@ -32,27 +32,32 @@ module Tribe
32
32
  # Thread safe public methods.
33
33
  # Notes: These are the methods that actors use to communicate with each other.
34
34
  # Actors should avoid sharing mutable state in order to remain thread safe.
35
+ # Methods with a ! are designed for asynchronous communication.
35
36
  #
36
37
 
37
38
  public
38
39
 
39
- def enqueue(command, data = nil)
40
- return false unless alive?
40
+ def message!(command, data = nil)
41
+ return forward!(Workers::Event.new(command, data))
42
+ end
43
+
44
+ def forward!(event)
45
+ return nil unless alive?
41
46
 
42
- @_as.mailbox.push(Workers::Event.new(command, data)) do
47
+ @_as.mailbox.push(event) do
43
48
  @_as.pool.perform { process_events }
44
49
  end
45
50
 
46
- return true
51
+ return nil
47
52
  end
48
53
 
49
- def enqueue_future(command, data = nil)
54
+ def future!(command, data = nil)
50
55
  @_as.futures ||= Tribe::SafeSet.new # Lazy instantiation for performance.
51
56
 
52
57
  future = Tribe::Future.new
53
58
  @_as.futures.add(future)
54
59
 
55
- perform do
60
+ perform! do
56
61
  begin
57
62
  result = event_handler(Workers::Event.new(command, data))
58
63
  future.result = result
@@ -67,6 +72,14 @@ module Tribe
67
72
  return future
68
73
  end
69
74
 
75
+ def shutdown!
76
+ return message!(:shutdown)
77
+ end
78
+
79
+ def perform!(&block)
80
+ return message!(:perform, block)
81
+ end
82
+
70
83
  def alive?
71
84
  @_as.mailbox.synchronize { return @_as.alive }
72
85
  end
@@ -79,14 +92,6 @@ module Tribe
79
92
  return @_as.name ? "#{object_id}:#{@_as.name}" : object_id
80
93
  end
81
94
 
82
- def shutdown
83
- return enqueue(:shutdown)
84
- end
85
-
86
- def perform(&block)
87
- return enqueue(:perform, block)
88
- end
89
-
90
95
  #
91
96
  # Private event handlers.
92
97
  # Notes: These methods are designed to be overriden (make sure you call super).
@@ -145,7 +150,7 @@ module Tribe
145
150
 
146
151
  timer = Workers::Timer.new(delay, :scheduler => @_as.scheduler) do
147
152
  @_as.timers.delete(timer)
148
- enqueue(command, data)
153
+ message!(command, data)
149
154
  end
150
155
 
151
156
  @_as.timers.add(timer)
@@ -159,7 +164,7 @@ module Tribe
159
164
  @_as.timers ||= Tribe::SafeSet.new
160
165
 
161
166
  timer = Workers::PeriodicTimer.new(delay, :scheduler => @_as.scheduler) do
162
- enqueue(command, data)
167
+ message!(command, data)
163
168
  unless alive?
164
169
  @_as.timers.delete(timer)
165
170
  timer.cancel
data/lib/tribe/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Tribe
2
- VERSION = '0.2.3'
2
+ VERSION = '0.3.0'
3
3
  end
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tribe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
5
- prerelease:
4
+ version: 0.3.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Chad Remesch
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-06-04 00:00:00.000000000 Z
11
+ date: 2013-06-08 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: workers
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - '='
20
18
  - !ruby/object:Gem::Version
@@ -22,7 +20,6 @@ dependencies:
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - '='
28
25
  - !ruby/object:Gem::Version
@@ -54,27 +51,26 @@ files:
54
51
  - tribe.gemspec
55
52
  homepage: https://github.com/chadrem/tribe
56
53
  licenses: []
54
+ metadata: {}
57
55
  post_install_message:
58
56
  rdoc_options: []
59
57
  require_paths:
60
58
  - lib
61
59
  required_ruby_version: !ruby/object:Gem::Requirement
62
- none: false
63
60
  requirements:
64
- - - ! '>='
61
+ - - '>='
65
62
  - !ruby/object:Gem::Version
66
63
  version: '0'
67
64
  required_rubygems_version: !ruby/object:Gem::Requirement
68
- none: false
69
65
  requirements:
70
- - - ! '>='
66
+ - - '>='
71
67
  - !ruby/object:Gem::Version
72
68
  version: '0'
73
69
  requirements: []
74
70
  rubyforge_project:
75
- rubygems_version: 1.8.25
71
+ rubygems_version: 2.0.3
76
72
  signing_key:
77
- specification_version: 3
73
+ specification_version: 4
78
74
  summary: Actors are lightweight concurrent objects that use asynchronous message passing
79
75
  for communication. Tribe focuses on high performance, low latency, an easy to use
80
76
  API, and flexibility.