sidekiq-cron 1.9.1 → 2.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +98 -3
- data/Gemfile +6 -0
- data/README.md +320 -44
- data/lib/sidekiq/cron/job.rb +336 -176
- data/lib/sidekiq/cron/launcher.rb +4 -6
- 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 +16 -13
- data/lib/sidekiq/cron/schedule_loader.rb +56 -16
- data/lib/sidekiq/cron/support.rb +4 -30
- data/lib/sidekiq/cron/version.rb +1 -1
- data/lib/sidekiq/cron/views/cron.erb +98 -92
- data/lib/sidekiq/cron/views/cron_show.erb +87 -82
- data/lib/sidekiq/cron/views/legacy/cron.erb +114 -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 +95 -5
- data/lib/sidekiq/options.rb +9 -7
- data/lib/sidekiq-cron.rb +6 -0
- data/sidekiq-cron.gemspec +10 -7
- metadata +65 -23
- data/test/integration/performance_test.rb +0 -46
- data/test/test_helper.rb +0 -87
- 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 -1258
- 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,58 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'fugit'
|
|
2
|
-
require '
|
|
4
|
+
require 'cronex'
|
|
5
|
+
require 'globalid'
|
|
3
6
|
require 'sidekiq/cron/support'
|
|
4
|
-
require 'sidekiq/options'
|
|
5
7
|
|
|
6
8
|
module Sidekiq
|
|
7
9
|
module Cron
|
|
8
10
|
class Job
|
|
9
|
-
# How long we would like to store
|
|
11
|
+
# How long we would like to store information about previous enqueues.
|
|
10
12
|
REMEMBER_THRESHOLD = 24 * 60 * 60
|
|
11
13
|
|
|
12
14
|
# Time format for enqueued jobs.
|
|
13
15
|
LAST_ENQUEUE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S %z'
|
|
14
16
|
|
|
15
|
-
# Use
|
|
16
|
-
|
|
17
|
+
# Use serialize/deserialize key of GlobalID.
|
|
18
|
+
GLOBALID_KEY = "_sc_globalid"
|
|
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
|
|
17
101
|
|
|
18
102
|
# Crucial part of whole enqueuing job.
|
|
19
|
-
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
|
+
|
|
20
108
|
enqueue = Sidekiq.redis do |conn|
|
|
21
|
-
|
|
22
|
-
not_past_scheduled_time?(time) &&
|
|
23
|
-
not_enqueued_after?(time) &&
|
|
24
|
-
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))
|
|
25
110
|
end
|
|
26
111
|
enqueue == true || enqueue == 1
|
|
27
112
|
end
|
|
28
113
|
|
|
29
114
|
# Remove previous information about run times,
|
|
30
115
|
# this will clear Redis and make sure that Redis will not overflow with memory.
|
|
31
|
-
def
|
|
116
|
+
def remove_previous_enqueues time
|
|
32
117
|
Sidekiq.redis do |conn|
|
|
33
118
|
conn.zremrangebyscore(job_enqueued_key, 0, "(#{(time.to_f - REMEMBER_THRESHOLD).to_s}")
|
|
34
119
|
end
|
|
35
120
|
end
|
|
36
121
|
|
|
37
122
|
# Test if job should be enqueued.
|
|
38
|
-
def
|
|
39
|
-
if
|
|
40
|
-
|
|
123
|
+
def test_and_enqueue_for_time! time
|
|
124
|
+
if should_enqueue?(time)
|
|
125
|
+
enqueue!
|
|
41
126
|
|
|
42
|
-
|
|
127
|
+
remove_previous_enqueues(time)
|
|
43
128
|
end
|
|
44
129
|
end
|
|
45
130
|
|
|
46
131
|
# Enqueue cron job to queue.
|
|
47
|
-
def
|
|
48
|
-
@last_enqueue_time = time
|
|
49
|
-
|
|
50
|
-
klass_const =
|
|
51
|
-
begin
|
|
52
|
-
Sidekiq::Cron::Support.constantize(@klass.to_s)
|
|
53
|
-
rescue NameError
|
|
54
|
-
nil
|
|
55
|
-
end
|
|
132
|
+
def enqueue! time = Time.now.utc
|
|
133
|
+
@last_enqueue_time = time
|
|
134
|
+
|
|
135
|
+
klass_const = Sidekiq::Cron::Support.safe_constantize(@klass.to_s)
|
|
56
136
|
|
|
57
137
|
jid =
|
|
58
138
|
if klass_const
|
|
@@ -75,9 +155,10 @@ module Sidekiq
|
|
|
75
155
|
end
|
|
76
156
|
|
|
77
157
|
def is_active_job?(klass = nil)
|
|
78
|
-
@active_job || defined?(ActiveJob::Base) &&
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
81
162
|
end
|
|
82
163
|
|
|
83
164
|
def date_as_argument?
|
|
@@ -85,7 +166,8 @@ module Sidekiq
|
|
|
85
166
|
end
|
|
86
167
|
|
|
87
168
|
def enqueue_args
|
|
88
|
-
date_as_argument? ? @args + [Time.now.to_f] : @args
|
|
169
|
+
args = date_as_argument? ? @args + [Time.now.to_f] : @args
|
|
170
|
+
deserialize_argument(args)
|
|
89
171
|
end
|
|
90
172
|
|
|
91
173
|
def enqueue_active_job(klass_const)
|
|
@@ -93,7 +175,7 @@ module Sidekiq
|
|
|
93
175
|
end
|
|
94
176
|
|
|
95
177
|
def enqueue_sidekiq_worker(klass_const)
|
|
96
|
-
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)
|
|
97
179
|
end
|
|
98
180
|
|
|
99
181
|
# Sidekiq worker message.
|
|
@@ -108,16 +190,16 @@ module Sidekiq
|
|
|
108
190
|
|
|
109
191
|
if !"#{@active_job_queue_name_delimiter}".empty?
|
|
110
192
|
queue_name_delimiter = @active_job_queue_name_delimiter
|
|
111
|
-
elsif defined?(ActiveJob::Base) && defined?(ActiveJob::Base.queue_name_delimiter) &&
|
|
112
|
-
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
|
|
113
195
|
else
|
|
114
196
|
queue_name_delimiter = '_'
|
|
115
197
|
end
|
|
116
198
|
|
|
117
199
|
if !"#{@active_job_queue_name_prefix}".empty?
|
|
118
200
|
queue_name = "#{@active_job_queue_name_prefix}#{queue_name_delimiter}#{@queue}"
|
|
119
|
-
elsif defined?(ActiveJob::Base) && defined?(ActiveJob::Base.queue_name_prefix) && !"#{ActiveJob::Base.queue_name_prefix}".empty?
|
|
120
|
-
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}"
|
|
121
203
|
else
|
|
122
204
|
queue_name = @queue
|
|
123
205
|
end
|
|
@@ -146,6 +228,7 @@ module Sidekiq
|
|
|
146
228
|
# Input structure should look like:
|
|
147
229
|
# {
|
|
148
230
|
# 'name_of_job' => {
|
|
231
|
+
# 'namespace' => 'MyNamespace',
|
|
149
232
|
# 'class' => 'MyClass',
|
|
150
233
|
# 'cron' => '1 * * * *',
|
|
151
234
|
# 'args' => '(OPTIONAL) [Array or Hash]',
|
|
@@ -157,25 +240,26 @@ module Sidekiq
|
|
|
157
240
|
# }
|
|
158
241
|
# }
|
|
159
242
|
#
|
|
160
|
-
def self.load_from_hash
|
|
243
|
+
def self.load_from_hash(hash, options = {})
|
|
161
244
|
array = hash.map do |key, job|
|
|
162
245
|
job['name'] = key
|
|
163
246
|
job
|
|
164
247
|
end
|
|
165
|
-
load_from_array
|
|
248
|
+
load_from_array(array, options)
|
|
166
249
|
end
|
|
167
250
|
|
|
168
251
|
# Like #load_from_hash.
|
|
169
252
|
# If exists old jobs in Redis but removed from args, destroy old jobs.
|
|
170
|
-
def self.load_from_hash!
|
|
253
|
+
def self.load_from_hash!(hash, options = {})
|
|
171
254
|
destroy_removed_jobs(hash.keys)
|
|
172
|
-
load_from_hash(hash)
|
|
255
|
+
load_from_hash(hash, options)
|
|
173
256
|
end
|
|
174
257
|
|
|
175
258
|
# Load cron jobs from Array.
|
|
176
259
|
# Input structure should look like:
|
|
177
260
|
# [
|
|
178
261
|
# {
|
|
262
|
+
# 'namespace' => 'MyNamespace',
|
|
179
263
|
# 'name' => 'name_of_job',
|
|
180
264
|
# 'class' => 'MyClass',
|
|
181
265
|
# 'cron' => '1 * * * *',
|
|
@@ -189,10 +273,10 @@ module Sidekiq
|
|
|
189
273
|
# }
|
|
190
274
|
# ]
|
|
191
275
|
#
|
|
192
|
-
def self.load_from_array
|
|
276
|
+
def self.load_from_array(array, options = {})
|
|
193
277
|
errors = {}
|
|
194
278
|
array.each do |job_data|
|
|
195
|
-
job = new(job_data)
|
|
279
|
+
job = new(job_data.merge(options))
|
|
196
280
|
errors[job.name] = job.errors unless job.save
|
|
197
281
|
end
|
|
198
282
|
errors
|
|
@@ -200,20 +284,21 @@ module Sidekiq
|
|
|
200
284
|
|
|
201
285
|
# Like #load_from_array.
|
|
202
286
|
# If exists old jobs in Redis but removed from args, destroy old jobs.
|
|
203
|
-
def self.load_from_array!
|
|
204
|
-
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] }
|
|
205
289
|
destroy_removed_jobs(job_names)
|
|
206
|
-
load_from_array(array)
|
|
290
|
+
load_from_array(array, options)
|
|
207
291
|
end
|
|
208
292
|
|
|
209
293
|
# Get all cron jobs.
|
|
210
|
-
def self.all
|
|
294
|
+
def self.all(namespace = Sidekiq::Cron.configuration.default_namespace)
|
|
211
295
|
job_hashes = nil
|
|
212
296
|
Sidekiq.redis do |conn|
|
|
213
|
-
|
|
297
|
+
job_keys = job_keys_from_namespace(namespace)
|
|
298
|
+
|
|
214
299
|
job_hashes = conn.pipelined do |pipeline|
|
|
215
|
-
|
|
216
|
-
pipeline.hgetall(
|
|
300
|
+
job_keys.each do |job_key|
|
|
301
|
+
pipeline.hgetall(job_key)
|
|
217
302
|
end
|
|
218
303
|
end
|
|
219
304
|
end
|
|
@@ -223,22 +308,25 @@ module Sidekiq
|
|
|
223
308
|
end
|
|
224
309
|
end
|
|
225
310
|
|
|
226
|
-
def self.count
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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)) }
|
|
230
318
|
end
|
|
231
|
-
out
|
|
232
319
|
end
|
|
233
320
|
|
|
234
|
-
def self.find
|
|
321
|
+
def self.find(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
|
235
322
|
# If name is hash try to get name from it.
|
|
236
323
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
|
324
|
+
return unless exists? name, namespace
|
|
237
325
|
|
|
238
326
|
output = nil
|
|
239
327
|
Sidekiq.redis do |conn|
|
|
240
|
-
if exists? name
|
|
241
|
-
output = Job.new conn.hgetall(
|
|
328
|
+
if exists? name, namespace
|
|
329
|
+
output = Job.new conn.hgetall(redis_key(name, namespace))
|
|
242
330
|
end
|
|
243
331
|
end
|
|
244
332
|
output if output && output.valid?
|
|
@@ -250,92 +338,17 @@ module Sidekiq
|
|
|
250
338
|
end
|
|
251
339
|
|
|
252
340
|
# Destroy job by name.
|
|
253
|
-
def self.destroy
|
|
341
|
+
def self.destroy(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
|
254
342
|
# If name is hash try to get name from it.
|
|
255
343
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
|
256
344
|
|
|
257
|
-
if job = find(name)
|
|
345
|
+
if (job = find(name, namespace))
|
|
258
346
|
job.destroy
|
|
259
347
|
else
|
|
260
348
|
false
|
|
261
349
|
end
|
|
262
350
|
end
|
|
263
351
|
|
|
264
|
-
attr_accessor :name, :cron, :description, :klass, :args, :message
|
|
265
|
-
attr_reader :last_enqueue_time, :fetch_missing_args
|
|
266
|
-
|
|
267
|
-
def initialize input_args = {}
|
|
268
|
-
args = Hash[input_args.map{ |k, v| [k.to_s, v] }]
|
|
269
|
-
@fetch_missing_args = args.delete('fetch_missing_args')
|
|
270
|
-
@fetch_missing_args = true if @fetch_missing_args.nil?
|
|
271
|
-
|
|
272
|
-
@name = args["name"]
|
|
273
|
-
@cron = args["cron"]
|
|
274
|
-
@description = args["description"] if args["description"]
|
|
275
|
-
|
|
276
|
-
# Get class from klass or class.
|
|
277
|
-
@klass = args["klass"] || args["class"]
|
|
278
|
-
|
|
279
|
-
# Set status of job.
|
|
280
|
-
@status = args['status'] || status_from_redis
|
|
281
|
-
|
|
282
|
-
# Set last enqueue time - from args or from existing job.
|
|
283
|
-
if args['last_enqueue_time'] && !args['last_enqueue_time'].empty?
|
|
284
|
-
@last_enqueue_time = parse_enqueue_time(args['last_enqueue_time'])
|
|
285
|
-
else
|
|
286
|
-
@last_enqueue_time = last_enqueue_time_from_redis
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
# Get right arguments for job.
|
|
290
|
-
@symbolize_args = args["symbolize_args"] == true || ("#{args["symbolize_args"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
|
291
|
-
@args = args["args"].nil? ? [] : parse_args( args["args"] )
|
|
292
|
-
|
|
293
|
-
@date_as_argument = args["date_as_argument"] == true || ("#{args["date_as_argument"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
|
294
|
-
|
|
295
|
-
@active_job = args["active_job"] == true || ("#{args["active_job"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
|
296
|
-
@active_job_queue_name_prefix = args["queue_name_prefix"]
|
|
297
|
-
@active_job_queue_name_delimiter = args["queue_name_delimiter"]
|
|
298
|
-
|
|
299
|
-
if args["message"]
|
|
300
|
-
@message = args["message"]
|
|
301
|
-
message_data = Sidekiq.load_json(@message) || {}
|
|
302
|
-
@queue = message_data['queue'] || "default"
|
|
303
|
-
elsif @klass
|
|
304
|
-
message_data = {
|
|
305
|
-
"class" => @klass.to_s,
|
|
306
|
-
"args" => @args,
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
# Get right data for message,
|
|
310
|
-
# only if message wasn't specified before.
|
|
311
|
-
klass_data = case @klass
|
|
312
|
-
when Class
|
|
313
|
-
@klass.get_sidekiq_options
|
|
314
|
-
when String
|
|
315
|
-
begin
|
|
316
|
-
Sidekiq::Cron::Support.constantize(@klass).get_sidekiq_options
|
|
317
|
-
rescue Exception => e
|
|
318
|
-
# Unknown class
|
|
319
|
-
{"queue"=>"default"}
|
|
320
|
-
end
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
message_data = klass_data.merge(message_data)
|
|
324
|
-
|
|
325
|
-
# Override queue if setted in config,
|
|
326
|
-
# only if message is hash - can be string (dumped JSON).
|
|
327
|
-
if args['queue']
|
|
328
|
-
@queue = message_data['queue'] = args['queue']
|
|
329
|
-
else
|
|
330
|
-
@queue = message_data['queue'] || "default"
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
@message = message_data
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
@queue_name_with_prefix = queue_name_with_prefix
|
|
337
|
-
end
|
|
338
|
-
|
|
339
352
|
def status
|
|
340
353
|
@status
|
|
341
354
|
end
|
|
@@ -364,6 +377,12 @@ module Sidekiq
|
|
|
364
377
|
message
|
|
365
378
|
end
|
|
366
379
|
|
|
380
|
+
def human_cron
|
|
381
|
+
Cronex::ExpressionDescriptor.new(cron).description
|
|
382
|
+
rescue
|
|
383
|
+
cron
|
|
384
|
+
end
|
|
385
|
+
|
|
367
386
|
def status_from_redis
|
|
368
387
|
out = "enabled"
|
|
369
388
|
if fetch_missing_args
|
|
@@ -400,9 +419,11 @@ module Sidekiq
|
|
|
400
419
|
def to_hash
|
|
401
420
|
{
|
|
402
421
|
name: @name,
|
|
422
|
+
namespace: @namespace,
|
|
403
423
|
klass: @klass.to_s,
|
|
404
424
|
cron: @cron,
|
|
405
425
|
description: @description,
|
|
426
|
+
source: @source,
|
|
406
427
|
args: @args.is_a?(String) ? @args : Sidekiq.dump_json(@args || []),
|
|
407
428
|
date_as_argument: date_as_argument? ? "1" : "0",
|
|
408
429
|
message: @message.is_a?(String) ? @message : Sidekiq.dump_json(@message || {}),
|
|
@@ -410,7 +431,8 @@ module Sidekiq
|
|
|
410
431
|
active_job: @active_job ? "1" : "0",
|
|
411
432
|
queue_name_prefix: @active_job_queue_name_prefix,
|
|
412
433
|
queue_name_delimiter: @active_job_queue_name_delimiter,
|
|
413
|
-
|
|
434
|
+
retry: @retry.nil? || @retry.is_a?(Numeric) ? @retry : @retry.to_s,
|
|
435
|
+
last_enqueue_time: serialized_last_enqueue_time,
|
|
414
436
|
symbolize_args: symbolize_args? ? "1" : "0",
|
|
415
437
|
}
|
|
416
438
|
end
|
|
@@ -424,11 +446,14 @@ module Sidekiq
|
|
|
424
446
|
@errors = []
|
|
425
447
|
|
|
426
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
|
+
|
|
427
452
|
if @cron.nil? || @cron.size == 0
|
|
428
453
|
errors << "'cron' must be set"
|
|
429
454
|
else
|
|
430
455
|
begin
|
|
431
|
-
@parsed_cron =
|
|
456
|
+
@parsed_cron = do_parse_cron(@cron)
|
|
432
457
|
rescue => e
|
|
433
458
|
errors << "'cron' -> #{@cron.inspect} -> #{e.class}: #{e.message}"
|
|
434
459
|
end
|
|
@@ -454,25 +479,29 @@ module Sidekiq
|
|
|
454
479
|
return false unless valid?
|
|
455
480
|
|
|
456
481
|
Sidekiq.redis do |conn|
|
|
457
|
-
|
|
458
482
|
# Add to set of all jobs
|
|
459
|
-
conn.sadd self.class.jobs_key, [redis_key]
|
|
483
|
+
conn.sadd self.class.jobs_key(@namespace), [redis_key]
|
|
460
484
|
|
|
461
|
-
# Add
|
|
462
|
-
conn.
|
|
485
|
+
# Add information for this job!
|
|
486
|
+
conn.hset redis_key, to_hash.transform_values! { |v| v || '' }.flatten
|
|
463
487
|
|
|
464
|
-
# Add information about last time! - don't
|
|
488
|
+
# Add information about last time! - don't enqueue right after scheduler poller starts!
|
|
465
489
|
time = Time.now.utc
|
|
466
|
-
exists = conn.
|
|
467
|
-
|
|
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
|
|
468
496
|
end
|
|
469
|
-
|
|
497
|
+
|
|
498
|
+
true
|
|
470
499
|
end
|
|
471
500
|
|
|
472
501
|
def save_last_enqueue_time
|
|
473
502
|
Sidekiq.redis do |conn|
|
|
474
503
|
# Update last enqueue time.
|
|
475
|
-
conn.hset redis_key, 'last_enqueue_time',
|
|
504
|
+
conn.hset redis_key, 'last_enqueue_time', serialized_last_enqueue_time
|
|
476
505
|
end
|
|
477
506
|
end
|
|
478
507
|
|
|
@@ -482,7 +511,7 @@ module Sidekiq
|
|
|
482
511
|
enqueued: @last_enqueue_time
|
|
483
512
|
}
|
|
484
513
|
|
|
485
|
-
@history_size ||=
|
|
514
|
+
@history_size ||= Sidekiq::Cron.configuration.cron_history_size.to_i - 1
|
|
486
515
|
Sidekiq.redis do |conn|
|
|
487
516
|
conn.lpush jid_history_key,
|
|
488
517
|
Sidekiq.dump_json(jid_history)
|
|
@@ -494,9 +523,9 @@ module Sidekiq
|
|
|
494
523
|
def destroy
|
|
495
524
|
Sidekiq.redis do |conn|
|
|
496
525
|
# Delete from set.
|
|
497
|
-
conn.srem self.class.jobs_key, [redis_key]
|
|
526
|
+
conn.srem self.class.jobs_key(@namespace), [redis_key]
|
|
498
527
|
|
|
499
|
-
# Delete
|
|
528
|
+
# Delete ran timestamps.
|
|
500
529
|
conn.del job_enqueued_key
|
|
501
530
|
|
|
502
531
|
# Delete jid_history.
|
|
@@ -506,7 +535,7 @@ module Sidekiq
|
|
|
506
535
|
conn.del redis_key
|
|
507
536
|
end
|
|
508
537
|
|
|
509
|
-
Sidekiq.logger.info { "Cron Jobs - deleted job with name
|
|
538
|
+
Sidekiq.logger.info { "Cron Jobs - deleted job with name #{@name} from namespace #{@namespace}" }
|
|
510
539
|
end
|
|
511
540
|
|
|
512
541
|
# Remove all job from cron.
|
|
@@ -519,9 +548,17 @@ module Sidekiq
|
|
|
519
548
|
|
|
520
549
|
# Remove "removed jobs" between current jobs and new jobs
|
|
521
550
|
def self.destroy_removed_jobs new_job_names
|
|
522
|
-
|
|
551
|
+
current_jobs = Sidekiq::Cron::Job.all("*").filter_map { |j| j if j.source == "schedule" }
|
|
552
|
+
current_job_names = current_jobs.map(&:name)
|
|
523
553
|
removed_job_names = current_job_names - new_job_names
|
|
524
|
-
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
|
|
525
562
|
removed_job_names
|
|
526
563
|
end
|
|
527
564
|
|
|
@@ -539,29 +576,52 @@ module Sidekiq
|
|
|
539
576
|
last_time(now).getutc.iso8601
|
|
540
577
|
end
|
|
541
578
|
|
|
542
|
-
def self.exists?
|
|
579
|
+
def self.exists?(name, namespace = Sidekiq::Cron.configuration.default_namespace)
|
|
543
580
|
out = Sidekiq.redis do |conn|
|
|
544
|
-
conn.
|
|
581
|
+
conn.exists(redis_key(name, namespace))
|
|
545
582
|
end
|
|
546
|
-
|
|
583
|
+
|
|
584
|
+
[true, 1].include?(out)
|
|
547
585
|
end
|
|
548
586
|
|
|
549
587
|
def exists?
|
|
550
|
-
self.class.exists? @name
|
|
588
|
+
self.class.exists? @name, @namespace
|
|
551
589
|
end
|
|
552
590
|
|
|
553
591
|
def sort_name
|
|
554
592
|
"#{status == "enabled" ? 0 : 1}_#{name}".downcase
|
|
555
593
|
end
|
|
556
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
|
+
|
|
557
603
|
private
|
|
558
604
|
|
|
559
605
|
def parsed_cron
|
|
560
|
-
@parsed_cron ||=
|
|
606
|
+
@parsed_cron ||= do_parse_cron(@cron)
|
|
561
607
|
end
|
|
562
608
|
|
|
563
|
-
def
|
|
564
|
-
|
|
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
|
|
565
625
|
end
|
|
566
626
|
|
|
567
627
|
# Try parsing inbound args into an array.
|
|
@@ -569,6 +629,8 @@ module Sidekiq
|
|
|
569
629
|
# try to load JSON, then failover to string array.
|
|
570
630
|
def parse_args(args)
|
|
571
631
|
case args
|
|
632
|
+
when GlobalID::Identification
|
|
633
|
+
[convert_to_global_id_hash(args)]
|
|
572
634
|
when String
|
|
573
635
|
begin
|
|
574
636
|
parsed_args = Sidekiq.load_json(args)
|
|
@@ -577,8 +639,10 @@ module Sidekiq
|
|
|
577
639
|
[*args]
|
|
578
640
|
end
|
|
579
641
|
when Hash
|
|
642
|
+
args = serialize_argument(args)
|
|
580
643
|
symbolize_args? ? [symbolize_args(args)] : [args]
|
|
581
644
|
when Array
|
|
645
|
+
args = serialize_argument(args)
|
|
582
646
|
symbolize_args? ? symbolize_args(args) : args
|
|
583
647
|
else
|
|
584
648
|
[*args]
|
|
@@ -611,47 +675,143 @@ module Sidekiq
|
|
|
611
675
|
DateTime.parse(timestamp).to_time.utc
|
|
612
676
|
end
|
|
613
677
|
|
|
614
|
-
def
|
|
678
|
+
def past_scheduled_time?(current_time)
|
|
615
679
|
last_cron_time = parsed_cron.previous_time(current_time).utc
|
|
616
|
-
|
|
617
|
-
|
|
680
|
+
period = Sidekiq::Cron.configuration.reschedule_grace_period
|
|
681
|
+
|
|
682
|
+
current_time.to_i - last_cron_time.to_i > period
|
|
618
683
|
end
|
|
619
684
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
685
|
+
def self.default_if_blank(namespace)
|
|
686
|
+
if namespace.nil? || namespace == ''
|
|
687
|
+
Sidekiq::Cron.configuration.default_namespace
|
|
688
|
+
else
|
|
689
|
+
namespace
|
|
690
|
+
end
|
|
623
691
|
end
|
|
624
692
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
|
628
702
|
end
|
|
629
703
|
|
|
630
|
-
|
|
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
|
|
714
|
+
end
|
|
715
|
+
|
|
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
|
|
631
727
|
def redis_key
|
|
632
|
-
self.class.redis_key @name
|
|
728
|
+
self.class.redis_key @name, @namespace
|
|
633
729
|
end
|
|
634
730
|
|
|
635
|
-
# Redis key for storing one cron job run times
|
|
636
|
-
|
|
637
|
-
|
|
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"
|
|
638
735
|
end
|
|
639
736
|
|
|
640
|
-
def self.jid_history_key
|
|
641
|
-
"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"
|
|
642
739
|
end
|
|
643
740
|
|
|
741
|
+
# Redis key for storing one cron job run times
|
|
742
|
+
# (when poller added job to queue)
|
|
644
743
|
def job_enqueued_key
|
|
645
|
-
self.class.job_enqueued_key @name
|
|
744
|
+
self.class.job_enqueued_key @name, @namespace
|
|
646
745
|
end
|
|
647
746
|
|
|
648
747
|
def jid_history_key
|
|
649
|
-
self.class.jid_history_key @name
|
|
748
|
+
self.class.jid_history_key @name, @namespace
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
def serialized_last_enqueue_time
|
|
752
|
+
@last_enqueue_time&.strftime(LAST_ENQUEUE_TIME_FORMAT)
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
def convert_to_global_id_hash(argument)
|
|
756
|
+
{ GLOBALID_KEY => argument.to_global_id.to_s }
|
|
757
|
+
rescue URI::GID::MissingModelIdError
|
|
758
|
+
raise "Unable to serialize #{argument.class} " \
|
|
759
|
+
"without an id. (Maybe you forgot to call save?)"
|
|
650
760
|
end
|
|
651
761
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
762
|
+
def deserialize_argument(argument)
|
|
763
|
+
case argument
|
|
764
|
+
when String
|
|
765
|
+
argument
|
|
766
|
+
when Array
|
|
767
|
+
argument.map { |arg| deserialize_argument(arg) }
|
|
768
|
+
when Hash
|
|
769
|
+
if serialized_global_id?(argument)
|
|
770
|
+
deserialize_global_id argument
|
|
771
|
+
else
|
|
772
|
+
argument.transform_values { |v| deserialize_argument(v) }
|
|
773
|
+
end
|
|
774
|
+
else
|
|
775
|
+
argument
|
|
776
|
+
end
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
def serialized_global_id?(hash)
|
|
780
|
+
hash.size == 1 && hash.include?(GLOBALID_KEY)
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
def deserialize_global_id(hash)
|
|
784
|
+
GlobalID::Locator.locate hash[GLOBALID_KEY]
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
def serialize_argument(argument)
|
|
788
|
+
case argument
|
|
789
|
+
when GlobalID::Identification
|
|
790
|
+
convert_to_global_id_hash(argument)
|
|
791
|
+
when Array
|
|
792
|
+
argument.map { |arg| serialize_argument(arg) }
|
|
793
|
+
when Hash
|
|
794
|
+
argument.each_with_object({}) do |(key, value), hash|
|
|
795
|
+
hash[key] = serialize_argument(value)
|
|
796
|
+
end
|
|
797
|
+
else
|
|
798
|
+
argument
|
|
799
|
+
end
|
|
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
|
|
655
815
|
end
|
|
656
816
|
end
|
|
657
817
|
end
|