sidekiq-cron 0.6.3 → 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +145 -0
- data/Gemfile +3 -29
- data/README.md +175 -121
- data/Rakefile +3 -42
- data/lib/sidekiq/cron/job.rb +273 -144
- data/lib/sidekiq/cron/launcher.rb +39 -43
- data/lib/sidekiq/cron/locales/de.yml +2 -2
- data/lib/sidekiq/cron/locales/en.yml +6 -2
- data/lib/sidekiq/cron/locales/it.yml +23 -0
- data/lib/sidekiq/cron/locales/ja.yml +18 -0
- data/lib/sidekiq/cron/locales/pt.yml +22 -0
- data/lib/sidekiq/cron/locales/ru.yml +2 -2
- data/lib/sidekiq/cron/locales/zh-CN.yml +19 -0
- data/lib/sidekiq/cron/poller.rb +22 -12
- data/lib/sidekiq/cron/schedule_loader.rb +22 -0
- data/lib/sidekiq/cron/support.rb +8 -1
- data/lib/sidekiq/cron/version.rb +7 -0
- data/lib/sidekiq/cron/views/cron.erb +38 -28
- data/lib/sidekiq/cron/views/cron_show.erb +88 -0
- data/lib/sidekiq/cron/web.rb +1 -7
- data/lib/sidekiq/cron/web_extension.rb +19 -15
- data/lib/sidekiq/cron.rb +1 -0
- data/lib/sidekiq/options.rb +25 -0
- data/sidekiq-cron.gemspec +23 -108
- data/test/integration/performance_test.rb +13 -19
- data/test/models/person.rb +21 -0
- data/test/test_helper.rb +37 -38
- data/test/unit/fixtures/schedule_array.yml +13 -0
- data/test/unit/fixtures/schedule_erb.yml +6 -0
- data/test/unit/fixtures/schedule_hash.yml +12 -0
- data/test/unit/fixtures/schedule_string.yml +1 -0
- data/test/unit/job_test.rb +450 -35
- data/test/unit/launcher_test.rb +33 -0
- data/test/unit/poller_test.rb +28 -37
- data/test/unit/schedule_loader_test.rb +58 -0
- data/test/unit/web_extension_test.rb +59 -41
- metadata +72 -191
- data/.document +0 -5
- data/.travis.yml +0 -19
- data/Changes.md +0 -50
- data/Dockerfile +0 -32
- data/VERSION +0 -1
- data/config.ru +0 -14
- data/docker-compose.yml +0 -21
- data/examples/web-cron-ui.png +0 -0
- data/lib/sidekiq/cron/views/cron.slim +0 -69
data/lib/sidekiq/cron/job.rb
CHANGED
@@ -1,42 +1,45 @@
|
|
1
|
+
require 'fugit'
|
2
|
+
require 'globalid'
|
1
3
|
require 'sidekiq'
|
2
|
-
require 'sidekiq/util'
|
3
|
-
require 'rufus-scheduler'
|
4
4
|
require 'sidekiq/cron/support'
|
5
|
+
require 'sidekiq/options'
|
5
6
|
|
6
7
|
module Sidekiq
|
7
8
|
module Cron
|
8
|
-
|
9
9
|
class Job
|
10
|
-
|
11
|
-
extend Util
|
12
|
-
|
13
|
-
#how long we would like to store informations about previous enqueues
|
10
|
+
# How long we would like to store informations about previous enqueues.
|
14
11
|
REMEMBER_THRESHOLD = 24 * 60 * 60
|
15
12
|
|
16
|
-
#
|
13
|
+
# Time format for enqueued jobs.
|
14
|
+
LAST_ENQUEUE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S %z'
|
15
|
+
|
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
|
+
# Use serialize/deserialize key of GlobalID.
|
20
|
+
GLOBALID_KEY = "_sc_globalid"
|
21
|
+
|
22
|
+
# Crucial part of whole enqueuing job.
|
17
23
|
def should_enque? time
|
18
|
-
enqueue = false
|
19
24
|
enqueue = Sidekiq.redis do |conn|
|
20
25
|
status == "enabled" &&
|
21
26
|
not_past_scheduled_time?(time) &&
|
22
27
|
not_enqueued_after?(time) &&
|
23
|
-
conn.zadd(job_enqueued_key,
|
28
|
+
conn.zadd(job_enqueued_key, formatted_enqueue_time(time), formatted_last_time(time))
|
24
29
|
end
|
25
|
-
enqueue
|
30
|
+
enqueue == true || enqueue == 1
|
26
31
|
end
|
27
32
|
|
28
|
-
#
|
29
|
-
# this will clear
|
30
|
-
# not overflow with memory
|
33
|
+
# Remove previous information about run times,
|
34
|
+
# this will clear Redis and make sure that Redis will not overflow with memory.
|
31
35
|
def remove_previous_enques time
|
32
36
|
Sidekiq.redis do |conn|
|
33
37
|
conn.zremrangebyscore(job_enqueued_key, 0, "(#{(time.to_f - REMEMBER_THRESHOLD).to_s}")
|
34
38
|
end
|
35
39
|
end
|
36
40
|
|
37
|
-
#
|
41
|
+
# Test if job should be enqueued.
|
38
42
|
def test_and_enque_for_time! time
|
39
|
-
#should this job be enqued?
|
40
43
|
if should_enque?(time)
|
41
44
|
enque!
|
42
45
|
|
@@ -44,7 +47,7 @@ module Sidekiq
|
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
47
|
-
#
|
50
|
+
# Enqueue cron job to queue.
|
48
51
|
def enque! time = Time.now.utc
|
49
52
|
@last_enqueue_time = time
|
50
53
|
|
@@ -55,45 +58,54 @@ module Sidekiq
|
|
55
58
|
nil
|
56
59
|
end
|
57
60
|
|
58
|
-
|
59
|
-
if
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
if @active_job
|
66
|
-
Sidekiq::Client.push(active_job_message)
|
61
|
+
jid =
|
62
|
+
if klass_const
|
63
|
+
if is_active_job?(klass_const)
|
64
|
+
enqueue_active_job(klass_const).try :provider_job_id
|
65
|
+
else
|
66
|
+
enqueue_sidekiq_worker(klass_const)
|
67
|
+
end
|
67
68
|
else
|
68
|
-
|
69
|
+
if @active_job
|
70
|
+
Sidekiq::Client.push(active_job_message)
|
71
|
+
else
|
72
|
+
Sidekiq::Client.push(sidekiq_worker_message)
|
73
|
+
end
|
69
74
|
end
|
70
|
-
end
|
71
75
|
|
72
76
|
save_last_enqueue_time
|
73
|
-
|
77
|
+
add_jid_history jid
|
78
|
+
Sidekiq.logger.debug { "enqueued #{@name}: #{@message}" }
|
74
79
|
end
|
75
80
|
|
76
|
-
def is_active_job?
|
77
|
-
@active_job || defined?(ActiveJob::Base) && Sidekiq::Cron::Support.constantize(@klass.to_s) < ActiveJob::Base
|
81
|
+
def is_active_job?(klass = nil)
|
82
|
+
@active_job || defined?(ActiveJob::Base) && (klass || Sidekiq::Cron::Support.constantize(@klass.to_s)) < ActiveJob::Base
|
78
83
|
rescue NameError
|
79
84
|
false
|
80
85
|
end
|
81
86
|
|
82
|
-
def
|
83
|
-
|
87
|
+
def date_as_argument?
|
88
|
+
!!@date_as_argument
|
89
|
+
end
|
84
90
|
|
85
|
-
|
91
|
+
def enqueue_args
|
92
|
+
args = date_as_argument? ? @args + [Time.now.to_f] : @args
|
93
|
+
deserialize_argument(args)
|
86
94
|
end
|
87
95
|
|
88
|
-
def
|
89
|
-
klass_const.set(queue:
|
96
|
+
def enqueue_active_job(klass_const)
|
97
|
+
klass_const.set(queue: @queue).perform_later(*enqueue_args)
|
98
|
+
end
|
90
99
|
|
91
|
-
|
100
|
+
def enqueue_sidekiq_worker(klass_const)
|
101
|
+
klass_const.set(queue: queue_name_with_prefix).perform_async(*enqueue_args)
|
92
102
|
end
|
93
103
|
|
94
|
-
#
|
104
|
+
# Sidekiq worker message.
|
95
105
|
def sidekiq_worker_message
|
96
|
-
@message.is_a?(String) ? Sidekiq.load_json(@message) : @message
|
106
|
+
message = @message.is_a?(String) ? Sidekiq.load_json(@message) : @message
|
107
|
+
message["args"] = enqueue_args
|
108
|
+
message
|
97
109
|
end
|
98
110
|
|
99
111
|
def queue_name_with_prefix
|
@@ -118,24 +130,25 @@ module Sidekiq
|
|
118
130
|
queue_name
|
119
131
|
end
|
120
132
|
|
121
|
-
#
|
122
|
-
# queue, it
|
133
|
+
# Active Job has different structure how it is loading data from Sidekiq
|
134
|
+
# queue, it creates a wrapper around job.
|
123
135
|
def active_job_message
|
124
136
|
{
|
125
137
|
'class' => 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper',
|
138
|
+
'wrapped' => @klass,
|
126
139
|
'queue' => @queue_name_with_prefix,
|
127
140
|
'description' => @description,
|
128
141
|
'args' => [{
|
129
142
|
'job_class' => @klass,
|
130
143
|
'job_id' => SecureRandom.uuid,
|
131
144
|
'queue_name' => @queue_name_with_prefix,
|
132
|
-
'arguments' =>
|
145
|
+
'arguments' => enqueue_args
|
133
146
|
}]
|
134
147
|
}
|
135
148
|
end
|
136
149
|
|
137
|
-
#
|
138
|
-
#
|
150
|
+
# Load cron jobs from Hash.
|
151
|
+
# Input structure should look like:
|
139
152
|
# {
|
140
153
|
# 'name_of_job' => {
|
141
154
|
# 'class' => 'MyClass',
|
@@ -150,22 +163,22 @@ module Sidekiq
|
|
150
163
|
# }
|
151
164
|
#
|
152
165
|
def self.load_from_hash hash
|
153
|
-
array = hash.
|
166
|
+
array = hash.map do |key, job|
|
154
167
|
job['name'] = key
|
155
|
-
|
168
|
+
job
|
156
169
|
end
|
157
170
|
load_from_array array
|
158
171
|
end
|
159
172
|
|
160
|
-
#
|
161
|
-
# If exists old jobs in
|
173
|
+
# Like #load_from_hash.
|
174
|
+
# If exists old jobs in Redis but removed from args, destroy old jobs.
|
162
175
|
def self.load_from_hash! hash
|
163
176
|
destroy_removed_jobs(hash.keys)
|
164
177
|
load_from_hash(hash)
|
165
178
|
end
|
166
179
|
|
167
|
-
#
|
168
|
-
#
|
180
|
+
# Load cron jobs from Array.
|
181
|
+
# Input structure should look like:
|
169
182
|
# [
|
170
183
|
# {
|
171
184
|
# 'name' => 'name_of_job',
|
@@ -190,27 +203,27 @@ module Sidekiq
|
|
190
203
|
errors
|
191
204
|
end
|
192
205
|
|
193
|
-
#
|
194
|
-
# If exists old jobs in
|
206
|
+
# Like #load_from_array.
|
207
|
+
# If exists old jobs in Redis but removed from args, destroy old jobs.
|
195
208
|
def self.load_from_array! array
|
196
209
|
job_names = array.map { |job| job["name"] }
|
197
210
|
destroy_removed_jobs(job_names)
|
198
211
|
load_from_array(array)
|
199
212
|
end
|
200
213
|
|
201
|
-
#
|
214
|
+
# Get all cron jobs.
|
202
215
|
def self.all
|
203
216
|
job_hashes = nil
|
204
217
|
Sidekiq.redis do |conn|
|
205
218
|
set_members = conn.smembers(jobs_key)
|
206
|
-
job_hashes = conn.pipelined do
|
219
|
+
job_hashes = conn.pipelined do |pipeline|
|
207
220
|
set_members.each do |key|
|
208
|
-
|
221
|
+
pipeline.hgetall(key)
|
209
222
|
end
|
210
223
|
end
|
211
224
|
end
|
212
225
|
job_hashes.compact.reject(&:empty?).collect do |h|
|
213
|
-
#
|
226
|
+
# No need to fetch missing args from Redis since we just got this hash from there
|
214
227
|
Sidekiq::Cron::Job.new(h.merge(fetch_missing_args: false))
|
215
228
|
end
|
216
229
|
end
|
@@ -224,7 +237,7 @@ module Sidekiq
|
|
224
237
|
end
|
225
238
|
|
226
239
|
def self.find name
|
227
|
-
#
|
240
|
+
# If name is hash try to get name from it.
|
228
241
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
229
242
|
|
230
243
|
output = nil
|
@@ -233,17 +246,17 @@ module Sidekiq
|
|
233
246
|
output = Job.new conn.hgetall( redis_key(name) )
|
234
247
|
end
|
235
248
|
end
|
236
|
-
output
|
249
|
+
output if output && output.valid?
|
237
250
|
end
|
238
251
|
|
239
|
-
#
|
252
|
+
# Create new instance of cron job.
|
240
253
|
def self.create hash
|
241
254
|
new(hash).save
|
242
255
|
end
|
243
256
|
|
244
|
-
#
|
257
|
+
# Destroy job by name.
|
245
258
|
def self.destroy name
|
246
|
-
#
|
259
|
+
# If name is hash try to get name from it.
|
247
260
|
name = name[:name] || name['name'] if name.is_a?(Hash)
|
248
261
|
|
249
262
|
if job = find(name)
|
@@ -265,22 +278,25 @@ module Sidekiq
|
|
265
278
|
@cron = args["cron"]
|
266
279
|
@description = args["description"] if args["description"]
|
267
280
|
|
268
|
-
#
|
281
|
+
# Get class from klass or class.
|
269
282
|
@klass = args["klass"] || args["class"]
|
270
283
|
|
271
|
-
#
|
284
|
+
# Set status of job.
|
272
285
|
@status = args['status'] || status_from_redis
|
273
286
|
|
274
|
-
#
|
287
|
+
# Set last enqueue time - from args or from existing job.
|
275
288
|
if args['last_enqueue_time'] && !args['last_enqueue_time'].empty?
|
276
|
-
@last_enqueue_time =
|
289
|
+
@last_enqueue_time = parse_enqueue_time(args['last_enqueue_time'])
|
277
290
|
else
|
278
291
|
@last_enqueue_time = last_enqueue_time_from_redis
|
279
292
|
end
|
280
293
|
|
281
|
-
#
|
294
|
+
# Get right arguments for job.
|
295
|
+
@symbolize_args = args["symbolize_args"] == true || ("#{args["symbolize_args"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
282
296
|
@args = args["args"].nil? ? [] : parse_args( args["args"] )
|
283
297
|
|
298
|
+
@date_as_argument = args["date_as_argument"] == true || ("#{args["date_as_argument"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
299
|
+
|
284
300
|
@active_job = args["active_job"] == true || ("#{args["active_job"]}" =~ (/^(true|t|yes|y|1)$/i)) == 0 || false
|
285
301
|
@active_job_queue_name_prefix = args["queue_name_prefix"]
|
286
302
|
@active_job_queue_name_delimiter = args["queue_name_delimiter"]
|
@@ -295,8 +311,8 @@ module Sidekiq
|
|
295
311
|
"args" => @args,
|
296
312
|
}
|
297
313
|
|
298
|
-
#
|
299
|
-
#only if message wasn't specified before
|
314
|
+
# Get right data for message,
|
315
|
+
# only if message wasn't specified before.
|
300
316
|
klass_data = case @klass
|
301
317
|
when Class
|
302
318
|
@klass.get_sidekiq_options
|
@@ -304,21 +320,21 @@ module Sidekiq
|
|
304
320
|
begin
|
305
321
|
Sidekiq::Cron::Support.constantize(@klass).get_sidekiq_options
|
306
322
|
rescue Exception => e
|
307
|
-
#Unknown class
|
323
|
+
# Unknown class
|
308
324
|
{"queue"=>"default"}
|
309
325
|
end
|
310
326
|
end
|
311
327
|
|
312
328
|
message_data = klass_data.merge(message_data)
|
313
|
-
|
314
|
-
#
|
329
|
+
|
330
|
+
# Override queue if setted in config,
|
331
|
+
# only if message is hash - can be string (dumped JSON).
|
315
332
|
if args['queue']
|
316
333
|
@queue = message_data['queue'] = args['queue']
|
317
334
|
else
|
318
335
|
@queue = message_data['queue'] || "default"
|
319
336
|
end
|
320
337
|
|
321
|
-
#dump message as json
|
322
338
|
@message = message_data
|
323
339
|
end
|
324
340
|
|
@@ -347,6 +363,12 @@ module Sidekiq
|
|
347
363
|
!enabled?
|
348
364
|
end
|
349
365
|
|
366
|
+
def pretty_message
|
367
|
+
JSON.pretty_generate Sidekiq.load_json(message)
|
368
|
+
rescue JSON::ParserError
|
369
|
+
message
|
370
|
+
end
|
371
|
+
|
350
372
|
def status_from_redis
|
351
373
|
out = "enabled"
|
352
374
|
if fetch_missing_args
|
@@ -362,27 +384,45 @@ module Sidekiq
|
|
362
384
|
out = nil
|
363
385
|
if fetch_missing_args
|
364
386
|
Sidekiq.redis do |conn|
|
365
|
-
out =
|
387
|
+
out = parse_enqueue_time(conn.hget(redis_key, "last_enqueue_time")) rescue nil
|
366
388
|
end
|
367
389
|
end
|
368
390
|
out
|
369
391
|
end
|
370
392
|
|
371
|
-
|
393
|
+
def jid_history_from_redis
|
394
|
+
out =
|
395
|
+
Sidekiq.redis do |conn|
|
396
|
+
conn.lrange(jid_history_key, 0, -1) rescue nil
|
397
|
+
end
|
398
|
+
|
399
|
+
out && out.map do |jid_history_raw|
|
400
|
+
Sidekiq.load_json jid_history_raw
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# Export job data to hash.
|
372
405
|
def to_hash
|
373
|
-
{
|
406
|
+
hash = {
|
374
407
|
name: @name,
|
375
|
-
klass: @klass,
|
408
|
+
klass: @klass.to_s,
|
376
409
|
cron: @cron,
|
377
410
|
description: @description,
|
378
411
|
args: @args.is_a?(String) ? @args : Sidekiq.dump_json(@args || []),
|
379
412
|
message: @message.is_a?(String) ? @message : Sidekiq.dump_json(@message || {}),
|
380
413
|
status: @status,
|
381
|
-
active_job: @active_job,
|
414
|
+
active_job: @active_job ? "1" : "0",
|
382
415
|
queue_name_prefix: @active_job_queue_name_prefix,
|
383
416
|
queue_name_delimiter: @active_job_queue_name_delimiter,
|
384
|
-
last_enqueue_time:
|
417
|
+
last_enqueue_time: serialized_last_enqueue_time,
|
418
|
+
symbolize_args: symbolize_args? ? "1" : "0",
|
385
419
|
}
|
420
|
+
|
421
|
+
if date_as_argument?
|
422
|
+
hash.merge!(date_as_argument: "1")
|
423
|
+
end
|
424
|
+
|
425
|
+
hash
|
386
426
|
end
|
387
427
|
|
388
428
|
def errors
|
@@ -390,7 +430,7 @@ module Sidekiq
|
|
390
430
|
end
|
391
431
|
|
392
432
|
def valid?
|
393
|
-
#
|
433
|
+
# Clear previous errors.
|
394
434
|
@errors = []
|
395
435
|
|
396
436
|
errors << "'name' must be set" if @name.nil? || @name.size == 0
|
@@ -398,21 +438,15 @@ module Sidekiq
|
|
398
438
|
errors << "'cron' must be set"
|
399
439
|
else
|
400
440
|
begin
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
#fix for different versions of cron-parser
|
405
|
-
if e.message == "Bad Vixie-style specification bad"
|
406
|
-
errors << "'cron' -> #{@cron}: not a valid cronline"
|
407
|
-
else
|
408
|
-
errors << "'cron' -> #{@cron}: #{e.message}"
|
409
|
-
end
|
441
|
+
@parsed_cron = Fugit.do_parse_cronish(@cron)
|
442
|
+
rescue => e
|
443
|
+
errors << "'cron' -> #{@cron.inspect} -> #{e.class}: #{e.message}"
|
410
444
|
end
|
411
445
|
end
|
412
446
|
|
413
447
|
errors << "'klass' (or class) must be set" unless klass_valid
|
414
448
|
|
415
|
-
|
449
|
+
errors.empty?
|
416
450
|
end
|
417
451
|
|
418
452
|
def klass_valid
|
@@ -425,67 +459,75 @@ module Sidekiq
|
|
425
459
|
end
|
426
460
|
end
|
427
461
|
|
428
|
-
# add job to cron jobs
|
429
|
-
# input:
|
430
|
-
# name: (string) - name of job
|
431
|
-
# cron: (string: '* * * * *' - cron specification when to run job
|
432
|
-
# class: (string|class) - which class to perform
|
433
|
-
# optional input:
|
434
|
-
# queue: (string) - which queue to use for enquing (will override class queue)
|
435
|
-
# args: (array|hash|nil) - arguments for permorm method
|
436
|
-
|
437
462
|
def save
|
438
|
-
#
|
463
|
+
# If job is invalid, return false.
|
439
464
|
return false unless valid?
|
440
465
|
|
441
466
|
Sidekiq.redis do |conn|
|
442
467
|
|
443
|
-
#
|
444
|
-
conn.sadd self.class.jobs_key, redis_key
|
468
|
+
# Add to set of all jobs
|
469
|
+
conn.sadd self.class.jobs_key, [redis_key]
|
445
470
|
|
446
|
-
#
|
471
|
+
# Add informations for this job!
|
447
472
|
conn.hmset redis_key, *hash_to_redis(to_hash)
|
448
473
|
|
449
|
-
#
|
474
|
+
# Add information about last time! - don't enque right after scheduler poller starts!
|
450
475
|
time = Time.now.utc
|
451
|
-
conn.
|
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
|
452
478
|
end
|
453
|
-
logger.info { "Cron Jobs -
|
479
|
+
Sidekiq.logger.info { "Cron Jobs - added job with name: #{@name}" }
|
454
480
|
end
|
455
481
|
|
456
482
|
def save_last_enqueue_time
|
457
483
|
Sidekiq.redis do |conn|
|
458
|
-
#
|
459
|
-
conn.hset redis_key, 'last_enqueue_time',
|
484
|
+
# Update last enqueue time.
|
485
|
+
conn.hset redis_key, 'last_enqueue_time', serialized_last_enqueue_time
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
def add_jid_history(jid)
|
490
|
+
jid_history = {
|
491
|
+
jid: jid,
|
492
|
+
enqueued: @last_enqueue_time
|
493
|
+
}
|
494
|
+
|
495
|
+
@history_size ||= (Sidekiq::Options[:cron_history_size] || 10).to_i - 1
|
496
|
+
Sidekiq.redis do |conn|
|
497
|
+
conn.lpush jid_history_key,
|
498
|
+
Sidekiq.dump_json(jid_history)
|
499
|
+
# Keep only last 10 entries in a fifo manner.
|
500
|
+
conn.ltrim jid_history_key, 0, @history_size
|
460
501
|
end
|
461
502
|
end
|
462
503
|
|
463
|
-
# remove job from cron jobs by name
|
464
|
-
# input:
|
465
|
-
# first arg: name (string) - name of job (must be same - case sensitive)
|
466
504
|
def destroy
|
467
505
|
Sidekiq.redis do |conn|
|
468
|
-
#
|
469
|
-
conn.srem self.class.jobs_key, redis_key
|
506
|
+
# Delete from set.
|
507
|
+
conn.srem self.class.jobs_key, [redis_key]
|
470
508
|
|
471
|
-
#
|
509
|
+
# Delete runned timestamps.
|
472
510
|
conn.del job_enqueued_key
|
473
511
|
|
474
|
-
#
|
512
|
+
# Delete jid_history.
|
513
|
+
conn.del jid_history_key
|
514
|
+
|
515
|
+
# Delete main job.
|
475
516
|
conn.del redis_key
|
476
517
|
end
|
477
|
-
|
518
|
+
|
519
|
+
Sidekiq.logger.info { "Cron Jobs - deleted job with name: #{@name}" }
|
478
520
|
end
|
479
521
|
|
480
|
-
#
|
522
|
+
# Remove all job from cron.
|
481
523
|
def self.destroy_all!
|
482
524
|
all.each do |job|
|
483
525
|
job.destroy
|
484
526
|
end
|
485
|
-
logger.info { "Cron Jobs - deleted all jobs" }
|
527
|
+
Sidekiq.logger.info { "Cron Jobs - deleted all jobs" }
|
486
528
|
end
|
487
529
|
|
488
|
-
#
|
530
|
+
# Remove "removed jobs" between current jobs and new jobs
|
489
531
|
def self.destroy_removed_jobs new_job_names
|
490
532
|
current_job_names = Sidekiq::Cron::Job.all.map(&:name)
|
491
533
|
removed_job_names = current_job_names - new_job_names
|
@@ -496,23 +538,22 @@ module Sidekiq
|
|
496
538
|
# Parse cron specification '* * * * *' and returns
|
497
539
|
# time when last run should be performed
|
498
540
|
def last_time now = Time.now.utc
|
499
|
-
|
541
|
+
parsed_cron.previous_time(now.utc).utc
|
500
542
|
end
|
501
543
|
|
502
|
-
def
|
544
|
+
def formatted_enqueue_time now = Time.now.utc
|
503
545
|
last_time(now).getutc.to_f.to_s
|
504
546
|
end
|
505
547
|
|
506
|
-
def
|
548
|
+
def formatted_last_time now = Time.now.utc
|
507
549
|
last_time(now).getutc.iso8601
|
508
550
|
end
|
509
551
|
|
510
552
|
def self.exists? name
|
511
|
-
out =
|
512
|
-
|
513
|
-
out = conn.exists redis_key name
|
553
|
+
out = Sidekiq.redis do |conn|
|
554
|
+
conn.public_send(REDIS_EXISTS_METHOD, redis_key(name))
|
514
555
|
end
|
515
|
-
out
|
556
|
+
out == true || out == 1
|
516
557
|
end
|
517
558
|
|
518
559
|
def exists?
|
@@ -525,70 +566,158 @@ module Sidekiq
|
|
525
566
|
|
526
567
|
private
|
527
568
|
|
569
|
+
def parsed_cron
|
570
|
+
@parsed_cron ||= Fugit.parse_cronish(@cron)
|
571
|
+
end
|
572
|
+
|
528
573
|
def not_enqueued_after?(time)
|
529
574
|
@last_enqueue_time.nil? || @last_enqueue_time.to_i < last_time(time).to_i
|
530
575
|
end
|
531
576
|
|
532
577
|
# Try parsing inbound args into an array.
|
533
|
-
#
|
534
|
-
# try to load JSON, then failover
|
535
|
-
# to string array.
|
578
|
+
# Args from Redis will be encoded JSON,
|
579
|
+
# try to load JSON, then failover to string array.
|
536
580
|
def parse_args(args)
|
537
581
|
case args
|
582
|
+
when GlobalID::Identification
|
583
|
+
[convert_to_global_id_hash(args)]
|
538
584
|
when String
|
539
585
|
begin
|
540
|
-
Sidekiq.load_json(args)
|
586
|
+
parsed_args = Sidekiq.load_json(args)
|
587
|
+
symbolize_args? ? symbolize_args(parsed_args) : parsed_args
|
541
588
|
rescue JSON::ParserError
|
542
|
-
[*args]
|
589
|
+
[*args]
|
543
590
|
end
|
544
591
|
when Hash
|
545
|
-
|
592
|
+
args = serialize_argument(args)
|
593
|
+
symbolize_args? ? [symbolize_args(args)] : [args]
|
546
594
|
when Array
|
547
|
-
args
|
595
|
+
args = serialize_argument(args)
|
596
|
+
symbolize_args? ? symbolize_args(args) : args
|
597
|
+
else
|
598
|
+
[*args]
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
602
|
+
def symbolize_args?
|
603
|
+
@symbolize_args
|
604
|
+
end
|
605
|
+
|
606
|
+
def symbolize_args(input)
|
607
|
+
if input.is_a?(Array)
|
608
|
+
input.map do |arg|
|
609
|
+
if arg.respond_to?(:symbolize_keys)
|
610
|
+
arg.symbolize_keys
|
611
|
+
else
|
612
|
+
arg
|
613
|
+
end
|
614
|
+
end
|
615
|
+
elsif input.is_a?(Hash) && input.respond_to?(:symbolize_keys)
|
616
|
+
input.symbolize_keys
|
548
617
|
else
|
549
|
-
|
618
|
+
input
|
550
619
|
end
|
551
620
|
end
|
552
621
|
|
622
|
+
def parse_enqueue_time(timestamp)
|
623
|
+
DateTime.strptime(timestamp, LAST_ENQUEUE_TIME_FORMAT).to_time.utc
|
624
|
+
rescue ArgumentError
|
625
|
+
DateTime.parse(timestamp).to_time.utc
|
626
|
+
end
|
627
|
+
|
553
628
|
def not_past_scheduled_time?(current_time)
|
554
|
-
last_cron_time =
|
629
|
+
last_cron_time = parsed_cron.previous_time(current_time).utc
|
555
630
|
return false if (current_time.to_i - last_cron_time.to_i) > 60
|
556
631
|
true
|
557
632
|
end
|
558
633
|
|
559
|
-
# Redis key for set of all cron jobs
|
634
|
+
# Redis key for set of all cron jobs.
|
560
635
|
def self.jobs_key
|
561
636
|
"cron_jobs"
|
562
637
|
end
|
563
638
|
|
564
|
-
# Redis key for storing one cron job
|
639
|
+
# Redis key for storing one cron job.
|
565
640
|
def self.redis_key name
|
566
641
|
"cron_job:#{name}"
|
567
642
|
end
|
568
643
|
|
569
|
-
# Redis key for storing one cron job
|
644
|
+
# Redis key for storing one cron job.
|
570
645
|
def redis_key
|
571
646
|
self.class.redis_key @name
|
572
647
|
end
|
573
648
|
|
574
|
-
# Redis key for storing one cron job run times
|
575
|
-
# (when poller added job to queue)
|
649
|
+
# Redis key for storing one cron job run times (when poller added job to queue)
|
576
650
|
def self.job_enqueued_key name
|
577
651
|
"cron_job:#{name}:enqueued"
|
578
652
|
end
|
579
653
|
|
580
|
-
|
581
|
-
|
654
|
+
def self.jid_history_key name
|
655
|
+
"cron_job:#{name}:jid_history"
|
656
|
+
end
|
657
|
+
|
582
658
|
def job_enqueued_key
|
583
659
|
self.class.job_enqueued_key @name
|
584
660
|
end
|
585
661
|
|
586
|
-
|
587
|
-
|
662
|
+
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
|
588
667
|
def hash_to_redis hash
|
589
|
-
hash.
|
668
|
+
hash.flat_map{ |key, value| [key, value || ""] }
|
590
669
|
end
|
591
670
|
|
671
|
+
def serialized_last_enqueue_time
|
672
|
+
@last_enqueue_time&.strftime(LAST_ENQUEUE_TIME_FORMAT)
|
673
|
+
end
|
674
|
+
|
675
|
+
def convert_to_global_id_hash(argument)
|
676
|
+
{ GLOBALID_KEY => argument.to_global_id.to_s }
|
677
|
+
rescue URI::GID::MissingModelIdError
|
678
|
+
raise "Unable to serialize #{argument.class} " \
|
679
|
+
"without an id. (Maybe you forgot to call save?)"
|
680
|
+
end
|
681
|
+
|
682
|
+
def deserialize_argument(argument)
|
683
|
+
case argument
|
684
|
+
when String
|
685
|
+
argument
|
686
|
+
when Array
|
687
|
+
argument.map { |arg| deserialize_argument(arg) }
|
688
|
+
when Hash
|
689
|
+
if serialized_global_id?(argument)
|
690
|
+
deserialize_global_id argument
|
691
|
+
else
|
692
|
+
argument.transform_values { |v| deserialize_argument(v) }
|
693
|
+
end
|
694
|
+
else
|
695
|
+
argument
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
699
|
+
def serialized_global_id?(hash)
|
700
|
+
hash.size == 1 && hash.include?(GLOBALID_KEY)
|
701
|
+
end
|
702
|
+
|
703
|
+
def deserialize_global_id(hash)
|
704
|
+
GlobalID::Locator.locate hash[GLOBALID_KEY]
|
705
|
+
end
|
706
|
+
|
707
|
+
def serialize_argument(argument)
|
708
|
+
case argument
|
709
|
+
when GlobalID::Identification
|
710
|
+
convert_to_global_id_hash(argument)
|
711
|
+
when Array
|
712
|
+
argument.map { |arg| serialize_argument(arg) }
|
713
|
+
when Hash
|
714
|
+
argument.each_with_object({}) do |(key, value), hash|
|
715
|
+
hash[key] = serialize_argument(value)
|
716
|
+
end
|
717
|
+
else
|
718
|
+
argument
|
719
|
+
end
|
720
|
+
end
|
592
721
|
end
|
593
722
|
end
|
594
723
|
end
|