tribe 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|