sidekiq-cron 1.11.0 → 2.0.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 +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
|