tribe 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +9 -12
- data/lib/tribe.rb +1 -0
- data/lib/tribe/actable.rb +110 -80
- data/lib/tribe/actor_state.rb +13 -0
- data/lib/tribe/exceptions.rb +5 -0
- data/lib/tribe/future.rb +5 -5
- data/lib/tribe/registry.rb +1 -1
- data/lib/tribe/version.rb +1 -1
- metadata +3 -2
data/README.md
CHANGED
@@ -2,9 +2,10 @@
|
|
2
2
|
|
3
3
|
Tribe is a Ruby gem that implements event-driven [actors] (http://en.wikipedia.org/wiki/Actor_model "actors").
|
4
4
|
Actors are lightweight concurrent objects that use asynchronous message passing for communication.
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
|
6
|
+
Tribe focuses on high performance, low latency, a simple API, and flexibility.
|
7
|
+
It's goal is to support at least one million actors running on a small group of threads.
|
8
|
+
It is built on top of the [Workers] (https://github.com/chadrem/workers "Workers") gem.
|
8
9
|
|
9
10
|
Event-driven servers can be built using [Tribe EM] (https://github.com/chadrem/tribe_em "Tribe EM").
|
10
11
|
|
@@ -78,12 +79,8 @@ Actors that block for long periods of time should use a dedicated thread (:dedic
|
|
78
79
|
:name => nil # The name of the actor (must be unique in the registry).
|
79
80
|
)
|
80
81
|
|
81
|
-
|
82
|
-
|
83
|
-
:mailbox => Tribe::Mailbox.new, # The mailbox used to receive events.
|
84
|
-
: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).
|
86
|
-
)
|
82
|
+
The DedicatedActor class is a simple wrapper around the Actor class.
|
83
|
+
It takes all the same options except for :pool and :dedicated since they aren't applicable.
|
87
84
|
|
88
85
|
## Registries
|
89
86
|
|
@@ -99,7 +96,7 @@ In general you shouldn't have to create your own since there is a global one (Tr
|
|
99
96
|
## Timers
|
100
97
|
|
101
98
|
Actors can create timers to perform some work in the future.
|
102
|
-
Both one-shot and periodic timers are
|
99
|
+
Both one-shot and periodic timers are provided.
|
103
100
|
|
104
101
|
class MyActor < Tribe::Actor
|
105
102
|
private
|
@@ -253,9 +250,9 @@ The actor won't process any other events until the future has a result.
|
|
253
250
|
#### Futures and Performance
|
254
251
|
|
255
252
|
You should prefer non-blocking futures as much as possible in your application code.
|
256
|
-
This is because blocking
|
253
|
+
This is because a blocking future (Future#wait) causes the current actor (and thread) to sleep.
|
257
254
|
|
258
|
-
Tribe is designed specifically to support
|
255
|
+
Tribe is designed specifically to support a large number of actors running on a small number of threads.
|
259
256
|
Thus, you will run into performance and/or deadlock problems if too many actors are waiting at the same time.
|
260
257
|
|
261
258
|
If you choose to use blocing futures then it is highly recommended that you only use them with dedicated actors.
|
data/lib/tribe.rb
CHANGED
data/lib/tribe/actable.rb
CHANGED
@@ -1,54 +1,66 @@
|
|
1
|
-
# This module is designed to be mixed in with your application code.
|
2
|
-
# Because of this, all instance variables are prefixed with an underscore.
|
3
|
-
# The hope is to minimize the chances of conflicts.
|
4
|
-
# Long term my goal is to move all of these variables into an ActorState object.
|
5
|
-
|
6
1
|
module Tribe
|
7
2
|
module Actable
|
8
3
|
include Workers::Helpers
|
9
4
|
|
5
|
+
#
|
6
|
+
# Initialization method.
|
7
|
+
# Notes: Call this in your constructor.
|
8
|
+
#
|
9
|
+
|
10
10
|
private
|
11
11
|
|
12
12
|
def init_actable(options = {})
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@
|
13
|
+
# Symbols aren't GCed in JRuby so force string names.
|
14
|
+
if options[:name] && !options[:name].is_a?(String)
|
15
|
+
raise Tribe::ActorNameError.new('Name must be a string.')
|
16
|
+
end
|
17
|
+
|
18
|
+
@logger = Workers::LogProxy.new(options[:logger])
|
19
|
+
@_as = Tribe::ActorState.new
|
20
|
+
@_as.dedicated = options[:dedicated] || false
|
21
|
+
@_as.mailbox = options[:mailbox] || Tribe::Mailbox.new
|
22
|
+
@_as.registry = options[:registry] || Tribe.registry
|
23
|
+
@_as.scheduler = options[:scheduler]
|
24
|
+
@_as.name = options[:name]
|
25
|
+
@_as.pool = @_as.dedicated ? Workers::Pool.new(:size => 1) : (options[:pool] || Workers.pool)
|
26
|
+
@_as.alive = true
|
23
27
|
|
24
|
-
@
|
28
|
+
@_as.registry.register(self)
|
25
29
|
end
|
26
30
|
|
31
|
+
#
|
32
|
+
# Thread safe public methods.
|
33
|
+
# Notes: These are the methods that actors use to communicate with each other.
|
34
|
+
# Actors should avoid sharing mutable state in order to remain thread safe.
|
35
|
+
#
|
36
|
+
|
27
37
|
public
|
28
38
|
|
29
39
|
def enqueue(command, data = nil)
|
30
40
|
return false unless alive?
|
31
41
|
|
32
|
-
@
|
33
|
-
@
|
42
|
+
@_as.mailbox.push(Workers::Event.new(command, data)) do
|
43
|
+
@_as.pool.perform { process_events }
|
34
44
|
end
|
35
45
|
|
36
46
|
return true
|
37
47
|
end
|
38
48
|
|
39
49
|
def enqueue_future(command, data = nil)
|
50
|
+
@_as.futures ||= Tribe::SafeSet.new # Lazy instantiation for performance.
|
51
|
+
|
40
52
|
future = Tribe::Future.new
|
41
|
-
@
|
53
|
+
@_as.futures.add(future)
|
42
54
|
|
43
55
|
perform do
|
44
56
|
begin
|
45
|
-
result =
|
57
|
+
result = event_handler(Workers::Event.new(command, data))
|
46
58
|
future.result = result
|
47
|
-
rescue Exception =>
|
48
|
-
future.result =
|
59
|
+
rescue Exception => exception
|
60
|
+
future.result = exception
|
49
61
|
raise
|
50
62
|
ensure
|
51
|
-
@
|
63
|
+
@_as.futures.delete(future)
|
52
64
|
end
|
53
65
|
end
|
54
66
|
|
@@ -56,15 +68,15 @@ module Tribe
|
|
56
68
|
end
|
57
69
|
|
58
70
|
def alive?
|
59
|
-
@
|
71
|
+
@_as.mailbox.synchronize { return @_as.alive }
|
60
72
|
end
|
61
73
|
|
62
74
|
def name
|
63
|
-
return @
|
75
|
+
return @_as.name
|
64
76
|
end
|
65
77
|
|
66
78
|
def identifier
|
67
|
-
return @
|
79
|
+
return @_as.name ? "#{object_id}:#{@_as.name}" : object_id
|
68
80
|
end
|
69
81
|
|
70
82
|
def shutdown
|
@@ -75,99 +87,117 @@ module Tribe
|
|
75
87
|
return enqueue(:perform, block)
|
76
88
|
end
|
77
89
|
|
90
|
+
#
|
91
|
+
# Private event handlers.
|
92
|
+
# Notes: These methods are designed to be overriden (make sure you call super).
|
93
|
+
#
|
94
|
+
|
78
95
|
private
|
79
96
|
|
80
|
-
|
81
|
-
|
97
|
+
# The return value is used as the result of a future.
|
98
|
+
def event_handler(event)
|
99
|
+
return send("on_#{event.command}", event)
|
82
100
|
end
|
83
101
|
|
84
|
-
def
|
85
|
-
return
|
102
|
+
def exception_handler(exception)
|
103
|
+
return nil
|
86
104
|
end
|
87
105
|
|
88
|
-
def
|
89
|
-
return
|
106
|
+
def shutdown_handler(event)
|
107
|
+
return nil
|
90
108
|
end
|
91
109
|
|
92
|
-
def
|
93
|
-
|
94
|
-
case event.command
|
95
|
-
when :shutdown
|
96
|
-
cleanup
|
97
|
-
shutdown_handler(event)
|
98
|
-
when :perform
|
99
|
-
perform_handler(event)
|
100
|
-
else
|
101
|
-
process_event(event)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
rescue Exception => e
|
106
|
-
cleanup(e)
|
107
|
-
exception_handler(e)
|
108
|
-
ensure
|
109
|
-
@_mailbox.release do
|
110
|
-
@_pool.perform { process_events if @_alive }
|
111
|
-
end
|
110
|
+
def perform_handler(event)
|
111
|
+
event.data.call
|
112
112
|
|
113
113
|
return nil
|
114
114
|
end
|
115
115
|
|
116
|
-
def
|
117
|
-
@
|
118
|
-
@
|
119
|
-
@
|
120
|
-
@
|
121
|
-
@
|
116
|
+
def cleanup_handler(exception = nil)
|
117
|
+
@_as.pool.shutdown if @_as.dedicated
|
118
|
+
@_as.mailbox.synchronize { @_as.alive = false }
|
119
|
+
@_as.registry.unregister(self)
|
120
|
+
@_as.timers.each { |t| t.cancel } if @_as.timers
|
121
|
+
@_as.futures.each { |f| f.result = exception || Tribe::ActorShutdownError.new } if @_as.futures
|
122
122
|
|
123
123
|
return nil
|
124
124
|
end
|
125
125
|
|
126
|
-
#
|
127
|
-
#
|
128
|
-
|
129
|
-
|
130
|
-
end
|
126
|
+
#
|
127
|
+
# Private API methods.
|
128
|
+
# Notes: Use these methods internally in your actor.
|
129
|
+
#
|
131
130
|
|
132
|
-
|
133
|
-
def exception_handler(e)
|
134
|
-
return nil
|
135
|
-
end
|
131
|
+
private
|
136
132
|
|
137
|
-
|
138
|
-
|
139
|
-
return nil
|
133
|
+
def registry
|
134
|
+
return @_as.registry
|
140
135
|
end
|
141
136
|
|
142
|
-
def
|
143
|
-
|
144
|
-
|
145
|
-
return nil
|
137
|
+
def pool
|
138
|
+
return @_as.pool
|
146
139
|
end
|
147
140
|
|
148
141
|
def timer(delay, command, data = nil)
|
149
|
-
|
150
|
-
|
142
|
+
# Lazy instantiation for performance.
|
143
|
+
@_as.scheduler ||= Workers.scheduler
|
144
|
+
@_as.timers ||= Tribe::SafeSet.new
|
145
|
+
|
146
|
+
timer = Workers::Timer.new(delay, :scheduler => @_as.scheduler) do
|
147
|
+
@_as.timers.delete(timer)
|
151
148
|
enqueue(command, data)
|
152
149
|
end
|
153
150
|
|
154
|
-
@
|
151
|
+
@_as.timers.add(timer)
|
155
152
|
|
156
153
|
return timer
|
157
154
|
end
|
158
155
|
|
159
156
|
def periodic_timer(delay, command, data = nil)
|
160
|
-
|
157
|
+
# Lazy instantiation for performance.
|
158
|
+
@_as.scheduler ||= Workers.scheduler
|
159
|
+
@_as.timers ||= Tribe::SafeSet.new
|
160
|
+
|
161
|
+
timer = Workers::PeriodicTimer.new(delay, :scheduler => @_as.scheduler) do
|
161
162
|
enqueue(command, data)
|
162
163
|
unless alive?
|
163
|
-
@
|
164
|
+
@_as.timers.delete(timer)
|
164
165
|
timer.cancel
|
165
166
|
end
|
166
167
|
end
|
167
168
|
|
168
|
-
@
|
169
|
+
@_as.timers.add(timer)
|
169
170
|
|
170
171
|
return timer
|
171
172
|
end
|
173
|
+
|
174
|
+
#
|
175
|
+
# Private internal methods.
|
176
|
+
# Notes: These are used by the actor system and you should never call them directly.
|
177
|
+
#
|
178
|
+
|
179
|
+
def process_events
|
180
|
+
while (event = @_as.mailbox.shift)
|
181
|
+
case event.command
|
182
|
+
when :shutdown
|
183
|
+
cleanup_handler
|
184
|
+
shutdown_handler(event)
|
185
|
+
when :perform
|
186
|
+
perform_handler(event)
|
187
|
+
else
|
188
|
+
event_handler(event)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
rescue Exception => exception
|
193
|
+
cleanup_handler(exception)
|
194
|
+
exception_handler(exception)
|
195
|
+
ensure
|
196
|
+
@_as.mailbox.release do
|
197
|
+
@_as.pool.perform { process_events if @_as.alive }
|
198
|
+
end
|
199
|
+
|
200
|
+
return nil
|
201
|
+
end
|
172
202
|
end
|
173
203
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Tribe
|
2
|
+
class ActorState
|
3
|
+
attr_accessor :dedicated
|
4
|
+
attr_accessor :mailbox
|
5
|
+
attr_accessor :registry
|
6
|
+
attr_accessor :scheduler
|
7
|
+
attr_accessor :timers
|
8
|
+
attr_accessor :name
|
9
|
+
attr_accessor :pool
|
10
|
+
attr_accessor :alive
|
11
|
+
attr_accessor :futures
|
12
|
+
end
|
13
|
+
end
|
data/lib/tribe/exceptions.rb
CHANGED
data/lib/tribe/future.rb
CHANGED
@@ -19,7 +19,7 @@ module Tribe
|
|
19
19
|
|
20
20
|
def result=(val)
|
21
21
|
@mutex.synchronize do
|
22
|
-
raise 'Result must only be set once.' unless @state == :initialized
|
22
|
+
raise Tribe::FutureError.new('Result must only be set once.') unless @state == :initialized
|
23
23
|
|
24
24
|
@result = val
|
25
25
|
@state = :finished
|
@@ -37,7 +37,7 @@ module Tribe
|
|
37
37
|
|
38
38
|
def result
|
39
39
|
@mutex.synchronize do
|
40
|
-
raise 'Result must be set first.' unless @state == :finished
|
40
|
+
raise Tribe::FutureError.new('Result must be set first.') unless @state == :finished
|
41
41
|
|
42
42
|
return @result
|
43
43
|
end
|
@@ -55,7 +55,7 @@ module Tribe
|
|
55
55
|
|
56
56
|
def success?
|
57
57
|
@mutex.synchronize do
|
58
|
-
raise 'Result must be set first.' unless @state == :finished
|
58
|
+
raise Tribe::FutureError.new('Result must be set first.') unless @state == :finished
|
59
59
|
|
60
60
|
return !@result.is_a?(Exception)
|
61
61
|
end
|
@@ -73,7 +73,7 @@ module Tribe
|
|
73
73
|
when :finished
|
74
74
|
yield(@result) unless @result.is_a?(Exception)
|
75
75
|
else
|
76
|
-
raise 'Invalid state.'
|
76
|
+
raise Tribe::FutureError.new('Invalid state.')
|
77
77
|
end
|
78
78
|
|
79
79
|
return nil
|
@@ -88,7 +88,7 @@ module Tribe
|
|
88
88
|
when :finished
|
89
89
|
yield(@result) if @result.is_a?(Exception)
|
90
90
|
else
|
91
|
-
raise 'Invalid state.'
|
91
|
+
raise Tribe::FutureError.new('Invalid state.')
|
92
92
|
end
|
93
93
|
|
94
94
|
return nil
|
data/lib/tribe/registry.rb
CHANGED
@@ -8,7 +8,7 @@ module Tribe
|
|
8
8
|
|
9
9
|
def register(actor)
|
10
10
|
@mutex.synchronize do
|
11
|
-
raise("Actor already exists (#{actor.name}).") if @actors_by_name.key?(actor.name)
|
11
|
+
raise Tribe::RegistryError.new("Actor already exists (#{actor.name}).") if @actors_by_name.key?(actor.name)
|
12
12
|
|
13
13
|
@actors_by_name[actor.name] = actor if actor.name
|
14
14
|
@actors_by_oid[actor.object_id] = actor
|
data/lib/tribe/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tribe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: workers
|
@@ -43,6 +43,7 @@ files:
|
|
43
43
|
- lib/tribe.rb
|
44
44
|
- lib/tribe/actable.rb
|
45
45
|
- lib/tribe/actor.rb
|
46
|
+
- lib/tribe/actor_state.rb
|
46
47
|
- lib/tribe/dedicated_actor.rb
|
47
48
|
- lib/tribe/exceptions.rb
|
48
49
|
- lib/tribe/future.rb
|