reqless 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +8 -0
- data/README.md +648 -0
- data/Rakefile +117 -0
- data/bin/docker-build-and-test +22 -0
- data/exe/reqless-web +11 -0
- data/lib/reqless/config.rb +31 -0
- data/lib/reqless/failure_formatter.rb +43 -0
- data/lib/reqless/job.rb +496 -0
- data/lib/reqless/job_reservers/ordered.rb +29 -0
- data/lib/reqless/job_reservers/round_robin.rb +46 -0
- data/lib/reqless/job_reservers/shuffled_round_robin.rb +21 -0
- data/lib/reqless/lua/reqless-lib.lua +2965 -0
- data/lib/reqless/lua/reqless.lua +2545 -0
- data/lib/reqless/lua_script.rb +90 -0
- data/lib/reqless/middleware/requeue_exceptions.rb +94 -0
- data/lib/reqless/middleware/retry_exceptions.rb +72 -0
- data/lib/reqless/middleware/sentry.rb +66 -0
- data/lib/reqless/middleware/timeout.rb +63 -0
- data/lib/reqless/queue.rb +189 -0
- data/lib/reqless/queue_priority_pattern.rb +16 -0
- data/lib/reqless/server/static/css/bootstrap-responsive.css +686 -0
- data/lib/reqless/server/static/css/bootstrap-responsive.min.css +12 -0
- data/lib/reqless/server/static/css/bootstrap.css +3991 -0
- data/lib/reqless/server/static/css/bootstrap.min.css +689 -0
- data/lib/reqless/server/static/css/codemirror.css +112 -0
- data/lib/reqless/server/static/css/docs.css +839 -0
- data/lib/reqless/server/static/css/jquery.noty.css +105 -0
- data/lib/reqless/server/static/css/noty_theme_twitter.css +137 -0
- data/lib/reqless/server/static/css/style.css +200 -0
- data/lib/reqless/server/static/favicon.ico +0 -0
- data/lib/reqless/server/static/img/glyphicons-halflings-white.png +0 -0
- data/lib/reqless/server/static/img/glyphicons-halflings.png +0 -0
- data/lib/reqless/server/static/js/bootstrap-alert.js +94 -0
- data/lib/reqless/server/static/js/bootstrap-scrollspy.js +125 -0
- data/lib/reqless/server/static/js/bootstrap-tab.js +130 -0
- data/lib/reqless/server/static/js/bootstrap-tooltip.js +270 -0
- data/lib/reqless/server/static/js/bootstrap-typeahead.js +285 -0
- data/lib/reqless/server/static/js/bootstrap.js +1726 -0
- data/lib/reqless/server/static/js/bootstrap.min.js +6 -0
- data/lib/reqless/server/static/js/codemirror.js +2972 -0
- data/lib/reqless/server/static/js/jquery.noty.js +220 -0
- data/lib/reqless/server/static/js/mode/javascript.js +360 -0
- data/lib/reqless/server/static/js/theme/cobalt.css +18 -0
- data/lib/reqless/server/static/js/theme/eclipse.css +25 -0
- data/lib/reqless/server/static/js/theme/elegant.css +10 -0
- data/lib/reqless/server/static/js/theme/lesser-dark.css +45 -0
- data/lib/reqless/server/static/js/theme/monokai.css +28 -0
- data/lib/reqless/server/static/js/theme/neat.css +9 -0
- data/lib/reqless/server/static/js/theme/night.css +21 -0
- data/lib/reqless/server/static/js/theme/rubyblue.css +21 -0
- data/lib/reqless/server/static/js/theme/xq-dark.css +46 -0
- data/lib/reqless/server/views/_job.erb +259 -0
- data/lib/reqless/server/views/_job_list.erb +8 -0
- data/lib/reqless/server/views/_pagination.erb +7 -0
- data/lib/reqless/server/views/about.erb +130 -0
- data/lib/reqless/server/views/completed.erb +11 -0
- data/lib/reqless/server/views/config.erb +14 -0
- data/lib/reqless/server/views/failed.erb +48 -0
- data/lib/reqless/server/views/failed_type.erb +18 -0
- data/lib/reqless/server/views/job.erb +17 -0
- data/lib/reqless/server/views/layout.erb +451 -0
- data/lib/reqless/server/views/overview.erb +137 -0
- data/lib/reqless/server/views/queue.erb +125 -0
- data/lib/reqless/server/views/queues.erb +45 -0
- data/lib/reqless/server/views/tag.erb +6 -0
- data/lib/reqless/server/views/throttles.erb +38 -0
- data/lib/reqless/server/views/track.erb +75 -0
- data/lib/reqless/server/views/worker.erb +34 -0
- data/lib/reqless/server/views/workers.erb +14 -0
- data/lib/reqless/server.rb +549 -0
- data/lib/reqless/subscriber.rb +74 -0
- data/lib/reqless/test_helpers/worker_helpers.rb +55 -0
- data/lib/reqless/throttle.rb +57 -0
- data/lib/reqless/version.rb +5 -0
- data/lib/reqless/worker/base.rb +237 -0
- data/lib/reqless/worker/forking.rb +215 -0
- data/lib/reqless/worker/serial.rb +41 -0
- data/lib/reqless/worker.rb +5 -0
- data/lib/reqless.rb +309 -0
- metadata +399 -0
data/lib/reqless/job.rb
ADDED
@@ -0,0 +1,496 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'reqless'
|
4
|
+
require 'reqless/queue'
|
5
|
+
require 'reqless/lua_script'
|
6
|
+
require 'redis'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
module Reqless
|
10
|
+
# The base for both Job and RecurringJob
|
11
|
+
class BaseJob
|
12
|
+
attr_reader :client
|
13
|
+
|
14
|
+
def initialize(client, jid)
|
15
|
+
@client = client
|
16
|
+
@jid = jid
|
17
|
+
end
|
18
|
+
|
19
|
+
def klass
|
20
|
+
@klass ||= @klass_name.split('::').reduce(Object) do |context, name|
|
21
|
+
context.const_get(name, false)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def queue
|
26
|
+
@queue ||= Queue.new(@queue_name, @client)
|
27
|
+
end
|
28
|
+
|
29
|
+
def ==(other)
|
30
|
+
self.class == other.class &&
|
31
|
+
jid == other.jid &&
|
32
|
+
client == other.client
|
33
|
+
end
|
34
|
+
alias eql? ==
|
35
|
+
|
36
|
+
def hash
|
37
|
+
self.class.hash ^ jid.hash ^ client.hash
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# A Reqless job
|
42
|
+
class Job < BaseJob
|
43
|
+
attr_reader :jid, :expires_at, :state, :queue_name, :worker_name, :failure, :spawned_from_jid
|
44
|
+
attr_reader :klass_name, :tracked, :dependencies, :dependents
|
45
|
+
attr_reader :original_retries, :retries_left, :raw_queue_history
|
46
|
+
attr_reader :state_changed
|
47
|
+
attr_accessor :data, :priority, :tags, :throttles
|
48
|
+
|
49
|
+
alias_method(:state_changed?, :state_changed)
|
50
|
+
|
51
|
+
MiddlewareMisconfiguredError = Class.new(StandardError)
|
52
|
+
|
53
|
+
module SupportsMiddleware
|
54
|
+
def around_perform(job)
|
55
|
+
perform(job)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def perform
|
60
|
+
# If we can't find the class, we should fail the job, not try to process
|
61
|
+
begin
|
62
|
+
klass
|
63
|
+
rescue NameError
|
64
|
+
return fail("#{queue_name}-NameError", "Cannot find #{klass_name}")
|
65
|
+
end
|
66
|
+
|
67
|
+
# log a real process executing job -- before we start processing
|
68
|
+
log("started by pid:#{Process.pid}")
|
69
|
+
|
70
|
+
middlewares = Job.middlewares_on(klass)
|
71
|
+
|
72
|
+
if middlewares.last == SupportsMiddleware
|
73
|
+
klass.around_perform(self)
|
74
|
+
elsif middlewares.any?
|
75
|
+
raise MiddlewareMisconfiguredError, 'The middleware chain for ' +
|
76
|
+
"#{klass} (#{middlewares.inspect}) is misconfigured." +
|
77
|
+
'Reqless::Job::SupportsMiddleware must be extended onto your job' +
|
78
|
+
'class first if you want to use any middleware.'
|
79
|
+
elsif !klass.respond_to?(:perform)
|
80
|
+
# If the klass doesn't have a :perform method, we should raise an error
|
81
|
+
fail("#{queue_name}-method-missing",
|
82
|
+
"#{klass_name} has no perform method")
|
83
|
+
else
|
84
|
+
klass.perform(self)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.build(client, klass, attributes = {})
|
89
|
+
defaults = {
|
90
|
+
'jid' => Reqless.generate_jid,
|
91
|
+
'spawned_from_jid' => nil,
|
92
|
+
'data' => {},
|
93
|
+
'klass' => klass.to_s,
|
94
|
+
'priority' => 0,
|
95
|
+
'tags' => [],
|
96
|
+
'worker' => 'mock_worker',
|
97
|
+
'expires' => Time.now + (60 * 60), # an hour from now
|
98
|
+
'state' => 'running',
|
99
|
+
'tracked' => false,
|
100
|
+
'queue' => 'mock_queue',
|
101
|
+
'retries' => 5,
|
102
|
+
'remaining' => 5,
|
103
|
+
'failure' => {},
|
104
|
+
'history' => [],
|
105
|
+
'dependencies' => [],
|
106
|
+
'dependents' => [],
|
107
|
+
'throttles' => [],
|
108
|
+
}
|
109
|
+
attributes = defaults.merge(Reqless.stringify_hash_keys(attributes))
|
110
|
+
attributes['data'] = JSON.dump(attributes['data'])
|
111
|
+
new(client, attributes)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Converts a hash of job options (as returned by job.to_hash) into the array
|
115
|
+
# format the reqless api expects.
|
116
|
+
def self.build_opts_array(opts)
|
117
|
+
result = []
|
118
|
+
result << JSON.generate(opts.fetch(:data, {}))
|
119
|
+
result.concat([opts.fetch(:delay, 0)])
|
120
|
+
result.concat(['priority', opts.fetch(:priority, 0)])
|
121
|
+
result.concat(['tags', JSON.generate(opts.fetch(:tags, []))])
|
122
|
+
result.concat(['retries', opts.fetch(:retries, 5)])
|
123
|
+
result.concat(['depends', JSON.generate(opts.fetch(:depends, []))])
|
124
|
+
result.concat(['throttles', JSON.generate(opts.fetch(:throttles, []))])
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.middlewares_on(job_klass)
|
128
|
+
singleton_klass = job_klass.singleton_class
|
129
|
+
singleton_klass.ancestors.select do |ancestor|
|
130
|
+
ancestor != singleton_klass && ancestor.method_defined?(:around_perform)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def initialize(client, atts)
|
135
|
+
super(client, atts.fetch('jid'))
|
136
|
+
%w{
|
137
|
+
data failure dependencies dependents jid priority state tags throttles
|
138
|
+
tracked
|
139
|
+
}.each do |att|
|
140
|
+
instance_variable_set(:"@#{att}", atts.fetch(att))
|
141
|
+
# Redis doesn't handle nil values so well, sometimes instead returning false,
|
142
|
+
# so massage spawned_by_jid to consistent be nil or a jid
|
143
|
+
@spawned_from_jid = atts.fetch('spawned_from_jid', nil) || nil
|
144
|
+
end
|
145
|
+
|
146
|
+
# Parse the data string
|
147
|
+
@data = JSON.parse(@data)
|
148
|
+
|
149
|
+
@expires_at = atts.fetch('expires')
|
150
|
+
@klass_name = atts.fetch('klass')
|
151
|
+
@queue_name = atts.fetch('queue')
|
152
|
+
@worker_name = atts.fetch('worker')
|
153
|
+
@original_retries = atts.fetch('retries')
|
154
|
+
@retries_left = atts.fetch('remaining')
|
155
|
+
@raw_queue_history = atts.fetch('history')
|
156
|
+
|
157
|
+
# This is a silly side-effect of Lua doing JSON serialization
|
158
|
+
@tags = [] if @tags == {}
|
159
|
+
@dependents = [] if @dependents == {}
|
160
|
+
@dependencies = [] if @dependencies == {}
|
161
|
+
@state_changed = false
|
162
|
+
@before_callbacks = Hash.new { |h, k| h[k] = [] }
|
163
|
+
@after_callbacks = Hash.new { |h, k| h[k] = [] }
|
164
|
+
end
|
165
|
+
|
166
|
+
def priority=(priority)
|
167
|
+
@priority = priority if @client.call('job.setPriority', @jid, priority)
|
168
|
+
end
|
169
|
+
|
170
|
+
def [](key)
|
171
|
+
@data[key]
|
172
|
+
end
|
173
|
+
|
174
|
+
def []=(key, val)
|
175
|
+
@data[key] = val
|
176
|
+
end
|
177
|
+
|
178
|
+
def to_s
|
179
|
+
inspect
|
180
|
+
end
|
181
|
+
|
182
|
+
def description
|
183
|
+
"#{@klass_name} (#{@jid} / #{@queue_name} / #{@state})"
|
184
|
+
end
|
185
|
+
|
186
|
+
def inspect
|
187
|
+
"<Reqless::Job #{description}>"
|
188
|
+
end
|
189
|
+
|
190
|
+
def ttl
|
191
|
+
@expires_at - Time.now.to_f
|
192
|
+
end
|
193
|
+
|
194
|
+
def throttle_objects
|
195
|
+
throttles.map { |name| Throttle.new(name, client) }
|
196
|
+
end
|
197
|
+
|
198
|
+
def history
|
199
|
+
warn 'WARNING: Reqless::Job#history is deprecated; use' +
|
200
|
+
"Reqless::Job#raw_queue_history instead; from:\n#{caller.first}"
|
201
|
+
raw_queue_history
|
202
|
+
end
|
203
|
+
|
204
|
+
def queue_history
|
205
|
+
@queue_history ||= @raw_queue_history.map do |history_event|
|
206
|
+
history_event.each_with_object({}) do |(key, value), hash|
|
207
|
+
# The only Numeric (Integer or Float) values we get in the history
|
208
|
+
# are timestamps
|
209
|
+
if value.is_a?(Numeric)
|
210
|
+
hash[key] = Time.at(value).utc
|
211
|
+
else
|
212
|
+
hash[key] = value
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def initially_put_at
|
219
|
+
@initially_put_at ||= history_timestamp('put', :min)
|
220
|
+
end
|
221
|
+
|
222
|
+
def spawned_from
|
223
|
+
return nil if @spawned_from_jid.nil?
|
224
|
+
@spawned_from ||= @client.jobs[@spawned_from_jid]
|
225
|
+
end
|
226
|
+
|
227
|
+
def to_hash
|
228
|
+
{
|
229
|
+
jid: jid,
|
230
|
+
spawned_from_jid: spawned_from_jid,
|
231
|
+
expires_at: expires_at,
|
232
|
+
state: state,
|
233
|
+
queue_name: queue_name,
|
234
|
+
history: raw_queue_history,
|
235
|
+
worker_name: worker_name,
|
236
|
+
failure: failure,
|
237
|
+
klass_name: klass_name,
|
238
|
+
tracked: tracked,
|
239
|
+
dependencies: dependencies,
|
240
|
+
dependents: dependents,
|
241
|
+
original_retries: original_retries,
|
242
|
+
retries_left: retries_left,
|
243
|
+
data: data,
|
244
|
+
priority: priority,
|
245
|
+
tags: tags,
|
246
|
+
throttles: throttles,
|
247
|
+
}
|
248
|
+
end
|
249
|
+
|
250
|
+
# Extract the enqueue options from the job
|
251
|
+
# @return [Hash] options
|
252
|
+
# @option options [Integer] :retries
|
253
|
+
# @option options [Integer] :priority
|
254
|
+
# @option options [Array<String>] :depends
|
255
|
+
# @option options [Array<String>] :tags
|
256
|
+
# @option options [Array<String>] throttles
|
257
|
+
# @option options [Hash] :data
|
258
|
+
def enqueue_opts
|
259
|
+
{
|
260
|
+
retries: original_retries,
|
261
|
+
priority: priority,
|
262
|
+
depends: dependents,
|
263
|
+
tags: tags,
|
264
|
+
throttles: throttles,
|
265
|
+
data: data,
|
266
|
+
}
|
267
|
+
end
|
268
|
+
|
269
|
+
# Move this from it's current queue into another
|
270
|
+
def requeue(queue, opts = {})
|
271
|
+
note_state_change :requeue do
|
272
|
+
@client.call('job.requeue', @client.worker_name, queue, @jid, @klass_name,
|
273
|
+
*self.class.build_opts_array(self.enqueue_opts.merge!(opts))
|
274
|
+
)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
alias move requeue # for backwards compatibility
|
278
|
+
|
279
|
+
CantFailError = Class.new(Reqless::LuaScriptError)
|
280
|
+
|
281
|
+
# Fail a job
|
282
|
+
def fail(group, message)
|
283
|
+
note_state_change :fail do
|
284
|
+
@client.call(
|
285
|
+
'job.fail',
|
286
|
+
@jid,
|
287
|
+
@worker_name,
|
288
|
+
group, message,
|
289
|
+
JSON.dump(@data)) || false
|
290
|
+
end
|
291
|
+
rescue Reqless::LuaScriptError => err
|
292
|
+
raise CantFailError.new(err.message)
|
293
|
+
end
|
294
|
+
|
295
|
+
# Heartbeat a job
|
296
|
+
def heartbeat
|
297
|
+
@expires_at = @client.call(
|
298
|
+
'job.heartbeat',
|
299
|
+
@jid,
|
300
|
+
@worker_name,
|
301
|
+
JSON.dump(@data))
|
302
|
+
end
|
303
|
+
|
304
|
+
CantCompleteError = Class.new(Reqless::LuaScriptError)
|
305
|
+
|
306
|
+
# Complete a job
|
307
|
+
# Options include
|
308
|
+
# => next (String) the next queue
|
309
|
+
# => delay (int) how long to delay it in the next queue
|
310
|
+
def complete(nxt = nil, options = {})
|
311
|
+
note_state_change :complete do
|
312
|
+
if nxt.nil?
|
313
|
+
@client.call(
|
314
|
+
'job.complete', @jid, @worker_name, @queue_name, JSON.dump(@data))
|
315
|
+
else
|
316
|
+
@client.call('job.completeAndRequeue', @jid, @worker_name, @queue_name,
|
317
|
+
JSON.dump(@data), 'next', nxt, 'delay',
|
318
|
+
options.fetch(:delay, 0), 'depends',
|
319
|
+
JSON.dump(options.fetch(:depends, [])))
|
320
|
+
end
|
321
|
+
end
|
322
|
+
rescue Reqless::LuaScriptError => err
|
323
|
+
raise CantCompleteError.new(err.message)
|
324
|
+
end
|
325
|
+
|
326
|
+
def cancel
|
327
|
+
note_state_change :cancel do
|
328
|
+
@client.call('job.cancel', @jid)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def track
|
333
|
+
@client.call('job.track', @jid)
|
334
|
+
end
|
335
|
+
|
336
|
+
def untrack
|
337
|
+
@client.call('job.untrack', @jid)
|
338
|
+
end
|
339
|
+
|
340
|
+
def tag(*tags)
|
341
|
+
JSON.parse(@client.call('job.addTag', @jid, *tags))
|
342
|
+
end
|
343
|
+
|
344
|
+
def untag(*tags)
|
345
|
+
JSON.parse(@client.call('job.removeTag', @jid, *tags))
|
346
|
+
end
|
347
|
+
|
348
|
+
def retry(delay = 0, group = nil, message = nil)
|
349
|
+
note_state_change :retry do
|
350
|
+
if group.nil?
|
351
|
+
results = @client.call(
|
352
|
+
'job.retry', @jid, @queue_name, @worker_name, delay)
|
353
|
+
results.nil? ? false : results
|
354
|
+
else
|
355
|
+
results = @client.call(
|
356
|
+
'job.retry', @jid, @queue_name, @worker_name, delay, group, message)
|
357
|
+
results.nil? ? false : results
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def depend(*jids)
|
363
|
+
!!@client.call('job.addDependency', @jid, *jids)
|
364
|
+
end
|
365
|
+
|
366
|
+
def undepend(*jids)
|
367
|
+
!!@client.call('job.removeDependency', @jid, *jids)
|
368
|
+
end
|
369
|
+
|
370
|
+
def timeout
|
371
|
+
@client.call('job.timeout', @jid)
|
372
|
+
end
|
373
|
+
|
374
|
+
def log(message, data = nil)
|
375
|
+
if data
|
376
|
+
@client.call('job.log', @jid, message, JSON.dump(data))
|
377
|
+
else
|
378
|
+
@client.call('job.log', @jid, message)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
[:fail, :complete, :cancel, :requeue, :retry].each do |event|
|
383
|
+
define_method :"before_#{event}" do |&block|
|
384
|
+
@before_callbacks[event] << block
|
385
|
+
end
|
386
|
+
|
387
|
+
define_method :"after_#{event}" do |&block|
|
388
|
+
@after_callbacks[event].unshift block
|
389
|
+
end
|
390
|
+
end
|
391
|
+
alias before_move before_requeue
|
392
|
+
alias after_move after_requeue
|
393
|
+
|
394
|
+
def note_state_change(event)
|
395
|
+
@before_callbacks[event].each { |blk| blk.call(self) }
|
396
|
+
result = yield
|
397
|
+
@state_changed = true
|
398
|
+
@after_callbacks[event].each { |blk| blk.call(self) }
|
399
|
+
result
|
400
|
+
end
|
401
|
+
|
402
|
+
private
|
403
|
+
|
404
|
+
def history_timestamp(name, selector)
|
405
|
+
items = queue_history.select do |q|
|
406
|
+
q['what'] == name
|
407
|
+
end
|
408
|
+
items.map do |q|
|
409
|
+
q['when']
|
410
|
+
end.public_send(selector)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
# Wraps a recurring job
|
415
|
+
class RecurringJob < BaseJob
|
416
|
+
attr_reader :jid, :data, :priority, :tags, :retries, :interval, :count
|
417
|
+
attr_reader :queue_name, :klass_name, :backlog
|
418
|
+
|
419
|
+
def initialize(client, atts)
|
420
|
+
super(client, atts.fetch('jid'))
|
421
|
+
%w{jid data priority tags retries interval count backlog}.each do |att|
|
422
|
+
instance_variable_set("@#{att}".to_sym, atts.fetch(att))
|
423
|
+
end
|
424
|
+
|
425
|
+
# Parse the data string
|
426
|
+
@data = JSON.parse(@data)
|
427
|
+
@klass_name = atts.fetch('klass')
|
428
|
+
@queue_name = atts.fetch('queue')
|
429
|
+
@tags = [] if @tags == {}
|
430
|
+
end
|
431
|
+
|
432
|
+
def priority=(value)
|
433
|
+
@client.call('recurringJob.update', @jid, 'priority', value)
|
434
|
+
@priority = value
|
435
|
+
end
|
436
|
+
|
437
|
+
def retries=(value)
|
438
|
+
@client.call('recurringJob.update', @jid, 'retries', value)
|
439
|
+
@retries = value
|
440
|
+
end
|
441
|
+
|
442
|
+
def interval=(value)
|
443
|
+
@client.call('recurringJob.update', @jid, 'interval', value)
|
444
|
+
@interval = value
|
445
|
+
end
|
446
|
+
|
447
|
+
def data=(value)
|
448
|
+
@client.call('recurringJob.update', @jid, 'data', JSON.dump(value))
|
449
|
+
@data = value
|
450
|
+
end
|
451
|
+
|
452
|
+
def klass=(value)
|
453
|
+
@client.call('recurringJob.update', @jid, 'klass', value.to_s)
|
454
|
+
@klass_name = value.to_s
|
455
|
+
end
|
456
|
+
|
457
|
+
def backlog=(value)
|
458
|
+
@client.call('recurringJob.update', @jid, 'backlog', value.to_s)
|
459
|
+
@backlog = value
|
460
|
+
end
|
461
|
+
|
462
|
+
def move(queue)
|
463
|
+
@client.call('recurringJob.update', @jid, 'queue', queue)
|
464
|
+
@queue_name = queue
|
465
|
+
end
|
466
|
+
alias requeue move # for API parity with normal jobs
|
467
|
+
|
468
|
+
def cancel
|
469
|
+
@client.call('recurringJob.cancel', @jid)
|
470
|
+
end
|
471
|
+
|
472
|
+
def tag(*tags)
|
473
|
+
@client.call('recurringJob.addTag', @jid, *tags)
|
474
|
+
end
|
475
|
+
|
476
|
+
def untag(*tags)
|
477
|
+
@client.call('recurringJob.removeTag', @jid, *tags)
|
478
|
+
end
|
479
|
+
|
480
|
+
def last_spawned_jid
|
481
|
+
return nil if never_spawned?
|
482
|
+
"#{jid}-#{count}"
|
483
|
+
end
|
484
|
+
|
485
|
+
def last_spawned_job
|
486
|
+
return nil if never_spawned?
|
487
|
+
@client.jobs[last_spawned_jid]
|
488
|
+
end
|
489
|
+
|
490
|
+
private
|
491
|
+
|
492
|
+
def never_spawned?
|
493
|
+
count.zero?
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
module Reqless
|
4
|
+
module JobReservers
|
5
|
+
class Ordered
|
6
|
+
attr_reader :queues
|
7
|
+
|
8
|
+
def initialize(queues)
|
9
|
+
@queues = queues
|
10
|
+
end
|
11
|
+
|
12
|
+
def reserve
|
13
|
+
@queues.each do |q|
|
14
|
+
job = q.pop
|
15
|
+
return job if job
|
16
|
+
end
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def prep_for_work!
|
21
|
+
# nothing here on purpose
|
22
|
+
end
|
23
|
+
|
24
|
+
def description
|
25
|
+
@description ||= @queues.map(&:name).join(', ') + ' (ordered)'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
module Reqless
|
4
|
+
module JobReservers
|
5
|
+
# Round-robins through all the provided queues
|
6
|
+
class RoundRobin
|
7
|
+
attr_reader :queues
|
8
|
+
|
9
|
+
def initialize(queues)
|
10
|
+
@queues = queues
|
11
|
+
@num_queues = queues.size
|
12
|
+
@last_popped_queue_index = @num_queues - 1
|
13
|
+
end
|
14
|
+
|
15
|
+
def reserve
|
16
|
+
@num_queues.times do |i|
|
17
|
+
job = next_queue.pop
|
18
|
+
return job if job
|
19
|
+
end
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def prep_for_work!
|
24
|
+
# nothing here on purpose
|
25
|
+
end
|
26
|
+
|
27
|
+
def description
|
28
|
+
@description ||=
|
29
|
+
@queues.map(&:name).join(', ') + " (#{self.class::TYPE_DESCRIPTION})"
|
30
|
+
end
|
31
|
+
|
32
|
+
def reset_description!
|
33
|
+
@description = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
TYPE_DESCRIPTION = 'round robin'
|
39
|
+
|
40
|
+
def next_queue
|
41
|
+
@last_popped_queue_index = (@last_popped_queue_index + 1) % @num_queues
|
42
|
+
@queues[@last_popped_queue_index]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'reqless/job_reservers/round_robin'
|
4
|
+
|
5
|
+
module Reqless
|
6
|
+
module JobReservers
|
7
|
+
# Like round-robin but shuffles the order of the queues
|
8
|
+
class ShuffledRoundRobin < RoundRobin
|
9
|
+
def initialize(queues)
|
10
|
+
super(queues.shuffle)
|
11
|
+
end
|
12
|
+
|
13
|
+
def prep_for_work!
|
14
|
+
@queues = @queues.shuffle
|
15
|
+
reset_description!
|
16
|
+
end
|
17
|
+
|
18
|
+
TYPE_DESCRIPTION = 'shuffled round robin'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|