sidekiq-cron 1.10.0 → 2.3.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +81 -3
  3. data/Gemfile +3 -0
  4. data/README.md +283 -38
  5. data/lib/sidekiq/cron/job.rb +277 -182
  6. data/lib/sidekiq/cron/launcher.rb +2 -5
  7. data/lib/sidekiq/cron/locales/de.yml +15 -6
  8. data/lib/sidekiq/cron/locales/en.yml +14 -14
  9. data/lib/sidekiq/cron/locales/es.yml +22 -0
  10. data/lib/sidekiq/cron/locales/id.yml +22 -0
  11. data/lib/sidekiq/cron/locales/it.yml +15 -16
  12. data/lib/sidekiq/cron/locales/ja.yml +14 -10
  13. data/lib/sidekiq/cron/locales/pt.yml +14 -14
  14. data/lib/sidekiq/cron/locales/ru.yml +15 -7
  15. data/lib/sidekiq/cron/locales/zh-CN.yml +14 -11
  16. data/lib/sidekiq/cron/namespace.rb +43 -0
  17. data/lib/sidekiq/cron/poller.rb +12 -13
  18. data/lib/sidekiq/cron/schedule_loader.rb +51 -16
  19. data/lib/sidekiq/cron/support.rb +4 -30
  20. data/lib/sidekiq/cron/version.rb +1 -1
  21. data/lib/sidekiq/cron/views/cron.erb +97 -92
  22. data/lib/sidekiq/cron/views/cron_show.erb +87 -82
  23. data/lib/sidekiq/cron/views/legacy/cron.erb +113 -0
  24. data/lib/sidekiq/cron/views/legacy/cron_show.erb +92 -0
  25. data/lib/sidekiq/cron/web.rb +21 -2
  26. data/lib/sidekiq/cron/web_extension.rb +99 -31
  27. data/lib/sidekiq/cron.rb +73 -5
  28. data/lib/sidekiq/options.rb +9 -7
  29. data/lib/sidekiq-cron.rb +6 -0
  30. data/sidekiq-cron.gemspec +7 -6
  31. metadata +40 -27
  32. data/test/integration/performance_test.rb +0 -46
  33. data/test/models/person.rb +0 -21
  34. data/test/test_helper.rb +0 -93
  35. data/test/unit/fixtures/schedule_array.yml +0 -13
  36. data/test/unit/fixtures/schedule_erb.yml +0 -6
  37. data/test/unit/fixtures/schedule_hash.yml +0 -12
  38. data/test/unit/fixtures/schedule_string.yml +0 -1
  39. data/test/unit/job_test.rb +0 -1386
  40. data/test/unit/launcher_test.rb +0 -33
  41. data/test/unit/poller_test.rb +0 -144
  42. data/test/unit/schedule_loader_test.rb +0 -58
  43. data/test/unit/web_extension_test.rb +0 -155
@@ -1,62 +1,138 @@
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, :message
21
+ attr_reader :last_enqueue_time, :fetch_missing_args, :source, :args
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)
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
104
+ return false unless status == "enabled"
105
+ return false if past_scheduled_time?(time)
106
+ return false if enqueued_after?(time)
107
+
24
108
  enqueue = Sidekiq.redis do |conn|
25
- status == "enabled" &&
26
- not_past_scheduled_time?(time) &&
27
- not_enqueued_after?(time) &&
28
- conn.zadd(job_enqueued_key, formatted_enqueue_time(time), formatted_last_time(time))
109
+ conn.zadd(job_enqueued_key, formatted_enqueue_time(time), formatted_last_time(time))
29
110
  end
30
111
  enqueue == true || enqueue == 1
31
112
  end
32
113
 
33
114
  # Remove previous information about run times,
34
115
  # this will clear Redis and make sure that Redis will not overflow with memory.
35
- def remove_previous_enques time
116
+ def remove_previous_enqueues time
36
117
  Sidekiq.redis do |conn|
37
118
  conn.zremrangebyscore(job_enqueued_key, 0, "(#{(time.to_f - REMEMBER_THRESHOLD).to_s}")
38
119
  end
39
120
  end
40
121
 
41
122
  # Test if job should be enqueued.
42
- def test_and_enque_for_time! time
43
- if should_enque?(time)
44
- enque!
123
+ def test_and_enqueue_for_time! time
124
+ if should_enqueue?(time)
125
+ enqueue!
45
126
 
46
- remove_previous_enques(time)
127
+ remove_previous_enqueues(time)
47
128
  end
48
129
  end
49
130
 
50
131
  # Enqueue cron job to queue.
51
- def enque! time = Time.now.utc
132
+ def enqueue! time = Time.now.utc
52
133
  @last_enqueue_time = time
53
134
 
54
- klass_const =
55
- begin
56
- Sidekiq::Cron::Support.constantize(@klass.to_s)
57
- rescue NameError
58
- nil
59
- end
135
+ klass_const = Sidekiq::Cron::Support.safe_constantize(@klass.to_s)
60
136
 
61
137
  jid =
62
138
  if klass_const
@@ -79,9 +155,10 @@ module Sidekiq
79
155
  end
80
156
 
81
157
  def is_active_job?(klass = nil)
82
- @active_job || defined?(ActiveJob::Base) && (klass || Sidekiq::Cron::Support.constantize(@klass.to_s)) < ActiveJob::Base
83
- rescue NameError
84
- 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
85
162
  end
86
163
 
87
164
  def date_as_argument?
@@ -98,7 +175,7 @@ module Sidekiq
98
175
  end
99
176
 
100
177
  def enqueue_sidekiq_worker(klass_const)
101
- 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)
102
179
  end
103
180
 
104
181
  # Sidekiq worker message.
@@ -113,16 +190,16 @@ module Sidekiq
113
190
 
114
191
  if !"#{@active_job_queue_name_delimiter}".empty?
115
192
  queue_name_delimiter = @active_job_queue_name_delimiter
116
- elsif defined?(ActiveJob::Base) && defined?(ActiveJob::Base.queue_name_delimiter) && !ActiveJob::Base.queue_name_delimiter.empty?
117
- 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
118
195
  else
119
196
  queue_name_delimiter = '_'
120
197
  end
121
198
 
122
199
  if !"#{@active_job_queue_name_prefix}".empty?
123
200
  queue_name = "#{@active_job_queue_name_prefix}#{queue_name_delimiter}#{@queue}"
124
- elsif defined?(ActiveJob::Base) && defined?(ActiveJob::Base.queue_name_prefix) && !"#{ActiveJob::Base.queue_name_prefix}".empty?
125
- queue_name = "#{ActiveJob::Base.queue_name_prefix}#{queue_name_delimiter}#{@queue}"
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}"
126
203
  else
127
204
  queue_name = @queue
128
205
  end
@@ -151,6 +228,7 @@ module Sidekiq
151
228
  # Input structure should look like:
152
229
  # {
153
230
  # 'name_of_job' => {
231
+ # 'namespace' => 'MyNamespace',
154
232
  # 'class' => 'MyClass',
155
233
  # 'cron' => '1 * * * *',
156
234
  # 'args' => '(OPTIONAL) [Array or Hash]',
@@ -162,25 +240,26 @@ module Sidekiq
162
240
  # }
163
241
  # }
164
242
  #
165
- def self.load_from_hash hash
243
+ def self.load_from_hash(hash, options = {})
166
244
  array = hash.map do |key, job|
167
245
  job['name'] = key
168
246
  job
169
247
  end
170
- load_from_array array
248
+ load_from_array(array, options)
171
249
  end
172
250
 
173
251
  # Like #load_from_hash.
174
252
  # If exists old jobs in Redis but removed from args, destroy old jobs.
175
- def self.load_from_hash! hash
253
+ def self.load_from_hash!(hash, options = {})
176
254
  destroy_removed_jobs(hash.keys)
177
- load_from_hash(hash)
255
+ load_from_hash(hash, options)
178
256
  end
179
257
 
180
258
  # Load cron jobs from Array.
181
259
  # Input structure should look like:
182
260
  # [
183
261
  # {
262
+ # 'namespace' => 'MyNamespace',
184
263
  # 'name' => 'name_of_job',
185
264
  # 'class' => 'MyClass',
186
265
  # 'cron' => '1 * * * *',
@@ -194,10 +273,10 @@ module Sidekiq
194
273
  # }
195
274
  # ]
196
275
  #
197
- def self.load_from_array array
276
+ def self.load_from_array(array, options = {})
198
277
  errors = {}
199
278
  array.each do |job_data|
200
- job = new(job_data)
279
+ job = new(job_data.merge(options))
201
280
  errors[job.name] = job.errors unless job.save
202
281
  end
203
282
  errors
@@ -205,20 +284,21 @@ module Sidekiq
205
284
 
206
285
  # Like #load_from_array.
207
286
  # If exists old jobs in Redis but removed from args, destroy old jobs.
208
- def self.load_from_array! array
209
- job_names = array.map { |job| job["name"] }
287
+ def self.load_from_array!(array, options = {})
288
+ job_names = array.map { |job| job["name"] || job[:name] }
210
289
  destroy_removed_jobs(job_names)
211
- load_from_array(array)
290
+ load_from_array(array, options)
212
291
  end
213
292
 
214
293
  # Get all cron jobs.
215
- def self.all
294
+ def self.all(namespace = Sidekiq::Cron.configuration.default_namespace)
216
295
  job_hashes = nil
217
296
  Sidekiq.redis do |conn|
218
- set_members = conn.smembers(jobs_key)
297
+ job_keys = job_keys_from_namespace(namespace)
298
+
219
299
  job_hashes = conn.pipelined do |pipeline|
220
- set_members.each do |key|
221
- pipeline.hgetall(key)
300
+ job_keys.each do |job_key|
301
+ pipeline.hgetall(job_key)
222
302
  end
223
303
  end
224
304
  end
@@ -228,22 +308,25 @@ module Sidekiq
228
308
  end
229
309
  end
230
310
 
231
- def self.count
232
- out = 0
233
- Sidekiq.redis do |conn|
234
- 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)) }
235
318
  end
236
- out
237
319
  end
238
320
 
239
- def self.find name
321
+ def self.find(name, namespace = Sidekiq::Cron.configuration.default_namespace)
240
322
  # If name is hash try to get name from it.
241
323
  name = name[:name] || name['name'] if name.is_a?(Hash)
324
+ return unless exists? name, namespace
242
325
 
243
326
  output = nil
244
327
  Sidekiq.redis do |conn|
245
- if exists? name
246
- output = Job.new conn.hgetall( redis_key(name) )
328
+ if exists? name, namespace
329
+ output = Job.new conn.hgetall(redis_key(name, namespace))
247
330
  end
248
331
  end
249
332
  output if output && output.valid?
@@ -255,92 +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
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
-
281
- # Get class from klass or class.
282
- @klass = args["klass"] || args["class"]
283
-
284
- # Set status of job.
285
- @status = args['status'] || status_from_redis
286
-
287
- # Set last enqueue time - from args or from existing job.
288
- if args['last_enqueue_time'] && !args['last_enqueue_time'].empty?
289
- @last_enqueue_time = parse_enqueue_time(args['last_enqueue_time'])
290
- else
291
- @last_enqueue_time = last_enqueue_time_from_redis
292
- end
293
-
294
- # Get right arguments for job.
295
- @symbolize_args = args["symbolize_args"] == true || ("#{args["symbolize_args"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
296
- @args = args["args"].nil? ? [] : parse_args( args["args"] )
297
-
298
- @date_as_argument = args["date_as_argument"] == true || ("#{args["date_as_argument"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
299
-
300
- @active_job = args["active_job"] == true || ("#{args["active_job"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
301
- @active_job_queue_name_prefix = args["queue_name_prefix"]
302
- @active_job_queue_name_delimiter = args["queue_name_delimiter"]
303
-
304
- if args["message"]
305
- @message = args["message"]
306
- message_data = Sidekiq.load_json(@message) || {}
307
- @queue = message_data['queue'] || "default"
308
- elsif @klass
309
- message_data = {
310
- "class" => @klass.to_s,
311
- "args" => @args,
312
- }
313
-
314
- # Get right data for message,
315
- # only if message wasn't specified before.
316
- klass_data = case @klass
317
- when Class
318
- @klass.get_sidekiq_options
319
- when String
320
- begin
321
- Sidekiq::Cron::Support.constantize(@klass).get_sidekiq_options
322
- rescue Exception => e
323
- # Unknown class
324
- {"queue"=>"default"}
325
- end
326
- end
327
-
328
- message_data = klass_data.merge(message_data)
329
-
330
- # Override queue if setted in config,
331
- # only if message is hash - can be string (dumped JSON).
332
- if args['queue']
333
- @queue = message_data['queue'] = args['queue']
334
- else
335
- @queue = message_data['queue'] || "default"
336
- end
337
-
338
- @message = message_data
339
- end
340
-
341
- @queue_name_with_prefix = queue_name_with_prefix
342
- end
343
-
344
352
  def status
345
353
  @status
346
354
  end
@@ -369,6 +377,12 @@ module Sidekiq
369
377
  message
370
378
  end
371
379
 
380
+ def human_cron
381
+ Cronex::ExpressionDescriptor.new(cron).description
382
+ rescue
383
+ cron
384
+ end
385
+
372
386
  def status_from_redis
373
387
  out = "enabled"
374
388
  if fetch_missing_args
@@ -403,26 +417,24 @@ module Sidekiq
403
417
 
404
418
  # Export job data to hash.
405
419
  def to_hash
406
- hash = {
420
+ {
407
421
  name: @name,
422
+ namespace: @namespace,
408
423
  klass: @klass.to_s,
409
424
  cron: @cron,
410
425
  description: @description,
426
+ source: @source,
411
427
  args: @args.is_a?(String) ? @args : Sidekiq.dump_json(@args || []),
428
+ date_as_argument: date_as_argument? ? "1" : "0",
412
429
  message: @message.is_a?(String) ? @message : Sidekiq.dump_json(@message || {}),
413
430
  status: @status,
414
431
  active_job: @active_job ? "1" : "0",
415
432
  queue_name_prefix: @active_job_queue_name_prefix,
416
433
  queue_name_delimiter: @active_job_queue_name_delimiter,
434
+ retry: @retry.nil? || @retry.is_a?(Numeric) ? @retry : @retry.to_s,
417
435
  last_enqueue_time: serialized_last_enqueue_time,
418
436
  symbolize_args: symbolize_args? ? "1" : "0",
419
437
  }
420
-
421
- if date_as_argument?
422
- hash.merge!(date_as_argument: "1")
423
- end
424
-
425
- hash
426
438
  end
427
439
 
428
440
  def errors
@@ -434,11 +446,14 @@ module Sidekiq
434
446
  @errors = []
435
447
 
436
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
+
437
452
  if @cron.nil? || @cron.size == 0
438
453
  errors << "'cron' must be set"
439
454
  else
440
455
  begin
441
- @parsed_cron = Fugit.do_parse_cronish(@cron)
456
+ @parsed_cron = do_parse_cron(@cron)
442
457
  rescue => e
443
458
  errors << "'cron' -> #{@cron.inspect} -> #{e.class}: #{e.message}"
444
459
  end
@@ -464,19 +479,23 @@ module Sidekiq
464
479
  return false unless valid?
465
480
 
466
481
  Sidekiq.redis do |conn|
467
-
468
482
  # Add to set of all jobs
469
- conn.sadd self.class.jobs_key, [redis_key]
483
+ conn.sadd self.class.jobs_key(@namespace), [redis_key]
470
484
 
471
- # Add informations for this job!
472
- conn.hmset redis_key, *hash_to_redis(to_hash)
485
+ # Add information for this job!
486
+ conn.hset redis_key, to_hash.transform_values! { |v| v || '' }.flatten
473
487
 
474
- # 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!
475
489
  time = Time.now.utc
476
- exists = conn.public_send(REDIS_EXISTS_METHOD, job_enqueued_key)
477
- 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
478
496
  end
479
- Sidekiq.logger.info { "Cron Jobs - added job with name: #{@name}" }
497
+
498
+ true
480
499
  end
481
500
 
482
501
  def save_last_enqueue_time
@@ -492,7 +511,7 @@ module Sidekiq
492
511
  enqueued: @last_enqueue_time
493
512
  }
494
513
 
495
- @history_size ||= (Sidekiq::Options[:cron_history_size] || 10).to_i - 1
514
+ @history_size ||= Sidekiq::Cron.configuration.cron_history_size.to_i - 1
496
515
  Sidekiq.redis do |conn|
497
516
  conn.lpush jid_history_key,
498
517
  Sidekiq.dump_json(jid_history)
@@ -504,9 +523,9 @@ module Sidekiq
504
523
  def destroy
505
524
  Sidekiq.redis do |conn|
506
525
  # Delete from set.
507
- conn.srem self.class.jobs_key, [redis_key]
526
+ conn.srem self.class.jobs_key(@namespace), [redis_key]
508
527
 
509
- # Delete runned timestamps.
528
+ # Delete ran timestamps.
510
529
  conn.del job_enqueued_key
511
530
 
512
531
  # Delete jid_history.
@@ -516,7 +535,7 @@ module Sidekiq
516
535
  conn.del redis_key
517
536
  end
518
537
 
519
- Sidekiq.logger.info { "Cron Jobs - deleted job with name: #{@name}" }
538
+ Sidekiq.logger.info { "Cron Jobs - deleted job with name #{@name} from namespace #{@namespace}" }
520
539
  end
521
540
 
522
541
  # Remove all job from cron.
@@ -529,9 +548,17 @@ module Sidekiq
529
548
 
530
549
  # Remove "removed jobs" between current jobs and new jobs
531
550
  def self.destroy_removed_jobs new_job_names
532
- current_job_names = Sidekiq::Cron::Job.all.map(&:name)
551
+ current_jobs = Sidekiq::Cron::Job.all("*").filter_map { |j| j if j.source == "schedule" }
552
+ current_job_names = current_jobs.map(&:name)
533
553
  removed_job_names = current_job_names - new_job_names
534
- 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
535
562
  removed_job_names
536
563
  end
537
564
 
@@ -549,29 +576,52 @@ module Sidekiq
549
576
  last_time(now).getutc.iso8601
550
577
  end
551
578
 
552
- def self.exists? name
579
+ def self.exists?(name, namespace = Sidekiq::Cron.configuration.default_namespace)
553
580
  out = Sidekiq.redis do |conn|
554
- conn.public_send(REDIS_EXISTS_METHOD, redis_key(name))
581
+ conn.exists(redis_key(name, namespace))
555
582
  end
556
- out == true || out == 1
583
+
584
+ [true, 1].include?(out)
557
585
  end
558
586
 
559
587
  def exists?
560
- self.class.exists? @name
588
+ self.class.exists? @name, @namespace
561
589
  end
562
590
 
563
591
  def sort_name
564
592
  "#{status == "enabled" ? 0 : 1}_#{name}".downcase
565
593
  end
566
594
 
595
+ def args=(args)
596
+ @args = parse_args(args)
597
+ end
598
+
599
+ def cron_expression_string
600
+ parsed_cron.to_cron_s
601
+ end
602
+
567
603
  private
568
604
 
569
605
  def parsed_cron
570
- @parsed_cron ||= Fugit.parse_cronish(@cron)
606
+ @parsed_cron ||= do_parse_cron(@cron)
571
607
  end
572
608
 
573
- def not_enqueued_after?(time)
574
- @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
575
625
  end
576
626
 
577
627
  # Try parsing inbound args into an array.
@@ -625,53 +675,83 @@ module Sidekiq
625
675
  DateTime.parse(timestamp).to_time.utc
626
676
  end
627
677
 
628
- def not_past_scheduled_time?(current_time)
678
+ def past_scheduled_time?(current_time)
629
679
  last_cron_time = parsed_cron.previous_time(current_time).utc
630
- return false if (current_time.to_i - last_cron_time.to_i) > 60
631
- 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
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::Namespace.all.map { jobs_key(_1) }
697
+ namespaces.flat_map { |name| conn.smembers(name) }
698
+ else
699
+ conn.smembers(jobs_key(namespace))
700
+ end
701
+ end
632
702
  end
633
703
 
634
- # Redis key for set of all cron jobs.
635
- def self.jobs_key
636
- "cron_jobs"
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
637
714
  end
638
715
 
639
- # Redis key for storing one cron job.
640
- def self.redis_key name
641
- "cron_job:#{name}"
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)}"
642
719
  end
643
720
 
644
- # Redis key for storing one cron job.
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
645
727
  def redis_key
646
- self.class.redis_key @name
728
+ self.class.redis_key @name, @namespace
647
729
  end
648
730
 
649
- # Redis key for storing one cron job run times (when poller added job to queue)
650
- def self.job_enqueued_key name
651
- "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"
652
735
  end
653
736
 
654
- def self.jid_history_key name
655
- "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"
656
739
  end
657
740
 
741
+ # Redis key for storing one cron job run times
742
+ # (when poller added job to queue)
658
743
  def job_enqueued_key
659
- self.class.job_enqueued_key @name
744
+ self.class.job_enqueued_key @name, @namespace
660
745
  end
661
746
 
662
747
  def jid_history_key
663
- self.class.jid_history_key @name
664
- end
665
-
666
- # Give Hash returns array for using it for redis.hmset
667
- def hash_to_redis hash
668
- hash.flat_map{ |key, value| [key, value || ""] }
748
+ self.class.jid_history_key @name, @namespace
669
749
  end
670
750
 
671
751
  def serialized_last_enqueue_time
672
752
  @last_enqueue_time&.strftime(LAST_ENQUEUE_TIME_FORMAT)
673
753
  end
674
-
754
+
675
755
  def convert_to_global_id_hash(argument)
676
756
  { GLOBALID_KEY => argument.to_global_id.to_s }
677
757
  rescue URI::GID::MissingModelIdError
@@ -718,6 +798,21 @@ module Sidekiq
718
798
  argument
719
799
  end
720
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
721
816
  end
722
817
  end
723
818
  end