job_boss 0.6.8 → 0.7.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +8 -0
- data/job_boss.gemspec +1 -1
- data/lib/job_boss/batch.rb +8 -5
- data/lib/job_boss/boss.rb +31 -14
- data/lib/job_boss/job.rb +2 -1
- data/lib/job_boss/queuer.rb +1 -0
- data/lib/migrate.rb +2 -0
- data/test/unit/job_test.rb +62 -5
- metadata +4 -10
data/README.markdown
CHANGED
@@ -92,6 +92,14 @@ You can even define a block to provide updates on progress (the value which is p
|
|
92
92
|
puts "We're now at #{progress * 100}%"
|
93
93
|
end
|
94
94
|
|
95
|
+
Prioritization of jobs is also supported. If a particular batch is more important than others, you can specify a higher priority
|
96
|
+
|
97
|
+
batch = Batch.new(:priority => 3)
|
98
|
+
|
99
|
+
In practical terms, the priority represents the number of jobs which are pulled from the queue to be processed each cycle, so by wary of increasing your priority beyond your maximum number of employees. No job queue will suffer from resource starvation, but you can greatly decrease the performance of other queues by over-prioritizing one.
|
100
|
+
|
101
|
+
Also note that job_boss uses a prioritized round-robin approach to scheduling jobs, the priority for jobs is increased throughout the run of the job queue, providing an approximation of a first-come first-serve approach to reduce latency.
|
102
|
+
|
95
103
|
For performance, it is recommended that you keep your jobs table clean scheduling execution of the `delete_jobs_before` command on the Job model, which will clean all jobs completed before the specified time:
|
96
104
|
|
97
105
|
Job.delete_jobs_before(2.days.ago)
|
data/job_boss.gemspec
CHANGED
data/lib/job_boss/batch.rb
CHANGED
@@ -2,15 +2,15 @@ require 'active_support'
|
|
2
2
|
|
3
3
|
module JobBoss
|
4
4
|
class Batch
|
5
|
-
attr_accessor :batch_id
|
5
|
+
attr_accessor :batch_id, :priority
|
6
6
|
|
7
7
|
extend ActiveSupport::Memoizable
|
8
8
|
# Used to queue jobs in a batch
|
9
9
|
# Usage:
|
10
10
|
# batch.queue.math.is_prime?(42)
|
11
|
-
def queue
|
11
|
+
def queue(attributes = {})
|
12
12
|
require 'job_boss/queuer'
|
13
|
-
Queuer.new(:batch_id => @batch_id)
|
13
|
+
Queuer.new({:priority => @priority, :batch_id => @batch_id}.merge(attributes))
|
14
14
|
end
|
15
15
|
memoize :queue
|
16
16
|
|
@@ -23,8 +23,11 @@ module JobBoss
|
|
23
23
|
queue.send(controller).send(action, *args)
|
24
24
|
end
|
25
25
|
|
26
|
-
def initialize(
|
27
|
-
|
26
|
+
def initialize(options = {})
|
27
|
+
options[:priority] ||= 1
|
28
|
+
|
29
|
+
@priority = options[:priority]
|
30
|
+
@batch_id = options[:batch_id] || Batch.generate_batch_id
|
28
31
|
end
|
29
32
|
|
30
33
|
# Returns ActiveRecord::Relation representing query for jobs in batch
|
data/lib/job_boss/boss.rb
CHANGED
@@ -18,9 +18,9 @@ module JobBoss
|
|
18
18
|
# Used to queue jobs
|
19
19
|
# Usage:
|
20
20
|
# Boss.queue.math.is_prime?(42)
|
21
|
-
def queue
|
21
|
+
def queue(attributes = {})
|
22
22
|
require 'job_boss/queuer'
|
23
|
-
Queuer.new
|
23
|
+
Queuer.new(attributes)
|
24
24
|
end
|
25
25
|
memoize :queue
|
26
26
|
|
@@ -86,28 +86,45 @@ module JobBoss
|
|
86
86
|
logger.info "Job Boss started"
|
87
87
|
logger.info "Employee limit: #{Boss.config.employee_limit}"
|
88
88
|
|
89
|
+
jobs = []
|
89
90
|
while true
|
90
|
-
|
91
|
-
sleep(config.sleep_interval)
|
92
|
-
next
|
93
|
-
end
|
91
|
+
available_employee_count = wait_for_available_employees
|
94
92
|
|
95
|
-
|
96
|
-
# long running jobs which would leave quicker jobs to suffocate
|
97
|
-
Job.pending.select('DISTINCT path, batch_id').reorder(nil).each do |distinct_job|
|
98
|
-
job = Job.pending.order('id').find_by_path_and_batch_id(distinct_job.path, distinct_job.batch_id)
|
99
|
-
next if job.nil?
|
93
|
+
jobs = dequeue_jobs if jobs.empty?
|
100
94
|
|
95
|
+
[available_employee_count, jobs.size].min.times do
|
96
|
+
job = jobs.shift
|
101
97
|
job.dispatch(self)
|
102
98
|
@running_jobs << job
|
103
|
-
|
104
|
-
children_count -= 1
|
105
|
-
break unless children_count > 0
|
106
99
|
end
|
107
100
|
|
108
101
|
end
|
109
102
|
end
|
110
103
|
|
104
|
+
# Waits until there is at least one available employee and then returns count
|
105
|
+
def wait_for_available_employees
|
106
|
+
until (employee_count = available_employees) > 0 && Job.pending.count > 0
|
107
|
+
sleep(config.sleep_interval)
|
108
|
+
end
|
109
|
+
|
110
|
+
employee_count
|
111
|
+
end
|
112
|
+
|
113
|
+
# Dequeues next set of jobs based on prioritized round robin algorithm
|
114
|
+
# Priority of a particular queue determines how many jobs get pulled from that queue each time we dequeue
|
115
|
+
# A priority adjustment is also done to give greater priority to sets of jobs which have been running longer
|
116
|
+
def dequeue_jobs
|
117
|
+
logger.info "Dequeuing jobs"
|
118
|
+
Job.pending.select('DISTINCT priority, path, batch_id').reorder(nil).collect do |distinct_job|
|
119
|
+
queue_scope = Job.where(:path => distinct_job.path, :batch_id => distinct_job.batch_id)
|
120
|
+
|
121
|
+
# Give queues which have are further along more priority to reduce latency
|
122
|
+
priority_adjustment = ((queue_scope.completed.count.to_f / queue_scope.count) * config.employee_limit).floor
|
123
|
+
|
124
|
+
queue_scope.pending.limit(distinct_job.priority + priority_adjustment)
|
125
|
+
end.flatten.sort_by(&:id)
|
126
|
+
end
|
127
|
+
|
111
128
|
def stop
|
112
129
|
logger.info "Stopping #{@running_jobs.size} running employees..."
|
113
130
|
|
data/lib/job_boss/job.rb
CHANGED
@@ -55,7 +55,7 @@ module JobBoss
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def batch
|
58
|
-
self.batch_id && Batch.new(self.batch_id)
|
58
|
+
self.batch_id && Batch.new(:batch_id => self.batch_id, :priority => self.priority)
|
59
59
|
end
|
60
60
|
|
61
61
|
def result
|
@@ -75,6 +75,7 @@ module JobBoss
|
|
75
75
|
self.result = nil
|
76
76
|
self.completed_at = nil
|
77
77
|
self.status = nil
|
78
|
+
self.error_class = nil
|
78
79
|
self.error_message = nil
|
79
80
|
self.error_backtrace = nil
|
80
81
|
self.employee_host = nil
|
data/lib/job_boss/queuer.rb
CHANGED
data/lib/migrate.rb
CHANGED
data/test/unit/job_test.rb
CHANGED
@@ -187,21 +187,78 @@ class DaemonTest < ActiveSupport::TestCase
|
|
187
187
|
|
188
188
|
|
189
189
|
first_jobs = (0..3).collect do
|
190
|
-
Boss.queue.sleep.sleep_for(
|
190
|
+
Boss.queue.sleep.sleep_for(4)
|
191
191
|
end
|
192
|
-
job2 = Boss.queue.sleep.sleep_for(
|
192
|
+
job2 = Boss.queue.sleep.sleep_for(4)
|
193
193
|
|
194
|
-
sleep(
|
194
|
+
sleep(2)
|
195
195
|
|
196
196
|
assert first_jobs.all? {|job| job.running? }
|
197
197
|
assert !job2.running?
|
198
|
-
sleep(
|
198
|
+
sleep(4)
|
199
199
|
assert first_jobs.all? {|job| !job.running? }
|
200
200
|
assert job2.running?
|
201
|
-
sleep(
|
201
|
+
sleep(4)
|
202
202
|
assert first_jobs.all? {|job| !job.running? }
|
203
203
|
assert !job2.running?
|
204
204
|
|
205
|
+
|
206
|
+
|
207
|
+
# Testing queue prioritization
|
208
|
+
first_batch = Batch.new
|
209
|
+
first_jobs = (0..2).collect do
|
210
|
+
first_batch.queue.sleep.sleep_for(4)
|
211
|
+
end
|
212
|
+
second_batch = Batch.new(:priority => 3)
|
213
|
+
second_jobs = (0..2).collect do
|
214
|
+
second_batch.queue.sleep.sleep_for(4)
|
215
|
+
end
|
216
|
+
|
217
|
+
sleep(2)
|
218
|
+
|
219
|
+
assert first_jobs.first.running?
|
220
|
+
assert first_jobs[1..-1].all? {|job| !job.running? }
|
221
|
+
assert second_jobs.all? {|job| job.running? }
|
222
|
+
|
223
|
+
sleep(4)
|
224
|
+
|
225
|
+
assert !first_jobs.first.running?
|
226
|
+
assert first_jobs[1..-1].all? {|job| job.running? }
|
227
|
+
assert second_jobs.all? {|job| !job.running? }
|
228
|
+
|
229
|
+
sleep(4)
|
230
|
+
|
231
|
+
|
232
|
+
|
233
|
+
|
234
|
+
# Testing adjusted priority which prioritizes jobs which are further along
|
235
|
+
first_batch = Batch.new
|
236
|
+
first_jobs = (0..7).collect do
|
237
|
+
first_batch.queue.sleep.sleep_for(4)
|
238
|
+
end
|
239
|
+
|
240
|
+
sleep(4)
|
241
|
+
|
242
|
+
assert first_jobs[0,4].all? {|job| job.running? }
|
243
|
+
assert first_jobs[4,4].all? {|job| !job.running? }
|
244
|
+
|
245
|
+
second_batch = Batch.new
|
246
|
+
second_jobs = (0..3).collect do
|
247
|
+
second_batch.queue.sleep.sleep_for(4)
|
248
|
+
end
|
249
|
+
|
250
|
+
sleep(2)
|
251
|
+
|
252
|
+
assert first_jobs[0,4].all? {|job| !job.running? }
|
253
|
+
assert first_jobs[4,3].all? {|job| job.running? } # First queue gets an extra employee since it's halfway done
|
254
|
+
assert !first_jobs.last.running?
|
255
|
+
assert second_jobs.first.running?
|
256
|
+
assert second_jobs[1,3].all? {|job| !job.running? }
|
257
|
+
|
258
|
+
|
259
|
+
|
260
|
+
|
261
|
+
|
205
262
|
stop_daemon
|
206
263
|
end
|
207
264
|
end
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: job_boss
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 23
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
7
|
+
- 7
|
8
|
+
- 5
|
9
|
+
version: 0.7.5
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Brian Underwood
|
@@ -15,7 +14,7 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date: 2011-
|
17
|
+
date: 2011-02-27 00:00:00 -05:00
|
19
18
|
default_executable: job_boss
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
@@ -26,7 +25,6 @@ dependencies:
|
|
26
25
|
requirements:
|
27
26
|
- - ">="
|
28
27
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 3
|
30
28
|
segments:
|
31
29
|
- 0
|
32
30
|
version: "0"
|
@@ -40,7 +38,6 @@ dependencies:
|
|
40
38
|
requirements:
|
41
39
|
- - ">="
|
42
40
|
- !ruby/object:Gem::Version
|
43
|
-
hash: 3
|
44
41
|
segments:
|
45
42
|
- 0
|
46
43
|
version: "0"
|
@@ -54,7 +51,6 @@ dependencies:
|
|
54
51
|
requirements:
|
55
52
|
- - ">="
|
56
53
|
- !ruby/object:Gem::Version
|
57
|
-
hash: 3
|
58
54
|
segments:
|
59
55
|
- 0
|
60
56
|
version: "0"
|
@@ -172,7 +168,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
172
168
|
requirements:
|
173
169
|
- - ">="
|
174
170
|
- !ruby/object:Gem::Version
|
175
|
-
hash: 3
|
176
171
|
segments:
|
177
172
|
- 0
|
178
173
|
version: "0"
|
@@ -181,7 +176,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
181
176
|
requirements:
|
182
177
|
- - ">="
|
183
178
|
- !ruby/object:Gem::Version
|
184
|
-
hash: 23
|
185
179
|
segments:
|
186
180
|
- 1
|
187
181
|
- 3
|