workers 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -116,35 +116,109 @@ You can create additional or custom ones as necessary:
116
116
  # Shutdown the scheduler.
117
117
  scheduler.dispose
118
118
 
119
+ ## Actors
120
+
121
+ Actors are light weight concurrent objects that use asynchronous message passing to communicate with each other.
122
+ They are event driven and use a worker pool in order to execute their event loop.
123
+
124
+ # Create your custom actor class.
125
+ class MyActor < Workers::Actor
126
+ private
127
+ def initialize(options = {})
128
+ super
129
+ end
130
+
131
+ def process_event(event)
132
+ case event.command
133
+ when :my_custom
134
+ my_custom_handler(event)
135
+ end
136
+ end
137
+
138
+ def my_custom_handler(event)
139
+ puts "Received a custom event (#{event.inspect})"
140
+ end
141
+
142
+ def exception_handler(e)
143
+ puts concat_e("MyActor (#{identifier}) died.", e)
144
+ end
145
+
146
+ def shutdown_handler(event)
147
+ puts "MyActor (#{identifier}) is shutting down. Put cleanup code here."
148
+ end
149
+ end
150
+
151
+ # Create some named actors.
152
+ 100.times do |i|
153
+ MyActor.new(:name => "my_actor_#{i}")
154
+ end
155
+
156
+ # Send an event to each actors. Find each actor using the registry.
157
+ 100.times do |i|
158
+ actor = Workers.registry["my_actor_#{i}"]
159
+ actor.enqueue(:my_custom, 'hello world')
160
+ end
161
+
162
+ # Shutdown the actors.
163
+ 100.times do |i|
164
+ actor = Workers.registry["my_actor_#{i}"]
165
+ actor.enqueue(:shutdown)
166
+ end
167
+
168
+ ### Implementation notes
169
+ Because actors use a shared worker pool, it is important that they don't block for long periods of time.
170
+ If you need an actor that can block for long periods then you should give the actor a dedicated thread (:dedicated => true).
171
+
172
+ ## Registries
173
+
174
+ Registries hold references to named actors.
175
+ In general you shouldn't have to create your own since there is a global one (Workers.registry).
176
+
119
177
  ## Options (defaults below):
120
178
 
121
179
  pool = Workers::Pool.new(
122
- :size => 20, # Number of threads to create.
123
- :logger => nil, # Ruby Logger instance.
124
- :worker_class => Workers::Worker # Class of worker to use for this pool.
180
+ :size => 20, # Number of threads to create.
181
+ :logger => nil, # Ruby Logger instance.
182
+ :worker_class => Workers::Worker # Class of worker to use for this pool.
125
183
  )
126
184
 
127
185
  worker = Workers::Worker.new(
128
- :logger => nil, # Ruby Logger instance.
129
- :input_queue => nil # Ruby Queue used for input events.
186
+ :logger => nil, # Ruby Logger instance.
187
+ :input_queue => nil # Ruby Queue used for input events.
130
188
  )
131
189
 
132
190
  timer = Workers::Timer.new(1,
133
- :logger => nil, # Ruby logger instance.
134
- :repeat => false, # Repeat the timer until 'cancel' is called.
135
- :scheduler => Workers.scheduler, # The scheduler that manages execution.
136
- :callback => nil # The proc to execute (provide this or a block, but not both).
191
+ :logger => nil, # Ruby logger instance.
192
+ :repeat => false, # Repeat the timer until 'cancel' is called.
193
+ :scheduler => Workers.scheduler, # The scheduler that manages execution.
194
+ :callback => nil # The proc to execute (provide this or a block, but not both).
137
195
  )
138
196
 
139
197
  timer = Workers::PeriodicTimer.new(1,
140
- :logger => nil, # Ruby logger instance.
141
- :scheduler => Workers.scheduler, # The scheduler that manages execution.
142
- :callback => nil # The proc to execute (provide this or a block, but not both).
198
+ :logger => nil, # Ruby logger instance.
199
+ :scheduler => Workers.scheduler, # The scheduler that manages execution.
200
+ :callback => nil # The proc to execute (provide this or a block, but not both).
143
201
  )
144
202
 
145
203
  scheduler = Workers::Scheduler.new(
146
- :logger => nil, # Ruby logger instance.
147
- :pool => Workers::Pool.new # The workers pool used to execute timer callbacks.
204
+ :logger => nil, # Ruby logger instance.
205
+ :pool => Workers::Pool.new # The workers pool used to execute timer callbacks.
206
+ )
207
+
208
+ actor = Workers::Actor.new(
209
+ :logger => nil, # Ruby logger instance.
210
+ :dedicated => false, # If true, the actor runs with a worker pool that has one thread.
211
+ :pool => Workers.pool, # The workers pool used to execute events.
212
+ :mailbox => Workers::Mailbox.new, # The mailbox used to receive events.
213
+ :registry => Workers.registry, # The registry used to store a reference to the actor if it has a name.
214
+ :name => nil # The name of the actor (must be unique in the registry).
215
+ )
216
+
217
+ actor = Workers::DedicatedActor.new(
218
+ :logger => nil, # Ruby logger instance.
219
+ :mailbox => Workers::Mailbox.new, # The mailbox used to receive events.
220
+ :registry => Workers.registry, # The registry used to store a reference to the actor if it has a name.
221
+ :name => nil # The name of the actor (must be unique in the registry).
148
222
  )
149
223
 
150
224
 
@@ -0,0 +1,79 @@
1
+ module Workers
2
+ class Actor
3
+ include Workers::Helpers
4
+
5
+ def initialize(options = {})
6
+ @logger = Workers::LogProxy.new(options[:logger])
7
+ @dedicated = options[:dedicated] || false
8
+ @mailbox = options[:mailbox] || Workers::Mailbox.new
9
+ @registry = options[:registry] || Workers.registry
10
+ @name = options[:name]
11
+ @pool = @dedicated ? Workers::Pool.new(:size => 1) : (options[:pool] || Workers.pool)
12
+ @alive = true
13
+
14
+ @registry.register(self)
15
+ end
16
+
17
+ def enqueue(command, data = nil)
18
+ return false unless @alive
19
+
20
+ @mailbox.push(Event.new(command, data))
21
+
22
+ @pool.perform do
23
+ process_events
24
+ end
25
+
26
+ return true
27
+ end
28
+
29
+ def alive?
30
+ @mailbox.synchronize do
31
+ return @alive
32
+ end
33
+ end
34
+
35
+ def name
36
+ return @name
37
+ end
38
+
39
+ def identifier
40
+ return @name ? "#{object_id}:#{@name}" : object_id
41
+ end
42
+
43
+ private
44
+
45
+ def process_events
46
+ while (event = @mailbox.shift)
47
+ case event.command
48
+ when :shutdown
49
+ shutdown_handler(event)
50
+ @pool.shutdown if @dedicated
51
+ @mailbox.synchronize do
52
+ @alive = false
53
+ end
54
+ else
55
+ process_event(event)
56
+ end
57
+ end
58
+ rescue Exception => e
59
+ @alive = false
60
+ exception_handler(e)
61
+ end
62
+
63
+ #
64
+ # Subclass and override the below methods.
65
+ #
66
+
67
+ def process_event(event)
68
+ puts "Actor (#{identifier}) received event (#{event.inspect})."
69
+ end
70
+
71
+ def exception_handler(e)
72
+ puts concat_e("Actor (#{identifier}) died.", e)
73
+ end
74
+
75
+ def shutdown_handler(event)
76
+ puts "Actor (#{identifier}) is shutting down."
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,9 @@
1
+ module Workers
2
+ class DedicatedActor < Workers::Actor
3
+ def initialize(options = {})
4
+ options[:dedicated] = true
5
+
6
+ super(options)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ module Workers
2
+ class Mailbox
3
+ def initialize(options = {})
4
+ @messages = []
5
+ @mutex = Mutex.new
6
+ end
7
+
8
+ def push(event)
9
+ @mutex.synchronize do
10
+ @messages.push(event)
11
+ end
12
+ end
13
+
14
+ def shift
15
+ @mutex.synchronize do
16
+ @messages.shift
17
+ end
18
+ end
19
+
20
+ def synchronize(&block)
21
+ @mutex.synchronize do
22
+ block.call
23
+ end
24
+ end
25
+ end
26
+ end
data/lib/workers/pool.rb CHANGED
@@ -14,7 +14,7 @@ module Workers
14
14
  @size.times { @workers << @worker_class.new(:input_queue => @input_queue) }
15
15
  end
16
16
 
17
- def enqueue(command, data)
17
+ def enqueue(command, data = nil)
18
18
  @input_queue.push(Event.new(command, data))
19
19
 
20
20
  return nil
@@ -35,5 +35,12 @@ module Workers
35
35
  def join(max_wait = nil)
36
36
  return @workers.map { |w| w.join(max_wait) }
37
37
  end
38
+
39
+ def dispose
40
+ shutdown
41
+ join
42
+
43
+ return nil
44
+ end
38
45
  end
39
46
  end
@@ -0,0 +1,45 @@
1
+ module Workers
2
+ class Registry
3
+ def initialize
4
+ @mutex = Mutex.new
5
+ @actors_by_name = {}
6
+ end
7
+
8
+ def register(actor)
9
+ @mutex.synchronize do
10
+ return false unless actor.name
11
+
12
+ if @actors_by_name[actor.name]
13
+ raise "Actor already exists (#{actor.name})."
14
+ else
15
+ @actors_by_name[actor.name] = actor
16
+ end
17
+
18
+ return true
19
+ end
20
+ end
21
+
22
+ def unregister(actor)
23
+ @mutex.synchronize do
24
+ return false unless actor.name
25
+ return false unless @actors_by_name[actor.name]
26
+
27
+ @actors_by_name.delete(actor.name)
28
+
29
+ return true
30
+ end
31
+ end
32
+
33
+ def [](val)
34
+ @mutex.synchronize do
35
+ return @actors_by_name[val]
36
+ end
37
+ end
38
+
39
+ def dispose
40
+ @mutex.synchronize do
41
+ @actors_by_name.clear
42
+ end
43
+ end
44
+ end
45
+ end
@@ -4,7 +4,7 @@ module Workers
4
4
 
5
5
  def initialize(options = {})
6
6
  @logger = Workers::LogProxy.new(options[:logger])
7
- @pool = options[:pool] || Workers::Pool.new
7
+ @pool = options[:pool] || Workers.pool
8
8
  @schedule = SortedSet.new
9
9
  @mutex = Mutex.new
10
10
  @thread = Thread.new { start_loop }
@@ -1,3 +1,3 @@
1
1
  module Workers
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.5'
3
3
  end
@@ -9,7 +9,7 @@ module Workers
9
9
  @thread = Thread.new { start_event_loop }
10
10
  end
11
11
 
12
- def enqueue(command, data)
12
+ def enqueue(command, data = nil)
13
13
  @input_queue.push(Event.new(command, data))
14
14
 
15
15
  return nil
data/lib/workers.rb CHANGED
@@ -10,11 +10,41 @@ require 'workers/log_proxy'
10
10
  require 'workers/scheduler'
11
11
  require 'workers/timer'
12
12
  require 'workers/periodic_timer'
13
+ require 'workers/mailbox'
14
+ require 'workers/actor'
15
+ require 'workers/dedicated_actor'
16
+ require 'workers/registry'
13
17
 
14
18
  module Workers
19
+ def self.pool
20
+ return @pool ||= Workers::Pool.new
21
+ end
22
+
23
+ def self.pool=(val)
24
+ @pool.dispose if @pool
25
+ @pool = val
26
+ end
27
+
15
28
  def self.scheduler
16
29
  return @scheduler ||= Workers::Scheduler.new
17
30
  end
31
+
32
+ def self.scheduler=(val)
33
+ @scheduler.dispose if @scheduler
34
+ @scheduler = val
35
+ end
36
+
37
+ def self.registry
38
+ return @registry ||= Workers::Registry.new
39
+ end
40
+
41
+ def self.registry=(val)
42
+ @registry.dispose if @registry
43
+ @registry = val
44
+ end
18
45
  end
19
46
 
20
- Workers.scheduler # Force initialization of default scheduler.
47
+ # Force initialization of defaults.
48
+ Workers.pool
49
+ Workers.scheduler
50
+ Workers.registry
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: workers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -24,11 +24,15 @@ files:
24
24
  - README.md
25
25
  - Rakefile
26
26
  - lib/workers.rb
27
+ - lib/workers/actor.rb
28
+ - lib/workers/dedicated_actor.rb
27
29
  - lib/workers/event.rb
28
30
  - lib/workers/helpers.rb
29
31
  - lib/workers/log_proxy.rb
32
+ - lib/workers/mailbox.rb
30
33
  - lib/workers/periodic_timer.rb
31
34
  - lib/workers/pool.rb
35
+ - lib/workers/registry.rb
32
36
  - lib/workers/scheduler.rb
33
37
  - lib/workers/timer.rb
34
38
  - lib/workers/version.rb