sidekiq-cron 1.11.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -2
- data/README.md +197 -35
- data/lib/sidekiq/cron/job.rb +256 -162
- data/lib/sidekiq/cron/launcher.rb +2 -5
- data/lib/sidekiq/cron/locales/de.yml +15 -6
- data/lib/sidekiq/cron/locales/en.yml +14 -14
- data/lib/sidekiq/cron/locales/id.yml +22 -0
- data/lib/sidekiq/cron/locales/it.yml +15 -16
- data/lib/sidekiq/cron/locales/ja.yml +14 -10
- data/lib/sidekiq/cron/locales/pt.yml +14 -14
- data/lib/sidekiq/cron/locales/ru.yml +15 -7
- data/lib/sidekiq/cron/locales/zh-CN.yml +14 -11
- data/lib/sidekiq/cron/namespace.rb +43 -0
- data/lib/sidekiq/cron/poller.rb +12 -13
- data/lib/sidekiq/cron/schedule_loader.rb +11 -17
- data/lib/sidekiq/cron/support.rb +1 -2
- data/lib/sidekiq/cron/version.rb +1 -1
- data/lib/sidekiq/cron/views/cron.erb +68 -53
- data/lib/sidekiq/cron/views/cron_show.erb +16 -12
- data/lib/sidekiq/cron/web.rb +12 -2
- data/lib/sidekiq/cron/web_extension.rb +77 -30
- data/lib/sidekiq/cron.rb +61 -5
- data/lib/sidekiq/options.rb +9 -7
- data/lib/sidekiq-cron.rb +6 -0
- data/sidekiq-cron.gemspec +3 -2
- metadata +31 -9
data/lib/sidekiq/cron/job.rb
CHANGED
@@ -1,54 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fugit'
|
4
|
+
require 'cronex'
|
2
5
|
require 'globalid'
|
3
|
-
require 'sidekiq'
|
4
6
|
require 'sidekiq/cron/support'
|
5
|
-
require 'sidekiq/options'
|
6
7
|
|
7
8
|
module Sidekiq
|
8
9
|
module Cron
|
9
10
|
class Job
|
10
|
-
# How long we would like to store
|
11
|
+
# How long we would like to store information about previous enqueues.
|
11
12
|
REMEMBER_THRESHOLD = 24 * 60 * 60
|
12
13
|
|
13
14
|
# Time format for enqueued jobs.
|
14
15
|
LAST_ENQUEUE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S %z'
|
15
16
|
|
16
|
-
# Use the exists? method if we're on a newer version of Redis.
|
17
|
-
REDIS_EXISTS_METHOD = Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new("7.0.0") || Gem.loaded_specs['redis'].version < Gem::Version.new('4.2') ? :exists : :exists?
|
18
|
-
|
19
17
|
# Use serialize/deserialize key of GlobalID.
|
20
18
|
GLOBALID_KEY = "_sc_globalid"
|
21
19
|
|
20
|
+
attr_accessor :name, :namespace, :cron, :description, :klass, :args, :message
|
21
|
+
attr_reader :last_enqueue_time, :fetch_missing_args, :source
|
22
|
+
|
23
|
+
def initialize input_args = {}
|
24
|
+
args = Hash[input_args.map{ |k, v| [k.to_s, v] }]
|
25
|
+
@fetch_missing_args = args.delete('fetch_missing_args')
|
26
|
+
@fetch_missing_args = true if @fetch_missing_args.nil?
|
27
|
+
|
28
|
+
@name = args["name"]
|
29
|
+
@namespace = args["namespace"] || Sidekiq::Cron.configuration.default_namespace
|
30
|
+
@cron = args["cron"]
|
31
|
+
@description = args["description"] if args["description"]
|
32
|
+
@source = args["source"] == "schedule" ? "schedule" : "dynamic"
|
33
|
+
|
34
|
+
# Get class from klass or class.
|
35
|
+
@klass = args["klass"] || args["class"]
|
36
|
+
|
37
|
+
# Set status of job.
|
38
|
+
@status = args['status'] || status_from_redis
|
39
|
+
|
40
|
+
# Set last enqueue time - from args or from existing job.
|
41
|
+
if args['last_enqueue_time'] && !args['last_enqueue_time'].empty?
|
42
|
+
@last_enqueue_time = parse_enqueue_time(args['last_enqueue_time'])
|
43
|
+
else
|
44
|
+
@last_enqueue_time = last_enqueue_time_from_redis
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get right arguments for job.
|
48
|
+
@symbolize_args = args["symbolize_args"] == true || ("#{args["symbolize_args"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
49
|
+
@args = parse_args(args["args"])
|
50
|
+
|
51
|
+
@date_as_argument = args["date_as_argument"] == true || ("#{args["date_as_argument"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
52
|
+
|
53
|
+
@active_job = args["active_job"] == true || ("#{args["active_job"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
54
|
+
@active_job_queue_name_prefix = args["queue_name_prefix"]
|
55
|
+
@active_job_queue_name_delimiter = args["queue_name_delimiter"]
|
56
|
+
|
57
|
+
# symbolize_args is only used when active_job is true
|
58
|
+
Sidekiq.logger.warn { "Cron Jobs - 'symbolize_args' is gonna be ignored, as it is only used when 'active_job' is true" } if @symbolize_args && !@active_job
|
59
|
+
|
60
|
+
if args["message"]
|
61
|
+
@message = args["message"]
|
62
|
+
message_data = Sidekiq.load_json(@message) || {}
|
63
|
+
@queue = message_data['queue'] || "default"
|
64
|
+
@retry = message_data['retry']
|
65
|
+
elsif @klass
|
66
|
+
message_data = {
|
67
|
+
"class" => @klass.to_s,
|
68
|
+
"args" => @args,
|
69
|
+
}
|
70
|
+
|
71
|
+
# Get right data for message,
|
72
|
+
# only if message wasn't specified before.
|
73
|
+
klass_data = get_job_class_options(@klass)
|
74
|
+
message_data = klass_data.merge(message_data)
|
75
|
+
|
76
|
+
# Override queue and retry if set in config,
|
77
|
+
# only if message is hash - can be string (dumped JSON).
|
78
|
+
if args['queue']
|
79
|
+
@queue = message_data['queue'] = args['queue']
|
80
|
+
else
|
81
|
+
@queue = message_data['queue'] || "default"
|
82
|
+
end
|
83
|
+
|
84
|
+
if args['retry'] != nil
|
85
|
+
@retry = message_data['retry'] = args['retry']
|
86
|
+
else
|
87
|
+
@retry = message_data['retry']
|
88
|
+
end
|
89
|
+
|
90
|
+
@message = message_data
|
91
|
+
end
|
92
|
+
|
93
|
+
@queue_name_with_prefix = queue_name_with_prefix
|
94
|
+
end
|
95
|
+
|
22
96
|
# Crucial part of whole enqueuing job.
|
23
|
-
def
|
97
|
+
def should_enqueue? time
|
98
|
+
return false unless status == "enabled"
|
99
|
+
return false if past_scheduled_time?(time)
|
100
|
+
return false if enqueued_after?(time)
|
101
|
+
|
24
102
|
enqueue = Sidekiq.redis do |conn|
|
25
|
-
|
26
|
-
not_past_scheduled_time?(time) &&
|
27
|
-
not_enqueued_after?(time) &&
|
28
|
-
conn.zadd(job_enqueued_key, formatted_enqueue_time(time), formatted_last_time(time))
|
103
|
+
conn.zadd(job_enqueued_key, formatted_enqueue_time(time), formatted_last_time(time))
|
29
104
|
end
|
30
105
|
enqueue == true || enqueue == 1
|
31
106
|
end
|
32
107
|
|
33
108
|
# Remove previous information about run times,
|
34
109
|
# this will clear Redis and make sure that Redis will not overflow with memory.
|
35
|
-
def
|
110
|
+
def remove_previous_enqueues time
|
36
111
|
Sidekiq.redis do |conn|
|
37
112
|
conn.zremrangebyscore(job_enqueued_key, 0, "(#{(time.to_f - REMEMBER_THRESHOLD).to_s}")
|
38
113
|
end
|
39
114
|
end
|
40
115
|
|
41
116
|
# Test if job should be enqueued.
|
42
|
-
def
|
43
|
-
if
|
44
|
-
|
117
|
+
def test_and_enqueue_for_time! time
|
118
|
+
if should_enqueue?(time)
|
119
|
+
enqueue!
|
45
120
|
|
46
|
-
|
121
|
+
remove_previous_enqueues(time)
|
47
122
|
end
|
48
123
|
end
|
49
124
|
|
50
125
|
# Enqueue cron job to queue.
|
51
|
-
def
|
126
|
+
def enqueue! time = Time.now.utc
|
52
127
|
@last_enqueue_time = time
|
53
128
|
|
54
129
|
klass_const =
|
@@ -79,7 +154,7 @@ module Sidekiq
|
|
79
154
|
end
|
80
155
|
|
81
156
|
def is_active_job?(klass = nil)
|
82
|
-
@active_job || defined?(ActiveJob::Base) && (klass || Sidekiq::Cron::Support.constantize(@klass.to_s)) < ActiveJob::Base
|
157
|
+
@active_job || defined?(::ActiveJob::Base) && (klass || Sidekiq::Cron::Support.constantize(@klass.to_s)) < ::ActiveJob::Base
|
83
158
|
rescue NameError
|
84
159
|
false
|
85
160
|
end
|
@@ -98,7 +173,7 @@ module Sidekiq
|
|
98
173
|
end
|
99
174
|
|
100
175
|
def enqueue_sidekiq_worker(klass_const)
|
101
|
-
klass_const.set(queue: queue_name_with_prefix).perform_async(*enqueue_args)
|
176
|
+
klass_const.set(queue: queue_name_with_prefix, retry: @retry).perform_async(*enqueue_args)
|
102
177
|
end
|
103
178
|
|
104
179
|
# Sidekiq worker message.
|
@@ -113,16 +188,16 @@ module Sidekiq
|
|
113
188
|
|
114
189
|
if !"#{@active_job_queue_name_delimiter}".empty?
|
115
190
|
queue_name_delimiter = @active_job_queue_name_delimiter
|
116
|
-
elsif defined?(ActiveJob::Base) && defined?(ActiveJob::Base.queue_name_delimiter) &&
|
117
|
-
queue_name_delimiter = ActiveJob::Base.queue_name_delimiter
|
191
|
+
elsif defined?(::ActiveJob::Base) && defined?(::ActiveJob::Base.queue_name_delimiter) && !::ActiveJob::Base.queue_name_delimiter.empty?
|
192
|
+
queue_name_delimiter = ::ActiveJob::Base.queue_name_delimiter
|
118
193
|
else
|
119
194
|
queue_name_delimiter = '_'
|
120
195
|
end
|
121
196
|
|
122
197
|
if !"#{@active_job_queue_name_prefix}".empty?
|
123
198
|
queue_name = "#{@active_job_queue_name_prefix}#{queue_name_delimiter}#{@queue}"
|
124
|
-
elsif defined?(ActiveJob::Base) && defined?(ActiveJob::Base.queue_name_prefix) && !"#{ActiveJob::Base.queue_name_prefix}".empty?
|
125
|
-
queue_name = "#{ActiveJob::Base.queue_name_prefix}#{queue_name_delimiter}#{@queue}"
|
199
|
+
elsif defined?(::ActiveJob::Base) && defined?(::ActiveJob::Base.queue_name_prefix) && !"#{::ActiveJob::Base.queue_name_prefix}".empty?
|
200
|
+
queue_name = "#{::ActiveJob::Base.queue_name_prefix}#{queue_name_delimiter}#{@queue}"
|
126
201
|
else
|
127
202
|
queue_name = @queue
|
128
203
|
end
|
@@ -151,6 +226,7 @@ module Sidekiq
|
|
151
226
|
# Input structure should look like:
|
152
227
|
# {
|
153
228
|
# 'name_of_job' => {
|
229
|
+
# 'namespace' => 'MyNamespace',
|
154
230
|
# 'class' => 'MyClass',
|
155
231
|
# 'cron' => '1 * * * *',
|
156
232
|
# 'args' => '(OPTIONAL) [Array or Hash]',
|
@@ -181,6 +257,7 @@ module Sidekiq
|
|
181
257
|
# Input structure should look like:
|
182
258
|
# [
|
183
259
|
# {
|
260
|
+
# 'namespace' => 'MyNamespace',
|
184
261
|
# 'name' => 'name_of_job',
|
185
262
|
# 'class' => 'MyClass',
|
186
263
|
# 'cron' => '1 * * * *',
|
@@ -206,19 +283,20 @@ module Sidekiq
|
|
206
283
|
# Like #load_from_array.
|
207
284
|
# If exists old jobs in Redis but removed from args, destroy old jobs.
|
208
285
|
def self.load_from_array!(array, options = {})
|
209
|
-
job_names = array.map { |job| job["name"] }
|
286
|
+
job_names = array.map { |job| job["name"] || job[:name] }
|
210
287
|
destroy_removed_jobs(job_names)
|
211
288
|
load_from_array(array, options)
|
212
289
|
end
|
213
290
|
|
214
291
|
# Get all cron jobs.
|
215
|
-
def self.all
|
292
|
+
def self.all(namespace = Sidekiq::Cron.configuration.default_namespace)
|
216
293
|
job_hashes = nil
|
217
294
|
Sidekiq.redis do |conn|
|
218
|
-
|
295
|
+
job_keys = job_keys_from_namespace(namespace)
|
296
|
+
|
219
297
|
job_hashes = conn.pipelined do |pipeline|
|
220
|
-
|
221
|
-
pipeline.hgetall(
|
298
|
+
job_keys.each do |job_key|
|
299
|
+
pipeline.hgetall(job_key)
|
222
300
|
end
|
223
301
|
end
|
224
302
|
end
|
@@ -228,22 +306,25 @@ module Sidekiq
|
|
228
306
|
end
|
229
307
|
end
|
230
308
|
|
231
|
-
def self.count
|
232
|
-
|
233
|
-
|
234
|
-
|
309
|
+
def self.count(namespace = Sidekiq::Cron.configuration.default_namespace)
|
310
|
+
if namespace == '*'
|
311
|
+
Namespace.all_with_count.reduce(0) do |memo, namespace_count|
|
312
|
+
memo + namespace_count[:count]
|
313
|
+
end
|
314
|
+
else
|
315
|
+
Sidekiq.redis { |conn| conn.scard(jobs_key(namespace)) }
|
235
316
|
end
|
236
|
-
out
|
237
317
|
end
|
238
318
|
|
239
|
-
def self.find
|
319
|
+
def self.find(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
240
320
|
# If name is hash try to get name from it.
|
241
321
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
322
|
+
return unless exists? name, namespace
|
242
323
|
|
243
324
|
output = nil
|
244
325
|
Sidekiq.redis do |conn|
|
245
|
-
if exists? name
|
246
|
-
output = Job.new conn.hgetall(
|
326
|
+
if exists? name, namespace
|
327
|
+
output = Job.new conn.hgetall(redis_key(name, namespace))
|
247
328
|
end
|
248
329
|
end
|
249
330
|
output if output && output.valid?
|
@@ -255,93 +336,17 @@ module Sidekiq
|
|
255
336
|
end
|
256
337
|
|
257
338
|
# Destroy job by name.
|
258
|
-
def self.destroy
|
339
|
+
def self.destroy(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
259
340
|
# If name is hash try to get name from it.
|
260
341
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
261
342
|
|
262
|
-
if job = find(name)
|
343
|
+
if (job = find(name, namespace))
|
263
344
|
job.destroy
|
264
345
|
else
|
265
346
|
false
|
266
347
|
end
|
267
348
|
end
|
268
349
|
|
269
|
-
attr_accessor :name, :cron, :description, :klass, :args, :message
|
270
|
-
attr_reader :last_enqueue_time, :fetch_missing_args, :source
|
271
|
-
|
272
|
-
def initialize input_args = {}
|
273
|
-
args = Hash[input_args.map{ |k, v| [k.to_s, v] }]
|
274
|
-
@fetch_missing_args = args.delete('fetch_missing_args')
|
275
|
-
@fetch_missing_args = true if @fetch_missing_args.nil?
|
276
|
-
|
277
|
-
@name = args["name"]
|
278
|
-
@cron = args["cron"]
|
279
|
-
@description = args["description"] if args["description"]
|
280
|
-
@source = args["source"] == "schedule" ? "schedule" : "dynamic"
|
281
|
-
|
282
|
-
# Get class from klass or class.
|
283
|
-
@klass = args["klass"] || args["class"]
|
284
|
-
|
285
|
-
# Set status of job.
|
286
|
-
@status = args['status'] || status_from_redis
|
287
|
-
|
288
|
-
# Set last enqueue time - from args or from existing job.
|
289
|
-
if args['last_enqueue_time'] && !args['last_enqueue_time'].empty?
|
290
|
-
@last_enqueue_time = parse_enqueue_time(args['last_enqueue_time'])
|
291
|
-
else
|
292
|
-
@last_enqueue_time = last_enqueue_time_from_redis
|
293
|
-
end
|
294
|
-
|
295
|
-
# Get right arguments for job.
|
296
|
-
@symbolize_args = args["symbolize_args"] == true || ("#{args["symbolize_args"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
297
|
-
@args = args["args"].nil? ? [] : parse_args( args["args"] )
|
298
|
-
|
299
|
-
@date_as_argument = args["date_as_argument"] == true || ("#{args["date_as_argument"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
300
|
-
|
301
|
-
@active_job = args["active_job"] == true || ("#{args["active_job"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
302
|
-
@active_job_queue_name_prefix = args["queue_name_prefix"]
|
303
|
-
@active_job_queue_name_delimiter = args["queue_name_delimiter"]
|
304
|
-
|
305
|
-
if args["message"]
|
306
|
-
@message = args["message"]
|
307
|
-
message_data = Sidekiq.load_json(@message) || {}
|
308
|
-
@queue = message_data['queue'] || "default"
|
309
|
-
elsif @klass
|
310
|
-
message_data = {
|
311
|
-
"class" => @klass.to_s,
|
312
|
-
"args" => @args,
|
313
|
-
}
|
314
|
-
|
315
|
-
# Get right data for message,
|
316
|
-
# only if message wasn't specified before.
|
317
|
-
klass_data = case @klass
|
318
|
-
when Class
|
319
|
-
@klass.get_sidekiq_options
|
320
|
-
when String
|
321
|
-
begin
|
322
|
-
Sidekiq::Cron::Support.constantize(@klass).get_sidekiq_options
|
323
|
-
rescue Exception => e
|
324
|
-
# Unknown class
|
325
|
-
{"queue"=>"default"}
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
message_data = klass_data.merge(message_data)
|
330
|
-
|
331
|
-
# Override queue if setted in config,
|
332
|
-
# only if message is hash - can be string (dumped JSON).
|
333
|
-
if args['queue']
|
334
|
-
@queue = message_data['queue'] = args['queue']
|
335
|
-
else
|
336
|
-
@queue = message_data['queue'] || "default"
|
337
|
-
end
|
338
|
-
|
339
|
-
@message = message_data
|
340
|
-
end
|
341
|
-
|
342
|
-
@queue_name_with_prefix = queue_name_with_prefix
|
343
|
-
end
|
344
|
-
|
345
350
|
def status
|
346
351
|
@status
|
347
352
|
end
|
@@ -370,6 +375,12 @@ module Sidekiq
|
|
370
375
|
message
|
371
376
|
end
|
372
377
|
|
378
|
+
def human_cron
|
379
|
+
Cronex::ExpressionDescriptor.new(cron).description
|
380
|
+
rescue => e
|
381
|
+
cron
|
382
|
+
end
|
383
|
+
|
373
384
|
def status_from_redis
|
374
385
|
out = "enabled"
|
375
386
|
if fetch_missing_args
|
@@ -404,27 +415,24 @@ module Sidekiq
|
|
404
415
|
|
405
416
|
# Export job data to hash.
|
406
417
|
def to_hash
|
407
|
-
|
418
|
+
{
|
408
419
|
name: @name,
|
420
|
+
namespace: @namespace,
|
409
421
|
klass: @klass.to_s,
|
410
422
|
cron: @cron,
|
411
423
|
description: @description,
|
412
424
|
source: @source,
|
413
425
|
args: @args.is_a?(String) ? @args : Sidekiq.dump_json(@args || []),
|
426
|
+
date_as_argument: date_as_argument? ? "1" : "0",
|
414
427
|
message: @message.is_a?(String) ? @message : Sidekiq.dump_json(@message || {}),
|
415
428
|
status: @status,
|
416
429
|
active_job: @active_job ? "1" : "0",
|
417
430
|
queue_name_prefix: @active_job_queue_name_prefix,
|
418
431
|
queue_name_delimiter: @active_job_queue_name_delimiter,
|
432
|
+
retry: @retry.nil? || @retry.is_a?(Numeric) ? @retry : @retry.to_s,
|
419
433
|
last_enqueue_time: serialized_last_enqueue_time,
|
420
434
|
symbolize_args: symbolize_args? ? "1" : "0",
|
421
435
|
}
|
422
|
-
|
423
|
-
if date_as_argument?
|
424
|
-
hash.merge!(date_as_argument: "1")
|
425
|
-
end
|
426
|
-
|
427
|
-
hash
|
428
436
|
end
|
429
437
|
|
430
438
|
def errors
|
@@ -436,11 +444,14 @@ module Sidekiq
|
|
436
444
|
@errors = []
|
437
445
|
|
438
446
|
errors << "'name' must be set" if @name.nil? || @name.size == 0
|
447
|
+
errors << "'namespace' must be set" if @namespace.nil? || @namespace.size == 0
|
448
|
+
errors << "'namespace' cannot be '*'" if @namespace == "*"
|
449
|
+
|
439
450
|
if @cron.nil? || @cron.size == 0
|
440
451
|
errors << "'cron' must be set"
|
441
452
|
else
|
442
453
|
begin
|
443
|
-
@parsed_cron =
|
454
|
+
@parsed_cron = do_parse_cron(@cron)
|
444
455
|
rescue => e
|
445
456
|
errors << "'cron' -> #{@cron.inspect} -> #{e.class}: #{e.message}"
|
446
457
|
end
|
@@ -466,19 +477,23 @@ module Sidekiq
|
|
466
477
|
return false unless valid?
|
467
478
|
|
468
479
|
Sidekiq.redis do |conn|
|
469
|
-
|
470
480
|
# Add to set of all jobs
|
471
|
-
conn.sadd self.class.jobs_key, [redis_key]
|
481
|
+
conn.sadd self.class.jobs_key(@namespace), [redis_key]
|
472
482
|
|
473
|
-
# Add
|
474
|
-
conn.hset redis_key, to_hash.transform_values! { |v| v ||
|
483
|
+
# Add information for this job!
|
484
|
+
conn.hset redis_key, to_hash.transform_values! { |v| v || '' }.flatten
|
475
485
|
|
476
|
-
# Add information about last time! - don't
|
486
|
+
# Add information about last time! - don't enqueue right after scheduler poller starts!
|
477
487
|
time = Time.now.utc
|
478
|
-
exists = conn.
|
479
|
-
|
488
|
+
exists = conn.exists(job_enqueued_key)
|
489
|
+
|
490
|
+
unless exists == true || exists == 1
|
491
|
+
conn.zadd(job_enqueued_key, time.to_f.to_s, formatted_last_time(time).to_s)
|
492
|
+
Sidekiq.logger.info { "Cron Jobs - added job with name #{@name} in the namespace #{@namespace}" }
|
493
|
+
end
|
480
494
|
end
|
481
|
-
|
495
|
+
|
496
|
+
true
|
482
497
|
end
|
483
498
|
|
484
499
|
def save_last_enqueue_time
|
@@ -494,7 +509,7 @@ module Sidekiq
|
|
494
509
|
enqueued: @last_enqueue_time
|
495
510
|
}
|
496
511
|
|
497
|
-
@history_size ||=
|
512
|
+
@history_size ||= Sidekiq::Cron.configuration.cron_history_size.to_i - 1
|
498
513
|
Sidekiq.redis do |conn|
|
499
514
|
conn.lpush jid_history_key,
|
500
515
|
Sidekiq.dump_json(jid_history)
|
@@ -506,9 +521,9 @@ module Sidekiq
|
|
506
521
|
def destroy
|
507
522
|
Sidekiq.redis do |conn|
|
508
523
|
# Delete from set.
|
509
|
-
conn.srem self.class.jobs_key, [redis_key]
|
524
|
+
conn.srem self.class.jobs_key(@namespace), [redis_key]
|
510
525
|
|
511
|
-
# Delete
|
526
|
+
# Delete ran timestamps.
|
512
527
|
conn.del job_enqueued_key
|
513
528
|
|
514
529
|
# Delete jid_history.
|
@@ -518,7 +533,7 @@ module Sidekiq
|
|
518
533
|
conn.del redis_key
|
519
534
|
end
|
520
535
|
|
521
|
-
Sidekiq.logger.info { "Cron Jobs - deleted job with name
|
536
|
+
Sidekiq.logger.info { "Cron Jobs - deleted job with name #{@name} from namespace #{@namespace}" }
|
522
537
|
end
|
523
538
|
|
524
539
|
# Remove all job from cron.
|
@@ -531,9 +546,17 @@ module Sidekiq
|
|
531
546
|
|
532
547
|
# Remove "removed jobs" between current jobs and new jobs
|
533
548
|
def self.destroy_removed_jobs new_job_names
|
534
|
-
|
549
|
+
current_jobs = Sidekiq::Cron::Job.all("*").filter_map { |j| j if j.source == "schedule" }
|
550
|
+
current_job_names = current_jobs.map(&:name)
|
535
551
|
removed_job_names = current_job_names - new_job_names
|
536
|
-
removed_job_names.each
|
552
|
+
removed_job_names.each do |j|
|
553
|
+
job_to_destroy = current_jobs.detect { |job| job.name == j }
|
554
|
+
|
555
|
+
Sidekiq::Cron::Job.destroy(
|
556
|
+
job_to_destroy.name,
|
557
|
+
job_to_destroy.namespace
|
558
|
+
)
|
559
|
+
end
|
537
560
|
removed_job_names
|
538
561
|
end
|
539
562
|
|
@@ -551,29 +574,48 @@ module Sidekiq
|
|
551
574
|
last_time(now).getutc.iso8601
|
552
575
|
end
|
553
576
|
|
554
|
-
def self.exists?
|
577
|
+
def self.exists?(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
555
578
|
out = Sidekiq.redis do |conn|
|
556
|
-
conn.
|
579
|
+
conn.exists(redis_key(name, namespace))
|
557
580
|
end
|
558
|
-
|
581
|
+
|
582
|
+
[true, 1].include?(out)
|
559
583
|
end
|
560
584
|
|
561
585
|
def exists?
|
562
|
-
self.class.exists? @name
|
586
|
+
self.class.exists? @name, @namespace
|
563
587
|
end
|
564
588
|
|
565
589
|
def sort_name
|
566
590
|
"#{status == "enabled" ? 0 : 1}_#{name}".downcase
|
567
591
|
end
|
568
592
|
|
593
|
+
def args=(args)
|
594
|
+
@args = parse_args(args)
|
595
|
+
end
|
596
|
+
|
569
597
|
private
|
570
598
|
|
571
599
|
def parsed_cron
|
572
|
-
@parsed_cron ||=
|
600
|
+
@parsed_cron ||= do_parse_cron(@cron)
|
573
601
|
end
|
574
602
|
|
575
|
-
def
|
576
|
-
|
603
|
+
def do_parse_cron(cron)
|
604
|
+
case Sidekiq::Cron.configuration.natural_cron_parsing_mode
|
605
|
+
when :single
|
606
|
+
Fugit.do_parse_cronish(cron)
|
607
|
+
when :strict
|
608
|
+
Fugit.parse_cron(cron) || # Ex. '11 1 * * 1'
|
609
|
+
Fugit.parse_nat(cron, :multi => :fail) || # Ex. 'every Monday at 01:11'
|
610
|
+
fail(ArgumentError.new("invalid cron string #{cron.inspect}"))
|
611
|
+
else
|
612
|
+
mode = Sidekiq::Cron.configuration.natural_cron_parsing_mode
|
613
|
+
raise ArgumentError, "Unknown natural cron parsing mode: #{mode.inspect}"
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
def enqueued_after?(time)
|
618
|
+
@last_enqueue_time && @last_enqueue_time.to_i >= last_time(time).to_i
|
577
619
|
end
|
578
620
|
|
579
621
|
# Try parsing inbound args into an array.
|
@@ -627,48 +669,83 @@ module Sidekiq
|
|
627
669
|
DateTime.parse(timestamp).to_time.utc
|
628
670
|
end
|
629
671
|
|
630
|
-
def
|
672
|
+
def past_scheduled_time?(current_time)
|
631
673
|
last_cron_time = parsed_cron.previous_time(current_time).utc
|
632
|
-
|
633
|
-
|
674
|
+
period = Sidekiq::Cron.configuration.reschedule_grace_period
|
675
|
+
|
676
|
+
current_time.to_i - last_cron_time.to_i > period
|
634
677
|
end
|
635
678
|
|
636
|
-
|
637
|
-
|
638
|
-
|
679
|
+
def self.default_if_blank(namespace)
|
680
|
+
if namespace.nil? || namespace == ''
|
681
|
+
Sidekiq::Cron.configuration.default_namespace
|
682
|
+
else
|
683
|
+
namespace
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
def self.job_keys_from_namespace(namespace = Sidekiq::Cron.configuration.default_namespace)
|
688
|
+
Sidekiq.redis do |conn|
|
689
|
+
if namespace == '*'
|
690
|
+
namespaces = conn.keys(jobs_key(namespace))
|
691
|
+
namespaces.flat_map { |name| conn.smembers(name) }
|
692
|
+
else
|
693
|
+
conn.smembers(jobs_key(namespace))
|
694
|
+
end
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
def self.migrate_old_jobs_if_needed!
|
699
|
+
Sidekiq.redis do |conn|
|
700
|
+
old_job_keys = conn.smembers('cron_jobs')
|
701
|
+
old_job_keys.each do |old_job|
|
702
|
+
old_job_hash = conn.hgetall(old_job)
|
703
|
+
old_job_hash[:namespace] = Sidekiq::Cron.configuration.default_namespace
|
704
|
+
create(old_job_hash)
|
705
|
+
conn.srem('cron_jobs', old_job)
|
706
|
+
end
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
710
|
+
# Redis key for set of all cron jobs
|
711
|
+
def self.jobs_key(namespace = Sidekiq::Cron.configuration.default_namespace)
|
712
|
+
"cron_jobs:#{default_if_blank(namespace)}"
|
639
713
|
end
|
640
714
|
|
641
|
-
# Redis key for storing one cron job
|
642
|
-
def self.redis_key
|
643
|
-
"cron_job:#{name}"
|
715
|
+
# Redis key for storing one cron job
|
716
|
+
def self.redis_key(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
717
|
+
"cron_job:#{default_if_blank(namespace)}:#{name}"
|
644
718
|
end
|
645
719
|
|
646
|
-
# Redis key for storing one cron job
|
720
|
+
# Redis key for storing one cron job
|
647
721
|
def redis_key
|
648
|
-
self.class.redis_key @name
|
722
|
+
self.class.redis_key @name, @namespace
|
649
723
|
end
|
650
724
|
|
651
|
-
# Redis key for storing one cron job run times
|
652
|
-
|
653
|
-
|
725
|
+
# Redis key for storing one cron job run times
|
726
|
+
# (when poller added job to queue)
|
727
|
+
def self.job_enqueued_key(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
728
|
+
"cron_job:#{default_if_blank(namespace)}:#{name}:enqueued"
|
654
729
|
end
|
655
730
|
|
656
|
-
def self.jid_history_key
|
657
|
-
"cron_job:#{name}:jid_history"
|
731
|
+
def self.jid_history_key(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
732
|
+
"cron_job:#{default_if_blank(namespace)}:#{name}:jid_history"
|
658
733
|
end
|
659
734
|
|
735
|
+
# Redis key for storing one cron job run times
|
736
|
+
# (when poller added job to queue)
|
660
737
|
def job_enqueued_key
|
661
|
-
self.class.job_enqueued_key @name
|
738
|
+
self.class.job_enqueued_key @name, @namespace
|
662
739
|
end
|
663
740
|
|
664
741
|
def jid_history_key
|
665
|
-
self.class.jid_history_key @name
|
742
|
+
self.class.jid_history_key @name, @namespace
|
666
743
|
end
|
667
744
|
|
668
745
|
def serialized_last_enqueue_time
|
669
746
|
@last_enqueue_time&.strftime(LAST_ENQUEUE_TIME_FORMAT)
|
670
747
|
end
|
671
|
-
|
748
|
+
|
672
749
|
def convert_to_global_id_hash(argument)
|
673
750
|
{ GLOBALID_KEY => argument.to_global_id.to_s }
|
674
751
|
rescue URI::GID::MissingModelIdError
|
@@ -715,6 +792,23 @@ module Sidekiq
|
|
715
792
|
argument
|
716
793
|
end
|
717
794
|
end
|
795
|
+
|
796
|
+
def get_job_class_options(klass)
|
797
|
+
klass = klass.is_a?(Class) ? klass : begin
|
798
|
+
Sidekiq::Cron::Support.constantize(klass)
|
799
|
+
rescue NameError
|
800
|
+
# noop
|
801
|
+
end
|
802
|
+
|
803
|
+
if klass.nil?
|
804
|
+
# Unknown class
|
805
|
+
{"queue"=>"default"}
|
806
|
+
elsif is_active_job?(klass)
|
807
|
+
{"queue"=>klass.queue_name}
|
808
|
+
else
|
809
|
+
klass.get_sidekiq_options
|
810
|
+
end
|
811
|
+
end
|
718
812
|
end
|
719
813
|
end
|
720
814
|
end
|