sidekiq-cron 1.12.0 → 2.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 +4 -4
- data/CHANGELOG.md +39 -2
- data/Gemfile +3 -0
- data/README.md +197 -35
- data/lib/sidekiq/cron/job.rb +252 -160
- 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 +1 -5
- 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 +3 -5
- data/lib/sidekiq-cron.rb +6 -0
- data/sidekiq-cron.gemspec +3 -2
- metadata +31 -9
data/lib/sidekiq/cron/job.rb
CHANGED
@@ -1,29 +1,103 @@
|
|
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_options(@klass, @args)
|
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
|
24
98
|
return false unless status == "enabled"
|
25
|
-
return false
|
26
|
-
return false
|
99
|
+
return false if past_scheduled_time?(time)
|
100
|
+
return false if enqueued_after?(time)
|
27
101
|
|
28
102
|
enqueue = Sidekiq.redis do |conn|
|
29
103
|
conn.zadd(job_enqueued_key, formatted_enqueue_time(time), formatted_last_time(time))
|
@@ -33,23 +107,23 @@ module Sidekiq
|
|
33
107
|
|
34
108
|
# Remove previous information about run times,
|
35
109
|
# this will clear Redis and make sure that Redis will not overflow with memory.
|
36
|
-
def
|
110
|
+
def remove_previous_enqueues time
|
37
111
|
Sidekiq.redis do |conn|
|
38
112
|
conn.zremrangebyscore(job_enqueued_key, 0, "(#{(time.to_f - REMEMBER_THRESHOLD).to_s}")
|
39
113
|
end
|
40
114
|
end
|
41
115
|
|
42
116
|
# Test if job should be enqueued.
|
43
|
-
def
|
44
|
-
if
|
45
|
-
|
117
|
+
def test_and_enqueue_for_time! time
|
118
|
+
if should_enqueue?(time)
|
119
|
+
enqueue!
|
46
120
|
|
47
|
-
|
121
|
+
remove_previous_enqueues(time)
|
48
122
|
end
|
49
123
|
end
|
50
124
|
|
51
125
|
# Enqueue cron job to queue.
|
52
|
-
def
|
126
|
+
def enqueue! time = Time.now.utc
|
53
127
|
@last_enqueue_time = time
|
54
128
|
|
55
129
|
klass_const =
|
@@ -80,7 +154,7 @@ module Sidekiq
|
|
80
154
|
end
|
81
155
|
|
82
156
|
def is_active_job?(klass = nil)
|
83
|
-
@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
|
84
158
|
rescue NameError
|
85
159
|
false
|
86
160
|
end
|
@@ -99,7 +173,7 @@ module Sidekiq
|
|
99
173
|
end
|
100
174
|
|
101
175
|
def enqueue_sidekiq_worker(klass_const)
|
102
|
-
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)
|
103
177
|
end
|
104
178
|
|
105
179
|
# Sidekiq worker message.
|
@@ -114,16 +188,16 @@ module Sidekiq
|
|
114
188
|
|
115
189
|
if !"#{@active_job_queue_name_delimiter}".empty?
|
116
190
|
queue_name_delimiter = @active_job_queue_name_delimiter
|
117
|
-
elsif defined?(ActiveJob::Base) && defined?(ActiveJob::Base.queue_name_delimiter) &&
|
118
|
-
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
|
119
193
|
else
|
120
194
|
queue_name_delimiter = '_'
|
121
195
|
end
|
122
196
|
|
123
197
|
if !"#{@active_job_queue_name_prefix}".empty?
|
124
198
|
queue_name = "#{@active_job_queue_name_prefix}#{queue_name_delimiter}#{@queue}"
|
125
|
-
elsif defined?(ActiveJob::Base) && defined?(ActiveJob::Base.queue_name_prefix) && !"#{ActiveJob::Base.queue_name_prefix}".empty?
|
126
|
-
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}"
|
127
201
|
else
|
128
202
|
queue_name = @queue
|
129
203
|
end
|
@@ -152,6 +226,7 @@ module Sidekiq
|
|
152
226
|
# Input structure should look like:
|
153
227
|
# {
|
154
228
|
# 'name_of_job' => {
|
229
|
+
# 'namespace' => 'MyNamespace',
|
155
230
|
# 'class' => 'MyClass',
|
156
231
|
# 'cron' => '1 * * * *',
|
157
232
|
# 'args' => '(OPTIONAL) [Array or Hash]',
|
@@ -182,6 +257,7 @@ module Sidekiq
|
|
182
257
|
# Input structure should look like:
|
183
258
|
# [
|
184
259
|
# {
|
260
|
+
# 'namespace' => 'MyNamespace',
|
185
261
|
# 'name' => 'name_of_job',
|
186
262
|
# 'class' => 'MyClass',
|
187
263
|
# 'cron' => '1 * * * *',
|
@@ -207,19 +283,20 @@ module Sidekiq
|
|
207
283
|
# Like #load_from_array.
|
208
284
|
# If exists old jobs in Redis but removed from args, destroy old jobs.
|
209
285
|
def self.load_from_array!(array, options = {})
|
210
|
-
job_names = array.map { |job| job["name"] }
|
286
|
+
job_names = array.map { |job| job["name"] || job[:name] }
|
211
287
|
destroy_removed_jobs(job_names)
|
212
288
|
load_from_array(array, options)
|
213
289
|
end
|
214
290
|
|
215
291
|
# Get all cron jobs.
|
216
|
-
def self.all
|
292
|
+
def self.all(namespace = Sidekiq::Cron.configuration.default_namespace)
|
217
293
|
job_hashes = nil
|
218
294
|
Sidekiq.redis do |conn|
|
219
|
-
|
295
|
+
job_keys = job_keys_from_namespace(namespace)
|
296
|
+
|
220
297
|
job_hashes = conn.pipelined do |pipeline|
|
221
|
-
|
222
|
-
pipeline.hgetall(
|
298
|
+
job_keys.each do |job_key|
|
299
|
+
pipeline.hgetall(job_key)
|
223
300
|
end
|
224
301
|
end
|
225
302
|
end
|
@@ -229,22 +306,26 @@ module Sidekiq
|
|
229
306
|
end
|
230
307
|
end
|
231
308
|
|
232
|
-
def self.count
|
233
|
-
|
234
|
-
|
235
|
-
|
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)) }
|
236
316
|
end
|
237
|
-
out
|
238
317
|
end
|
239
318
|
|
240
|
-
def self.find
|
319
|
+
def self.find(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
241
320
|
# If name is hash try to get name from it.
|
242
321
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
243
|
-
return unless exists? name
|
322
|
+
return unless exists? name, namespace
|
244
323
|
|
245
324
|
output = nil
|
246
325
|
Sidekiq.redis do |conn|
|
247
|
-
|
326
|
+
if exists? name, namespace
|
327
|
+
output = Job.new conn.hgetall(redis_key(name, namespace))
|
328
|
+
end
|
248
329
|
end
|
249
330
|
output if output && output.valid?
|
250
331
|
end
|
@@ -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 = 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,15 +574,16 @@ 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
|
@@ -573,11 +597,25 @@ module Sidekiq
|
|
573
597
|
private
|
574
598
|
|
575
599
|
def parsed_cron
|
576
|
-
@parsed_cron ||=
|
600
|
+
@parsed_cron ||= do_parse_cron(@cron)
|
577
601
|
end
|
578
602
|
|
579
|
-
def
|
580
|
-
|
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
|
581
619
|
end
|
582
620
|
|
583
621
|
# Try parsing inbound args into an array.
|
@@ -631,48 +669,83 @@ module Sidekiq
|
|
631
669
|
DateTime.parse(timestamp).to_time.utc
|
632
670
|
end
|
633
671
|
|
634
|
-
def
|
672
|
+
def past_scheduled_time?(current_time)
|
635
673
|
last_cron_time = parsed_cron.previous_time(current_time).utc
|
636
|
-
|
637
|
-
|
674
|
+
period = Sidekiq::Cron.configuration.reschedule_grace_period
|
675
|
+
|
676
|
+
current_time.to_i - last_cron_time.to_i > period
|
638
677
|
end
|
639
678
|
|
640
|
-
|
641
|
-
|
642
|
-
|
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)}"
|
643
713
|
end
|
644
714
|
|
645
|
-
# Redis key for storing one cron job
|
646
|
-
def self.redis_key
|
647
|
-
"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}"
|
648
718
|
end
|
649
719
|
|
650
|
-
# Redis key for storing one cron job
|
720
|
+
# Redis key for storing one cron job
|
651
721
|
def redis_key
|
652
|
-
self.class.redis_key @name
|
722
|
+
self.class.redis_key @name, @namespace
|
653
723
|
end
|
654
724
|
|
655
|
-
# Redis key for storing one cron job run times
|
656
|
-
|
657
|
-
|
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"
|
658
729
|
end
|
659
730
|
|
660
|
-
def self.jid_history_key
|
661
|
-
"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"
|
662
733
|
end
|
663
734
|
|
735
|
+
# Redis key for storing one cron job run times
|
736
|
+
# (when poller added job to queue)
|
664
737
|
def job_enqueued_key
|
665
|
-
self.class.job_enqueued_key @name
|
738
|
+
self.class.job_enqueued_key @name, @namespace
|
666
739
|
end
|
667
740
|
|
668
741
|
def jid_history_key
|
669
|
-
self.class.jid_history_key @name
|
742
|
+
self.class.jid_history_key @name, @namespace
|
670
743
|
end
|
671
744
|
|
672
745
|
def serialized_last_enqueue_time
|
673
746
|
@last_enqueue_time&.strftime(LAST_ENQUEUE_TIME_FORMAT)
|
674
747
|
end
|
675
|
-
|
748
|
+
|
676
749
|
def convert_to_global_id_hash(argument)
|
677
750
|
{ GLOBALID_KEY => argument.to_global_id.to_s }
|
678
751
|
rescue URI::GID::MissingModelIdError
|
@@ -719,6 +792,25 @@ module Sidekiq
|
|
719
792
|
argument
|
720
793
|
end
|
721
794
|
end
|
795
|
+
|
796
|
+
def get_job_options(klass, args)
|
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
|
+
job = klass.new(args)
|
808
|
+
|
809
|
+
{"queue"=>job.queue_name}
|
810
|
+
else
|
811
|
+
klass.get_sidekiq_options
|
812
|
+
end
|
813
|
+
end
|
722
814
|
end
|
723
815
|
end
|
724
816
|
end
|