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 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
- Tribe focuses on high performance, low latency, an easy to use API, and flexibility.
6
- It is built on top of the [Workers] (https://github.com/chadrem/workers "Workers") gem, which allows it to support many actors (millions should be possible).
7
- Actors can use a shared thread pool (default) or dedicted threads.
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
- actor = Tribe::DedicatedActor.new(
82
- :logger => nil, # Ruby logger instance.
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 provides.
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 futures (Future#wait) causes the current actor (and thread) to sleep.
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 having a large number of actors running on a small number of threads.
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
@@ -5,6 +5,7 @@ require 'workers'
5
5
  require 'tribe/safe_set'
6
6
  require 'tribe/exceptions'
7
7
  require 'tribe/mailbox'
8
+ require 'tribe/actor_state'
8
9
  require 'tribe/actable'
9
10
  require 'tribe/actor'
10
11
  require 'tribe/dedicated_actor'
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
- @_logger = Workers::LogProxy.new(options[:logger])
14
- @_dedicated = options[:dedicated] || false
15
- @_mailbox = options[:mailbox] || Tribe::Mailbox.new
16
- @_registry = options[:registry] || Tribe.registry
17
- @_scheduler = options[:scheduler] || Workers.scheduler
18
- @_timers = Tribe::SafeSet.new
19
- @_name = options[:name]
20
- @_pool = @_dedicated ? Workers::Pool.new(:size => 1) : (options[:pool] || Workers.pool)
21
- @_alive = true
22
- @_futures = Tribe::SafeSet.new
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
- @_registry.register(self)
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
- @_mailbox.push(Workers::Event.new(command, data)) do
33
- @_pool.perform { process_events }
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
- @_futures.add(future)
53
+ @_as.futures.add(future)
42
54
 
43
55
  perform do
44
56
  begin
45
- result = result = process_event(Workers::Event.new(command, data))
57
+ result = event_handler(Workers::Event.new(command, data))
46
58
  future.result = result
47
- rescue Exception => e
48
- future.result = e
59
+ rescue Exception => exception
60
+ future.result = exception
49
61
  raise
50
62
  ensure
51
- @_futures.delete(future)
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
- @_mailbox.synchronize { return @_alive }
71
+ @_as.mailbox.synchronize { return @_as.alive }
60
72
  end
61
73
 
62
74
  def name
63
- return @_name
75
+ return @_as.name
64
76
  end
65
77
 
66
78
  def identifier
67
- return @_name ? "#{object_id}:#{@_name}" : object_id
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
- def registry
81
- return @_registry
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 pool
85
- return @_pool
102
+ def exception_handler(exception)
103
+ return nil
86
104
  end
87
105
 
88
- def logger
89
- return @_logger
106
+ def shutdown_handler(event)
107
+ return nil
90
108
  end
91
109
 
92
- def process_events
93
- while (event = @_mailbox.shift)
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 cleanup(e = nil)
117
- @_pool.shutdown if @_dedicated
118
- @_mailbox.synchronize { @_alive = false }
119
- @_registry.unregister(self)
120
- @_timers.each { |t| t.cancel }
121
- @_futures.each { |f| f.result = e || Tribe::ActorShutdownError.new }
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
- # Override and call super as necessary.
127
- # Note that the return value is used as the result of a future.
128
- def process_event(event)
129
- return send("on_#{event.command}", event)
130
- end
126
+ #
127
+ # Private API methods.
128
+ # Notes: Use these methods internally in your actor.
129
+ #
131
130
 
132
- # Override and call super as necessary.
133
- def exception_handler(e)
134
- return nil
135
- end
131
+ private
136
132
 
137
- # Override and call super as necessary.
138
- def shutdown_handler(event)
139
- return nil
133
+ def registry
134
+ return @_as.registry
140
135
  end
141
136
 
142
- def perform_handler(event)
143
- event.data.call
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
- timer = Workers::Timer.new(delay, :scheduler => @_scheduler) do
150
- @_timers.delete(timer)
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
- @_timers.add(timer)
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
- timer = Workers::PeriodicTimer.new(delay, :scheduler => @_scheduler) do
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
- @_timers.delete(timer)
164
+ @_as.timers.delete(timer)
164
165
  timer.cancel
165
166
  end
166
167
  end
167
168
 
168
- @_timers.add(timer)
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
@@ -1,3 +1,8 @@
1
1
  module Tribe
2
2
  class ActorShutdownError < RuntimeError; end
3
+ class ActorNameError < RuntimeError; end
4
+
5
+ class FutureError < RuntimeError; end
6
+
7
+ class RegistryError < RuntimeError; end
3
8
  end
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Tribe
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.1'
3
3
  end
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.0
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-25 00:00:00.000000000 Z
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