sidekiq-cron 1.12.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,29 +1,103 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fugit'
4
+ require 'cronex'
2
5
  require 'globalid'
3
- require 'sidekiq'
4
6
  require 'sidekiq/cron/support'
5
- require 'sidekiq/options'
6
7
 
7
8
  module Sidekiq
8
9
  module Cron
9
10
  class Job
10
- # How long we would like to store 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
+ @namespace = args["namespace"] || Sidekiq::Cron.configuration.default_namespace
30
+ @cron = args["cron"]
31
+ @description = args["description"] if args["description"]
32
+ @source = args["source"] == "schedule" ? "schedule" : "dynamic"
33
+
34
+ # Get class from klass or class.
35
+ @klass = args["klass"] || args["class"]
36
+
37
+ # Set status of job.
38
+ @status = args['status'] || status_from_redis
39
+
40
+ # Set last enqueue time - from args or from existing job.
41
+ if args['last_enqueue_time'] && !args['last_enqueue_time'].empty?
42
+ @last_enqueue_time = parse_enqueue_time(args['last_enqueue_time'])
43
+ else
44
+ @last_enqueue_time = last_enqueue_time_from_redis
45
+ end
46
+
47
+ # Get right arguments for job.
48
+ @symbolize_args = args["symbolize_args"] == true || ("#{args["symbolize_args"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
49
+ @args = parse_args(args["args"])
50
+
51
+ @date_as_argument = args["date_as_argument"] == true || ("#{args["date_as_argument"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
52
+
53
+ @active_job = args["active_job"] == true || ("#{args["active_job"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
54
+ @active_job_queue_name_prefix = args["queue_name_prefix"]
55
+ @active_job_queue_name_delimiter = args["queue_name_delimiter"]
56
+
57
+ # symbolize_args is only used when active_job is true
58
+ Sidekiq.logger.warn { "Cron Jobs - 'symbolize_args' is gonna be ignored, as it is only used when 'active_job' is true" } if @symbolize_args && !@active_job
59
+
60
+ if args["message"]
61
+ @message = args["message"]
62
+ message_data = Sidekiq.load_json(@message) || {}
63
+ @queue = message_data['queue'] || "default"
64
+ @retry = message_data['retry']
65
+ elsif @klass
66
+ message_data = {
67
+ "class" => @klass.to_s,
68
+ "args" => @args,
69
+ }
70
+
71
+ # Get right data for message,
72
+ # only if message wasn't specified before.
73
+ klass_data = get_job_options(@klass, @args)
74
+ message_data = klass_data.merge(message_data)
75
+
76
+ # Override queue and retry if set in config,
77
+ # only if message is hash - can be string (dumped JSON).
78
+ if args['queue']
79
+ @queue = message_data['queue'] = args['queue']
80
+ else
81
+ @queue = message_data['queue'] || "default"
82
+ end
83
+
84
+ if args['retry'] != nil
85
+ @retry = message_data['retry'] = args['retry']
86
+ else
87
+ @retry = message_data['retry']
88
+ end
89
+
90
+ @message = message_data
91
+ end
92
+
93
+ @queue_name_with_prefix = queue_name_with_prefix
94
+ end
95
+
22
96
  # Crucial part of whole enqueuing job.
23
- def should_enque? time
97
+ def should_enqueue? time
24
98
  return false unless status == "enabled"
25
- return false unless not_past_scheduled_time?(time)
26
- return false unless not_enqueued_after?(time)
99
+ return false if past_scheduled_time?(time)
100
+ return false if enqueued_after?(time)
27
101
 
28
102
  enqueue = Sidekiq.redis do |conn|
29
103
  conn.zadd(job_enqueued_key, formatted_enqueue_time(time), formatted_last_time(time))
@@ -33,23 +107,23 @@ module Sidekiq
33
107
 
34
108
  # Remove previous information about run times,
35
109
  # this will clear Redis and make sure that Redis will not overflow with memory.
36
- def remove_previous_enques time
110
+ def remove_previous_enqueues time
37
111
  Sidekiq.redis do |conn|
38
112
  conn.zremrangebyscore(job_enqueued_key, 0, "(#{(time.to_f - REMEMBER_THRESHOLD).to_s}")
39
113
  end
40
114
  end
41
115
 
42
116
  # Test if job should be enqueued.
43
- def test_and_enque_for_time! time
44
- if should_enque?(time)
45
- enque!
117
+ def test_and_enqueue_for_time! time
118
+ if should_enqueue?(time)
119
+ enqueue!
46
120
 
47
- remove_previous_enques(time)
121
+ remove_previous_enqueues(time)
48
122
  end
49
123
  end
50
124
 
51
125
  # Enqueue cron job to queue.
52
- def enque! time = Time.now.utc
126
+ def enqueue! time = Time.now.utc
53
127
  @last_enqueue_time = time
54
128
 
55
129
  klass_const =
@@ -80,7 +154,7 @@ module Sidekiq
80
154
  end
81
155
 
82
156
  def is_active_job?(klass = nil)
83
- @active_job || defined?(ActiveJob::Base) && (klass || Sidekiq::Cron::Support.constantize(@klass.to_s)) < ActiveJob::Base
157
+ @active_job || defined?(::ActiveJob::Base) && (klass || Sidekiq::Cron::Support.constantize(@klass.to_s)) < ::ActiveJob::Base
84
158
  rescue NameError
85
159
  false
86
160
  end
@@ -99,7 +173,7 @@ module Sidekiq
99
173
  end
100
174
 
101
175
  def enqueue_sidekiq_worker(klass_const)
102
- klass_const.set(queue: queue_name_with_prefix).perform_async(*enqueue_args)
176
+ klass_const.set(queue: queue_name_with_prefix, retry: @retry).perform_async(*enqueue_args)
103
177
  end
104
178
 
105
179
  # Sidekiq worker message.
@@ -114,16 +188,16 @@ module Sidekiq
114
188
 
115
189
  if !"#{@active_job_queue_name_delimiter}".empty?
116
190
  queue_name_delimiter = @active_job_queue_name_delimiter
117
- elsif defined?(ActiveJob::Base) && defined?(ActiveJob::Base.queue_name_delimiter) && !ActiveJob::Base.queue_name_delimiter.empty?
118
- queue_name_delimiter = ActiveJob::Base.queue_name_delimiter
191
+ elsif defined?(::ActiveJob::Base) && defined?(::ActiveJob::Base.queue_name_delimiter) && !::ActiveJob::Base.queue_name_delimiter.empty?
192
+ queue_name_delimiter = ::ActiveJob::Base.queue_name_delimiter
119
193
  else
120
194
  queue_name_delimiter = '_'
121
195
  end
122
196
 
123
197
  if !"#{@active_job_queue_name_prefix}".empty?
124
198
  queue_name = "#{@active_job_queue_name_prefix}#{queue_name_delimiter}#{@queue}"
125
- elsif defined?(ActiveJob::Base) && defined?(ActiveJob::Base.queue_name_prefix) && !"#{ActiveJob::Base.queue_name_prefix}".empty?
126
- queue_name = "#{ActiveJob::Base.queue_name_prefix}#{queue_name_delimiter}#{@queue}"
199
+ elsif defined?(::ActiveJob::Base) && defined?(::ActiveJob::Base.queue_name_prefix) && !"#{::ActiveJob::Base.queue_name_prefix}".empty?
200
+ queue_name = "#{::ActiveJob::Base.queue_name_prefix}#{queue_name_delimiter}#{@queue}"
127
201
  else
128
202
  queue_name = @queue
129
203
  end
@@ -152,6 +226,7 @@ module Sidekiq
152
226
  # Input structure should look like:
153
227
  # {
154
228
  # 'name_of_job' => {
229
+ # 'namespace' => 'MyNamespace',
155
230
  # 'class' => 'MyClass',
156
231
  # 'cron' => '1 * * * *',
157
232
  # 'args' => '(OPTIONAL) [Array or Hash]',
@@ -182,6 +257,7 @@ module Sidekiq
182
257
  # Input structure should look like:
183
258
  # [
184
259
  # {
260
+ # 'namespace' => 'MyNamespace',
185
261
  # 'name' => 'name_of_job',
186
262
  # 'class' => 'MyClass',
187
263
  # 'cron' => '1 * * * *',
@@ -207,19 +283,20 @@ module Sidekiq
207
283
  # Like #load_from_array.
208
284
  # If exists old jobs in Redis but removed from args, destroy old jobs.
209
285
  def self.load_from_array!(array, options = {})
210
- job_names = array.map { |job| job["name"] }
286
+ job_names = array.map { |job| job["name"] || job[:name] }
211
287
  destroy_removed_jobs(job_names)
212
288
  load_from_array(array, options)
213
289
  end
214
290
 
215
291
  # Get all cron jobs.
216
- def self.all
292
+ def self.all(namespace = Sidekiq::Cron.configuration.default_namespace)
217
293
  job_hashes = nil
218
294
  Sidekiq.redis do |conn|
219
- set_members = conn.smembers(jobs_key)
295
+ job_keys = job_keys_from_namespace(namespace)
296
+
220
297
  job_hashes = conn.pipelined do |pipeline|
221
- set_members.each do |key|
222
- pipeline.hgetall(key)
298
+ job_keys.each do |job_key|
299
+ pipeline.hgetall(job_key)
223
300
  end
224
301
  end
225
302
  end
@@ -229,22 +306,26 @@ module Sidekiq
229
306
  end
230
307
  end
231
308
 
232
- def self.count
233
- out = 0
234
- Sidekiq.redis do |conn|
235
- out = conn.scard(jobs_key)
309
+ def self.count(namespace = Sidekiq::Cron.configuration.default_namespace)
310
+ if namespace == '*'
311
+ Namespace.all_with_count.reduce(0) do |memo, namespace_count|
312
+ memo + namespace_count[:count]
313
+ end
314
+ else
315
+ Sidekiq.redis { |conn| conn.scard(jobs_key(namespace)) }
236
316
  end
237
- out
238
317
  end
239
318
 
240
- def self.find name
319
+ def self.find(name, namespace = Sidekiq::Cron.configuration.default_namespace)
241
320
  # If name is hash try to get name from it.
242
321
  name = name[:name] || name['name'] if name.is_a?(Hash)
243
- return unless exists? name
322
+ return unless exists? name, namespace
244
323
 
245
324
  output = nil
246
325
  Sidekiq.redis do |conn|
247
- output = Job.new conn.hgetall( redis_key(name) )
326
+ if exists? name, namespace
327
+ output = Job.new conn.hgetall(redis_key(name, namespace))
328
+ end
248
329
  end
249
330
  output if output && output.valid?
250
331
  end
@@ -255,93 +336,17 @@ module Sidekiq
255
336
  end
256
337
 
257
338
  # Destroy job by name.
258
- def self.destroy name
339
+ def self.destroy(name, namespace = Sidekiq::Cron.configuration.default_namespace)
259
340
  # If name is hash try to get name from it.
260
341
  name = name[:name] || name['name'] if name.is_a?(Hash)
261
342
 
262
- if job = find(name)
343
+ if (job = find(name, namespace))
263
344
  job.destroy
264
345
  else
265
346
  false
266
347
  end
267
348
  end
268
349
 
269
- attr_accessor :name, :cron, :description, :klass, :args, :message
270
- attr_reader :last_enqueue_time, :fetch_missing_args, :source
271
-
272
- def initialize input_args = {}
273
- args = Hash[input_args.map{ |k, v| [k.to_s, v] }]
274
- @fetch_missing_args = args.delete('fetch_missing_args')
275
- @fetch_missing_args = true if @fetch_missing_args.nil?
276
-
277
- @name = args["name"]
278
- @cron = args["cron"]
279
- @description = args["description"] if args["description"]
280
- @source = args["source"] == "schedule" ? "schedule" : "dynamic"
281
-
282
- # Get class from klass or class.
283
- @klass = args["klass"] || args["class"]
284
-
285
- # Set status of job.
286
- @status = args['status'] || status_from_redis
287
-
288
- # Set last enqueue time - from args or from existing job.
289
- if args['last_enqueue_time'] && !args['last_enqueue_time'].empty?
290
- @last_enqueue_time = parse_enqueue_time(args['last_enqueue_time'])
291
- else
292
- @last_enqueue_time = last_enqueue_time_from_redis
293
- end
294
-
295
- # Get right arguments for job.
296
- @symbolize_args = args["symbolize_args"] == true || ("#{args["symbolize_args"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
297
- @args = parse_args(args["args"])
298
-
299
- @date_as_argument = args["date_as_argument"] == true || ("#{args["date_as_argument"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
300
-
301
- @active_job = args["active_job"] == true || ("#{args["active_job"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
302
- @active_job_queue_name_prefix = args["queue_name_prefix"]
303
- @active_job_queue_name_delimiter = args["queue_name_delimiter"]
304
-
305
- if args["message"]
306
- @message = args["message"]
307
- message_data = Sidekiq.load_json(@message) || {}
308
- @queue = message_data['queue'] || "default"
309
- elsif @klass
310
- message_data = {
311
- "class" => @klass.to_s,
312
- "args" => @args,
313
- }
314
-
315
- # Get right data for message,
316
- # only if message wasn't specified before.
317
- klass_data = case @klass
318
- when Class
319
- @klass.get_sidekiq_options
320
- when String
321
- begin
322
- Sidekiq::Cron::Support.constantize(@klass).get_sidekiq_options
323
- rescue Exception => e
324
- # Unknown class
325
- {"queue"=>"default"}
326
- end
327
- end
328
-
329
- message_data = klass_data.merge(message_data)
330
-
331
- # Override queue if setted in config,
332
- # only if message is hash - can be string (dumped JSON).
333
- if args['queue']
334
- @queue = message_data['queue'] = args['queue']
335
- else
336
- @queue = message_data['queue'] || "default"
337
- end
338
-
339
- @message = message_data
340
- end
341
-
342
- @queue_name_with_prefix = queue_name_with_prefix
343
- end
344
-
345
350
  def status
346
351
  @status
347
352
  end
@@ -370,6 +375,12 @@ module Sidekiq
370
375
  message
371
376
  end
372
377
 
378
+ def human_cron
379
+ Cronex::ExpressionDescriptor.new(cron).description
380
+ rescue => e
381
+ cron
382
+ end
383
+
373
384
  def status_from_redis
374
385
  out = "enabled"
375
386
  if fetch_missing_args
@@ -404,27 +415,24 @@ module Sidekiq
404
415
 
405
416
  # Export job data to hash.
406
417
  def to_hash
407
- hash = {
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 = Fugit.do_parse_cronish(@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 informations for this job!
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 enque right after scheduler poller starts!
486
+ # Add information about last time! - don't enqueue right after scheduler poller starts!
477
487
  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
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
- Sidekiq.logger.info { "Cron Jobs - added job with name: #{@name}" }
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 ||= (Sidekiq::Options[:cron_history_size] || 10).to_i - 1
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 runned timestamps.
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: #{@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
- current_job_names = Sidekiq::Cron::Job.all.filter_map { |j| j.name if j.source == "schedule" }
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 { |j| Sidekiq::Cron::Job.destroy(j) }
552
+ removed_job_names.each do |j|
553
+ job_to_destroy = current_jobs.detect { |job| job.name == j }
554
+
555
+ Sidekiq::Cron::Job.destroy(
556
+ job_to_destroy.name,
557
+ job_to_destroy.namespace
558
+ )
559
+ end
537
560
  removed_job_names
538
561
  end
539
562
 
@@ -551,15 +574,16 @@ module Sidekiq
551
574
  last_time(now).getutc.iso8601
552
575
  end
553
576
 
554
- def self.exists? name
577
+ def self.exists?(name, namespace = Sidekiq::Cron.configuration.default_namespace)
555
578
  out = Sidekiq.redis do |conn|
556
- conn.public_send(REDIS_EXISTS_METHOD, redis_key(name))
579
+ conn.exists(redis_key(name, namespace))
557
580
  end
558
- out == true || out == 1
581
+
582
+ [true, 1].include?(out)
559
583
  end
560
584
 
561
585
  def exists?
562
- self.class.exists? @name
586
+ self.class.exists? @name, @namespace
563
587
  end
564
588
 
565
589
  def sort_name
@@ -573,11 +597,25 @@ module Sidekiq
573
597
  private
574
598
 
575
599
  def parsed_cron
576
- @parsed_cron ||= Fugit.parse_cronish(@cron)
600
+ @parsed_cron ||= do_parse_cron(@cron)
577
601
  end
578
602
 
579
- def not_enqueued_after?(time)
580
- @last_enqueue_time.nil? || @last_enqueue_time.to_i < last_time(time).to_i
603
+ def do_parse_cron(cron)
604
+ case Sidekiq::Cron.configuration.natural_cron_parsing_mode
605
+ when :single
606
+ Fugit.do_parse_cronish(cron)
607
+ when :strict
608
+ Fugit.parse_cron(cron) || # Ex. '11 1 * * 1'
609
+ Fugit.parse_nat(cron, :multi => :fail) || # Ex. 'every Monday at 01:11'
610
+ fail(ArgumentError.new("invalid cron string #{cron.inspect}"))
611
+ else
612
+ mode = Sidekiq::Cron.configuration.natural_cron_parsing_mode
613
+ raise ArgumentError, "Unknown natural cron parsing mode: #{mode.inspect}"
614
+ end
615
+ end
616
+
617
+ def enqueued_after?(time)
618
+ @last_enqueue_time && @last_enqueue_time.to_i >= last_time(time).to_i
581
619
  end
582
620
 
583
621
  # Try parsing inbound args into an array.
@@ -631,48 +669,83 @@ module Sidekiq
631
669
  DateTime.parse(timestamp).to_time.utc
632
670
  end
633
671
 
634
- def not_past_scheduled_time?(current_time)
672
+ def past_scheduled_time?(current_time)
635
673
  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
674
+ period = Sidekiq::Cron.configuration.reschedule_grace_period
675
+
676
+ current_time.to_i - last_cron_time.to_i > period
638
677
  end
639
678
 
640
- # Redis key for set of all cron jobs.
641
- def self.jobs_key
642
- "cron_jobs"
679
+ def self.default_if_blank(namespace)
680
+ if namespace.nil? || namespace == ''
681
+ Sidekiq::Cron.configuration.default_namespace
682
+ else
683
+ namespace
684
+ end
685
+ end
686
+
687
+ def self.job_keys_from_namespace(namespace = Sidekiq::Cron.configuration.default_namespace)
688
+ Sidekiq.redis do |conn|
689
+ if namespace == '*'
690
+ namespaces = conn.keys(jobs_key(namespace))
691
+ namespaces.flat_map { |name| conn.smembers(name) }
692
+ else
693
+ conn.smembers(jobs_key(namespace))
694
+ end
695
+ end
696
+ end
697
+
698
+ def self.migrate_old_jobs_if_needed!
699
+ Sidekiq.redis do |conn|
700
+ old_job_keys = conn.smembers('cron_jobs')
701
+ old_job_keys.each do |old_job|
702
+ old_job_hash = conn.hgetall(old_job)
703
+ old_job_hash[:namespace] = Sidekiq::Cron.configuration.default_namespace
704
+ create(old_job_hash)
705
+ conn.srem('cron_jobs', old_job)
706
+ end
707
+ end
708
+ end
709
+
710
+ # Redis key for set of all cron jobs
711
+ def self.jobs_key(namespace = Sidekiq::Cron.configuration.default_namespace)
712
+ "cron_jobs:#{default_if_blank(namespace)}"
643
713
  end
644
714
 
645
- # Redis key for storing one cron job.
646
- def self.redis_key name
647
- "cron_job:#{name}"
715
+ # Redis key for storing one cron job
716
+ def self.redis_key(name, namespace = Sidekiq::Cron.configuration.default_namespace)
717
+ "cron_job:#{default_if_blank(namespace)}:#{name}"
648
718
  end
649
719
 
650
- # Redis key for storing one cron job.
720
+ # Redis key for storing one cron job
651
721
  def redis_key
652
- self.class.redis_key @name
722
+ self.class.redis_key @name, @namespace
653
723
  end
654
724
 
655
- # Redis key for storing one cron job run times (when poller added job to queue)
656
- def self.job_enqueued_key name
657
- "cron_job:#{name}:enqueued"
725
+ # Redis key for storing one cron job run times
726
+ # (when poller added job to queue)
727
+ def self.job_enqueued_key(name, namespace = Sidekiq::Cron.configuration.default_namespace)
728
+ "cron_job:#{default_if_blank(namespace)}:#{name}:enqueued"
658
729
  end
659
730
 
660
- def self.jid_history_key name
661
- "cron_job:#{name}:jid_history"
731
+ def self.jid_history_key(name, namespace = Sidekiq::Cron.configuration.default_namespace)
732
+ "cron_job:#{default_if_blank(namespace)}:#{name}:jid_history"
662
733
  end
663
734
 
735
+ # Redis key for storing one cron job run times
736
+ # (when poller added job to queue)
664
737
  def job_enqueued_key
665
- self.class.job_enqueued_key @name
738
+ self.class.job_enqueued_key @name, @namespace
666
739
  end
667
740
 
668
741
  def jid_history_key
669
- self.class.jid_history_key @name
742
+ self.class.jid_history_key @name, @namespace
670
743
  end
671
744
 
672
745
  def serialized_last_enqueue_time
673
746
  @last_enqueue_time&.strftime(LAST_ENQUEUE_TIME_FORMAT)
674
747
  end
675
-
748
+
676
749
  def convert_to_global_id_hash(argument)
677
750
  { GLOBALID_KEY => argument.to_global_id.to_s }
678
751
  rescue URI::GID::MissingModelIdError
@@ -719,6 +792,25 @@ module Sidekiq
719
792
  argument
720
793
  end
721
794
  end
795
+
796
+ def get_job_options(klass, args)
797
+ klass = klass.is_a?(Class) ? klass : begin
798
+ Sidekiq::Cron::Support.constantize(klass)
799
+ rescue NameError
800
+ # noop
801
+ end
802
+
803
+ if klass.nil?
804
+ # Unknown class
805
+ {"queue"=>"default"}
806
+ elsif is_active_job?(klass)
807
+ job = klass.new(args)
808
+
809
+ {"queue"=>job.queue_name}
810
+ else
811
+ klass.get_sidekiq_options
812
+ end
813
+ end
722
814
  end
723
815
  end
724
816
  end