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.
@@ -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 informations about previous enqueues.
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 should_enque? time
103
+ def should_enqueue? time
24
104
  return false unless status == "enabled"
25
- return false unless not_past_scheduled_time?(time)
26
- return false unless not_enqueued_after?(time)
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 remove_previous_enques time
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 test_and_enque_for_time! time
44
- if should_enque?(time)
45
- enque!
123
+ def test_and_enqueue_for_time! time
124
+ if should_enqueue?(time)
125
+ enqueue!
46
126
 
47
- remove_previous_enques(time)
127
+ remove_previous_enqueues(time)
48
128
  end
49
129
  end
50
130
 
51
131
  # Enqueue cron job to queue.
52
- def enque! time = Time.now.utc
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) && !ActiveJob::Base.queue_name_delimiter.empty?
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
- set_members = conn.smembers(jobs_key)
301
+ job_keys = job_keys_from_namespace(namespace)
302
+
220
303
  job_hashes = conn.pipelined do |pipeline|
221
- set_members.each do |key|
222
- pipeline.hgetall(key)
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
- out = 0
234
- Sidekiq.redis do |conn|
235
- out = conn.scard(jobs_key)
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 name
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
- output = Job.new conn.hgetall( redis_key(name) )
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 name
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
- hash = {
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 = Fugit.do_parse_cronish(@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 informations for this job!
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 enque right after scheduler poller starts!
492
+ # Add information about last time! - don't enqueue right after scheduler poller starts!
477
493
  time = Time.now.utc
478
- exists = conn.public_send(REDIS_EXISTS_METHOD, job_enqueued_key)
479
- conn.zadd(job_enqueued_key, time.to_f.to_s, formatted_last_time(time).to_s) unless exists == true || exists == 1
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
- Sidekiq.logger.info { "Cron Jobs - added job with name: #{@name}" }
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 ||= (Sidekiq::Options[:cron_history_size] || 10).to_i - 1
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 runned timestamps.
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: #{@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
- current_job_names = Sidekiq::Cron::Job.all.filter_map { |j| j.name if j.source == "schedule" }
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 { |j| Sidekiq::Cron::Job.destroy(j) }
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? name
583
+ def self.exists?(name, namespace = Sidekiq::Cron.configuration.default_namespace)
555
584
  out = Sidekiq.redis do |conn|
556
- conn.public_send(REDIS_EXISTS_METHOD, redis_key(name))
585
+ conn.exists(redis_key(name, namespace))
557
586
  end
558
- out == true || out == 1
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 ||= Fugit.parse_cronish(@cron)
606
+ @parsed_cron ||= do_parse_cron(@cron)
577
607
  end
578
608
 
579
- def not_enqueued_after?(time)
580
- @last_enqueue_time.nil? || @last_enqueue_time.to_i < last_time(time).to_i
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 not_past_scheduled_time?(current_time)
678
+ def past_scheduled_time?(current_time)
635
679
  last_cron_time = parsed_cron.previous_time(current_time).utc
636
- return false if (current_time.to_i - last_cron_time.to_i) > 60
637
- true
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
- # Redis key for set of all cron jobs.
641
- def self.jobs_key
642
- "cron_jobs"
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
- # Redis key for storing one cron job.
646
- def self.redis_key name
647
- "cron_job:#{name}"
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 storing one cron job.
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 (when poller added job to queue)
656
- def self.job_enqueued_key name
657
- "cron_job:#{name}:enqueued"
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 name
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