sidekiq-scheduler 2.2.2 → 3.0.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/README.md +8 -6
- data/lib/sidekiq-scheduler.rb +1 -26
- data/lib/sidekiq-scheduler/manager.rb +30 -8
- data/lib/sidekiq-scheduler/scheduler.rb +259 -314
- data/lib/sidekiq-scheduler/utils.rb +86 -0
- data/lib/sidekiq-scheduler/version.rb +1 -1
- data/lib/sidekiq-scheduler/web.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9baa032bdb214ffc3f0d229c0a5181f36a8be200
|
4
|
+
data.tar.gz: 011c8eaf157ceea568cc64ba125af2c7cc0706db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c2f4d826b83179acee13b422dfb4ab00542b747b17d4f30685d675e8938d1b4b8b06d362a348590a9363b65d4acbb3711c0577db4b1ab6f5d5a0cac32d44b97
|
7
|
+
data.tar.gz: db0db5b8556f897a50800856031102e3f2001d8bf4658e9daabd8b41a269da92d475b37f5f01026539c435433636cb441cc3123c7fe292091b43c7c5ff04a232
|
data/README.md
CHANGED
@@ -30,6 +30,8 @@
|
|
30
30
|
`sidekiq-scheduler` is an extension to [Sidekiq](http://github.com/mperham/sidekiq) that
|
31
31
|
pushes jobs in a scheduled way, mimicking cron utility.
|
32
32
|
|
33
|
+
__Note:__ If you are looking for version 2.2.*, go to [2.2-stable branch](https://github.com/moove-it/sidekiq-scheduler/tree/2.2-stable).
|
34
|
+
|
33
35
|
## Installation
|
34
36
|
|
35
37
|
``` shell
|
@@ -228,7 +230,7 @@ require 'sidekiq-scheduler'
|
|
228
230
|
Sidekiq.configure_server do |config|
|
229
231
|
config.on(:startup) do
|
230
232
|
Sidekiq.schedule = YAML.load_file(File.expand_path('../../sidekiq_scheduler.yml', __FILE__))
|
231
|
-
|
233
|
+
SidekiqScheduler::Scheduler.instance.reload_schedule!
|
232
234
|
end
|
233
235
|
end
|
234
236
|
```
|
@@ -256,7 +258,7 @@ If `:dynamic` flag is set to `false`, you'll have to reload the schedule manuall
|
|
256
258
|
side:
|
257
259
|
|
258
260
|
``` ruby
|
259
|
-
|
261
|
+
SidekiqScheduler::Scheduler.instance.reload_schedule!
|
260
262
|
```
|
261
263
|
|
262
264
|
Invoke `Sidekiq.get_schedule` to obtain the current schedule:
|
@@ -367,12 +369,12 @@ if Rails.env == 'production' && (defined?(Rails::Server) || defined?(Unicorn))
|
|
367
369
|
|
368
370
|
config.on(:startup) do
|
369
371
|
Sidekiq.schedule = YAML.load_file(File.expand_path('../../scheduler.yml', __FILE__))
|
370
|
-
|
372
|
+
SidekiqScheduler::Scheduler.instance.reload_schedule!
|
371
373
|
end
|
372
374
|
end
|
373
375
|
else
|
374
|
-
|
375
|
-
puts "
|
376
|
+
SidekiqScheduler::Scheduler.instance.enabled = false
|
377
|
+
puts "SidekiqScheduler::Scheduler.instance.enabled is #{SidekiqScheduler::Scheduler.instance.enabled.inspect}"
|
376
378
|
end
|
377
379
|
```
|
378
380
|
|
@@ -382,6 +384,6 @@ MIT License
|
|
382
384
|
|
383
385
|
## Copyright
|
384
386
|
|
385
|
-
Copyright 2013 -
|
387
|
+
Copyright 2013 - 2018 Moove-IT.
|
386
388
|
Copyright 2012 Morton Jonuschat.
|
387
389
|
Some parts copyright 2010 Ben VandenBos.
|
data/lib/sidekiq-scheduler.rb
CHANGED
@@ -10,35 +10,10 @@ require_relative 'sidekiq-scheduler/extensions/schedule'
|
|
10
10
|
Sidekiq.configure_server do |config|
|
11
11
|
|
12
12
|
config.on(:startup) do
|
13
|
-
dynamic = Sidekiq::Scheduler.dynamic
|
14
|
-
dynamic = dynamic.nil? ? config.options.fetch(:dynamic, false) : dynamic
|
15
|
-
|
16
|
-
dynamic_every = Sidekiq::Scheduler.dynamic_every
|
17
|
-
dynamic_every = dynamic_every.nil? ? config.options.fetch(:dynamic_every, '5s') : dynamic_every
|
18
|
-
|
19
|
-
enabled = Sidekiq::Scheduler.enabled
|
20
|
-
enabled = enabled.nil? ? config.options.fetch(:enabled, true) : enabled
|
21
|
-
|
22
|
-
scheduler = config.options.fetch(:scheduler, {})
|
23
|
-
|
24
|
-
listened_queues_only = Sidekiq::Scheduler.listened_queues_only
|
25
|
-
listened_queues_only = listened_queues_only.nil? ? scheduler[:listened_queues_only] : listened_queues_only
|
26
|
-
|
27
|
-
schedule = Sidekiq.schedule
|
28
|
-
schedule ||= config.options[:schedule] || {}
|
29
|
-
|
30
|
-
scheduler_options = {
|
31
|
-
dynamic: dynamic,
|
32
|
-
dynamic_every: dynamic_every,
|
33
|
-
enabled: enabled,
|
34
|
-
schedule: schedule,
|
35
|
-
listened_queues_only: listened_queues_only
|
36
|
-
}
|
37
|
-
|
38
13
|
# schedules_changed's type was changed from SET to ZSET, so we remove old versions at startup
|
39
14
|
SidekiqScheduler::RedisManager.clean_schedules_changed
|
40
15
|
|
41
|
-
schedule_manager = SidekiqScheduler::Manager.new(
|
16
|
+
schedule_manager = SidekiqScheduler::Manager.new(config.options)
|
42
17
|
config.options[:schedule_manager] = schedule_manager
|
43
18
|
config.options[:schedule_manager].start
|
44
19
|
end
|
@@ -14,26 +14,48 @@ module SidekiqScheduler
|
|
14
14
|
class Manager
|
15
15
|
include Sidekiq::Util
|
16
16
|
|
17
|
+
DEFAULT_SCHEDULER_OPTIONS = {
|
18
|
+
enabled: true,
|
19
|
+
dynamic: false,
|
20
|
+
dynamic_every: '5s',
|
21
|
+
schedule: {}
|
22
|
+
}
|
23
|
+
|
17
24
|
def initialize(options)
|
18
|
-
|
19
|
-
|
20
|
-
SidekiqScheduler::Scheduler.
|
21
|
-
SidekiqScheduler::Scheduler.
|
22
|
-
Sidekiq.schedule =
|
25
|
+
scheduler_options = load_scheduler_options(options)
|
26
|
+
|
27
|
+
@scheduler_instance = SidekiqScheduler::Scheduler.new(scheduler_options)
|
28
|
+
SidekiqScheduler::Scheduler.instance = @scheduler_instance
|
29
|
+
Sidekiq.schedule = scheduler_options[:schedule] if @scheduler_instance.enabled
|
23
30
|
end
|
24
31
|
|
25
32
|
def stop
|
26
|
-
|
33
|
+
@scheduler_instance.clear_schedule!
|
27
34
|
end
|
28
35
|
|
29
36
|
def start
|
30
|
-
|
37
|
+
@scheduler_instance.load_schedule!
|
31
38
|
end
|
32
39
|
|
33
40
|
def reset
|
34
41
|
clear_scheduled_work
|
35
42
|
end
|
36
43
|
|
37
|
-
|
44
|
+
private
|
45
|
+
|
46
|
+
def load_scheduler_options(options)
|
47
|
+
options[:listened_queues_only] = options.fetch(:scheduler, {})[:listened_queues_only]
|
48
|
+
scheduler_options = DEFAULT_SCHEDULER_OPTIONS.merge(options)
|
38
49
|
|
50
|
+
current_options = {
|
51
|
+
enabled: SidekiqScheduler::Scheduler.enabled,
|
52
|
+
dynamic: SidekiqScheduler::Scheduler.dynamic,
|
53
|
+
dynamic_every: SidekiqScheduler::Scheduler.dynamic_every,
|
54
|
+
schedule: Sidekiq.schedule,
|
55
|
+
listened_queues_only: SidekiqScheduler::Scheduler.listened_queues_only
|
56
|
+
}.delete_if { |_, value| value.nil? }
|
57
|
+
|
58
|
+
scheduler_options.merge(current_options)
|
59
|
+
end
|
60
|
+
end
|
39
61
|
end
|
@@ -10,400 +10,345 @@ module SidekiqScheduler
|
|
10
10
|
class Scheduler
|
11
11
|
extend Sidekiq::Util
|
12
12
|
|
13
|
-
RUFUS_METADATA_KEYS = %w(description at cron every in interval enabled)
|
14
|
-
|
15
13
|
# We expect rufus jobs to have #params
|
16
14
|
Rufus::Scheduler::Job.module_eval do
|
17
|
-
|
18
15
|
alias_method :params, :opts
|
19
|
-
|
20
16
|
end
|
21
17
|
|
22
|
-
|
18
|
+
# Set to enable or disable the scheduler.
|
19
|
+
attr_accessor :enabled
|
23
20
|
|
24
|
-
|
25
|
-
|
21
|
+
# Set to update the schedule in runtime in a given time period.
|
22
|
+
attr_accessor :dynamic
|
26
23
|
|
27
|
-
|
28
|
-
|
24
|
+
# Set to update the schedule in runtime dynamically per this period.
|
25
|
+
attr_accessor :dynamic_every
|
29
26
|
|
30
|
-
|
31
|
-
|
27
|
+
# Set to schedule jobs only when will be pushed to queues listened by sidekiq
|
28
|
+
attr_accessor :listened_queues_only
|
32
29
|
|
33
|
-
|
34
|
-
attr_accessor :listened_queues_only
|
30
|
+
class << self
|
35
31
|
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
def instance
|
33
|
+
@instance = new unless @instance
|
34
|
+
@instance
|
39
35
|
end
|
40
36
|
|
41
|
-
def
|
42
|
-
|
43
|
-
logger.info "Scheduling Info\tLast Run"
|
44
|
-
scheduler_jobs = rufus_scheduler.all_jobs
|
45
|
-
scheduler_jobs.each_value do |v|
|
46
|
-
logger.info "#{v.t}\t#{v.last}\t"
|
47
|
-
end
|
48
|
-
end
|
37
|
+
def instance=(value)
|
38
|
+
@instance = value
|
49
39
|
end
|
50
40
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
logger.info 'Loading Schedule'
|
56
|
-
|
57
|
-
# Load schedule from redis for the first time if dynamic
|
58
|
-
if dynamic
|
59
|
-
Sidekiq.reload_schedule!
|
60
|
-
@current_changed_score = Time.now.to_f
|
61
|
-
rufus_scheduler.every(dynamic_every) do
|
62
|
-
update_schedule
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
logger.info 'Schedule empty! Set Sidekiq.schedule' if Sidekiq.schedule.empty?
|
67
|
-
|
41
|
+
def method_missing(method, *arguments, &block)
|
42
|
+
instance_methods.include?(method) ? instance.public_send(method, *arguments) : super
|
43
|
+
end
|
44
|
+
end
|
68
45
|
|
69
|
-
|
70
|
-
|
46
|
+
def initialize(options = {})
|
47
|
+
self.enabled = options[:enabled]
|
48
|
+
self.dynamic = options[:dynamic]
|
49
|
+
self.dynamic_every = options[:dynamic_every]
|
50
|
+
self.listened_queues_only = options[:listened_queues_only]
|
51
|
+
end
|
71
52
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
logger.info { "Ignoring #{name}, job's queue is not enabled." }
|
77
|
-
end
|
78
|
-
end
|
53
|
+
# the Rufus::Scheduler jobs that are scheduled
|
54
|
+
def scheduled_jobs
|
55
|
+
@scheduled_jobs
|
56
|
+
end
|
79
57
|
|
80
|
-
|
81
|
-
|
82
|
-
|
58
|
+
def print_schedule
|
59
|
+
if rufus_scheduler
|
60
|
+
Sidekiq.logger.info "Scheduling Info\tLast Run"
|
61
|
+
scheduler_jobs = rufus_scheduler.all_jobs
|
62
|
+
scheduler_jobs.each_value do |v|
|
63
|
+
Sidekiq.logger.info "#{v.t}\t#{v.last}\t"
|
83
64
|
end
|
84
65
|
end
|
66
|
+
end
|
85
67
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
# to.
|
92
|
-
if config['rails_env'].nil? || rails_env_matches?(config)
|
93
|
-
logger.info "Scheduling #{name} #{config}"
|
94
|
-
interval_defined = false
|
95
|
-
interval_types = %w{cron every at in interval}
|
96
|
-
interval_types.each do |interval_type|
|
97
|
-
config_interval_type = config[interval_type]
|
98
|
-
|
99
|
-
if !config_interval_type.nil? && config_interval_type.length > 0
|
68
|
+
# Pulls the schedule from Sidekiq.schedule and loads it into the
|
69
|
+
# rufus scheduler instance
|
70
|
+
def load_schedule!
|
71
|
+
if enabled
|
72
|
+
Sidekiq.logger.info 'Loading Schedule'
|
100
73
|
|
101
|
-
|
74
|
+
# Load schedule from redis for the first time if dynamic
|
75
|
+
if dynamic
|
76
|
+
Sidekiq.reload_schedule!
|
77
|
+
@current_changed_score = Time.now.to_f
|
78
|
+
rufus_scheduler.every(dynamic_every) do
|
79
|
+
update_schedule
|
80
|
+
end
|
81
|
+
end
|
102
82
|
|
103
|
-
|
104
|
-
@@scheduled_jobs[name] = rufus_job
|
105
|
-
update_job_next_time(name, rufus_job.next_time)
|
83
|
+
Sidekiq.logger.info 'Schedule empty! Set Sidekiq.schedule' if Sidekiq.schedule.empty?
|
106
84
|
|
107
|
-
interval_defined = true
|
108
85
|
|
109
|
-
|
110
|
-
|
111
|
-
end
|
86
|
+
@scheduled_jobs = {}
|
87
|
+
queues = sidekiq_queues
|
112
88
|
|
113
|
-
|
114
|
-
|
89
|
+
Sidekiq.schedule.each do |name, config|
|
90
|
+
if !listened_queues_only || enabled_queue?(config['queue'].to_s, queues)
|
91
|
+
load_schedule_job(name, config)
|
92
|
+
else
|
93
|
+
Sidekiq.logger.info { "Ignoring #{name}, job's queue is not enabled." }
|
115
94
|
end
|
116
95
|
end
|
117
|
-
end
|
118
96
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
def idempotent_job_enqueue(job_name, time, config)
|
125
|
-
registered = register_job_instance(job_name, time)
|
97
|
+
Sidekiq.logger.info 'Schedules Loaded'
|
98
|
+
else
|
99
|
+
Sidekiq.logger.info 'SidekiqScheduler is disabled'
|
100
|
+
end
|
101
|
+
end
|
126
102
|
|
127
|
-
|
128
|
-
|
103
|
+
# Loads a job schedule into the Rufus::Scheduler and stores it in @scheduled_jobs
|
104
|
+
def load_schedule_job(name, config)
|
105
|
+
# If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
|
106
|
+
# required for the jobs to be scheduled. If rails_env is missing, the
|
107
|
+
# job should be scheduled regardless of what ENV['RAILS_ENV'] is set
|
108
|
+
# to.
|
109
|
+
if config['rails_env'].nil? || rails_env_matches?(config)
|
110
|
+
Sidekiq.logger.info "Scheduling #{name} #{config}"
|
111
|
+
interval_defined = false
|
112
|
+
interval_types = %w(cron every at in interval)
|
113
|
+
interval_types.each do |interval_type|
|
114
|
+
config_interval_type = config[interval_type]
|
129
115
|
|
130
|
-
|
116
|
+
if !config_interval_type.nil? && config_interval_type.length > 0
|
131
117
|
|
132
|
-
|
133
|
-
else
|
134
|
-
logger.debug { "Ignoring #{job_name} job as it has been already enqueued" }
|
135
|
-
end
|
136
|
-
end
|
118
|
+
schedule, options = SidekiqScheduler::RufusUtils.normalize_schedule_options(config_interval_type)
|
137
119
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
# @param [Time] next_time The job's next time execution
|
142
|
-
def update_job_next_time(name, next_time)
|
143
|
-
if next_time
|
144
|
-
SidekiqScheduler::RedisManager.set_job_next_time(name, next_time)
|
145
|
-
else
|
146
|
-
SidekiqScheduler::RedisManager.remove_job_next_time(name)
|
147
|
-
end
|
148
|
-
end
|
120
|
+
rufus_job = new_job(name, interval_type, config, schedule, options)
|
121
|
+
@scheduled_jobs[name] = rufus_job
|
122
|
+
SidekiqScheduler::Utils.update_job_next_time(name, rufus_job.next_time)
|
149
123
|
|
150
|
-
|
151
|
-
#
|
152
|
-
# @param [String] name The job's name
|
153
|
-
# @param [Time] last_time The job's last execution time
|
154
|
-
def update_job_last_time(name, last_time)
|
155
|
-
SidekiqScheduler::RedisManager.set_job_last_time(name, last_time) if last_time
|
156
|
-
end
|
124
|
+
interval_defined = true
|
157
125
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/, '').split(',').include?(ENV['RAILS_ENV'])
|
162
|
-
end
|
126
|
+
break
|
127
|
+
end
|
128
|
+
end
|
163
129
|
|
164
|
-
|
165
|
-
|
166
|
-
yield
|
167
|
-
rescue StandardError => e
|
168
|
-
logger.info "#{e.class.name}: #{e.message}"
|
130
|
+
unless interval_defined
|
131
|
+
Sidekiq.logger.info "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
|
169
132
|
end
|
170
133
|
end
|
134
|
+
end
|
171
135
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
136
|
+
# Pushes the job into Sidekiq if not already pushed for the given time
|
137
|
+
#
|
138
|
+
# @param [String] job_name The job's name
|
139
|
+
# @param [Time] time The time when the job got cleared for triggering
|
140
|
+
# @param [Hash] config Job's config hash
|
141
|
+
def idempotent_job_enqueue(job_name, time, config)
|
142
|
+
registered = SidekiqScheduler::RedisManager.register_job_instance(job_name, time)
|
178
143
|
|
179
|
-
|
180
|
-
|
181
|
-
end
|
144
|
+
if registered
|
145
|
+
Sidekiq.logger.info "queueing #{config['class']} (#{job_name})"
|
182
146
|
|
183
|
-
|
184
|
-
enqueue_with_active_job(config)
|
185
|
-
else
|
186
|
-
enqueue_with_sidekiq(config)
|
187
|
-
end
|
188
|
-
end
|
147
|
+
handle_errors { enqueue_job(config, time) }
|
189
148
|
|
190
|
-
|
191
|
-
|
149
|
+
SidekiqScheduler::RedisManager.remove_elder_job_instances(job_name)
|
150
|
+
else
|
151
|
+
Sidekiq.logger.debug { "Ignoring #{job_name} job as it has been already enqueued" }
|
192
152
|
end
|
153
|
+
end
|
193
154
|
|
194
|
-
|
195
|
-
|
196
|
-
|
155
|
+
# Enqueue a job based on a config hash
|
156
|
+
#
|
157
|
+
# @param job_config [Hash] the job configuration
|
158
|
+
# @param time [Time] time the job is enqueued
|
159
|
+
def enqueue_job(job_config, time = Time.now)
|
160
|
+
config = prepare_arguments(job_config.dup)
|
197
161
|
|
198
|
-
|
199
|
-
|
162
|
+
if config.delete('include_metadata')
|
163
|
+
config['args'] = arguments_with_metadata(config['args'], scheduled_at: time.to_f)
|
200
164
|
end
|
201
165
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
def clear_schedule!(stop_option = :wait)
|
207
|
-
rufus_scheduler.stop(stop_option)
|
208
|
-
@rufus_scheduler = nil
|
209
|
-
@@scheduled_jobs = {}
|
210
|
-
rufus_scheduler
|
166
|
+
if active_job_enqueue?(config['class'])
|
167
|
+
SidekiqScheduler::Utils.enqueue_with_active_job(config)
|
168
|
+
else
|
169
|
+
SidekiqScheduler::Utils.enqueue_with_sidekiq(config)
|
211
170
|
end
|
171
|
+
end
|
212
172
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
clear_schedule!
|
217
|
-
load_schedule!
|
218
|
-
else
|
219
|
-
logger.info 'SidekiqScheduler is disabled'
|
220
|
-
end
|
221
|
-
end
|
173
|
+
def rufus_scheduler_options
|
174
|
+
@rufus_scheduler_options ||= {}
|
175
|
+
end
|
222
176
|
|
223
|
-
|
224
|
-
|
225
|
-
|
177
|
+
def rufus_scheduler_options=(options)
|
178
|
+
@rufus_scheduler_options = options
|
179
|
+
end
|
226
180
|
|
227
|
-
|
228
|
-
|
181
|
+
def rufus_scheduler
|
182
|
+
@rufus_scheduler ||= SidekiqScheduler::Utils.new_rufus_scheduler(rufus_scheduler_options)
|
183
|
+
end
|
229
184
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
end
|
241
|
-
end
|
185
|
+
# Stops old rufus scheduler and creates a new one. Returns the new
|
186
|
+
# rufus scheduler
|
187
|
+
#
|
188
|
+
# @param [Symbol] stop_option The option to be passed to Rufus::Scheduler#stop
|
189
|
+
def clear_schedule!(stop_option = :wait)
|
190
|
+
rufus_scheduler.stop(stop_option)
|
191
|
+
@rufus_scheduler = nil
|
192
|
+
@@scheduled_jobs = {}
|
193
|
+
rufus_scheduler
|
194
|
+
end
|
242
195
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
196
|
+
def reload_schedule!
|
197
|
+
if enabled
|
198
|
+
Sidekiq.logger.info 'Reloading Schedule'
|
199
|
+
clear_schedule!
|
200
|
+
load_schedule!
|
201
|
+
else
|
202
|
+
Sidekiq.logger.info 'SidekiqScheduler is disabled'
|
249
203
|
end
|
204
|
+
end
|
250
205
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
}.keep_if { |_, v| !v.nil? }
|
255
|
-
|
256
|
-
initialize_active_job(config['class'], config['args']).enqueue(options)
|
257
|
-
end
|
206
|
+
def update_schedule
|
207
|
+
last_changed_score, @current_changed_score = @current_changed_score, Time.now.to_f
|
208
|
+
schedule_changes = SidekiqScheduler::RedisManager.get_schedule_changes(last_changed_score, @current_changed_score)
|
258
209
|
|
259
|
-
|
260
|
-
Sidekiq
|
261
|
-
end
|
210
|
+
if schedule_changes.size > 0
|
211
|
+
Sidekiq.logger.info 'Updating schedule'
|
262
212
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
213
|
+
Sidekiq.reload_schedule!
|
214
|
+
schedule_changes.each do |schedule_name|
|
215
|
+
if Sidekiq.schedule.keys.include?(schedule_name)
|
216
|
+
unschedule_job(schedule_name)
|
217
|
+
load_schedule_job(schedule_name, Sidekiq.schedule[schedule_name])
|
218
|
+
else
|
219
|
+
unschedule_job(schedule_name)
|
220
|
+
end
|
268
221
|
end
|
222
|
+
Sidekiq.logger.info 'Schedule updated'
|
269
223
|
end
|
224
|
+
end
|
270
225
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
#
|
276
|
-
# @return [Boolean]
|
277
|
-
def active_job_enqueue?(klass)
|
278
|
-
klass.is_a?(Class) && defined?(ActiveJob::Enqueuing) &&
|
279
|
-
klass.included_modules.include?(ActiveJob::Enqueuing)
|
280
|
-
end
|
281
|
-
|
282
|
-
# Convert the given arguments in the format expected to be enqueued.
|
283
|
-
#
|
284
|
-
# @param [Hash] config the options to be converted
|
285
|
-
# @option config [String] class the job class
|
286
|
-
# @option config [Hash/Array] args the arguments to be passed to the job
|
287
|
-
# class
|
288
|
-
#
|
289
|
-
# @return [Hash]
|
290
|
-
def prepare_arguments(config)
|
291
|
-
config['class'] = try_to_constantize(config['class'])
|
292
|
-
|
293
|
-
if config['args'].is_a?(Hash)
|
294
|
-
config['args'].symbolize_keys! if config['args'].respond_to?(:symbolize_keys!)
|
295
|
-
else
|
296
|
-
config['args'] = Array(config['args'])
|
297
|
-
end
|
226
|
+
def job_enabled?(name)
|
227
|
+
job = Sidekiq.schedule[name]
|
228
|
+
schedule_state(name).fetch('enabled', job.fetch('enabled', true)) if job
|
229
|
+
end
|
298
230
|
|
299
|
-
|
300
|
-
|
231
|
+
def toggle_job_enabled(name)
|
232
|
+
state = schedule_state(name)
|
233
|
+
state['enabled'] = !job_enabled?(name)
|
234
|
+
set_schedule_state(name, state)
|
235
|
+
end
|
301
236
|
|
302
|
-
|
303
|
-
klass.is_a?(String) ? klass.constantize : klass
|
304
|
-
rescue NameError
|
305
|
-
klass
|
306
|
-
end
|
237
|
+
private
|
307
238
|
|
308
|
-
|
309
|
-
|
310
|
-
# If queues are empty, returns true.
|
311
|
-
#
|
312
|
-
# @param [String] job_queue Job's queue name
|
313
|
-
# @param [Array<String>] queues
|
314
|
-
#
|
315
|
-
# @return [Boolean]
|
316
|
-
def enabled_queue?(job_queue, queues)
|
317
|
-
queues.empty? || queues.include?(job_queue)
|
318
|
-
end
|
239
|
+
def new_job(name, interval_type, config, schedule, options)
|
240
|
+
options = options.merge({ :job => true, :tags => [name] })
|
319
241
|
|
320
|
-
|
321
|
-
|
322
|
-
# @param [String] job_name The job's name
|
323
|
-
# @param [Time] time Time at which the job was cleared by the scheduler
|
324
|
-
#
|
325
|
-
# @return [Boolean] true if the job was registered, false when otherwise
|
326
|
-
def register_job_instance(job_name, time)
|
327
|
-
SidekiqScheduler::RedisManager.register_job_instance(job_name, time)
|
242
|
+
rufus_scheduler.send(interval_type, schedule, options) do |job, time|
|
243
|
+
idempotent_job_enqueue(name, time, SidekiqScheduler::Utils.sanitize_job_config(config)) if job_enabled?(name)
|
328
244
|
end
|
245
|
+
end
|
329
246
|
|
330
|
-
|
331
|
-
|
247
|
+
def unschedule_job(name)
|
248
|
+
if scheduled_jobs[name]
|
249
|
+
Sidekiq.logger.debug "Removing schedule #{name}"
|
250
|
+
scheduled_jobs[name].unschedule
|
251
|
+
scheduled_jobs.delete(name)
|
332
252
|
end
|
253
|
+
end
|
333
254
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
255
|
+
# Retrieves a schedule state
|
256
|
+
#
|
257
|
+
# @param name [String] with the schedule's name
|
258
|
+
# @return [Hash] with the schedule's state
|
259
|
+
def schedule_state(name)
|
260
|
+
state = SidekiqScheduler::RedisManager.get_job_state(name)
|
338
261
|
|
339
|
-
|
340
|
-
|
341
|
-
state['enabled'] = !job_enabled?(name)
|
342
|
-
set_schedule_state(name, state)
|
343
|
-
end
|
262
|
+
state ? JSON.parse(state) : {}
|
263
|
+
end
|
344
264
|
|
345
|
-
|
265
|
+
# Saves a schedule state
|
266
|
+
#
|
267
|
+
# @param name [String] with the schedule's name
|
268
|
+
# @param name [Hash] with the schedule's state
|
269
|
+
def set_schedule_state(name, state)
|
270
|
+
SidekiqScheduler::RedisManager.set_job_state(name, state)
|
271
|
+
end
|
346
272
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
273
|
+
# Adds a Hash with schedule metadata as the last argument to call the worker.
|
274
|
+
# It currently returns the schedule time as a Float number representing the milisencods
|
275
|
+
# since epoch.
|
276
|
+
#
|
277
|
+
# @example with hash argument
|
278
|
+
# arguments_with_metadata({value: 1}, scheduled_at: Time.now)
|
279
|
+
# #=> [{value: 1}, {scheduled_at: <miliseconds since epoch>}]
|
280
|
+
#
|
281
|
+
# @param args [Array|Hash]
|
282
|
+
# @param metadata [Hash]
|
283
|
+
# @return [Array] arguments with added metadata
|
284
|
+
def arguments_with_metadata(args, metadata)
|
285
|
+
if args.is_a? Array
|
286
|
+
[*args, metadata]
|
287
|
+
else
|
288
|
+
[args, metadata]
|
354
289
|
end
|
290
|
+
end
|
355
291
|
|
356
|
-
|
357
|
-
|
292
|
+
def sidekiq_queues
|
293
|
+
Sidekiq.options[:queues].map(&:to_s)
|
294
|
+
end
|
358
295
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
296
|
+
# Returns true if a job's queue is included in the array of queues
|
297
|
+
#
|
298
|
+
# If queues are empty, returns true.
|
299
|
+
#
|
300
|
+
# @param [String] job_queue Job's queue name
|
301
|
+
# @param [Array<String>] queues
|
302
|
+
#
|
303
|
+
# @return [Boolean]
|
304
|
+
def enabled_queue?(job_queue, queues)
|
305
|
+
queues.empty? || queues.include?(job_queue)
|
306
|
+
end
|
363
307
|
|
364
|
-
|
365
|
-
|
366
|
-
|
308
|
+
# Returns true if the enqueuing needs to be done for an ActiveJob
|
309
|
+
# class false otherwise.
|
310
|
+
#
|
311
|
+
# @param [Class] klass the class to check is decendant from ActiveJob
|
312
|
+
#
|
313
|
+
# @return [Boolean]
|
314
|
+
def active_job_enqueue?(klass)
|
315
|
+
klass.is_a?(Class) && defined?(ActiveJob::Enqueuing) &&
|
316
|
+
klass.included_modules.include?(ActiveJob::Enqueuing)
|
317
|
+
end
|
367
318
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
319
|
+
# Convert the given arguments in the format expected to be enqueued.
|
320
|
+
#
|
321
|
+
# @param [Hash] config the options to be converted
|
322
|
+
# @option config [String] class the job class
|
323
|
+
# @option config [Hash/Array] args the arguments to be passed to the job
|
324
|
+
# class
|
325
|
+
#
|
326
|
+
# @return [Hash]
|
327
|
+
def prepare_arguments(config)
|
328
|
+
config['class'] = SidekiqScheduler::Utils.try_to_constantize(config['class'])
|
374
329
|
|
375
|
-
|
330
|
+
if config['args'].is_a?(Hash)
|
331
|
+
config['args'].symbolize_keys! if config['args'].respond_to?(:symbolize_keys!)
|
332
|
+
else
|
333
|
+
config['args'] = Array(config['args'])
|
376
334
|
end
|
377
335
|
|
378
|
-
|
379
|
-
|
380
|
-
# @param name [String] with the schedule's name
|
381
|
-
# @param name [Hash] with the schedule's state
|
382
|
-
def set_schedule_state(name, state)
|
383
|
-
SidekiqScheduler::RedisManager.set_job_state(name, state)
|
384
|
-
end
|
336
|
+
config
|
337
|
+
end
|
385
338
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
#
|
394
|
-
# @param args [Array|Hash]
|
395
|
-
# @param metadata [Hash]
|
396
|
-
# @return [Array] arguments with added metadata
|
397
|
-
def arguments_with_metadata(args, metadata)
|
398
|
-
if args.is_a? Array
|
399
|
-
[*args, metadata]
|
400
|
-
else
|
401
|
-
[args, metadata]
|
402
|
-
end
|
403
|
-
end
|
339
|
+
# Returns true if the given schedule config hash matches the current ENV['RAILS_ENV']
|
340
|
+
# @param [Hash] config The schedule job configuration
|
341
|
+
#
|
342
|
+
# @return [Boolean] true if the schedule config matches the current ENV['RAILS_ENV']
|
343
|
+
def rails_env_matches?(config)
|
344
|
+
config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/, '').split(',').include?(ENV['RAILS_ENV'])
|
345
|
+
end
|
404
346
|
|
405
|
-
|
406
|
-
|
347
|
+
def handle_errors
|
348
|
+
begin
|
349
|
+
yield
|
350
|
+
rescue StandardError => e
|
351
|
+
Sidekiq.logger.info "#{e.class.name}: #{e.message}"
|
407
352
|
end
|
408
353
|
end
|
409
354
|
end
|
@@ -3,6 +3,8 @@ require 'set'
|
|
3
3
|
module SidekiqScheduler
|
4
4
|
module Utils
|
5
5
|
|
6
|
+
RUFUS_METADATA_KEYS = %w(description at cron every in interval enabled)
|
7
|
+
|
6
8
|
# Stringify keys belonging to a hash.
|
7
9
|
#
|
8
10
|
# Also stringifies nested keys and keys of hashes inside arrays, and sets
|
@@ -40,5 +42,89 @@ module SidekiqScheduler
|
|
40
42
|
object
|
41
43
|
end
|
42
44
|
end
|
45
|
+
|
46
|
+
# Constantize a given string.
|
47
|
+
#
|
48
|
+
# @param [String] klass The string to constantize
|
49
|
+
#
|
50
|
+
# @return [Class] the class corresponding to the klass param
|
51
|
+
def self.try_to_constantize(klass)
|
52
|
+
klass.is_a?(String) ? klass.constantize : klass
|
53
|
+
rescue NameError
|
54
|
+
klass
|
55
|
+
end
|
56
|
+
|
57
|
+
# Initializes active_job using the passed parameters.
|
58
|
+
#
|
59
|
+
# @param [Class] klass The class to initialize
|
60
|
+
# @param [Array/Hash] the parameters passed to the klass initializer
|
61
|
+
#
|
62
|
+
# @return [Object] instance of the class klass
|
63
|
+
def self.initialize_active_job(klass, args)
|
64
|
+
if args.is_a?(Array)
|
65
|
+
klass.new(*args)
|
66
|
+
else
|
67
|
+
klass.new(args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Enqueues the job using the Sidekiq client.
|
72
|
+
#
|
73
|
+
# @param [Hash] config The job configuration
|
74
|
+
def self.enqueue_with_sidekiq(config)
|
75
|
+
Sidekiq::Client.push(sanitize_job_config(config))
|
76
|
+
end
|
77
|
+
|
78
|
+
# Enqueues the job using the ActiveJob.
|
79
|
+
#
|
80
|
+
# @param [Hash] config The job configuration
|
81
|
+
def self.enqueue_with_active_job(config)
|
82
|
+
options = {
|
83
|
+
queue: config['queue']
|
84
|
+
}.keep_if { |_, v| !v.nil? }
|
85
|
+
|
86
|
+
initialize_active_job(config['class'], config['args']).enqueue(options)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Removes the hash values associated to the rufus metadata keys.
|
90
|
+
#
|
91
|
+
# @param [Hash] config The job configuration
|
92
|
+
#
|
93
|
+
# @return [Hash] the sanitized job config
|
94
|
+
def self.sanitize_job_config(config)
|
95
|
+
config.reject { |k, _| RUFUS_METADATA_KEYS.include?(k) }
|
96
|
+
end
|
97
|
+
|
98
|
+
# Creates a new instance of rufus scheduler.
|
99
|
+
#
|
100
|
+
# @return [Rufus::Scheduler] the scheduler instance
|
101
|
+
def self.new_rufus_scheduler(options = {})
|
102
|
+
Rufus::Scheduler.new(options).tap do |scheduler|
|
103
|
+
scheduler.define_singleton_method(:on_post_trigger) do |job, triggered_time|
|
104
|
+
SidekiqScheduler::Utils.update_job_last_time(job.tags[0], triggered_time)
|
105
|
+
SidekiqScheduler::Utils.update_job_next_time(job.tags[0], job.next_time)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Pushes job's next time execution
|
111
|
+
#
|
112
|
+
# @param [String] name The job's name
|
113
|
+
# @param [Time] next_time The job's next time execution
|
114
|
+
def self.update_job_next_time(name, next_time)
|
115
|
+
if next_time
|
116
|
+
SidekiqScheduler::RedisManager.set_job_next_time(name, next_time)
|
117
|
+
else
|
118
|
+
SidekiqScheduler::RedisManager.remove_job_next_time(name)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Pushes job's last execution time
|
123
|
+
#
|
124
|
+
# @param [String] name The job's name
|
125
|
+
# @param [Time] last_time The job's last execution time
|
126
|
+
def self.update_job_last_time(name, last_time)
|
127
|
+
SidekiqScheduler::RedisManager.set_job_last_time(name, last_time) if last_time
|
128
|
+
end
|
43
129
|
end
|
44
130
|
end
|
@@ -17,14 +17,14 @@ module SidekiqScheduler
|
|
17
17
|
|
18
18
|
app.get '/recurring-jobs/:name/enqueue' do
|
19
19
|
schedule = Sidekiq.get_schedule(params[:name])
|
20
|
-
SidekiqScheduler::Scheduler.enqueue_job(schedule)
|
20
|
+
SidekiqScheduler::Scheduler.instance.enqueue_job(schedule)
|
21
21
|
redirect "#{root_path}recurring-jobs"
|
22
22
|
end
|
23
23
|
|
24
24
|
app.get '/recurring-jobs/:name/toggle' do
|
25
25
|
Sidekiq.reload_schedule!
|
26
26
|
|
27
|
-
SidekiqScheduler::Scheduler.toggle_job_enabled(params[:name])
|
27
|
+
SidekiqScheduler::Scheduler.instance.toggle_job_enabled(params[:name])
|
28
28
|
redirect "#{root_path}recurring-jobs"
|
29
29
|
end
|
30
30
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-scheduler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Morton Jonuschat
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-
|
12
|
+
date: 2018-06-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sidekiq
|