workers 0.0.2 → 0.0.3
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 +83 -14
- data/lib/workers/periodic_timer.rb +9 -0
- data/lib/workers/pool.rb +2 -2
- data/lib/workers/scheduler.rb +85 -0
- data/lib/workers/timer.rb +58 -0
- data/lib/workers/version.rb +1 -1
- data/lib/workers/worker.rb +2 -2
- data/lib/workers.rb +12 -0
- data/workers.gemspec +2 -2
- metadata +9 -5
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Workers
|
2
2
|
|
3
3
|
Workers is a Ruby gem for performing work in background threads.
|
4
|
-
Design goals include high performance, low latency, simple API, and
|
4
|
+
Design goals include high performance, low latency, simple API, customizability, and multi-layered architecture.
|
5
5
|
|
6
6
|
## Installation
|
7
7
|
|
@@ -21,7 +21,7 @@ Or install it yourself as:
|
|
21
21
|
|
22
22
|
# Initialize a worker pool.
|
23
23
|
pool = Workers::Pool.new
|
24
|
-
|
24
|
+
|
25
25
|
# Perform some work in the background.
|
26
26
|
100.times do
|
27
27
|
pool.perform do
|
@@ -29,18 +29,18 @@ Or install it yourself as:
|
|
29
29
|
puts "Hello world from thread #{Thread.current.object_id}"
|
30
30
|
end
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
# Tell the workers to shutdown.
|
34
34
|
pool.shutdown do
|
35
35
|
puts "Worker thread #{Thread.current.object_id} is shutting down."
|
36
36
|
end
|
37
|
-
|
38
|
-
# Wait for the workers to
|
37
|
+
|
38
|
+
# Wait for the workers to shutdown.
|
39
39
|
pool.join
|
40
40
|
|
41
41
|
## Advanced Usage
|
42
42
|
|
43
|
-
The Worker class is designed to be customized
|
43
|
+
The Worker class is designed to be customized through inheritence and its event system:
|
44
44
|
|
45
45
|
# Create a custom worker class that handles custom commands.
|
46
46
|
class CustomWorker < Workers::Worker
|
@@ -53,32 +53,101 @@ The Worker class is designed to be customized.
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
# Create a pool that uses your custom worker class.
|
58
58
|
pool = Workers::Pool.new(:worker_class => CustomWorker)
|
59
|
-
|
59
|
+
|
60
60
|
# Tell the workers to do some work using custom events.
|
61
61
|
100.times do |i|
|
62
62
|
pool.enqueue(:my_custom, i)
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
# Tell the workers to shutdown.
|
66
66
|
pool.shutdown do
|
67
67
|
puts "Worker thread #{Thread.current.object_id} is shutting down."
|
68
68
|
end
|
69
|
-
|
70
|
-
# Wait for
|
69
|
+
|
70
|
+
# Wait for the workers to shutdown.
|
71
71
|
pool.join
|
72
|
+
|
73
|
+
## Timers
|
74
|
+
|
75
|
+
Timers provide a way to execute code in the future:
|
76
|
+
|
77
|
+
# Create a one shot timer that executes in 1.5 seconds.
|
78
|
+
timer = Workers::Timer.new(1.5) do
|
79
|
+
puts 'Hello world'
|
80
|
+
end
|
72
81
|
|
73
|
-
|
82
|
+
# Create a periodic timer that loops infinitely or until 'cancel' is called.
|
83
|
+
timer = Workers::PeriodicTimer.new(1) do
|
84
|
+
puts 'Hello world many times'
|
85
|
+
end
|
86
|
+
sleep 5
|
87
|
+
timer.cancel
|
88
|
+
|
89
|
+
Callbacks execute using a Workers::Pool in case they contain blocking operations.
|
74
90
|
|
75
|
-
|
91
|
+
## Options (defaults below):
|
76
92
|
|
77
93
|
pool = Workers::Pool.new(
|
78
94
|
:size => 20, # Number of threads to create.
|
79
|
-
:logger => nil
|
95
|
+
:logger => nil, # Ruby Logger instance.
|
80
96
|
:worker_class => Workers::Worker # Class of worker to use for this pool.
|
97
|
+
)
|
98
|
+
|
99
|
+
worker = Workers::Worker.new(
|
100
|
+
:logger => nil, # Ruby Logger instance.
|
101
|
+
:input_queue => nil # Ruby Queue used for input events.
|
102
|
+
)
|
103
|
+
|
104
|
+
timer = Workers::Timer.new(1,
|
105
|
+
:logger => nil, # Ruby logger instance.
|
106
|
+
:repeat => false, # Repeat the timer until 'cancel' is called.
|
107
|
+
:scheduler => Workers.scheduler, # The scheduler that manages execution.
|
108
|
+
:callback => nil # The proc to execute (provide this or a block, but not both).
|
109
|
+
)
|
110
|
+
|
111
|
+
timer = Workers::PeriodicTimer.new(1,
|
112
|
+
:logger => nil, # Ruby logger instance.
|
113
|
+
:scheduler => Workers.scheduler, # The scheduler that manages execution.
|
114
|
+
:callback => nil # The proc to execute (provide this or a block, but not both).
|
115
|
+
)
|
81
116
|
|
117
|
+
|
118
|
+
## TODO - not yet implemented features
|
119
|
+
|
120
|
+
### Tasks
|
121
|
+
|
122
|
+
Tasks and task groups build on top of worker pools.
|
123
|
+
They provide a means of parallelizing expensive computations and collecing the results:
|
124
|
+
|
125
|
+
# Create a task group (it contains a pool of workers).
|
126
|
+
group = Workers::TaskGroup.new
|
127
|
+
|
128
|
+
# Add tasks to the group.
|
129
|
+
100.times do |i|
|
130
|
+
group.add(i) do
|
131
|
+
i * i
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Execute the tasks (blocks until the tasks complete).
|
136
|
+
group.run
|
137
|
+
|
138
|
+
# Review the results.
|
139
|
+
group.tasks.each do |t|
|
140
|
+
t.succeeded? # True or false (false if an exception occurred).
|
141
|
+
t.args # Input arguments (the value of i in this example).
|
142
|
+
t.result # Output value (the result of i * i in this example).
|
143
|
+
t.exception # The exception if one exists.
|
144
|
+
end
|
145
|
+
|
146
|
+
TaskGroup and Task can then be used to build an easy to use parallel map.
|
147
|
+
Care will have to taken regarding global data and the thread safety of data structures:
|
148
|
+
|
149
|
+
Workers.map([1, 2, 3, 4]) { |i| i * i }
|
150
|
+
|
82
151
|
## Contributing
|
83
152
|
|
84
153
|
1. Fork it
|
data/lib/workers/pool.rb
CHANGED
@@ -20,13 +20,13 @@ module Workers
|
|
20
20
|
return nil
|
21
21
|
end
|
22
22
|
|
23
|
-
def perform(
|
23
|
+
def perform(&block)
|
24
24
|
enqueue(:perform, block)
|
25
25
|
|
26
26
|
return nil
|
27
27
|
end
|
28
28
|
|
29
|
-
def shutdown(
|
29
|
+
def shutdown(&block)
|
30
30
|
@size.times { enqueue(:shutdown, block) }
|
31
31
|
|
32
32
|
return nil
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Workers
|
2
|
+
class Scheduler
|
3
|
+
def initialize(options = {})
|
4
|
+
@pool = options[:pool] || Workers::Pool.new
|
5
|
+
@schedule = SortedSet.new
|
6
|
+
@mutex = Mutex.new
|
7
|
+
@thread = Thread.new { start_loop }
|
8
|
+
end
|
9
|
+
|
10
|
+
def schedule(timer)
|
11
|
+
@mutex.synchronize do
|
12
|
+
@schedule << timer
|
13
|
+
end
|
14
|
+
|
15
|
+
wakeup
|
16
|
+
|
17
|
+
return nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def unschedule(timer)
|
21
|
+
@mutex.synchronize do
|
22
|
+
@schedule.delete(timer)
|
23
|
+
end
|
24
|
+
|
25
|
+
return true
|
26
|
+
end
|
27
|
+
|
28
|
+
def wakeup
|
29
|
+
@thread.wakeup
|
30
|
+
|
31
|
+
return nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def dispose
|
35
|
+
@mutex.synchronize do
|
36
|
+
@pool.shutdown
|
37
|
+
@pool.join
|
38
|
+
@thread.kill
|
39
|
+
end
|
40
|
+
|
41
|
+
return nil
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def start_loop
|
47
|
+
while true
|
48
|
+
delay = nil
|
49
|
+
|
50
|
+
@mutex.synchronize do
|
51
|
+
process_overdue
|
52
|
+
delay = next_delay
|
53
|
+
end
|
54
|
+
|
55
|
+
delay ? sleep(delay) : sleep
|
56
|
+
end
|
57
|
+
|
58
|
+
return nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def process_overdue
|
62
|
+
overdue = []
|
63
|
+
|
64
|
+
while @schedule.first && @schedule.first.overdue?
|
65
|
+
overdue << @schedule.first
|
66
|
+
@schedule.delete(@schedule.first)
|
67
|
+
end
|
68
|
+
|
69
|
+
overdue.each do |timer|
|
70
|
+
@pool.perform do
|
71
|
+
timer.fire
|
72
|
+
end
|
73
|
+
|
74
|
+
timer.reset
|
75
|
+
@schedule << timer if timer.repeat
|
76
|
+
end
|
77
|
+
|
78
|
+
return nil
|
79
|
+
end
|
80
|
+
|
81
|
+
def next_delay
|
82
|
+
@schedule.first ? @schedule.first.sec_remaining : nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Workers
|
2
|
+
class Timer
|
3
|
+
attr_reader :delay
|
4
|
+
attr_reader :repeat
|
5
|
+
|
6
|
+
def initialize(delay, options = {}, &block)
|
7
|
+
@delay = delay
|
8
|
+
@callback = options[:callback] || block
|
9
|
+
@repeat = options[:repeat] || false
|
10
|
+
@scheduler = options[:scheduler] || Workers.scheduler
|
11
|
+
@logger = options[:logger]
|
12
|
+
|
13
|
+
@mutex = Mutex.new
|
14
|
+
|
15
|
+
reset
|
16
|
+
|
17
|
+
@scheduler.schedule(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def <=>(other)
|
21
|
+
return sec_remaining <=> other.sec_remaining
|
22
|
+
end
|
23
|
+
|
24
|
+
def sec_remaining
|
25
|
+
@mutex.synchronize do
|
26
|
+
diff = @fire_at.to_f - Time.now.utc.to_f
|
27
|
+
|
28
|
+
return (diff > 0) ? diff : 0
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def overdue?
|
33
|
+
return sec_remaining <= 0
|
34
|
+
end
|
35
|
+
|
36
|
+
def fire
|
37
|
+
@mutex.synchronize do
|
38
|
+
@callback.call if @callback
|
39
|
+
end
|
40
|
+
|
41
|
+
return nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def cancel
|
45
|
+
@scheduler.unschedule(self)
|
46
|
+
|
47
|
+
return nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def reset
|
51
|
+
@mutex.synchronize do
|
52
|
+
@fire_at = Time.now.utc + @delay
|
53
|
+
end
|
54
|
+
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/workers/version.rb
CHANGED
data/lib/workers/worker.rb
CHANGED
@@ -15,13 +15,13 @@ module Workers
|
|
15
15
|
return nil
|
16
16
|
end
|
17
17
|
|
18
|
-
def perform(
|
18
|
+
def perform(&block)
|
19
19
|
enqueue(:perform, block)
|
20
20
|
|
21
21
|
return nil
|
22
22
|
end
|
23
23
|
|
24
|
-
def shutdown(
|
24
|
+
def shutdown(&block)
|
25
25
|
enqueue(:shutdown, block)
|
26
26
|
|
27
27
|
return nil
|
data/lib/workers.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'set'
|
2
3
|
|
3
4
|
require 'workers/version'
|
4
5
|
require 'workers/helpers'
|
@@ -6,3 +7,14 @@ require 'workers/worker'
|
|
6
7
|
require 'workers/pool'
|
7
8
|
require 'workers/event'
|
8
9
|
require 'workers/log_proxy'
|
10
|
+
require 'workers/scheduler'
|
11
|
+
require 'workers/timer'
|
12
|
+
require 'workers/periodic_timer'
|
13
|
+
|
14
|
+
module Workers
|
15
|
+
def self.scheduler
|
16
|
+
return @scheduler ||= Workers::Scheduler.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Workers.scheduler # Force initialization of default scheduler.
|
data/workers.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = Workers::VERSION
|
9
9
|
gem.authors = ["Chad Remesch"]
|
10
10
|
gem.email = ["chad@remesch.com"]
|
11
|
-
gem.description = %q{Simple
|
12
|
-
gem.summary = %q{
|
11
|
+
gem.description = %q{Simple Ruby workers for performing work in background threads.}
|
12
|
+
gem.summary = %q{Workers is a Ruby gem for performing work in background threads. Design goals include high performance, low latency, simple API, customizability, and multi-layered architecture.}
|
13
13
|
gem.homepage = "https://github.com/chadrem/workers"
|
14
14
|
|
15
15
|
gem.files = `git ls-files`.split($/)
|
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
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,9 +9,9 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-18 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
|
-
description: Simple
|
14
|
+
description: Simple Ruby workers for performing work in background threads.
|
15
15
|
email:
|
16
16
|
- chad@remesch.com
|
17
17
|
executables: []
|
@@ -27,7 +27,10 @@ files:
|
|
27
27
|
- lib/workers/event.rb
|
28
28
|
- lib/workers/helpers.rb
|
29
29
|
- lib/workers/log_proxy.rb
|
30
|
+
- lib/workers/periodic_timer.rb
|
30
31
|
- lib/workers/pool.rb
|
32
|
+
- lib/workers/scheduler.rb
|
33
|
+
- lib/workers/timer.rb
|
31
34
|
- lib/workers/version.rb
|
32
35
|
- lib/workers/worker.rb
|
33
36
|
- workers.gemspec
|
@@ -54,6 +57,7 @@ rubyforge_project:
|
|
54
57
|
rubygems_version: 1.8.24
|
55
58
|
signing_key:
|
56
59
|
specification_version: 3
|
57
|
-
summary:
|
58
|
-
|
60
|
+
summary: Workers is a Ruby gem for performing work in background threads. Design goals
|
61
|
+
include high performance, low latency, simple API, customizability, and multi-layered
|
62
|
+
architecture.
|
59
63
|
test_files: []
|