sidekiq-cron 1.12.0 → 2.1.0
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 +44 -2
- data/Gemfile +3 -0
- data/README.md +217 -36
- data/lib/sidekiq/cron/job.rb +258 -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/es.yml +22 -0
- 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 +48 -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 +73 -5
- data/lib/sidekiq/options.rb +3 -5
- data/lib/sidekiq-cron.rb +6 -0
- data/sidekiq-cron.gemspec +3 -2
- metadata +31 -8
data/lib/sidekiq/cron/job.rb
CHANGED
@@ -1,29 +1,109 @@
|
|
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
|
+
@cron = args["cron"]
|
30
|
+
@description = args["description"] if args["description"]
|
31
|
+
@source = args["source"] == "schedule" ? "schedule" : "dynamic"
|
32
|
+
|
33
|
+
default_namespace = Sidekiq::Cron.configuration.default_namespace
|
34
|
+
@namespace = args["namespace"] || default_namespace
|
35
|
+
if Sidekiq::Cron::Namespace.available_namespaces_provided? && !Sidekiq::Cron::Namespace.all.include?(@namespace) && @namespace != default_namespace
|
36
|
+
Sidekiq.logger.warn { "Cron Jobs - unexpected namespace #{@namespace} encountered. Assigning to default namespace." }
|
37
|
+
@namespace = default_namespace
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get class from klass or class.
|
41
|
+
@klass = args["klass"] || args["class"]
|
42
|
+
|
43
|
+
# Set status of job.
|
44
|
+
@status = args['status'] || status_from_redis
|
45
|
+
|
46
|
+
# Set last enqueue time - from args or from existing job.
|
47
|
+
if args['last_enqueue_time'] && !args['last_enqueue_time'].empty?
|
48
|
+
@last_enqueue_time = parse_enqueue_time(args['last_enqueue_time'])
|
49
|
+
else
|
50
|
+
@last_enqueue_time = last_enqueue_time_from_redis
|
51
|
+
end
|
52
|
+
|
53
|
+
# Get right arguments for job.
|
54
|
+
@symbolize_args = args["symbolize_args"] == true || ("#{args["symbolize_args"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
55
|
+
@args = parse_args(args["args"])
|
56
|
+
|
57
|
+
@date_as_argument = args["date_as_argument"] == true || ("#{args["date_as_argument"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
58
|
+
|
59
|
+
@active_job = args["active_job"] == true || ("#{args["active_job"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
60
|
+
@active_job_queue_name_prefix = args["queue_name_prefix"]
|
61
|
+
@active_job_queue_name_delimiter = args["queue_name_delimiter"]
|
62
|
+
|
63
|
+
# symbolize_args is only used when active_job is true
|
64
|
+
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
|
65
|
+
|
66
|
+
if args["message"]
|
67
|
+
@message = args["message"]
|
68
|
+
message_data = Sidekiq.load_json(@message) || {}
|
69
|
+
@queue = message_data['queue'] || "default"
|
70
|
+
@retry = message_data['retry']
|
71
|
+
elsif @klass
|
72
|
+
message_data = {
|
73
|
+
"class" => @klass.to_s,
|
74
|
+
"args" => @args,
|
75
|
+
}
|
76
|
+
|
77
|
+
# Get right data for message,
|
78
|
+
# only if message wasn't specified before.
|
79
|
+
klass_data = get_job_options(@klass, @args)
|
80
|
+
message_data = klass_data.merge(message_data)
|
81
|
+
|
82
|
+
# Override queue and retry if set in config,
|
83
|
+
# only if message is hash - can be string (dumped JSON).
|
84
|
+
if args['queue']
|
85
|
+
@queue = message_data['queue'] = args['queue']
|
86
|
+
else
|
87
|
+
@queue = message_data['queue'] || "default"
|
88
|
+
end
|
89
|
+
|
90
|
+
if args['retry'] != nil
|
91
|
+
@retry = message_data['retry'] = args['retry']
|
92
|
+
else
|
93
|
+
@retry = message_data['retry']
|
94
|
+
end
|
95
|
+
|
96
|
+
@message = message_data
|
97
|
+
end
|
98
|
+
|
99
|
+
@queue_name_with_prefix = queue_name_with_prefix
|
100
|
+
end
|
101
|
+
|
22
102
|
# Crucial part of whole enqueuing job.
|
23
|
-
def
|
103
|
+
def should_enqueue? time
|
24
104
|
return false unless status == "enabled"
|
25
|
-
return false
|
26
|
-
return false
|
105
|
+
return false if past_scheduled_time?(time)
|
106
|
+
return false if enqueued_after?(time)
|
27
107
|
|
28
108
|
enqueue = Sidekiq.redis do |conn|
|
29
109
|
conn.zadd(job_enqueued_key, formatted_enqueue_time(time), formatted_last_time(time))
|
@@ -33,23 +113,23 @@ module Sidekiq
|
|
33
113
|
|
34
114
|
# Remove previous information about run times,
|
35
115
|
# this will clear Redis and make sure that Redis will not overflow with memory.
|
36
|
-
def
|
116
|
+
def remove_previous_enqueues time
|
37
117
|
Sidekiq.redis do |conn|
|
38
118
|
conn.zremrangebyscore(job_enqueued_key, 0, "(#{(time.to_f - REMEMBER_THRESHOLD).to_s}")
|
39
119
|
end
|
40
120
|
end
|
41
121
|
|
42
122
|
# Test if job should be enqueued.
|
43
|
-
def
|
44
|
-
if
|
45
|
-
|
123
|
+
def test_and_enqueue_for_time! time
|
124
|
+
if should_enqueue?(time)
|
125
|
+
enqueue!
|
46
126
|
|
47
|
-
|
127
|
+
remove_previous_enqueues(time)
|
48
128
|
end
|
49
129
|
end
|
50
130
|
|
51
131
|
# Enqueue cron job to queue.
|
52
|
-
def
|
132
|
+
def enqueue! time = Time.now.utc
|
53
133
|
@last_enqueue_time = time
|
54
134
|
|
55
135
|
klass_const =
|
@@ -80,7 +160,7 @@ module Sidekiq
|
|
80
160
|
end
|
81
161
|
|
82
162
|
def is_active_job?(klass = nil)
|
83
|
-
@active_job || defined?(ActiveJob::Base) && (klass || Sidekiq::Cron::Support.constantize(@klass.to_s)) < ActiveJob::Base
|
163
|
+
@active_job || defined?(::ActiveJob::Base) && (klass || Sidekiq::Cron::Support.constantize(@klass.to_s)) < ::ActiveJob::Base
|
84
164
|
rescue NameError
|
85
165
|
false
|
86
166
|
end
|
@@ -99,7 +179,7 @@ module Sidekiq
|
|
99
179
|
end
|
100
180
|
|
101
181
|
def enqueue_sidekiq_worker(klass_const)
|
102
|
-
klass_const.set(queue: queue_name_with_prefix).perform_async(*enqueue_args)
|
182
|
+
klass_const.set(queue: queue_name_with_prefix, retry: @retry).perform_async(*enqueue_args)
|
103
183
|
end
|
104
184
|
|
105
185
|
# Sidekiq worker message.
|
@@ -114,16 +194,16 @@ module Sidekiq
|
|
114
194
|
|
115
195
|
if !"#{@active_job_queue_name_delimiter}".empty?
|
116
196
|
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
|
197
|
+
elsif defined?(::ActiveJob::Base) && defined?(::ActiveJob::Base.queue_name_delimiter) && !::ActiveJob::Base.queue_name_delimiter.empty?
|
198
|
+
queue_name_delimiter = ::ActiveJob::Base.queue_name_delimiter
|
119
199
|
else
|
120
200
|
queue_name_delimiter = '_'
|
121
201
|
end
|
122
202
|
|
123
203
|
if !"#{@active_job_queue_name_prefix}".empty?
|
124
204
|
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}"
|
205
|
+
elsif defined?(::ActiveJob::Base) && defined?(::ActiveJob::Base.queue_name_prefix) && !"#{::ActiveJob::Base.queue_name_prefix}".empty?
|
206
|
+
queue_name = "#{::ActiveJob::Base.queue_name_prefix}#{queue_name_delimiter}#{@queue}"
|
127
207
|
else
|
128
208
|
queue_name = @queue
|
129
209
|
end
|
@@ -152,6 +232,7 @@ module Sidekiq
|
|
152
232
|
# Input structure should look like:
|
153
233
|
# {
|
154
234
|
# 'name_of_job' => {
|
235
|
+
# 'namespace' => 'MyNamespace',
|
155
236
|
# 'class' => 'MyClass',
|
156
237
|
# 'cron' => '1 * * * *',
|
157
238
|
# 'args' => '(OPTIONAL) [Array or Hash]',
|
@@ -182,6 +263,7 @@ module Sidekiq
|
|
182
263
|
# Input structure should look like:
|
183
264
|
# [
|
184
265
|
# {
|
266
|
+
# 'namespace' => 'MyNamespace',
|
185
267
|
# 'name' => 'name_of_job',
|
186
268
|
# 'class' => 'MyClass',
|
187
269
|
# 'cron' => '1 * * * *',
|
@@ -207,19 +289,20 @@ module Sidekiq
|
|
207
289
|
# Like #load_from_array.
|
208
290
|
# If exists old jobs in Redis but removed from args, destroy old jobs.
|
209
291
|
def self.load_from_array!(array, options = {})
|
210
|
-
job_names = array.map { |job| job["name"] }
|
292
|
+
job_names = array.map { |job| job["name"] || job[:name] }
|
211
293
|
destroy_removed_jobs(job_names)
|
212
294
|
load_from_array(array, options)
|
213
295
|
end
|
214
296
|
|
215
297
|
# Get all cron jobs.
|
216
|
-
def self.all
|
298
|
+
def self.all(namespace = Sidekiq::Cron.configuration.default_namespace)
|
217
299
|
job_hashes = nil
|
218
300
|
Sidekiq.redis do |conn|
|
219
|
-
|
301
|
+
job_keys = job_keys_from_namespace(namespace)
|
302
|
+
|
220
303
|
job_hashes = conn.pipelined do |pipeline|
|
221
|
-
|
222
|
-
pipeline.hgetall(
|
304
|
+
job_keys.each do |job_key|
|
305
|
+
pipeline.hgetall(job_key)
|
223
306
|
end
|
224
307
|
end
|
225
308
|
end
|
@@ -229,22 +312,26 @@ module Sidekiq
|
|
229
312
|
end
|
230
313
|
end
|
231
314
|
|
232
|
-
def self.count
|
233
|
-
|
234
|
-
|
235
|
-
|
315
|
+
def self.count(namespace = Sidekiq::Cron.configuration.default_namespace)
|
316
|
+
if namespace == '*'
|
317
|
+
Namespace.all_with_count.reduce(0) do |memo, namespace_count|
|
318
|
+
memo + namespace_count[:count]
|
319
|
+
end
|
320
|
+
else
|
321
|
+
Sidekiq.redis { |conn| conn.scard(jobs_key(namespace)) }
|
236
322
|
end
|
237
|
-
out
|
238
323
|
end
|
239
324
|
|
240
|
-
def self.find
|
325
|
+
def self.find(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
241
326
|
# If name is hash try to get name from it.
|
242
327
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
243
|
-
return unless exists? name
|
328
|
+
return unless exists? name, namespace
|
244
329
|
|
245
330
|
output = nil
|
246
331
|
Sidekiq.redis do |conn|
|
247
|
-
|
332
|
+
if exists? name, namespace
|
333
|
+
output = Job.new conn.hgetall(redis_key(name, namespace))
|
334
|
+
end
|
248
335
|
end
|
249
336
|
output if output && output.valid?
|
250
337
|
end
|
@@ -255,93 +342,17 @@ module Sidekiq
|
|
255
342
|
end
|
256
343
|
|
257
344
|
# Destroy job by name.
|
258
|
-
def self.destroy
|
345
|
+
def self.destroy(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
259
346
|
# If name is hash try to get name from it.
|
260
347
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
261
348
|
|
262
|
-
if job = find(name)
|
349
|
+
if (job = find(name, namespace))
|
263
350
|
job.destroy
|
264
351
|
else
|
265
352
|
false
|
266
353
|
end
|
267
354
|
end
|
268
355
|
|
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
356
|
def status
|
346
357
|
@status
|
347
358
|
end
|
@@ -370,6 +381,12 @@ module Sidekiq
|
|
370
381
|
message
|
371
382
|
end
|
372
383
|
|
384
|
+
def human_cron
|
385
|
+
Cronex::ExpressionDescriptor.new(cron).description
|
386
|
+
rescue => e
|
387
|
+
cron
|
388
|
+
end
|
389
|
+
|
373
390
|
def status_from_redis
|
374
391
|
out = "enabled"
|
375
392
|
if fetch_missing_args
|
@@ -404,27 +421,24 @@ module Sidekiq
|
|
404
421
|
|
405
422
|
# Export job data to hash.
|
406
423
|
def to_hash
|
407
|
-
|
424
|
+
{
|
408
425
|
name: @name,
|
426
|
+
namespace: @namespace,
|
409
427
|
klass: @klass.to_s,
|
410
428
|
cron: @cron,
|
411
429
|
description: @description,
|
412
430
|
source: @source,
|
413
431
|
args: @args.is_a?(String) ? @args : Sidekiq.dump_json(@args || []),
|
432
|
+
date_as_argument: date_as_argument? ? "1" : "0",
|
414
433
|
message: @message.is_a?(String) ? @message : Sidekiq.dump_json(@message || {}),
|
415
434
|
status: @status,
|
416
435
|
active_job: @active_job ? "1" : "0",
|
417
436
|
queue_name_prefix: @active_job_queue_name_prefix,
|
418
437
|
queue_name_delimiter: @active_job_queue_name_delimiter,
|
438
|
+
retry: @retry.nil? || @retry.is_a?(Numeric) ? @retry : @retry.to_s,
|
419
439
|
last_enqueue_time: serialized_last_enqueue_time,
|
420
440
|
symbolize_args: symbolize_args? ? "1" : "0",
|
421
441
|
}
|
422
|
-
|
423
|
-
if date_as_argument?
|
424
|
-
hash.merge!(date_as_argument: "1")
|
425
|
-
end
|
426
|
-
|
427
|
-
hash
|
428
442
|
end
|
429
443
|
|
430
444
|
def errors
|
@@ -436,11 +450,14 @@ module Sidekiq
|
|
436
450
|
@errors = []
|
437
451
|
|
438
452
|
errors << "'name' must be set" if @name.nil? || @name.size == 0
|
453
|
+
errors << "'namespace' must be set" if @namespace.nil? || @namespace.size == 0
|
454
|
+
errors << "'namespace' cannot be '*'" if @namespace == "*"
|
455
|
+
|
439
456
|
if @cron.nil? || @cron.size == 0
|
440
457
|
errors << "'cron' must be set"
|
441
458
|
else
|
442
459
|
begin
|
443
|
-
@parsed_cron =
|
460
|
+
@parsed_cron = do_parse_cron(@cron)
|
444
461
|
rescue => e
|
445
462
|
errors << "'cron' -> #{@cron.inspect} -> #{e.class}: #{e.message}"
|
446
463
|
end
|
@@ -466,19 +483,23 @@ module Sidekiq
|
|
466
483
|
return false unless valid?
|
467
484
|
|
468
485
|
Sidekiq.redis do |conn|
|
469
|
-
|
470
486
|
# Add to set of all jobs
|
471
|
-
conn.sadd self.class.jobs_key, [redis_key]
|
487
|
+
conn.sadd self.class.jobs_key(@namespace), [redis_key]
|
472
488
|
|
473
|
-
# Add
|
474
|
-
conn.hset redis_key, to_hash.transform_values! { |v| v ||
|
489
|
+
# Add information for this job!
|
490
|
+
conn.hset redis_key, to_hash.transform_values! { |v| v || '' }.flatten
|
475
491
|
|
476
|
-
# Add information about last time! - don't
|
492
|
+
# Add information about last time! - don't enqueue right after scheduler poller starts!
|
477
493
|
time = Time.now.utc
|
478
|
-
exists = conn.
|
479
|
-
|
494
|
+
exists = conn.exists(job_enqueued_key)
|
495
|
+
|
496
|
+
unless exists == true || exists == 1
|
497
|
+
conn.zadd(job_enqueued_key, time.to_f.to_s, formatted_last_time(time).to_s)
|
498
|
+
Sidekiq.logger.info { "Cron Jobs - added job with name #{@name} in the namespace #{@namespace}" }
|
499
|
+
end
|
480
500
|
end
|
481
|
-
|
501
|
+
|
502
|
+
true
|
482
503
|
end
|
483
504
|
|
484
505
|
def save_last_enqueue_time
|
@@ -494,7 +515,7 @@ module Sidekiq
|
|
494
515
|
enqueued: @last_enqueue_time
|
495
516
|
}
|
496
517
|
|
497
|
-
@history_size ||=
|
518
|
+
@history_size ||= Sidekiq::Cron.configuration.cron_history_size.to_i - 1
|
498
519
|
Sidekiq.redis do |conn|
|
499
520
|
conn.lpush jid_history_key,
|
500
521
|
Sidekiq.dump_json(jid_history)
|
@@ -506,9 +527,9 @@ module Sidekiq
|
|
506
527
|
def destroy
|
507
528
|
Sidekiq.redis do |conn|
|
508
529
|
# Delete from set.
|
509
|
-
conn.srem self.class.jobs_key, [redis_key]
|
530
|
+
conn.srem self.class.jobs_key(@namespace), [redis_key]
|
510
531
|
|
511
|
-
# Delete
|
532
|
+
# Delete ran timestamps.
|
512
533
|
conn.del job_enqueued_key
|
513
534
|
|
514
535
|
# Delete jid_history.
|
@@ -518,7 +539,7 @@ module Sidekiq
|
|
518
539
|
conn.del redis_key
|
519
540
|
end
|
520
541
|
|
521
|
-
Sidekiq.logger.info { "Cron Jobs - deleted job with name
|
542
|
+
Sidekiq.logger.info { "Cron Jobs - deleted job with name #{@name} from namespace #{@namespace}" }
|
522
543
|
end
|
523
544
|
|
524
545
|
# Remove all job from cron.
|
@@ -531,9 +552,17 @@ module Sidekiq
|
|
531
552
|
|
532
553
|
# Remove "removed jobs" between current jobs and new jobs
|
533
554
|
def self.destroy_removed_jobs new_job_names
|
534
|
-
|
555
|
+
current_jobs = Sidekiq::Cron::Job.all("*").filter_map { |j| j if j.source == "schedule" }
|
556
|
+
current_job_names = current_jobs.map(&:name)
|
535
557
|
removed_job_names = current_job_names - new_job_names
|
536
|
-
removed_job_names.each
|
558
|
+
removed_job_names.each do |j|
|
559
|
+
job_to_destroy = current_jobs.detect { |job| job.name == j }
|
560
|
+
|
561
|
+
Sidekiq::Cron::Job.destroy(
|
562
|
+
job_to_destroy.name,
|
563
|
+
job_to_destroy.namespace
|
564
|
+
)
|
565
|
+
end
|
537
566
|
removed_job_names
|
538
567
|
end
|
539
568
|
|
@@ -551,15 +580,16 @@ module Sidekiq
|
|
551
580
|
last_time(now).getutc.iso8601
|
552
581
|
end
|
553
582
|
|
554
|
-
def self.exists?
|
583
|
+
def self.exists?(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
555
584
|
out = Sidekiq.redis do |conn|
|
556
|
-
conn.
|
585
|
+
conn.exists(redis_key(name, namespace))
|
557
586
|
end
|
558
|
-
|
587
|
+
|
588
|
+
[true, 1].include?(out)
|
559
589
|
end
|
560
590
|
|
561
591
|
def exists?
|
562
|
-
self.class.exists? @name
|
592
|
+
self.class.exists? @name, @namespace
|
563
593
|
end
|
564
594
|
|
565
595
|
def sort_name
|
@@ -573,11 +603,25 @@ module Sidekiq
|
|
573
603
|
private
|
574
604
|
|
575
605
|
def parsed_cron
|
576
|
-
@parsed_cron ||=
|
606
|
+
@parsed_cron ||= do_parse_cron(@cron)
|
577
607
|
end
|
578
608
|
|
579
|
-
def
|
580
|
-
|
609
|
+
def do_parse_cron(cron)
|
610
|
+
case Sidekiq::Cron.configuration.natural_cron_parsing_mode
|
611
|
+
when :single
|
612
|
+
Fugit.do_parse_cronish(cron)
|
613
|
+
when :strict
|
614
|
+
Fugit.parse_cron(cron) || # Ex. '11 1 * * 1'
|
615
|
+
Fugit.parse_nat(cron, :multi => :fail) || # Ex. 'every Monday at 01:11'
|
616
|
+
fail(ArgumentError.new("invalid cron string #{cron.inspect}"))
|
617
|
+
else
|
618
|
+
mode = Sidekiq::Cron.configuration.natural_cron_parsing_mode
|
619
|
+
raise ArgumentError, "Unknown natural cron parsing mode: #{mode.inspect}"
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
def enqueued_after?(time)
|
624
|
+
@last_enqueue_time && @last_enqueue_time.to_i >= last_time(time).to_i
|
581
625
|
end
|
582
626
|
|
583
627
|
# Try parsing inbound args into an array.
|
@@ -631,48 +675,83 @@ module Sidekiq
|
|
631
675
|
DateTime.parse(timestamp).to_time.utc
|
632
676
|
end
|
633
677
|
|
634
|
-
def
|
678
|
+
def past_scheduled_time?(current_time)
|
635
679
|
last_cron_time = parsed_cron.previous_time(current_time).utc
|
636
|
-
|
637
|
-
|
680
|
+
period = Sidekiq::Cron.configuration.reschedule_grace_period
|
681
|
+
|
682
|
+
current_time.to_i - last_cron_time.to_i > period
|
638
683
|
end
|
639
684
|
|
640
|
-
|
641
|
-
|
642
|
-
|
685
|
+
def self.default_if_blank(namespace)
|
686
|
+
if namespace.nil? || namespace == ''
|
687
|
+
Sidekiq::Cron.configuration.default_namespace
|
688
|
+
else
|
689
|
+
namespace
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
def self.job_keys_from_namespace(namespace = Sidekiq::Cron.configuration.default_namespace)
|
694
|
+
Sidekiq.redis do |conn|
|
695
|
+
if namespace == '*'
|
696
|
+
namespaces = Sidekiq::Cron.configuration.available_namespaces&.map { jobs_key(_1) } || conn.keys(jobs_key(namespace))
|
697
|
+
namespaces.flat_map { |name| conn.smembers(name) }
|
698
|
+
else
|
699
|
+
conn.smembers(jobs_key(namespace))
|
700
|
+
end
|
701
|
+
end
|
643
702
|
end
|
644
703
|
|
645
|
-
|
646
|
-
|
647
|
-
|
704
|
+
def self.migrate_old_jobs_if_needed!
|
705
|
+
Sidekiq.redis do |conn|
|
706
|
+
old_job_keys = conn.smembers('cron_jobs')
|
707
|
+
old_job_keys.each do |old_job|
|
708
|
+
old_job_hash = conn.hgetall(old_job)
|
709
|
+
old_job_hash[:namespace] = Sidekiq::Cron.configuration.default_namespace
|
710
|
+
create(old_job_hash)
|
711
|
+
conn.srem('cron_jobs', old_job)
|
712
|
+
end
|
713
|
+
end
|
648
714
|
end
|
649
715
|
|
650
|
-
# Redis key for
|
716
|
+
# Redis key for set of all cron jobs
|
717
|
+
def self.jobs_key(namespace = Sidekiq::Cron.configuration.default_namespace)
|
718
|
+
"cron_jobs:#{default_if_blank(namespace)}"
|
719
|
+
end
|
720
|
+
|
721
|
+
# Redis key for storing one cron job
|
722
|
+
def self.redis_key(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
723
|
+
"cron_job:#{default_if_blank(namespace)}:#{name}"
|
724
|
+
end
|
725
|
+
|
726
|
+
# Redis key for storing one cron job
|
651
727
|
def redis_key
|
652
|
-
self.class.redis_key @name
|
728
|
+
self.class.redis_key @name, @namespace
|
653
729
|
end
|
654
730
|
|
655
|
-
# Redis key for storing one cron job run times
|
656
|
-
|
657
|
-
|
731
|
+
# Redis key for storing one cron job run times
|
732
|
+
# (when poller added job to queue)
|
733
|
+
def self.job_enqueued_key(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
734
|
+
"cron_job:#{default_if_blank(namespace)}:#{name}:enqueued"
|
658
735
|
end
|
659
736
|
|
660
|
-
def self.jid_history_key
|
661
|
-
"cron_job:#{name}:jid_history"
|
737
|
+
def self.jid_history_key(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
738
|
+
"cron_job:#{default_if_blank(namespace)}:#{name}:jid_history"
|
662
739
|
end
|
663
740
|
|
741
|
+
# Redis key for storing one cron job run times
|
742
|
+
# (when poller added job to queue)
|
664
743
|
def job_enqueued_key
|
665
|
-
self.class.job_enqueued_key @name
|
744
|
+
self.class.job_enqueued_key @name, @namespace
|
666
745
|
end
|
667
746
|
|
668
747
|
def jid_history_key
|
669
|
-
self.class.jid_history_key @name
|
748
|
+
self.class.jid_history_key @name, @namespace
|
670
749
|
end
|
671
750
|
|
672
751
|
def serialized_last_enqueue_time
|
673
752
|
@last_enqueue_time&.strftime(LAST_ENQUEUE_TIME_FORMAT)
|
674
753
|
end
|
675
|
-
|
754
|
+
|
676
755
|
def convert_to_global_id_hash(argument)
|
677
756
|
{ GLOBALID_KEY => argument.to_global_id.to_s }
|
678
757
|
rescue URI::GID::MissingModelIdError
|
@@ -719,6 +798,25 @@ module Sidekiq
|
|
719
798
|
argument
|
720
799
|
end
|
721
800
|
end
|
801
|
+
|
802
|
+
def get_job_options(klass, args)
|
803
|
+
klass = klass.is_a?(Class) ? klass : begin
|
804
|
+
Sidekiq::Cron::Support.constantize(klass)
|
805
|
+
rescue NameError
|
806
|
+
# noop
|
807
|
+
end
|
808
|
+
|
809
|
+
if klass.nil?
|
810
|
+
# Unknown class
|
811
|
+
{"queue"=>"default"}
|
812
|
+
elsif is_active_job?(klass)
|
813
|
+
job = klass.new(args)
|
814
|
+
|
815
|
+
{"queue"=>job.queue_name}
|
816
|
+
else
|
817
|
+
klass.get_sidekiq_options
|
818
|
+
end
|
819
|
+
end
|
722
820
|
end
|
723
821
|
end
|
724
822
|
end
|