reqless 0.0.1
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.
- 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
|