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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +81 -3
- data/Gemfile +3 -0
- data/README.md +283 -38
- data/lib/sidekiq/cron/job.rb +277 -182
- data/lib/sidekiq/cron/launcher.rb +2 -5
- data/lib/sidekiq/cron/locales/de.yml +15 -6
- data/lib/sidekiq/cron/locales/en.yml +14 -14
- data/lib/sidekiq/cron/locales/es.yml +22 -0
- data/lib/sidekiq/cron/locales/id.yml +22 -0
- data/lib/sidekiq/cron/locales/it.yml +15 -16
- data/lib/sidekiq/cron/locales/ja.yml +14 -10
- data/lib/sidekiq/cron/locales/pt.yml +14 -14
- data/lib/sidekiq/cron/locales/ru.yml +15 -7
- data/lib/sidekiq/cron/locales/zh-CN.yml +14 -11
- data/lib/sidekiq/cron/namespace.rb +43 -0
- data/lib/sidekiq/cron/poller.rb +12 -13
- data/lib/sidekiq/cron/schedule_loader.rb +51 -16
- data/lib/sidekiq/cron/support.rb +4 -30
- data/lib/sidekiq/cron/version.rb +1 -1
- data/lib/sidekiq/cron/views/cron.erb +97 -92
- data/lib/sidekiq/cron/views/cron_show.erb +87 -82
- data/lib/sidekiq/cron/views/legacy/cron.erb +113 -0
- data/lib/sidekiq/cron/views/legacy/cron_show.erb +92 -0
- data/lib/sidekiq/cron/web.rb +21 -2
- data/lib/sidekiq/cron/web_extension.rb +99 -31
- data/lib/sidekiq/cron.rb +73 -5
- data/lib/sidekiq/options.rb +9 -7
- data/lib/sidekiq-cron.rb +6 -0
- data/sidekiq-cron.gemspec +7 -6
- metadata +40 -27
- data/test/integration/performance_test.rb +0 -46
- data/test/models/person.rb +0 -21
- data/test/test_helper.rb +0 -93
- data/test/unit/fixtures/schedule_array.yml +0 -13
- data/test/unit/fixtures/schedule_erb.yml +0 -6
- data/test/unit/fixtures/schedule_hash.yml +0 -12
- data/test/unit/fixtures/schedule_string.yml +0 -1
- data/test/unit/job_test.rb +0 -1386
- data/test/unit/launcher_test.rb +0 -33
- data/test/unit/poller_test.rb +0 -144
- data/test/unit/schedule_loader_test.rb +0 -58
- data/test/unit/web_extension_test.rb +0 -155
data/lib/sidekiq/cron/job.rb
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
43
|
-
if
|
|
44
|
-
|
|
123
|
+
def test_and_enqueue_for_time! time
|
|
124
|
+
if should_enqueue?(time)
|
|
125
|
+
enqueue!
|
|
45
126
|
|
|
46
|
-
|
|
127
|
+
remove_previous_enqueues(time)
|
|
47
128
|
end
|
|
48
129
|
end
|
|
49
130
|
|
|
50
131
|
# Enqueue cron job to queue.
|
|
51
|
-
def
|
|
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) &&
|
|
83
|
-
|
|
84
|
-
|
|
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) &&
|
|
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
|
|
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
|
|
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!
|
|
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
|
|
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!
|
|
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
|
-
|
|
297
|
+
job_keys = job_keys_from_namespace(namespace)
|
|
298
|
+
|
|
219
299
|
job_hashes = conn.pipelined do |pipeline|
|
|
220
|
-
|
|
221
|
-
pipeline.hgetall(
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
472
|
-
conn.
|
|
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
|
|
488
|
+
# Add information about last time! - don't enqueue right after scheduler poller starts!
|
|
475
489
|
time = Time.now.utc
|
|
476
|
-
exists = conn.
|
|
477
|
-
|
|
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
|
-
|
|
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 ||=
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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?
|
|
579
|
+
def self.exists?(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
|
553
580
|
out = Sidekiq.redis do |conn|
|
|
554
|
-
conn.
|
|
581
|
+
conn.exists(redis_key(name, namespace))
|
|
555
582
|
end
|
|
556
|
-
|
|
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 ||=
|
|
606
|
+
@parsed_cron ||= do_parse_cron(@cron)
|
|
571
607
|
end
|
|
572
608
|
|
|
573
|
-
def
|
|
574
|
-
|
|
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
|
|
678
|
+
def past_scheduled_time?(current_time)
|
|
629
679
|
last_cron_time = parsed_cron.previous_time(current_time).utc
|
|
630
|
-
|
|
631
|
-
|
|
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
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
|
640
|
-
def self.
|
|
641
|
-
"
|
|
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
|
|
650
|
-
|
|
651
|
-
|
|
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
|
|
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
|