sidekiq-cron 1.12.0 → 2.2.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 +52 -2
- data/Gemfile +3 -0
- data/README.md +256 -36
- data/lib/sidekiq/cron/job.rb +262 -168
- 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 +51 -15
- data/lib/sidekiq/cron/support.rb +4 -30
- data/lib/sidekiq/cron/version.rb +1 -1
- data/lib/sidekiq/cron/views/cron.erb +96 -91
- data/lib/sidekiq/cron/views/cron_show.erb +87 -82
- data/lib/sidekiq/cron/views/legacy/cron.erb +113 -0
- data/lib/sidekiq/cron/views/legacy/cron_show.erb +92 -0
- data/lib/sidekiq/cron/web.rb +21 -2
- data/lib/sidekiq/cron/web_extension.rb +95 -31
- 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 +5 -4
- metadata +38 -13
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 :cron_expression_string, :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,31 +113,26 @@ 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
|
-
klass_const =
|
56
|
-
begin
|
57
|
-
Sidekiq::Cron::Support.constantize(@klass.to_s)
|
58
|
-
rescue NameError
|
59
|
-
nil
|
60
|
-
end
|
135
|
+
klass_const = Sidekiq::Cron::Support.safe_constantize(@klass.to_s)
|
61
136
|
|
62
137
|
jid =
|
63
138
|
if klass_const
|
@@ -80,9 +155,10 @@ module Sidekiq
|
|
80
155
|
end
|
81
156
|
|
82
157
|
def is_active_job?(klass = nil)
|
83
|
-
@active_job || defined?(ActiveJob::Base) &&
|
84
|
-
|
85
|
-
|
158
|
+
@active_job || defined?(::ActiveJob::Base) && begin
|
159
|
+
klass ||= Sidekiq::Cron::Support.safe_constantize(@klass.to_s)
|
160
|
+
klass ? klass < ::ActiveJob::Base : false
|
161
|
+
end
|
86
162
|
end
|
87
163
|
|
88
164
|
def date_as_argument?
|
@@ -99,7 +175,7 @@ module Sidekiq
|
|
99
175
|
end
|
100
176
|
|
101
177
|
def enqueue_sidekiq_worker(klass_const)
|
102
|
-
klass_const.set(queue: queue_name_with_prefix).perform_async(*enqueue_args)
|
178
|
+
klass_const.set(queue: queue_name_with_prefix, retry: @retry).perform_async(*enqueue_args)
|
103
179
|
end
|
104
180
|
|
105
181
|
# Sidekiq worker message.
|
@@ -114,16 +190,16 @@ module Sidekiq
|
|
114
190
|
|
115
191
|
if !"#{@active_job_queue_name_delimiter}".empty?
|
116
192
|
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
|
193
|
+
elsif defined?(::ActiveJob::Base) && defined?(::ActiveJob::Base.queue_name_delimiter) && !::ActiveJob::Base.queue_name_delimiter.empty?
|
194
|
+
queue_name_delimiter = ::ActiveJob::Base.queue_name_delimiter
|
119
195
|
else
|
120
196
|
queue_name_delimiter = '_'
|
121
197
|
end
|
122
198
|
|
123
199
|
if !"#{@active_job_queue_name_prefix}".empty?
|
124
200
|
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}"
|
201
|
+
elsif defined?(::ActiveJob::Base) && defined?(::ActiveJob::Base.queue_name_prefix) && !"#{::ActiveJob::Base.queue_name_prefix}".empty?
|
202
|
+
queue_name = "#{::ActiveJob::Base.queue_name_prefix}#{queue_name_delimiter}#{@queue}"
|
127
203
|
else
|
128
204
|
queue_name = @queue
|
129
205
|
end
|
@@ -152,6 +228,7 @@ module Sidekiq
|
|
152
228
|
# Input structure should look like:
|
153
229
|
# {
|
154
230
|
# 'name_of_job' => {
|
231
|
+
# 'namespace' => 'MyNamespace',
|
155
232
|
# 'class' => 'MyClass',
|
156
233
|
# 'cron' => '1 * * * *',
|
157
234
|
# 'args' => '(OPTIONAL) [Array or Hash]',
|
@@ -182,6 +259,7 @@ module Sidekiq
|
|
182
259
|
# Input structure should look like:
|
183
260
|
# [
|
184
261
|
# {
|
262
|
+
# 'namespace' => 'MyNamespace',
|
185
263
|
# 'name' => 'name_of_job',
|
186
264
|
# 'class' => 'MyClass',
|
187
265
|
# 'cron' => '1 * * * *',
|
@@ -207,19 +285,20 @@ module Sidekiq
|
|
207
285
|
# Like #load_from_array.
|
208
286
|
# If exists old jobs in Redis but removed from args, destroy old jobs.
|
209
287
|
def self.load_from_array!(array, options = {})
|
210
|
-
job_names = array.map { |job| job["name"] }
|
288
|
+
job_names = array.map { |job| job["name"] || job[:name] }
|
211
289
|
destroy_removed_jobs(job_names)
|
212
290
|
load_from_array(array, options)
|
213
291
|
end
|
214
292
|
|
215
293
|
# Get all cron jobs.
|
216
|
-
def self.all
|
294
|
+
def self.all(namespace = Sidekiq::Cron.configuration.default_namespace)
|
217
295
|
job_hashes = nil
|
218
296
|
Sidekiq.redis do |conn|
|
219
|
-
|
297
|
+
job_keys = job_keys_from_namespace(namespace)
|
298
|
+
|
220
299
|
job_hashes = conn.pipelined do |pipeline|
|
221
|
-
|
222
|
-
pipeline.hgetall(
|
300
|
+
job_keys.each do |job_key|
|
301
|
+
pipeline.hgetall(job_key)
|
223
302
|
end
|
224
303
|
end
|
225
304
|
end
|
@@ -229,22 +308,26 @@ module Sidekiq
|
|
229
308
|
end
|
230
309
|
end
|
231
310
|
|
232
|
-
def self.count
|
233
|
-
|
234
|
-
|
235
|
-
|
311
|
+
def self.count(namespace = Sidekiq::Cron.configuration.default_namespace)
|
312
|
+
if namespace == '*'
|
313
|
+
Namespace.all_with_count.reduce(0) do |memo, namespace_count|
|
314
|
+
memo + namespace_count[:count]
|
315
|
+
end
|
316
|
+
else
|
317
|
+
Sidekiq.redis { |conn| conn.scard(jobs_key(namespace)) }
|
236
318
|
end
|
237
|
-
out
|
238
319
|
end
|
239
320
|
|
240
|
-
def self.find
|
321
|
+
def self.find(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
241
322
|
# If name is hash try to get name from it.
|
242
323
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
243
|
-
return unless exists? name
|
324
|
+
return unless exists? name, namespace
|
244
325
|
|
245
326
|
output = nil
|
246
327
|
Sidekiq.redis do |conn|
|
247
|
-
|
328
|
+
if exists? name, namespace
|
329
|
+
output = Job.new conn.hgetall(redis_key(name, namespace))
|
330
|
+
end
|
248
331
|
end
|
249
332
|
output if output && output.valid?
|
250
333
|
end
|
@@ -255,93 +338,17 @@ module Sidekiq
|
|
255
338
|
end
|
256
339
|
|
257
340
|
# Destroy job by name.
|
258
|
-
def self.destroy
|
341
|
+
def self.destroy(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
259
342
|
# If name is hash try to get name from it.
|
260
343
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
261
344
|
|
262
|
-
if job = find(name)
|
345
|
+
if (job = find(name, namespace))
|
263
346
|
job.destroy
|
264
347
|
else
|
265
348
|
false
|
266
349
|
end
|
267
350
|
end
|
268
351
|
|
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
352
|
def status
|
346
353
|
@status
|
347
354
|
end
|
@@ -370,6 +377,12 @@ module Sidekiq
|
|
370
377
|
message
|
371
378
|
end
|
372
379
|
|
380
|
+
def human_cron
|
381
|
+
Cronex::ExpressionDescriptor.new(cron).description
|
382
|
+
rescue => e
|
383
|
+
cron
|
384
|
+
end
|
385
|
+
|
373
386
|
def status_from_redis
|
374
387
|
out = "enabled"
|
375
388
|
if fetch_missing_args
|
@@ -404,27 +417,24 @@ module Sidekiq
|
|
404
417
|
|
405
418
|
# Export job data to hash.
|
406
419
|
def to_hash
|
407
|
-
|
420
|
+
{
|
408
421
|
name: @name,
|
422
|
+
namespace: @namespace,
|
409
423
|
klass: @klass.to_s,
|
410
424
|
cron: @cron,
|
411
425
|
description: @description,
|
412
426
|
source: @source,
|
413
427
|
args: @args.is_a?(String) ? @args : Sidekiq.dump_json(@args || []),
|
428
|
+
date_as_argument: date_as_argument? ? "1" : "0",
|
414
429
|
message: @message.is_a?(String) ? @message : Sidekiq.dump_json(@message || {}),
|
415
430
|
status: @status,
|
416
431
|
active_job: @active_job ? "1" : "0",
|
417
432
|
queue_name_prefix: @active_job_queue_name_prefix,
|
418
433
|
queue_name_delimiter: @active_job_queue_name_delimiter,
|
434
|
+
retry: @retry.nil? || @retry.is_a?(Numeric) ? @retry : @retry.to_s,
|
419
435
|
last_enqueue_time: serialized_last_enqueue_time,
|
420
436
|
symbolize_args: symbolize_args? ? "1" : "0",
|
421
437
|
}
|
422
|
-
|
423
|
-
if date_as_argument?
|
424
|
-
hash.merge!(date_as_argument: "1")
|
425
|
-
end
|
426
|
-
|
427
|
-
hash
|
428
438
|
end
|
429
439
|
|
430
440
|
def errors
|
@@ -436,11 +446,14 @@ module Sidekiq
|
|
436
446
|
@errors = []
|
437
447
|
|
438
448
|
errors << "'name' must be set" if @name.nil? || @name.size == 0
|
449
|
+
errors << "'namespace' must be set" if @namespace.nil? || @namespace.size == 0
|
450
|
+
errors << "'namespace' cannot be '*'" if @namespace == "*"
|
451
|
+
|
439
452
|
if @cron.nil? || @cron.size == 0
|
440
453
|
errors << "'cron' must be set"
|
441
454
|
else
|
442
455
|
begin
|
443
|
-
@parsed_cron =
|
456
|
+
@parsed_cron = do_parse_cron(@cron)
|
444
457
|
rescue => e
|
445
458
|
errors << "'cron' -> #{@cron.inspect} -> #{e.class}: #{e.message}"
|
446
459
|
end
|
@@ -466,19 +479,23 @@ module Sidekiq
|
|
466
479
|
return false unless valid?
|
467
480
|
|
468
481
|
Sidekiq.redis do |conn|
|
469
|
-
|
470
482
|
# Add to set of all jobs
|
471
|
-
conn.sadd self.class.jobs_key, [redis_key]
|
483
|
+
conn.sadd self.class.jobs_key(@namespace), [redis_key]
|
472
484
|
|
473
|
-
# Add
|
474
|
-
conn.hset redis_key, to_hash.transform_values! { |v| v ||
|
485
|
+
# Add information for this job!
|
486
|
+
conn.hset redis_key, to_hash.transform_values! { |v| v || '' }.flatten
|
475
487
|
|
476
|
-
# Add information about last time! - don't
|
488
|
+
# Add information about last time! - don't enqueue right after scheduler poller starts!
|
477
489
|
time = Time.now.utc
|
478
|
-
exists = conn.
|
479
|
-
|
490
|
+
exists = conn.exists(job_enqueued_key)
|
491
|
+
|
492
|
+
unless exists == true || exists == 1
|
493
|
+
conn.zadd(job_enqueued_key, time.to_f.to_s, formatted_last_time(time).to_s)
|
494
|
+
Sidekiq.logger.info { "Cron Jobs - added job with name #{@name} in the namespace #{@namespace}" }
|
495
|
+
end
|
480
496
|
end
|
481
|
-
|
497
|
+
|
498
|
+
true
|
482
499
|
end
|
483
500
|
|
484
501
|
def save_last_enqueue_time
|
@@ -494,7 +511,7 @@ module Sidekiq
|
|
494
511
|
enqueued: @last_enqueue_time
|
495
512
|
}
|
496
513
|
|
497
|
-
@history_size ||=
|
514
|
+
@history_size ||= Sidekiq::Cron.configuration.cron_history_size.to_i - 1
|
498
515
|
Sidekiq.redis do |conn|
|
499
516
|
conn.lpush jid_history_key,
|
500
517
|
Sidekiq.dump_json(jid_history)
|
@@ -506,9 +523,9 @@ module Sidekiq
|
|
506
523
|
def destroy
|
507
524
|
Sidekiq.redis do |conn|
|
508
525
|
# Delete from set.
|
509
|
-
conn.srem self.class.jobs_key, [redis_key]
|
526
|
+
conn.srem self.class.jobs_key(@namespace), [redis_key]
|
510
527
|
|
511
|
-
# Delete
|
528
|
+
# Delete ran timestamps.
|
512
529
|
conn.del job_enqueued_key
|
513
530
|
|
514
531
|
# Delete jid_history.
|
@@ -518,7 +535,7 @@ module Sidekiq
|
|
518
535
|
conn.del redis_key
|
519
536
|
end
|
520
537
|
|
521
|
-
Sidekiq.logger.info { "Cron Jobs - deleted job with name
|
538
|
+
Sidekiq.logger.info { "Cron Jobs - deleted job with name #{@name} from namespace #{@namespace}" }
|
522
539
|
end
|
523
540
|
|
524
541
|
# Remove all job from cron.
|
@@ -531,9 +548,17 @@ module Sidekiq
|
|
531
548
|
|
532
549
|
# Remove "removed jobs" between current jobs and new jobs
|
533
550
|
def self.destroy_removed_jobs new_job_names
|
534
|
-
|
551
|
+
current_jobs = Sidekiq::Cron::Job.all("*").filter_map { |j| j if j.source == "schedule" }
|
552
|
+
current_job_names = current_jobs.map(&:name)
|
535
553
|
removed_job_names = current_job_names - new_job_names
|
536
|
-
removed_job_names.each
|
554
|
+
removed_job_names.each do |j|
|
555
|
+
job_to_destroy = current_jobs.detect { |job| job.name == j }
|
556
|
+
|
557
|
+
Sidekiq::Cron::Job.destroy(
|
558
|
+
job_to_destroy.name,
|
559
|
+
job_to_destroy.namespace
|
560
|
+
)
|
561
|
+
end
|
537
562
|
removed_job_names
|
538
563
|
end
|
539
564
|
|
@@ -551,15 +576,16 @@ module Sidekiq
|
|
551
576
|
last_time(now).getutc.iso8601
|
552
577
|
end
|
553
578
|
|
554
|
-
def self.exists?
|
579
|
+
def self.exists?(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
555
580
|
out = Sidekiq.redis do |conn|
|
556
|
-
conn.
|
581
|
+
conn.exists(redis_key(name, namespace))
|
557
582
|
end
|
558
|
-
|
583
|
+
|
584
|
+
[true, 1].include?(out)
|
559
585
|
end
|
560
586
|
|
561
587
|
def exists?
|
562
|
-
self.class.exists? @name
|
588
|
+
self.class.exists? @name, @namespace
|
563
589
|
end
|
564
590
|
|
565
591
|
def sort_name
|
@@ -570,14 +596,32 @@ module Sidekiq
|
|
570
596
|
@args = parse_args(args)
|
571
597
|
end
|
572
598
|
|
599
|
+
def cron_expression_string
|
600
|
+
parsed_cron.to_cron_s
|
601
|
+
end
|
602
|
+
|
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
|
683
|
+
end
|
684
|
+
|
685
|
+
def self.default_if_blank(namespace)
|
686
|
+
if namespace.nil? || namespace == ''
|
687
|
+
Sidekiq::Cron.configuration.default_namespace
|
688
|
+
else
|
689
|
+
namespace
|
690
|
+
end
|
638
691
|
end
|
639
692
|
|
640
|
-
|
641
|
-
|
642
|
-
|
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,21 @@ 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 : Sidekiq::Cron::Support.safe_constantize(klass)
|
804
|
+
|
805
|
+
if klass.nil?
|
806
|
+
# Unknown class
|
807
|
+
{"queue"=>"default"}
|
808
|
+
elsif is_active_job?(klass)
|
809
|
+
job = klass.new(args)
|
810
|
+
|
811
|
+
{"queue"=>job.queue_name}
|
812
|
+
else
|
813
|
+
klass.get_sidekiq_options
|
814
|
+
end
|
815
|
+
end
|
722
816
|
end
|
723
817
|
end
|
724
818
|
end
|