workers 0.0.4 → 0.0.5

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 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