sidekiq-cron 1.12.0 → 2.0.0.rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -2
- data/README.md +196 -35
- data/lib/sidekiq/cron/job.rb +236 -154
- data/lib/sidekiq/cron/launcher.rb +2 -5
- data/lib/sidekiq/cron/locales/id.yml +22 -0
- data/lib/sidekiq/cron/namespace.rb +43 -0
- data/lib/sidekiq/cron/poller.rb +12 -13
- data/lib/sidekiq/cron/schedule_loader.rb +1 -5
- data/lib/sidekiq/cron/support.rb +1 -2
- data/lib/sidekiq/cron/version.rb +1 -1
- data/lib/sidekiq/cron/views/cron.erb +53 -38
- data/lib/sidekiq/cron/views/cron_show.erb +11 -7
- data/lib/sidekiq/cron/web.rb +12 -2
- data/lib/sidekiq/cron/web_extension.rb +71 -25
- data/lib/sidekiq/cron.rb +61 -5
- data/lib/sidekiq/options.rb +3 -5
- data/lib/sidekiq-cron.rb +6 -0
- data/sidekiq-cron.gemspec +3 -2
- metadata +31 -9
data/lib/sidekiq/cron/job.rb
CHANGED
@@ -1,29 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fugit'
|
4
|
+
require 'cronex'
|
2
5
|
require 'globalid'
|
3
|
-
require 'sidekiq'
|
4
6
|
require 'sidekiq/cron/support'
|
5
|
-
require 'sidekiq/options'
|
6
7
|
|
7
8
|
module Sidekiq
|
8
9
|
module Cron
|
9
10
|
class Job
|
10
|
-
# How long we would like to store
|
11
|
+
# How long we would like to store information about previous enqueues.
|
11
12
|
REMEMBER_THRESHOLD = 24 * 60 * 60
|
12
13
|
|
13
14
|
# Time format for enqueued jobs.
|
14
15
|
LAST_ENQUEUE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S %z'
|
15
16
|
|
16
|
-
# Use the exists? method if we're on a newer version of Redis.
|
17
|
-
REDIS_EXISTS_METHOD = Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new("7.0.0") || Gem.loaded_specs['redis'].version < Gem::Version.new('4.2') ? :exists : :exists?
|
18
|
-
|
19
17
|
# Use serialize/deserialize key of GlobalID.
|
20
18
|
GLOBALID_KEY = "_sc_globalid"
|
21
19
|
|
20
|
+
attr_accessor :name, :namespace, :cron, :description, :klass, :args, :message
|
21
|
+
attr_reader :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
|
+
elsif @klass
|
65
|
+
message_data = {
|
66
|
+
"class" => @klass.to_s,
|
67
|
+
"args" => @args,
|
68
|
+
}
|
69
|
+
|
70
|
+
# Get right data for message,
|
71
|
+
# only if message wasn't specified before.
|
72
|
+
klass_data = get_job_class_options(@klass)
|
73
|
+
message_data = klass_data.merge(message_data)
|
74
|
+
|
75
|
+
# Override queue if set in config,
|
76
|
+
# only if message is hash - can be string (dumped JSON).
|
77
|
+
if args['queue']
|
78
|
+
@queue = message_data['queue'] = args['queue']
|
79
|
+
else
|
80
|
+
@queue = message_data['queue'] || "default"
|
81
|
+
end
|
82
|
+
|
83
|
+
@message = message_data
|
84
|
+
end
|
85
|
+
|
86
|
+
@queue_name_with_prefix = queue_name_with_prefix
|
87
|
+
end
|
88
|
+
|
22
89
|
# Crucial part of whole enqueuing job.
|
23
|
-
def
|
90
|
+
def should_enqueue? time
|
24
91
|
return false unless status == "enabled"
|
25
|
-
return false
|
26
|
-
return false
|
92
|
+
return false if past_scheduled_time?(time)
|
93
|
+
return false if enqueued_after?(time)
|
27
94
|
|
28
95
|
enqueue = Sidekiq.redis do |conn|
|
29
96
|
conn.zadd(job_enqueued_key, formatted_enqueue_time(time), formatted_last_time(time))
|
@@ -33,23 +100,23 @@ module Sidekiq
|
|
33
100
|
|
34
101
|
# Remove previous information about run times,
|
35
102
|
# this will clear Redis and make sure that Redis will not overflow with memory.
|
36
|
-
def
|
103
|
+
def remove_previous_enqueues time
|
37
104
|
Sidekiq.redis do |conn|
|
38
105
|
conn.zremrangebyscore(job_enqueued_key, 0, "(#{(time.to_f - REMEMBER_THRESHOLD).to_s}")
|
39
106
|
end
|
40
107
|
end
|
41
108
|
|
42
109
|
# Test if job should be enqueued.
|
43
|
-
def
|
44
|
-
if
|
45
|
-
|
110
|
+
def test_and_enqueue_for_time! time
|
111
|
+
if should_enqueue?(time)
|
112
|
+
enqueue!
|
46
113
|
|
47
|
-
|
114
|
+
remove_previous_enqueues(time)
|
48
115
|
end
|
49
116
|
end
|
50
117
|
|
51
118
|
# Enqueue cron job to queue.
|
52
|
-
def
|
119
|
+
def enqueue! time = Time.now.utc
|
53
120
|
@last_enqueue_time = time
|
54
121
|
|
55
122
|
klass_const =
|
@@ -152,6 +219,7 @@ module Sidekiq
|
|
152
219
|
# Input structure should look like:
|
153
220
|
# {
|
154
221
|
# 'name_of_job' => {
|
222
|
+
# 'namespace' => 'MyNamespace',
|
155
223
|
# 'class' => 'MyClass',
|
156
224
|
# 'cron' => '1 * * * *',
|
157
225
|
# 'args' => '(OPTIONAL) [Array or Hash]',
|
@@ -182,6 +250,7 @@ module Sidekiq
|
|
182
250
|
# Input structure should look like:
|
183
251
|
# [
|
184
252
|
# {
|
253
|
+
# 'namespace' => 'MyNamespace',
|
185
254
|
# 'name' => 'name_of_job',
|
186
255
|
# 'class' => 'MyClass',
|
187
256
|
# 'cron' => '1 * * * *',
|
@@ -207,19 +276,20 @@ module Sidekiq
|
|
207
276
|
# Like #load_from_array.
|
208
277
|
# If exists old jobs in Redis but removed from args, destroy old jobs.
|
209
278
|
def self.load_from_array!(array, options = {})
|
210
|
-
job_names = array.map { |job| job["name"] }
|
279
|
+
job_names = array.map { |job| job["name"] || job[:name] }
|
211
280
|
destroy_removed_jobs(job_names)
|
212
281
|
load_from_array(array, options)
|
213
282
|
end
|
214
283
|
|
215
284
|
# Get all cron jobs.
|
216
|
-
def self.all
|
285
|
+
def self.all(namespace = Sidekiq::Cron.configuration.default_namespace)
|
217
286
|
job_hashes = nil
|
218
287
|
Sidekiq.redis do |conn|
|
219
|
-
|
288
|
+
job_keys = job_keys_from_namespace(namespace)
|
289
|
+
|
220
290
|
job_hashes = conn.pipelined do |pipeline|
|
221
|
-
|
222
|
-
pipeline.hgetall(
|
291
|
+
job_keys.each do |job_key|
|
292
|
+
pipeline.hgetall(job_key)
|
223
293
|
end
|
224
294
|
end
|
225
295
|
end
|
@@ -229,22 +299,26 @@ module Sidekiq
|
|
229
299
|
end
|
230
300
|
end
|
231
301
|
|
232
|
-
def self.count
|
233
|
-
|
234
|
-
|
235
|
-
|
302
|
+
def self.count(namespace = Sidekiq::Cron.configuration.default_namespace)
|
303
|
+
if namespace == '*'
|
304
|
+
Namespace.all_with_count.reduce(0) do |memo, namespace_count|
|
305
|
+
memo + namespace_count[:count]
|
306
|
+
end
|
307
|
+
else
|
308
|
+
Sidekiq.redis { |conn| conn.scard(jobs_key(namespace)) }
|
236
309
|
end
|
237
|
-
out
|
238
310
|
end
|
239
311
|
|
240
|
-
def self.find
|
312
|
+
def self.find(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
241
313
|
# If name is hash try to get name from it.
|
242
314
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
243
|
-
return unless exists? name
|
315
|
+
return unless exists? name, namespace
|
244
316
|
|
245
317
|
output = nil
|
246
318
|
Sidekiq.redis do |conn|
|
247
|
-
|
319
|
+
if exists? name, namespace
|
320
|
+
output = Job.new conn.hgetall(redis_key(name, namespace))
|
321
|
+
end
|
248
322
|
end
|
249
323
|
output if output && output.valid?
|
250
324
|
end
|
@@ -255,93 +329,17 @@ module Sidekiq
|
|
255
329
|
end
|
256
330
|
|
257
331
|
# Destroy job by name.
|
258
|
-
def self.destroy
|
332
|
+
def self.destroy(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
259
333
|
# If name is hash try to get name from it.
|
260
334
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
261
335
|
|
262
|
-
if job = find(name)
|
336
|
+
if (job = find(name, namespace))
|
263
337
|
job.destroy
|
264
338
|
else
|
265
339
|
false
|
266
340
|
end
|
267
341
|
end
|
268
342
|
|
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
343
|
def status
|
346
344
|
@status
|
347
345
|
end
|
@@ -370,6 +368,12 @@ module Sidekiq
|
|
370
368
|
message
|
371
369
|
end
|
372
370
|
|
371
|
+
def human_cron
|
372
|
+
Cronex::ExpressionDescriptor.new(cron).description
|
373
|
+
rescue => e
|
374
|
+
cron
|
375
|
+
end
|
376
|
+
|
373
377
|
def status_from_redis
|
374
378
|
out = "enabled"
|
375
379
|
if fetch_missing_args
|
@@ -404,13 +408,15 @@ module Sidekiq
|
|
404
408
|
|
405
409
|
# Export job data to hash.
|
406
410
|
def to_hash
|
407
|
-
|
411
|
+
{
|
408
412
|
name: @name,
|
413
|
+
namespace: @namespace,
|
409
414
|
klass: @klass.to_s,
|
410
415
|
cron: @cron,
|
411
416
|
description: @description,
|
412
417
|
source: @source,
|
413
418
|
args: @args.is_a?(String) ? @args : Sidekiq.dump_json(@args || []),
|
419
|
+
date_as_argument: date_as_argument? ? "1" : "0",
|
414
420
|
message: @message.is_a?(String) ? @message : Sidekiq.dump_json(@message || {}),
|
415
421
|
status: @status,
|
416
422
|
active_job: @active_job ? "1" : "0",
|
@@ -419,12 +425,6 @@ module Sidekiq
|
|
419
425
|
last_enqueue_time: serialized_last_enqueue_time,
|
420
426
|
symbolize_args: symbolize_args? ? "1" : "0",
|
421
427
|
}
|
422
|
-
|
423
|
-
if date_as_argument?
|
424
|
-
hash.merge!(date_as_argument: "1")
|
425
|
-
end
|
426
|
-
|
427
|
-
hash
|
428
428
|
end
|
429
429
|
|
430
430
|
def errors
|
@@ -436,11 +436,14 @@ module Sidekiq
|
|
436
436
|
@errors = []
|
437
437
|
|
438
438
|
errors << "'name' must be set" if @name.nil? || @name.size == 0
|
439
|
+
errors << "'namespace' must be set" if @namespace.nil? || @namespace.size == 0
|
440
|
+
errors << "'namespace' cannot be '*'" if @namespace == "*"
|
441
|
+
|
439
442
|
if @cron.nil? || @cron.size == 0
|
440
443
|
errors << "'cron' must be set"
|
441
444
|
else
|
442
445
|
begin
|
443
|
-
@parsed_cron =
|
446
|
+
@parsed_cron = do_parse_cron(@cron)
|
444
447
|
rescue => e
|
445
448
|
errors << "'cron' -> #{@cron.inspect} -> #{e.class}: #{e.message}"
|
446
449
|
end
|
@@ -466,19 +469,23 @@ module Sidekiq
|
|
466
469
|
return false unless valid?
|
467
470
|
|
468
471
|
Sidekiq.redis do |conn|
|
469
|
-
|
470
472
|
# Add to set of all jobs
|
471
|
-
conn.sadd self.class.jobs_key, [redis_key]
|
473
|
+
conn.sadd self.class.jobs_key(@namespace), [redis_key]
|
472
474
|
|
473
|
-
# Add
|
474
|
-
conn.hset redis_key, to_hash.transform_values! { |v| v ||
|
475
|
+
# Add information for this job!
|
476
|
+
conn.hset redis_key, to_hash.transform_values! { |v| v || '' }.flatten
|
475
477
|
|
476
|
-
# Add information about last time! - don't
|
478
|
+
# Add information about last time! - don't enqueue right after scheduler poller starts!
|
477
479
|
time = Time.now.utc
|
478
|
-
exists = conn.
|
479
|
-
|
480
|
+
exists = conn.exists(job_enqueued_key)
|
481
|
+
|
482
|
+
unless exists == true || exists == 1
|
483
|
+
conn.zadd(job_enqueued_key, time.to_f.to_s, formatted_last_time(time).to_s)
|
484
|
+
Sidekiq.logger.info { "Cron Jobs - added job with name #{@name} in the namespace #{@namespace}" }
|
485
|
+
end
|
480
486
|
end
|
481
|
-
|
487
|
+
|
488
|
+
true
|
482
489
|
end
|
483
490
|
|
484
491
|
def save_last_enqueue_time
|
@@ -494,7 +501,7 @@ module Sidekiq
|
|
494
501
|
enqueued: @last_enqueue_time
|
495
502
|
}
|
496
503
|
|
497
|
-
@history_size ||=
|
504
|
+
@history_size ||= Sidekiq::Cron.configuration.cron_history_size.to_i - 1
|
498
505
|
Sidekiq.redis do |conn|
|
499
506
|
conn.lpush jid_history_key,
|
500
507
|
Sidekiq.dump_json(jid_history)
|
@@ -506,9 +513,9 @@ module Sidekiq
|
|
506
513
|
def destroy
|
507
514
|
Sidekiq.redis do |conn|
|
508
515
|
# Delete from set.
|
509
|
-
conn.srem self.class.jobs_key, [redis_key]
|
516
|
+
conn.srem self.class.jobs_key(@namespace), [redis_key]
|
510
517
|
|
511
|
-
# Delete
|
518
|
+
# Delete ran timestamps.
|
512
519
|
conn.del job_enqueued_key
|
513
520
|
|
514
521
|
# Delete jid_history.
|
@@ -518,7 +525,7 @@ module Sidekiq
|
|
518
525
|
conn.del redis_key
|
519
526
|
end
|
520
527
|
|
521
|
-
Sidekiq.logger.info { "Cron Jobs - deleted job with name
|
528
|
+
Sidekiq.logger.info { "Cron Jobs - deleted job with name #{@name} from namespace #{@namespace}" }
|
522
529
|
end
|
523
530
|
|
524
531
|
# Remove all job from cron.
|
@@ -531,9 +538,17 @@ module Sidekiq
|
|
531
538
|
|
532
539
|
# Remove "removed jobs" between current jobs and new jobs
|
533
540
|
def self.destroy_removed_jobs new_job_names
|
534
|
-
|
541
|
+
current_jobs = Sidekiq::Cron::Job.all("*").filter_map { |j| j if j.source == "schedule" }
|
542
|
+
current_job_names = current_jobs.map(&:name)
|
535
543
|
removed_job_names = current_job_names - new_job_names
|
536
|
-
removed_job_names.each
|
544
|
+
removed_job_names.each do |j|
|
545
|
+
job_to_destroy = current_jobs.detect { |job| job.name == j }
|
546
|
+
|
547
|
+
Sidekiq::Cron::Job.destroy(
|
548
|
+
job_to_destroy.name,
|
549
|
+
job_to_destroy.namespace
|
550
|
+
)
|
551
|
+
end
|
537
552
|
removed_job_names
|
538
553
|
end
|
539
554
|
|
@@ -551,15 +566,16 @@ module Sidekiq
|
|
551
566
|
last_time(now).getutc.iso8601
|
552
567
|
end
|
553
568
|
|
554
|
-
def self.exists?
|
569
|
+
def self.exists?(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
555
570
|
out = Sidekiq.redis do |conn|
|
556
|
-
conn.
|
571
|
+
conn.exists(redis_key(name, namespace))
|
557
572
|
end
|
558
|
-
|
573
|
+
|
574
|
+
[true, 1].include?(out)
|
559
575
|
end
|
560
576
|
|
561
577
|
def exists?
|
562
|
-
self.class.exists? @name
|
578
|
+
self.class.exists? @name, @namespace
|
563
579
|
end
|
564
580
|
|
565
581
|
def sort_name
|
@@ -573,11 +589,25 @@ module Sidekiq
|
|
573
589
|
private
|
574
590
|
|
575
591
|
def parsed_cron
|
576
|
-
@parsed_cron ||=
|
592
|
+
@parsed_cron ||= do_parse_cron(@cron)
|
577
593
|
end
|
578
594
|
|
579
|
-
def
|
580
|
-
|
595
|
+
def do_parse_cron(cron)
|
596
|
+
case Sidekiq::Cron.configuration.natural_cron_parsing_mode
|
597
|
+
when :single
|
598
|
+
Fugit.do_parse_cronish(cron)
|
599
|
+
when :strict
|
600
|
+
Fugit.parse_cron(cron) || # Ex. '11 1 * * 1'
|
601
|
+
Fugit.parse_nat(cron, :multi => :fail) || # Ex. 'every Monday at 01:11'
|
602
|
+
fail(ArgumentError.new("invalid cron string #{cron.inspect}"))
|
603
|
+
else
|
604
|
+
mode = Sidekiq::Cron.configuration.natural_cron_parsing_mode
|
605
|
+
raise ArgumentError, "Unknown natural cron parsing mode: #{mode.inspect}"
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
def enqueued_after?(time)
|
610
|
+
@last_enqueue_time && @last_enqueue_time.to_i >= last_time(time).to_i
|
581
611
|
end
|
582
612
|
|
583
613
|
# Try parsing inbound args into an array.
|
@@ -631,48 +661,83 @@ module Sidekiq
|
|
631
661
|
DateTime.parse(timestamp).to_time.utc
|
632
662
|
end
|
633
663
|
|
634
|
-
def
|
664
|
+
def past_scheduled_time?(current_time)
|
635
665
|
last_cron_time = parsed_cron.previous_time(current_time).utc
|
636
|
-
|
637
|
-
|
666
|
+
period = Sidekiq::Cron.configuration.reschedule_grace_period
|
667
|
+
|
668
|
+
current_time.to_i - last_cron_time.to_i > period
|
638
669
|
end
|
639
670
|
|
640
|
-
|
641
|
-
|
642
|
-
|
671
|
+
def self.default_if_blank(namespace)
|
672
|
+
if namespace.nil? || namespace == ''
|
673
|
+
Sidekiq::Cron.configuration.default_namespace
|
674
|
+
else
|
675
|
+
namespace
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
def self.job_keys_from_namespace(namespace = Sidekiq::Cron.configuration.default_namespace)
|
680
|
+
Sidekiq.redis do |conn|
|
681
|
+
if namespace == '*'
|
682
|
+
namespaces = conn.keys(jobs_key(namespace))
|
683
|
+
namespaces.flat_map { |name| conn.smembers(name) }
|
684
|
+
else
|
685
|
+
conn.smembers(jobs_key(namespace))
|
686
|
+
end
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
def self.migrate_old_jobs_if_needed!
|
691
|
+
Sidekiq.redis do |conn|
|
692
|
+
old_job_keys = conn.smembers('cron_jobs')
|
693
|
+
old_job_keys.each do |old_job|
|
694
|
+
old_job_hash = conn.hgetall(old_job)
|
695
|
+
old_job_hash[:namespace] = Sidekiq::Cron.configuration.default_namespace
|
696
|
+
create(old_job_hash)
|
697
|
+
conn.srem('cron_jobs', old_job)
|
698
|
+
end
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
702
|
+
# Redis key for set of all cron jobs
|
703
|
+
def self.jobs_key(namespace = Sidekiq::Cron.configuration.default_namespace)
|
704
|
+
"cron_jobs:#{default_if_blank(namespace)}"
|
643
705
|
end
|
644
706
|
|
645
|
-
# Redis key for storing one cron job
|
646
|
-
def self.redis_key
|
647
|
-
"cron_job:#{name}"
|
707
|
+
# Redis key for storing one cron job
|
708
|
+
def self.redis_key(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
709
|
+
"cron_job:#{default_if_blank(namespace)}:#{name}"
|
648
710
|
end
|
649
711
|
|
650
|
-
# Redis key for storing one cron job
|
712
|
+
# Redis key for storing one cron job
|
651
713
|
def redis_key
|
652
|
-
self.class.redis_key @name
|
714
|
+
self.class.redis_key @name, @namespace
|
653
715
|
end
|
654
716
|
|
655
|
-
# Redis key for storing one cron job run times
|
656
|
-
|
657
|
-
|
717
|
+
# Redis key for storing one cron job run times
|
718
|
+
# (when poller added job to queue)
|
719
|
+
def self.job_enqueued_key(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
720
|
+
"cron_job:#{default_if_blank(namespace)}:#{name}:enqueued"
|
658
721
|
end
|
659
722
|
|
660
|
-
def self.jid_history_key
|
661
|
-
"cron_job:#{name}:jid_history"
|
723
|
+
def self.jid_history_key(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
724
|
+
"cron_job:#{default_if_blank(namespace)}:#{name}:jid_history"
|
662
725
|
end
|
663
726
|
|
727
|
+
# Redis key for storing one cron job run times
|
728
|
+
# (when poller added job to queue)
|
664
729
|
def job_enqueued_key
|
665
|
-
self.class.job_enqueued_key @name
|
730
|
+
self.class.job_enqueued_key @name, @namespace
|
666
731
|
end
|
667
732
|
|
668
733
|
def jid_history_key
|
669
|
-
self.class.jid_history_key @name
|
734
|
+
self.class.jid_history_key @name, @namespace
|
670
735
|
end
|
671
736
|
|
672
737
|
def serialized_last_enqueue_time
|
673
738
|
@last_enqueue_time&.strftime(LAST_ENQUEUE_TIME_FORMAT)
|
674
739
|
end
|
675
|
-
|
740
|
+
|
676
741
|
def convert_to_global_id_hash(argument)
|
677
742
|
{ GLOBALID_KEY => argument.to_global_id.to_s }
|
678
743
|
rescue URI::GID::MissingModelIdError
|
@@ -719,6 +784,23 @@ module Sidekiq
|
|
719
784
|
argument
|
720
785
|
end
|
721
786
|
end
|
787
|
+
|
788
|
+
def get_job_class_options(klass)
|
789
|
+
klass = klass.is_a?(Class) ? klass : begin
|
790
|
+
Sidekiq::Cron::Support.constantize(klass)
|
791
|
+
rescue NameError
|
792
|
+
# noop
|
793
|
+
end
|
794
|
+
|
795
|
+
if klass.nil?
|
796
|
+
# Unknown class
|
797
|
+
{"queue"=>"default"}
|
798
|
+
elsif is_active_job?(klass)
|
799
|
+
{"queue"=>klass.queue_name}
|
800
|
+
else
|
801
|
+
klass.get_sidekiq_options
|
802
|
+
end
|
803
|
+
end
|
722
804
|
end
|
723
805
|
end
|
724
806
|
end
|
@@ -1,21 +1,18 @@
|
|
1
|
-
require 'sidekiq/cron/poller'
|
2
|
-
|
3
1
|
# For Cron we need to add some methods to Launcher
|
4
|
-
# so look at the code
|
2
|
+
# so look at the code below.
|
5
3
|
#
|
6
4
|
# We are creating new cron poller instance and
|
7
5
|
# adding start and stop commands to launcher.
|
8
6
|
module Sidekiq
|
9
7
|
module Cron
|
10
8
|
module Launcher
|
11
|
-
DEFAULT_POLL_INTERVAL = 30
|
12
9
|
|
13
10
|
# Add cron poller to launcher.
|
14
11
|
attr_reader :cron_poller
|
15
12
|
|
16
13
|
# Add cron poller and execute normal initialize of Sidekiq launcher.
|
17
14
|
def initialize(config, **kwargs)
|
18
|
-
config[:cron_poll_interval] =
|
15
|
+
config[:cron_poll_interval] = Sidekiq::Cron.configuration.cron_poll_interval.to_i
|
19
16
|
|
20
17
|
@cron_poller = Sidekiq::Cron::Poller.new(config) if config[:cron_poll_interval] > 0
|
21
18
|
super
|
@@ -0,0 +1,22 @@
|
|
1
|
+
id:
|
2
|
+
Job: Job
|
3
|
+
Cron: Cron
|
4
|
+
CronJobs: Cron Job
|
5
|
+
EnqueueNow: Tambahkan ke Antrian Sekarang
|
6
|
+
EnableAll: Aktifkan Semua
|
7
|
+
DisableAll: Nonaktifkan Semua
|
8
|
+
EnqueueAll: Tambahkan Semua ke Antrian
|
9
|
+
DeleteAll: Hapus Semua
|
10
|
+
'Cron string': Cron string
|
11
|
+
AreYouSureEnqueueCronJobs: Apakah Anda yakin ingin menambahkan SEMUA cron job ke antrian?
|
12
|
+
AreYouSureEnqueueCronJob: Apakah Anda yakin ingin menambahkan cron job %{job} ke antrian?
|
13
|
+
AreYouSureDeleteCronJobs: Apakah Anda yakin ingin menghapus SEMUA cron job?
|
14
|
+
AreYouSureDeleteCronJob: Apakah Anda yakin ingin menghapus cron job %{job}?
|
15
|
+
NoCronJobsWereFound: Tidak ada cron job
|
16
|
+
Enable: Aktifkan
|
17
|
+
Disable: Nonaktifkan
|
18
|
+
'Last enqueued': Terakhir kali ditambahkan ke antrian
|
19
|
+
disabled: dinonaktifkan
|
20
|
+
enabled: diaktifkan
|
21
|
+
NoHistoryWereFound: Tidak ada riwayat
|
22
|
+
Description: Deskripsi
|