tribe 0.4.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +9 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -1
- data/README.md +254 -223
- data/Rakefile +7 -11
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/tribe.rb +1 -9
- data/lib/tribe/actable.rb +192 -96
- data/lib/tribe/actor_state.rb +1 -0
- data/lib/tribe/benchmark/throughput.rb +4 -4
- data/lib/tribe/exceptions.rb +1 -0
- data/lib/tribe/future.rb +12 -11
- data/lib/tribe/mailbox.rb +6 -6
- data/lib/tribe/root.rb +5 -1
- data/lib/tribe/safe_set.rb +25 -7
- data/lib/tribe/version.rb +1 -1
- data/tribe.gemspec +22 -15
- metadata +61 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3bf6bd1aba1fc0bc7778e35002942f86a23160e
|
4
|
+
data.tar.gz: c8b4d5c3c3e28f2d3b0598f257a6769ee7e8708b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83ac8698abdc2012668474d87009d14156a0a8ef82c17553475f47284a427390bedba8c32f91260da9a14075e28602dfa2e07fe967edbf3963e85748d6b2e50a
|
7
|
+
data.tar.gz: 269e3611a89686367ed6f012af21eb0fd5ba49a0d9e722b2cd01ecd7025f74158d02484a10d951eebde217ab8e36f8b8731979221fcd8fe25382ed0aca65875c
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.2
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,31 +1,30 @@
|
|
1
|
-
# Tribe
|
1
|
+
# Tribe [![Build Status](https://travis-ci.org/chadrem/tribe.svg)](https://travis-ci.org/chadrem/tribe) [![Coverage Status](https://coveralls.io/repos/chadrem/tribe/badge.svg?branch=master&service=github)](https://coveralls.io/github/chadrem/tribe?branch=master)
|
2
2
|
|
3
|
-
Tribe is a Ruby gem that implements
|
4
|
-
Actors are lightweight concurrent objects that use asynchronous message passing for communication.
|
3
|
+
Tribe is a Ruby gem that implements the [actor model] (http://en.wikipedia.org/wiki/Actor_model "actors") in an event-driven way.
|
5
4
|
|
6
5
|
Tribe focuses on high performance, low latency, a simple API, and flexibility.
|
7
6
|
It's goal is to support at least one million actors running on a small group of threads.
|
8
7
|
It is built on top of the [Workers] (https://github.com/chadrem/workers "Workers") gem.
|
9
8
|
|
10
|
-
Event-driven servers can be built using [Tribe EM] (https://github.com/chadrem/tribe_em "Tribe EM").
|
11
|
-
|
12
9
|
## Contents
|
13
10
|
|
14
11
|
- [Installation](#installation)
|
15
12
|
- [Actors](#actors)
|
16
|
-
- [Root](#root
|
13
|
+
- [Root](#root)
|
17
14
|
- [Handlers](#handlers)
|
18
|
-
|
15
|
+
- [Messages](#messages)
|
19
16
|
- [Registries](#registries)
|
20
|
-
- [Timers](#timers)
|
21
17
|
- [Futures](#futures)
|
22
18
|
- [Non-blocking](#non-blocking)
|
23
19
|
- [Blocking](#blocking)
|
24
20
|
- [Timeouts](#timeouts)
|
25
21
|
- [Performance](#performance-summary)
|
26
22
|
- [Forwarding](#forwarding)
|
23
|
+
- [Timers](#timers)
|
27
24
|
- [Linking](#linking)
|
28
25
|
- [Supervisors](#supervisors)
|
26
|
+
- [Blocking code](#blocking-code)
|
27
|
+
- [Debugging](#debugging)
|
29
28
|
- [Benchmarks](#benchmarks)
|
30
29
|
- [Contributing](#contributing)
|
31
30
|
|
@@ -48,31 +47,51 @@ Or install it yourself as:
|
|
48
47
|
Actors are the building blocks of your application.
|
49
48
|
There are three ways to create an actor class:
|
50
49
|
|
51
|
-
- Inherit from Tribe::Actor (uses the shared thread pool).
|
52
|
-
- Inherit from Tribe::DedicatedActor (uses a dedicated thread).
|
53
|
-
- Mixin Tribe::Actable and call the
|
54
|
-
|
50
|
+
- Inherit from ````Tribe::Actor```` (uses the shared thread pool).
|
51
|
+
- Inherit from ````Tribe::DedicatedActor```` (uses a dedicated thread).
|
52
|
+
- Mixin ````Tribe::Actable```` and call the ````init_actable```` in your constructor.
|
55
53
|
|
56
54
|
#### Root
|
57
55
|
|
58
|
-
|
56
|
+
A well designed application organizes its actors in a tree like structure.
|
59
57
|
To encourage this, Tribe has a special built-in actor known as the root actor.
|
60
|
-
You should use
|
58
|
+
You should use the root actor to spawn all of your application specific actors.
|
59
|
+
|
60
|
+
class MyActor < Tribe::Actor
|
61
|
+
private
|
62
|
+
# Your code goes here.
|
63
|
+
end
|
64
|
+
|
65
|
+
Tribe.root.spawn!(MyActor)
|
66
|
+
|
67
|
+
#### Command Handlers
|
68
|
+
|
69
|
+
Command handlers are how you customize your actors.
|
70
|
+
They are private methods that are prefixed with "on_" and they define the commands your actor knows how to handle.
|
71
|
+
They accept one argument, an instance of ````Tribe::Event```` that shouuld always be named ````event````.
|
61
72
|
|
62
|
-
|
73
|
+
A few command handlers are built into every actor to handle system specific events. They are:
|
63
74
|
|
64
|
-
|
75
|
+
- ````on_initialize````: This handler takes the place of Ruby's ````initialize````. It is the first event processsed by all actors.
|
76
|
+
- ````on_exception````: This handler will be called whenever an exception occurs. You can access the exception through ````event.data```` in case you want to print it, log it, etc. An exception inside of an actor will result in that actor's death.
|
77
|
+
- ````on_shutdown````: This handler will be called whenever an actor is asked to shutdown cleanly.
|
78
|
+
- ````on_child_died````: This handler gives an actor a chance to spawn a replacement child. You can access a reference to the child through ````event.data````. If the actor is a supervisor, it will continue to live otherwise it will die too.
|
79
|
+
- ````on_child_shutdown````: This handler is similar to ````on_child_died````, but for when a child is shutdown cleanly.
|
80
|
+
- ````on_parent_died````: This handler is also similar to ````on_child_died```` except for the parent actor. Child actors die when their parent dies.
|
65
81
|
|
66
|
-
|
82
|
+
You should never call the built in command handlers yourself.
|
83
|
+
They are reserved for the actor system and calling them yourself could result in unexpected behavior.
|
67
84
|
|
68
|
-
|
69
|
-
2. *System handlers* are postfixed with "_handler" and are built into the actor system. These are hooks into the Tribe's actor system.
|
85
|
+
## Messages
|
70
86
|
|
71
|
-
|
87
|
+
Messages are the most basic type of communication. They are sent using using two methods:
|
72
88
|
|
73
|
-
|
74
|
-
|
75
|
-
|
89
|
+
- ````message!````: This method is used to tell one actor to send another actor a message. A reference to the source actor is included in the message in case the destination actor wants to respond. Usually it is used when your actor code wants to message another actor.
|
90
|
+
- ````direct_message!````: This method is used to directly message an actor. Usually it is used when non-actor code wants to message an actor. No source actor is associated with the message.
|
91
|
+
|
92
|
+
Since messages are fire-and-forget, both of the above methods always return ````nil````.
|
93
|
+
|
94
|
+
Messages can include data that you want to pass between actors. It is best practice to treat data as owned by only one actor at a time. By doing this, you prevent race conditions and the need to create locks for your data.
|
76
95
|
|
77
96
|
# Create your custom actor class.
|
78
97
|
class MyActor < Tribe::Actor
|
@@ -81,26 +100,20 @@ These methods always return nil since they are fire-and-forget.
|
|
81
100
|
puts "Received a custom event (#{event.inspect})."
|
82
101
|
end
|
83
102
|
|
84
|
-
def
|
85
|
-
|
86
|
-
puts concat_e("MyActor (#{identifier}) died.", e)
|
87
|
-
end
|
88
|
-
|
89
|
-
def shutdown_handler(event)
|
90
|
-
super
|
91
|
-
puts "MyActor (#{identifier}) is shutting down. Put cleanup code here."
|
103
|
+
def on_shutdown(event)
|
104
|
+
puts "MyActor (#{identifier}) is shutting down."
|
92
105
|
end
|
93
106
|
end
|
94
107
|
|
95
108
|
# Create some named actors that are children of the root actor.
|
96
109
|
100.times do |i|
|
97
|
-
Tribe.root.spawn(MyActor, :name => "my_actor_#{i}")
|
110
|
+
Tribe.root.spawn!(MyActor, :name => "my_actor_#{i}")
|
98
111
|
end
|
99
112
|
|
100
113
|
# Send an event to each actor.
|
101
114
|
100.times do |i|
|
102
115
|
actor = Tribe.registry["my_actor_#{i}"]
|
103
|
-
actor.
|
116
|
+
actor.direct_message!(:my_custom, 'hello world')
|
104
117
|
end
|
105
118
|
|
106
119
|
# Shutdown the actors.
|
@@ -112,69 +125,29 @@ These methods always return nil since they are fire-and-forget.
|
|
112
125
|
## Registries
|
113
126
|
|
114
127
|
Registries hold references to named actors so that you can easily find them.
|
115
|
-
|
128
|
+
You don't have to create your own since there is a global one called ````Tribe.registry````.
|
129
|
+
The Root actor is named 'root' and stored in the default registry.
|
116
130
|
|
117
|
-
actor = Tribe.root.spawn(Tribe::Actor, :name => 'some_actor')
|
131
|
+
actor = Tribe.root.spawn!(Tribe::Actor, :name => 'some_actor')
|
118
132
|
|
119
133
|
if actor == Tribe.registry['some_actor']
|
120
134
|
puts 'Successfully found some_actor in the registry.'
|
121
135
|
end
|
122
136
|
|
123
|
-
## Timers
|
124
|
-
|
125
|
-
Actors can create timers to perform some work in the future.
|
126
|
-
Both one-shot and periodic timers are provided.
|
127
|
-
|
128
|
-
class MyActor < Tribe::Actor
|
129
|
-
private
|
130
|
-
def initialize(options = {})
|
131
|
-
super
|
132
|
-
timer(1, :timer, Time.now)
|
133
|
-
periodic_timer(1, :periodic_timer, Time.now)
|
134
|
-
end
|
135
|
-
|
136
|
-
def on_timer(event)
|
137
|
-
puts "MyActor (#{identifier}) ONE-SHOT: #{event.data}"
|
138
|
-
end
|
139
|
-
|
140
|
-
def on_periodic_timer(event)
|
141
|
-
puts "MyActor (#{identifier}) PERIODIC: #{event.data}"
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
# Create some named actors.
|
146
|
-
10.times do |i|
|
147
|
-
Tribe.root.spawn(MyActor, :name => "my_actor_#{i}")
|
148
|
-
end
|
149
|
-
|
150
|
-
# Sleep in order to observe the timers.
|
151
|
-
sleep(10)
|
152
|
-
|
153
|
-
# Shutdown the actors.
|
154
|
-
10.times do |i|
|
155
|
-
actor = Tribe.registry["my_actor_#{i}"]
|
156
|
-
actor.shutdown!
|
157
|
-
end
|
158
|
-
|
159
137
|
## Futures
|
160
138
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
139
|
+
Messages are limited in that they are one way (fire-and-forget).
|
140
|
+
Many times you'll be interested in receiving a response and this is when futures become useful.
|
141
|
+
To send a future you use ````future!```` instead of ````message!````.
|
142
|
+
It will return a ````Future```` object (instead of ````nil````) that will give you access to the result when it becomes available.
|
165
143
|
|
166
|
-
#### Non-blocking
|
144
|
+
#### Non-blocking API
|
167
145
|
|
168
146
|
Non-blocking futures are asynchronous and use callbacks.
|
169
147
|
No waiting for a result is involved and the actor will continue to process other events.
|
170
148
|
|
171
149
|
class ActorA < Tribe::Actor
|
172
150
|
private
|
173
|
-
def exception_handler(e)
|
174
|
-
super
|
175
|
-
puts concat_e("ActorA (#{identifier}) died.", e)
|
176
|
-
end
|
177
|
-
|
178
151
|
def on_start(event)
|
179
152
|
friend = registry['actor_b']
|
180
153
|
future = future!(friend, :compute, 10)
|
@@ -182,19 +155,18 @@ No waiting for a result is involved and the actor will continue to process other
|
|
182
155
|
future.success do |result|
|
183
156
|
puts "ActorA (#{identifier}) future result: #{result}"
|
184
157
|
end
|
158
|
+
end
|
185
159
|
|
186
|
-
|
187
|
-
|
188
|
-
end
|
160
|
+
def on_shutdown(event)
|
161
|
+
puts "MyActor (#{identifier}) is shutting down."
|
189
162
|
end
|
190
163
|
end
|
191
164
|
|
192
165
|
class ActorB < Tribe::Actor
|
193
|
-
|
194
|
-
|
195
|
-
puts
|
166
|
+
private
|
167
|
+
def on_shutdown(event)
|
168
|
+
puts "MyActor (#{identifier}) is shutting down."
|
196
169
|
end
|
197
|
-
|
198
170
|
def on_compute(event)
|
199
171
|
return factorial(event.data)
|
200
172
|
end
|
@@ -205,31 +177,28 @@ No waiting for a result is involved and the actor will continue to process other
|
|
205
177
|
end
|
206
178
|
end
|
207
179
|
|
208
|
-
actor_a = Tribe.root.spawn(ActorA, :name => 'actor_a')
|
209
|
-
actor_b = Tribe.root.spawn(ActorB, :name => 'actor_b')
|
180
|
+
actor_a = Tribe.root.spawn!(ActorA, :name => 'actor_a')
|
181
|
+
actor_b = Tribe.root.spawn!(ActorB, :name => 'actor_b')
|
210
182
|
|
211
|
-
actor_a.
|
183
|
+
actor_a.direct_message!(:start)
|
212
184
|
|
185
|
+
# Shutdown the actors.
|
186
|
+
sleep(3)
|
213
187
|
actor_a.shutdown!
|
214
188
|
actor_b.shutdown!
|
215
189
|
|
216
|
-
#### Blocking
|
190
|
+
#### Blocking API
|
217
191
|
|
218
192
|
Blocking futures are synchronous.
|
219
193
|
The actor won't process any other events until the future has a result.
|
220
194
|
|
221
195
|
class ActorA < Tribe::Actor
|
222
196
|
private
|
223
|
-
def exception_handler(e)
|
224
|
-
super
|
225
|
-
puts concat_e("ActorA (#{identifier}) died.", e)
|
226
|
-
end
|
227
|
-
|
228
197
|
def on_start(event)
|
229
198
|
friend = registry['actor_b']
|
230
199
|
future = future!(friend, :compute, 10)
|
231
200
|
|
232
|
-
future
|
201
|
+
wait!(future) # The current thread will sleep until a result is available.
|
233
202
|
|
234
203
|
if future.success?
|
235
204
|
puts "ActorA (#{identifier}) future result: #{future.result}"
|
@@ -240,11 +209,7 @@ The actor won't process any other events until the future has a result.
|
|
240
209
|
end
|
241
210
|
|
242
211
|
class ActorB < Tribe::Actor
|
243
|
-
|
244
|
-
super
|
245
|
-
puts concat_e("ActorB (#{identifier}) died.", e)
|
246
|
-
end
|
247
|
-
|
212
|
+
private
|
248
213
|
def on_compute(event)
|
249
214
|
return factorial(event.data)
|
250
215
|
end
|
@@ -255,10 +220,12 @@ The actor won't process any other events until the future has a result.
|
|
255
220
|
end
|
256
221
|
end
|
257
222
|
|
258
|
-
actor_a = Tribe.root.spawn(ActorA, :name => 'actor_a')
|
259
|
-
actor_b = Tribe.root.spawn(ActorB, :name => 'actor_b')
|
223
|
+
actor_a = Tribe.root.spawn!(ActorA, :name => 'actor_a')
|
224
|
+
actor_b = Tribe.root.spawn!(ActorB, :name => 'actor_b')
|
260
225
|
|
261
|
-
actor_a.
|
226
|
+
actor_a.direct_message!(:start)
|
227
|
+
|
228
|
+
sleep(3)
|
262
229
|
|
263
230
|
actor_a.shutdown!
|
264
231
|
actor_b.shutdown!
|
@@ -266,27 +233,50 @@ The actor won't process any other events until the future has a result.
|
|
266
233
|
#### Timeouts
|
267
234
|
|
268
235
|
Futures can be confgured to timeout after a specified number of seconds.
|
269
|
-
When a timeout occurs, the result of the future will be a Tribe::FutureTimeout exception.
|
236
|
+
When a timeout occurs, the result of the future will be a ````Tribe::FutureTimeout```` exception.
|
270
237
|
|
271
|
-
|
272
|
-
|
238
|
+
class ActorA < Tribe::Actor
|
239
|
+
private
|
240
|
+
def on_start(event)
|
241
|
+
friend = registry['actor_b']
|
242
|
+
future = future!(friend, :compute, 10)
|
243
|
+
future.timeout = 2
|
273
244
|
|
274
|
-
|
275
|
-
future.timeout = 2
|
245
|
+
wait!(future) # The current thread will sleep until a result is available.
|
276
246
|
|
277
|
-
|
278
|
-
|
247
|
+
if future.success?
|
248
|
+
puts "ActorA (#{identifier}) future result: #{future.result}"
|
249
|
+
else
|
250
|
+
puts "ActorA (#{identifier}) future failure: #{future.result}"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
class ActorB < Tribe::Actor
|
256
|
+
private
|
257
|
+
def on_compute(event)
|
258
|
+
sleep(4) # Force a timeout.
|
259
|
+
return event.data * 2
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
actor_a = Tribe.root.spawn!(ActorA, :name => 'actor_a')
|
264
|
+
actor_b = Tribe.root.spawn!(ActorB, :name => 'actor_b')
|
279
265
|
|
280
|
-
|
281
|
-
|
266
|
+
actor_a.direct_message!(:start)
|
267
|
+
|
268
|
+
sleep(6)
|
269
|
+
|
270
|
+
actor_a.shutdown!
|
271
|
+
actor_b.shutdown!
|
282
272
|
|
283
273
|
#### Performance Summary
|
284
274
|
|
285
|
-
Below you will find a summary of performance recommendations
|
275
|
+
Below you will find a summary of performance recommendations for futures:
|
286
276
|
|
287
|
-
- Use
|
288
|
-
- If you use
|
289
|
-
- If you use the blocking API, the actor calling
|
277
|
+
- Use ````message!```` unless you really need ````future!```` since futures have overhead.
|
278
|
+
- If you use ````future!````, prefer the non-blocking API over the blocking one.
|
279
|
+
- If you use ````future!```` with the blocking API, the actor calling ````wait!```` will create a temporary thread. Since threads are a a finite resource, you should be careful to not create more of them than your operating system can simultaneously support. There is no such concern with the non-blocking API.
|
290
280
|
|
291
281
|
## Forwarding
|
292
282
|
|
@@ -296,25 +286,13 @@ This lets you build routers that delegate work to other actors.
|
|
296
286
|
# Create your router class.
|
297
287
|
class MyRouter < Tribe::Actor
|
298
288
|
private
|
299
|
-
def
|
300
|
-
|
301
|
-
@processors = 100.times.map { MyProcessor.new }
|
289
|
+
def on_initialize(event)
|
290
|
+
@processors = 100.times.map { spawn!(MyProcessor) }
|
302
291
|
end
|
303
292
|
|
304
293
|
def on_process(event)
|
305
294
|
forward!(@processors[rand(100)])
|
306
295
|
end
|
307
|
-
|
308
|
-
def exception_handler(e)
|
309
|
-
super
|
310
|
-
puts concat_e("MyRouter (#{identifier}) died.", e)
|
311
|
-
end
|
312
|
-
|
313
|
-
def shutdown_handler(event)
|
314
|
-
super
|
315
|
-
puts "MyRouter (#{identifier}) is shutting down. Put cleanup code here."
|
316
|
-
@processors.each { |p| p.shutdown! }
|
317
|
-
end
|
318
296
|
end
|
319
297
|
|
320
298
|
# Create your processor class.
|
@@ -323,141 +301,194 @@ This lets you build routers that delegate work to other actors.
|
|
323
301
|
def on_process(event)
|
324
302
|
puts "MyProcessor (#{identifier}) received a process event (#{event.inspect})."
|
325
303
|
end
|
326
|
-
|
327
|
-
def exception_handler(e)
|
328
|
-
super
|
329
|
-
puts concat_e("MyProcessor (#{identifier}) died.", e)
|
330
|
-
end
|
331
|
-
|
332
|
-
def shutdown_handler(event)
|
333
|
-
super
|
334
|
-
puts "MyProcessor (#{identifier}) is shutting down. Put cleanup code here."
|
335
|
-
end
|
336
304
|
end
|
337
305
|
|
338
306
|
# Create the router.
|
339
|
-
router = Tribe.root.spawn(MyRouter, :name => 'router')
|
307
|
+
router = Tribe.root.spawn!(MyRouter, :name => 'router')
|
340
308
|
|
341
309
|
# Send an event to the router and it will forward it to a random processor.
|
342
310
|
100.times do |i|
|
343
|
-
router.
|
311
|
+
router.direct_message!(:process, i)
|
344
312
|
end
|
345
313
|
|
346
314
|
# Shutdown the router.
|
315
|
+
sleep(3)
|
347
316
|
router.shutdown!
|
348
317
|
|
349
|
-
##
|
318
|
+
## Timers
|
350
319
|
|
351
|
-
|
352
|
-
|
353
|
-
To create a linked actor you use the Actable#spawn method.
|
354
|
-
By default, if a linked actor dies, it will cause its parent and children to die too.
|
355
|
-
Thus the entire tree lives or dies together.
|
320
|
+
Actors can create timers to perform some work in the future.
|
321
|
+
Both one-shot and periodic timers are provided.
|
356
322
|
|
357
|
-
|
358
|
-
class Level1 < Tribe::Actor
|
323
|
+
class MyActor < Tribe::Actor
|
359
324
|
private
|
360
|
-
def
|
361
|
-
|
362
|
-
|
363
|
-
puts name
|
364
|
-
actor = spawn(Level2, :name => name)
|
365
|
-
message!(actor, :spawn, i)
|
366
|
-
end
|
325
|
+
def on_initialize(event)
|
326
|
+
timer!(1, :timer, 'hello once')
|
327
|
+
periodic_timer!(1, :periodic_timer, 'hello many times')
|
367
328
|
end
|
368
|
-
end
|
369
329
|
|
370
|
-
|
371
|
-
|
372
|
-
private
|
373
|
-
def on_spawn(event)
|
374
|
-
5.times do |i|
|
375
|
-
name = "level3_#{event.data}_#{i}"
|
376
|
-
actor = spawn(Level3, :name => name)
|
377
|
-
message!(actor, :spawn)
|
378
|
-
end
|
330
|
+
def on_timer(event)
|
331
|
+
puts "MyActor (#{identifier}) ONE-SHOT: #{event.data}"
|
379
332
|
end
|
380
|
-
end
|
381
333
|
|
382
|
-
|
383
|
-
|
384
|
-
private
|
385
|
-
def on_spawn(event)
|
386
|
-
puts "#{identifier} hello world!"
|
334
|
+
def on_periodic_timer(event)
|
335
|
+
puts "MyActor (#{identifier}) PERIODIC: #{event.data}"
|
387
336
|
end
|
388
337
|
end
|
389
338
|
|
390
|
-
# Create
|
391
|
-
|
339
|
+
# Create some named actors.
|
340
|
+
10.times do |i|
|
341
|
+
Tribe.root.spawn!(MyActor, :name => "my_actor_#{i}")
|
342
|
+
end
|
343
|
+
|
344
|
+
# Sleep in order to observe the timers.
|
345
|
+
sleep(10)
|
346
|
+
|
347
|
+
# Shutdown the actors.
|
348
|
+
10.times do |i|
|
349
|
+
actor = Tribe.registry["my_actor_#{i}"]
|
350
|
+
actor.shutdown!
|
351
|
+
end
|
352
|
+
|
353
|
+
## Linking
|
354
|
+
|
355
|
+
Linking allows actors to group together so that they all live or die together.
|
356
|
+
Such linking is useful for breaking up complex problems into multiple smaller units.
|
357
|
+
To create a linked actor you use the ````spawn!```` method.
|
358
|
+
By default, if a linked actor dies, it will cause its parent and children to die too.
|
359
|
+
You an override this behavior by using supervisors.
|
392
360
|
|
393
|
-
#
|
394
|
-
top.
|
361
|
+
# Create some linked actors.
|
362
|
+
top = Tribe::Actor.new
|
363
|
+
middle = top.spawn!(Tribe::Actor)
|
364
|
+
bottom = middle.spawn!(Tribe::Actor)
|
365
|
+
|
366
|
+
# Force an exception on the middle actor (it has a parent and a child).
|
367
|
+
middle.perform! { raise 'uh oh' }
|
368
|
+
|
369
|
+
# Wait.
|
370
|
+
sleep(3)
|
371
|
+
|
372
|
+
# All actors died together.
|
373
|
+
puts "Top: #{top.alive?}: #{top.exception.class}"
|
374
|
+
puts "Middle: #{middle.alive?}: #{middle.exception.class}"
|
375
|
+
puts "Bottom: #{bottom.alive?}: #{bottom.exception.class}"
|
395
376
|
|
396
377
|
## Supervisors
|
397
378
|
|
398
379
|
A failure in a linked actor will cause all associated actors (parent and children) to die.
|
399
380
|
Supervisors can be used to block the failure from propogating.
|
400
|
-
You then have the option to re-
|
381
|
+
You then have the option to re-spawn the failed actor.
|
382
|
+
They are created by passing ````{:supervise => true}```` as a third argument to ````spawn!````.
|
383
|
+
You can then detect dead children by overriding ````on_child_died````.
|
384
|
+
|
385
|
+
# Create some linked actors.
|
386
|
+
top = Tribe::Actor.new
|
387
|
+
middle = top.spawn!(Tribe::Actor, {}, {:supervise => true})
|
388
|
+
bottom = middle.spawn!(Tribe::Actor)
|
389
|
+
|
390
|
+
# Force an exception on the middle actor (it has a parent and a child).
|
391
|
+
middle.perform! { raise 'uh oh' }
|
392
|
+
|
393
|
+
# Wait.
|
394
|
+
sleep(3)
|
395
|
+
|
396
|
+
# Top actor lives because it's a supervisor. The other two die.
|
397
|
+
puts "Top: #{top.alive?}: #{top.exception.class}"
|
398
|
+
puts "Middle: #{middle.alive?}: #{middle.exception.class}"
|
399
|
+
puts "Bottom: #{bottom.alive?}: #{bottom.exception.class}"
|
401
400
|
|
402
|
-
|
403
|
-
class Level1 < Tribe::Actor
|
404
|
-
private
|
405
|
-
def on_spawn(event)
|
406
|
-
5.times do |i|
|
407
|
-
create_subtree
|
408
|
-
end
|
409
|
-
end
|
401
|
+
#### Logging exceptions
|
410
402
|
|
411
|
-
|
412
|
-
|
413
|
-
message!(actor, :spawn)
|
414
|
-
end
|
403
|
+
It is common practice to log actor exceptions or print them to stdout.
|
404
|
+
This is easily accomplished with the ````on_exception```` handler in a base class:
|
415
405
|
|
416
|
-
|
417
|
-
|
418
|
-
|
406
|
+
class MyBaseActor < Tribe::Actor
|
407
|
+
private
|
408
|
+
def on_exception(event)
|
409
|
+
e = event.data[:exception]
|
410
|
+
puts "#{e.class.name}: #{e.message}:\n#{e.backtrace.join("\n")}"
|
419
411
|
end
|
420
412
|
end
|
421
413
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
414
|
+
class CustomActor < MyBaseActor
|
415
|
+
end
|
416
|
+
|
417
|
+
actor = Tribe.root.spawn!(CustomActor)
|
418
|
+
actor.perform! { raise 'goodbye' }
|
419
|
+
|
420
|
+
Note that you should be careful to make sure ````on_exception```` never raises an exception itself.
|
421
|
+
If it does, this second exception will be ignored.
|
422
|
+
Thus it is best to limit the use of ````on_exception```` to logging exceptions in a common base class.
|
423
|
+
|
424
|
+
## Blocking code
|
425
|
+
|
426
|
+
Occassionally you will have a need to execute blocking code in one of your actors.
|
427
|
+
The most common cases of blocking code are network IO, disk IO, database queries, and the ````sleep```` function.
|
428
|
+
|
429
|
+
Actors have a convenient method named ````blocking!```` that you should use to wrap such code.
|
430
|
+
Under the hood this method is expanding and contracting the thread pool to compensate for the blocked thread.
|
431
|
+
This will prevent thread pool starvation.
|
432
|
+
|
433
|
+
The ````blocking!```` method is designed to work with dedicated and non-dedicated actors.
|
434
|
+
By using this method in all of your actors, you will make it easy to convert between the two types.
|
435
|
+
|
436
|
+
An actor's ````wait!```` method (used with futures) already calls ````blocking!```` for you.
|
437
|
+
|
438
|
+
If for some reason Ruby can't create a new thread, Ruby will raise a ````ThreadError```` and your actor will die.
|
439
|
+
Most modern operating systems can support many thousands of simultanous threads so refer to your operating system documentation as you may need to increase the limits.
|
440
|
+
|
441
|
+
To support in the tens of thousands, hundreds of thousands, or potentially millions of actors, you will need to use non-blocking actors.
|
442
|
+
|
443
|
+
class MyActor < Tribe::Actor
|
444
|
+
private
|
445
|
+
def on_start(event)
|
446
|
+
blocking! do
|
447
|
+
sleep 6
|
429
448
|
end
|
430
449
|
end
|
450
|
+
|
431
451
|
end
|
432
452
|
|
433
|
-
#
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
453
|
+
# Print the default pool size.
|
454
|
+
puts "Pool size (before): #{Workers.pool.size}"
|
455
|
+
|
456
|
+
# Spawn some actors that go to sleep for a bit.
|
457
|
+
100.times do
|
458
|
+
actor = Tribe.root.spawn!(MyActor)
|
459
|
+
actor.direct_message!(:start)
|
440
460
|
end
|
441
461
|
|
442
|
-
#
|
443
|
-
|
462
|
+
# Wait for all of the actors to sleep.
|
463
|
+
sleep(2)
|
464
|
+
|
465
|
+
# The pool size is increased by 100 threads.
|
466
|
+
puts "Pool size (during): #{Workers.pool.size}"
|
444
467
|
|
445
|
-
#
|
446
|
-
|
468
|
+
# Wait for all of the actors to stop sleeping.
|
469
|
+
sleep(10)
|
470
|
+
|
471
|
+
# The pool size is back to the default size.
|
472
|
+
puts "Pool size (after): #{Workers.pool.size}"
|
473
|
+
|
474
|
+
## Debugging
|
447
475
|
|
448
|
-
|
476
|
+
Tribe is written in pure Ruby so it will work with all existing debuggers that support Ruby & threads.
|
477
|
+
[Byebug] (https://github.com/deivid-rodriguez/byebug) is commonly used with MRI Ruby 2.X and will let you set breakpoints.
|
449
478
|
|
450
|
-
|
451
|
-
|
479
|
+
The most common problem you will encounter with actors is that they die due to exceptions.
|
480
|
+
You can access the exception that caused an actor to die by calling the ````exception```` method on the actor:
|
481
|
+
|
482
|
+
actor = Tribe::Actor.new
|
483
|
+
actor.perform! { raise 'goodbye' }
|
484
|
+
sleep(3)
|
485
|
+
e = actor.exception
|
486
|
+
puts "#{e.class.name}: #{e.message}:\n#{e.backtrace.join("\n")}"
|
452
487
|
|
453
488
|
## Benchmarks
|
454
489
|
|
455
|
-
|
490
|
+
Please see the [performance] (https://github.com/chadrem/tribe/wiki/Performance "performance") wiki page for more information.
|
456
491
|
|
457
492
|
## Contributing
|
458
493
|
|
459
|
-
|
460
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
461
|
-
3. Commit your changes (`git commit -am 'Added some feature'`)
|
462
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
463
|
-
5. Create new Pull Request
|
494
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/chadrem/tribe.
|