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.
@@ -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 :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 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,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 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
- 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) && (klass || Sidekiq::Cron::Support.constantize(@klass.to_s)) < ActiveJob::Base
84
- rescue NameError
85
- false
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) && !ActiveJob::Base.queue_name_delimiter.empty?
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
- set_members = conn.smembers(jobs_key)
297
+ job_keys = job_keys_from_namespace(namespace)
298
+
220
299
  job_hashes = conn.pipelined do |pipeline|
221
- set_members.each do |key|
222
- pipeline.hgetall(key)
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
- out = 0
234
- Sidekiq.redis do |conn|
235
- out = conn.scard(jobs_key)
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 name
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
- output = Job.new conn.hgetall( redis_key(name) )
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 name
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
- hash = {
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 = Fugit.do_parse_cronish(@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 informations for this job!
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 enque right after scheduler poller starts!
488
+ # Add information about last time! - don't enqueue right after scheduler poller starts!
477
489
  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
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
- Sidekiq.logger.info { "Cron Jobs - added job with name: #{@name}" }
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 ||= (Sidekiq::Options[:cron_history_size] || 10).to_i - 1
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 runned timestamps.
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: #{@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
- current_job_names = Sidekiq::Cron::Job.all.filter_map { |j| j.name if j.source == "schedule" }
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 { |j| Sidekiq::Cron::Job.destroy(j) }
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? name
579
+ def self.exists?(name, namespace = Sidekiq::Cron.configuration.default_namespace)
555
580
  out = Sidekiq.redis do |conn|
556
- conn.public_send(REDIS_EXISTS_METHOD, redis_key(name))
581
+ conn.exists(redis_key(name, namespace))
557
582
  end
558
- out == true || out == 1
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 ||= 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
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
- # Redis key for set of all cron jobs.
641
- def self.jobs_key
642
- "cron_jobs"
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,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