sidekiq-scheduler 2.1.10 → 2.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a3f7cb5ee802d2e3b5c89b308fbcaff612911217
4
- data.tar.gz: e6b488b10334b3af01c9d2521e31ec3c53f0ef20
3
+ metadata.gz: ac3ac2c861cfb11bbe7e875ad7ed7cff7822c99b
4
+ data.tar.gz: 542352c4b0ed726ac98c4fe80a80c3f1e5280d88
5
5
  SHA512:
6
- metadata.gz: b56413d0c141f2ae15bfacb255a8eed2550b6b0f899703f5fd08d67539794878e411d9a5a48086f4fd9d7f542f6b78fe9a016b7edf32a78819b93c7860ecf2a2
7
- data.tar.gz: 6b8524661830f24e9233a95ec94a18ad31eac1f0880a63a3bc48f728e802f8f5ffe79437bfd5d17bdc3ec07a79d142b0d991b33a5a6363f79a4e6f1c387ee2c2
6
+ metadata.gz: 62ae98b53be7b7d25562d9ed8c0e16579cdcaa1fcdee4c75def8ba2f5c68496ac853487030577eb52feb6bdc6f883ab127f9f651eafb769589ad6612a0383722
7
+ data.tar.gz: bf5ef6b12451881a3be4ea14a6ced7fb1e384a870e1f608e6130a0fd51e7821e9d799f7db324ba07c9b6cf687e61c9f8b4bf9d8dd1d9572316c6092290430387
data/README.md CHANGED
@@ -287,9 +287,13 @@ ActiveSupport::TimeZone.find_tzinfo(Rails.configuration.time_zone).name
287
287
 
288
288
  ## Notes about running on Multiple Hosts
289
289
 
290
- `cron` and `at` jobs are pushed once regardless of the number of `sidekiq-scheduler` running instances,
290
+ Under normal conditions, `cron` and `at` jobs are pushed once regardless of the number of `sidekiq-scheduler` running instances,
291
291
  assumming that time deltas between hosts is less than 24 hours.
292
292
 
293
+ Non-normal conditions that could push a specific job multiple times are:
294
+ - high cpu load + a high number of jobs scheduled at the same time, like 100 jobs
295
+ - network / redis latency + 28 (see `MAX_WORK_THREADS` https://github.com/jmettraux/rufus-scheduler/blob/master/lib/rufus/scheduler.rb#L41) or more jobs scheduled within the same network latency window
296
+
293
297
  `every`, `interval` and `in` jobs will be pushed once per host.
294
298
 
295
299
  ## Sidekiq Web Integration
@@ -1,9 +1,11 @@
1
1
  require 'sidekiq'
2
2
  require 'tilt/erb'
3
3
 
4
+ require_relative 'sidekiq/scheduler'
4
5
  require_relative 'sidekiq-scheduler/version'
5
6
  require_relative 'sidekiq-scheduler/manager'
6
7
  require_relative 'sidekiq-scheduler/redis_manager'
8
+ require_relative 'sidekiq-scheduler/extensions/schedule'
7
9
 
8
10
  Sidekiq.configure_server do |config|
9
11
 
@@ -0,0 +1,4 @@
1
+ require 'sidekiq'
2
+ require_relative '../schedule'
3
+
4
+ Sidekiq.extend SidekiqScheduler::Schedule
@@ -0,0 +1,5 @@
1
+ require 'sidekiq/web' unless defined?(Sidekiq::Web)
2
+
3
+ Sidekiq::Web.register(SidekiqScheduler::Web)
4
+ Sidekiq::Web.tabs['recurring_jobs'] = 'recurring-jobs'
5
+ Sidekiq::Web.locales << File.expand_path(File.dirname(__FILE__) + '/../../../web/locales')
@@ -56,7 +56,7 @@ module SidekiqScheduler
56
56
  end
57
57
 
58
58
  def enabled?
59
- Sidekiq::Scheduler.job_enabled?(@name)
59
+ SidekiqScheduler::Scheduler.job_enabled?(@name)
60
60
  end
61
61
 
62
62
  # Builds the presenter instances for the schedule hash
@@ -2,8 +2,8 @@ require 'redis'
2
2
 
3
3
  require 'sidekiq/util'
4
4
 
5
- require 'sidekiq/scheduler'
6
5
  require 'sidekiq-scheduler/schedule'
6
+ require 'sidekiq-scheduler/scheduler'
7
7
 
8
8
  module SidekiqScheduler
9
9
 
@@ -15,19 +15,19 @@ module SidekiqScheduler
15
15
  include Sidekiq::Util
16
16
 
17
17
  def initialize(options)
18
- Sidekiq::Scheduler.enabled = options[:enabled]
19
- Sidekiq::Scheduler.dynamic = options[:dynamic]
20
- Sidekiq::Scheduler.dynamic_every = options[:dynamic_every]
21
- Sidekiq::Scheduler.listened_queues_only = options[:listened_queues_only]
22
- Sidekiq.schedule = options[:schedule] if Sidekiq::Scheduler.enabled
18
+ SidekiqScheduler::Scheduler.enabled = options[:enabled]
19
+ SidekiqScheduler::Scheduler.dynamic = options[:dynamic]
20
+ SidekiqScheduler::Scheduler.dynamic_every = options[:dynamic_every]
21
+ SidekiqScheduler::Scheduler.listened_queues_only = options[:listened_queues_only]
22
+ Sidekiq.schedule = options[:schedule] if SidekiqScheduler::Scheduler.enabled
23
23
  end
24
24
 
25
25
  def stop
26
- Sidekiq::Scheduler.clear_schedule!
26
+ SidekiqScheduler::Scheduler.clear_schedule!
27
27
  end
28
28
 
29
29
  def start
30
- Sidekiq::Scheduler.load_schedule!
30
+ SidekiqScheduler::Scheduler.load_schedule!
31
31
  end
32
32
 
33
33
  def reset
@@ -152,5 +152,3 @@ module SidekiqScheduler
152
152
  end
153
153
  end
154
154
  end
155
-
156
- Sidekiq.extend SidekiqScheduler::Schedule
@@ -0,0 +1,407 @@
1
+ require 'rufus/scheduler'
2
+ require 'thwait'
3
+ require 'sidekiq/util'
4
+ require 'json'
5
+ require 'sidekiq-scheduler/manager'
6
+ require 'sidekiq-scheduler/rufus_utils'
7
+ require 'sidekiq-scheduler/redis_manager'
8
+
9
+ module SidekiqScheduler
10
+ class Scheduler
11
+ extend Sidekiq::Util
12
+
13
+ RUFUS_METADATA_KEYS = %w(description at cron every in interval enabled)
14
+
15
+ # We expect rufus jobs to have #params
16
+ Rufus::Scheduler::Job.module_eval do
17
+
18
+ alias_method :params, :opts
19
+
20
+ end
21
+
22
+ class << self
23
+
24
+ # Set to enable or disable the scheduler.
25
+ attr_accessor :enabled
26
+
27
+ # Set to update the schedule in runtime in a given time period.
28
+ attr_accessor :dynamic
29
+
30
+ # Set to update the schedule in runtime dynamically per this period.
31
+ attr_accessor :dynamic_every
32
+
33
+ # Set to schedule jobs only when will be pushed to queues listened by sidekiq
34
+ attr_accessor :listened_queues_only
35
+
36
+ # the Rufus::Scheduler jobs that are scheduled
37
+ def scheduled_jobs
38
+ @@scheduled_jobs
39
+ end
40
+
41
+ def print_schedule
42
+ if rufus_scheduler
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
49
+ end
50
+
51
+ # Pulls the schedule from Sidekiq.schedule and loads it into the
52
+ # rufus scheduler instance
53
+ def load_schedule!
54
+ if enabled
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
+
68
+
69
+ @@scheduled_jobs = {}
70
+ queues = sidekiq_queues
71
+
72
+ Sidekiq.schedule.each do |name, config|
73
+ if !listened_queues_only || enabled_queue?(config['queue'].to_s, queues)
74
+ load_schedule_job(name, config)
75
+ else
76
+ logger.info { "Ignoring #{name}, job's queue is not enabled." }
77
+ end
78
+ end
79
+
80
+ logger.info 'Schedules Loaded'
81
+ else
82
+ logger.info 'SidekiqScheduler is disabled'
83
+ end
84
+ end
85
+
86
+ # Loads a job schedule into the Rufus::Scheduler and stores it in @@scheduled_jobs
87
+ def load_schedule_job(name, config)
88
+ # If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
89
+ # required for the jobs to be scheduled. If rails_env is missing, the
90
+ # job should be scheduled regardless of what ENV['RAILS_ENV'] is set
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
100
+
101
+ schedule, options = SidekiqScheduler::RufusUtils.normalize_schedule_options(config_interval_type)
102
+
103
+ rufus_job = new_job(name, interval_type, config, schedule, options)
104
+ @@scheduled_jobs[name] = rufus_job
105
+ update_job_next_time(name, rufus_job.next_time)
106
+
107
+ interval_defined = true
108
+
109
+ break
110
+ end
111
+ end
112
+
113
+ unless interval_defined
114
+ logger.info "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
115
+ end
116
+ end
117
+ end
118
+
119
+ # Pushes the job into Sidekiq if not already pushed for the given time
120
+ #
121
+ # @param [String] job_name The job's name
122
+ # @param [Time] time The time when the job got cleared for triggering
123
+ # @param [Hash] config Job's config hash
124
+ def idempotent_job_enqueue(job_name, time, config)
125
+ registered = register_job_instance(job_name, time)
126
+
127
+ if registered
128
+ logger.info "queueing #{config['class']} (#{job_name})"
129
+
130
+ handle_errors { enqueue_job(config, time) }
131
+
132
+ remove_elder_job_instances(job_name)
133
+ else
134
+ logger.debug { "Ignoring #{job_name} job as it has been already enqueued" }
135
+ end
136
+ end
137
+
138
+ # Pushes job's next time execution
139
+ #
140
+ # @param [String] name The job's name
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
149
+
150
+ # Pushes job's last execution time
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
157
+
158
+ # Returns true if the given schedule config hash matches the current
159
+ # ENV['RAILS_ENV']
160
+ def rails_env_matches?(config)
161
+ config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/, '').split(',').include?(ENV['RAILS_ENV'])
162
+ end
163
+
164
+ def handle_errors
165
+ begin
166
+ yield
167
+ rescue StandardError => e
168
+ logger.info "#{e.class.name}: #{e.message}"
169
+ end
170
+ end
171
+
172
+ # Enqueue a job based on a config hash
173
+ #
174
+ # @param job_config [Hash] the job configuration
175
+ # @param time [Time] time the job is enqueued
176
+ def enqueue_job(job_config, time=Time.now)
177
+ config = prepare_arguments(job_config.dup)
178
+
179
+ if config.delete('include_metadata')
180
+ config['args'] = arguments_with_metadata(config['args'], scheduled_at: time.to_f)
181
+ end
182
+
183
+ if active_job_enqueue?(config['class'])
184
+ enqueue_with_active_job(config)
185
+ else
186
+ enqueue_with_sidekiq(config)
187
+ end
188
+ end
189
+
190
+ def rufus_scheduler_options
191
+ @rufus_scheduler_options ||= {}
192
+ end
193
+
194
+ def rufus_scheduler_options=(options)
195
+ @rufus_scheduler_options = options
196
+ end
197
+
198
+ def rufus_scheduler
199
+ @rufus_scheduler ||= new_rufus_scheduler
200
+ end
201
+
202
+ # Stops old rufus scheduler and creates a new one. Returns the new
203
+ # rufus scheduler
204
+ def clear_schedule!
205
+ rufus_scheduler.stop
206
+ @rufus_scheduler = nil
207
+ @@scheduled_jobs = {}
208
+ rufus_scheduler
209
+ end
210
+
211
+ def reload_schedule!
212
+ if enabled
213
+ logger.info 'Reloading Schedule'
214
+ clear_schedule!
215
+ load_schedule!
216
+ else
217
+ logger.info 'SidekiqScheduler is disabled'
218
+ end
219
+ end
220
+
221
+ def update_schedule
222
+ last_changed_score, @current_changed_score = @current_changed_score, Time.now.to_f
223
+ schedule_changes = SidekiqScheduler::RedisManager.get_schedule_changes(last_changed_score, @current_changed_score)
224
+
225
+ if schedule_changes.size > 0
226
+ logger.info 'Updating schedule'
227
+ Sidekiq.reload_schedule!
228
+ schedule_changes.each do |schedule_name|
229
+ if Sidekiq.schedule.keys.include?(schedule_name)
230
+ unschedule_job(schedule_name)
231
+ load_schedule_job(schedule_name, Sidekiq.schedule[schedule_name])
232
+ else
233
+ unschedule_job(schedule_name)
234
+ end
235
+ end
236
+ logger.info 'Schedule updated'
237
+ end
238
+ end
239
+
240
+ def unschedule_job(name)
241
+ if scheduled_jobs[name]
242
+ logger.debug "Removing schedule #{name}"
243
+ scheduled_jobs[name].unschedule
244
+ scheduled_jobs.delete(name)
245
+ end
246
+ end
247
+
248
+ def enqueue_with_active_job(config)
249
+ options = {
250
+ queue: config['queue']
251
+ }.keep_if { |_, v| !v.nil? }
252
+
253
+ initialize_active_job(config['class'], config['args']).enqueue(options)
254
+ end
255
+
256
+ def enqueue_with_sidekiq(config)
257
+ Sidekiq::Client.push(sanitize_job_config(config))
258
+ end
259
+
260
+ def initialize_active_job(klass, args)
261
+ if args.is_a?(Array)
262
+ klass.new(*args)
263
+ else
264
+ klass.new(args)
265
+ end
266
+ end
267
+
268
+ # Returns true if the enqueuing needs to be done for an ActiveJob
269
+ # class false otherwise.
270
+ #
271
+ # @param [Class] klass the class to check is decendant from ActiveJob
272
+ #
273
+ # @return [Boolean]
274
+ def active_job_enqueue?(klass)
275
+ klass.is_a?(Class) && defined?(ActiveJob::Enqueuing) &&
276
+ klass.included_modules.include?(ActiveJob::Enqueuing)
277
+ end
278
+
279
+ # Convert the given arguments in the format expected to be enqueued.
280
+ #
281
+ # @param [Hash] config the options to be converted
282
+ # @option config [String] class the job class
283
+ # @option config [Hash/Array] args the arguments to be passed to the job
284
+ # class
285
+ #
286
+ # @return [Hash]
287
+ def prepare_arguments(config)
288
+ config['class'] = try_to_constantize(config['class'])
289
+
290
+ if config['args'].is_a?(Hash)
291
+ config['args'].symbolize_keys! if config['args'].respond_to?(:symbolize_keys!)
292
+ else
293
+ config['args'] = Array(config['args'])
294
+ end
295
+
296
+ config
297
+ end
298
+
299
+ def try_to_constantize(klass)
300
+ klass.is_a?(String) ? klass.constantize : klass
301
+ rescue NameError
302
+ klass
303
+ end
304
+
305
+ # Returns true if a job's queue is included in the array of queues
306
+ #
307
+ # If queues are empty, returns true.
308
+ #
309
+ # @param [String] job_queue Job's queue name
310
+ # @param [Array<String>] queues
311
+ #
312
+ # @return [Boolean]
313
+ def enabled_queue?(job_queue, queues)
314
+ queues.empty? || queues.include?(job_queue)
315
+ end
316
+
317
+ # Registers a queued job instance
318
+ #
319
+ # @param [String] job_name The job's name
320
+ # @param [Time] time Time at which the job was cleared by the scheduler
321
+ #
322
+ # @return [Boolean] true if the job was registered, false when otherwise
323
+ def register_job_instance(job_name, time)
324
+ SidekiqScheduler::RedisManager.register_job_instance(job_name, time)
325
+ end
326
+
327
+ def remove_elder_job_instances(job_name)
328
+ SidekiqScheduler::RedisManager.remove_elder_job_instances(job_name)
329
+ end
330
+
331
+ def job_enabled?(name)
332
+ job = Sidekiq.schedule[name]
333
+ schedule_state(name).fetch('enabled', job.fetch('enabled', true)) if job
334
+ end
335
+
336
+ def toggle_job_enabled(name)
337
+ state = schedule_state(name)
338
+ state['enabled'] = !job_enabled?(name)
339
+ set_schedule_state(name, state)
340
+ end
341
+
342
+ private
343
+
344
+ def new_rufus_scheduler
345
+ Rufus::Scheduler.new(rufus_scheduler_options).tap do |scheduler|
346
+ scheduler.define_singleton_method(:on_post_trigger) do |job, triggered_time|
347
+ SidekiqScheduler::Scheduler.update_job_last_time(job.tags[0], triggered_time)
348
+ SidekiqScheduler::Scheduler.update_job_next_time(job.tags[0], job.next_time)
349
+ end
350
+ end
351
+ end
352
+
353
+ def new_job(name, interval_type, config, schedule, options)
354
+ options = options.merge({ :job => true, :tags => [name] })
355
+
356
+ rufus_scheduler.send(interval_type, schedule, options) do |job, time|
357
+ idempotent_job_enqueue(name, time, sanitize_job_config(config)) if job_enabled?(name)
358
+ end
359
+ end
360
+
361
+ def sanitize_job_config(config)
362
+ config.reject { |k, _| RUFUS_METADATA_KEYS.include?(k) }
363
+ end
364
+
365
+ # Retrieves a schedule state
366
+ #
367
+ # @param name [String] with the schedule's name
368
+ # @return [Hash] with the schedule's state
369
+ def schedule_state(name)
370
+ state = SidekiqScheduler::RedisManager.get_job_state(name)
371
+
372
+ state ? JSON.parse(state) : {}
373
+ end
374
+
375
+ # Saves a schedule state
376
+ #
377
+ # @param name [String] with the schedule's name
378
+ # @param name [Hash] with the schedule's state
379
+ def set_schedule_state(name, state)
380
+ SidekiqScheduler::RedisManager.set_job_state(name, state)
381
+ end
382
+
383
+ # Adds a Hash with schedule metadata as the last argument to call the worker.
384
+ # It currently returns the schedule time as a Float number representing the milisencods
385
+ # since epoch.
386
+ #
387
+ # @example with hash argument
388
+ # arguments_with_metadata({value: 1}, scheduled_at: Time.now)
389
+ # #=> [{value: 1}, {scheduled_at: <miliseconds since epoch>}]
390
+ #
391
+ # @param args [Array|Hash]
392
+ # @param metadata [Hash]
393
+ # @return [Array] arguments with added metadata
394
+ def arguments_with_metadata(args, metadata)
395
+ if args.is_a? Array
396
+ [*args, metadata]
397
+ else
398
+ [args, metadata]
399
+ end
400
+ end
401
+
402
+ def sidekiq_queues
403
+ Sidekiq.options[:queues].map(&:to_s)
404
+ end
405
+ end
406
+ end
407
+ end
@@ -1,5 +1,5 @@
1
1
  module SidekiqScheduler
2
2
 
3
- VERSION = '2.1.10'
3
+ VERSION = '2.2.1'
4
4
 
5
5
  end
@@ -17,21 +17,18 @@ module SidekiqScheduler
17
17
 
18
18
  app.get '/recurring-jobs/:name/enqueue' do
19
19
  schedule = Sidekiq.get_schedule(params[:name])
20
- Sidekiq::Scheduler.enqueue_job(schedule)
20
+ SidekiqScheduler::Scheduler.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
- Sidekiq::Scheduler.toggle_job_enabled(params[:name])
27
+ SidekiqScheduler::Scheduler.toggle_job_enabled(params[:name])
28
28
  redirect "#{root_path}recurring-jobs"
29
29
  end
30
30
  end
31
31
  end
32
32
  end
33
33
 
34
- require 'sidekiq/web' unless defined?(Sidekiq::Web)
35
- Sidekiq::Web.register(SidekiqScheduler::Web)
36
- Sidekiq::Web.tabs['recurring_jobs'] = 'recurring-jobs'
37
- Sidekiq::Web.locales << File.expand_path(File.dirname(__FILE__) + "/../../web/locales")
34
+ require_relative 'extensions/web'
@@ -1,407 +1,3 @@
1
- require 'rufus/scheduler'
2
- require 'thwait'
3
- require 'sidekiq/util'
4
- require 'sidekiq-scheduler/manager'
5
- require 'sidekiq-scheduler/rufus_utils'
6
- require_relative '../sidekiq-scheduler/redis_manager'
7
- require 'json'
1
+ require 'sidekiq-scheduler/scheduler'
8
2
 
9
- module Sidekiq
10
- class Scheduler
11
- extend Sidekiq::Util
12
-
13
- RUFUS_METADATA_KEYS = %w(description at cron every in interval enabled)
14
-
15
- # We expect rufus jobs to have #params
16
- Rufus::Scheduler::Job.module_eval do
17
-
18
- alias_method :params, :opts
19
-
20
- end
21
-
22
- class << self
23
-
24
- # Set to enable or disable the scheduler.
25
- attr_accessor :enabled
26
-
27
- # Set to update the schedule in runtime in a given time period.
28
- attr_accessor :dynamic
29
-
30
- # Set to update the schedule in runtime dynamically per this period.
31
- attr_accessor :dynamic_every
32
-
33
- # Set to schedule jobs only when will be pushed to queues listened by sidekiq
34
- attr_accessor :listened_queues_only
35
-
36
- # the Rufus::Scheduler jobs that are scheduled
37
- def scheduled_jobs
38
- @@scheduled_jobs
39
- end
40
-
41
- def print_schedule
42
- if rufus_scheduler
43
- logger.info "Scheduling Info\tLast Run"
44
- scheduler_jobs = rufus_scheduler.all_jobs
45
- scheduler_jobs.each do |_, v|
46
- logger.info "#{v.t}\t#{v.last}\t"
47
- end
48
- end
49
- end
50
-
51
- # Pulls the schedule from Sidekiq.schedule and loads it into the
52
- # rufus scheduler instance
53
- def load_schedule!
54
- if enabled
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
-
68
-
69
- @@scheduled_jobs = {}
70
- queues = sidekiq_queues
71
-
72
- Sidekiq.schedule.each do |name, config|
73
- if !listened_queues_only || enabled_queue?(config['queue'].to_s, queues)
74
- load_schedule_job(name, config)
75
- else
76
- logger.info { "Ignoring #{name}, job's queue is not enabled." }
77
- end
78
- end
79
-
80
- logger.info 'Schedules Loaded'
81
- else
82
- logger.info 'SidekiqScheduler is disabled'
83
- end
84
- end
85
-
86
- # Loads a job schedule into the Rufus::Scheduler and stores it in @@scheduled_jobs
87
- def load_schedule_job(name, config)
88
- # If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
89
- # required for the jobs to be scheduled. If rails_env is missing, the
90
- # job should be scheduled regardless of what ENV['RAILS_ENV'] is set
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
100
-
101
- schedule, options = SidekiqScheduler::RufusUtils.normalize_schedule_options(config_interval_type)
102
-
103
- rufus_job = new_job(name, interval_type, config, schedule, options)
104
- @@scheduled_jobs[name] = rufus_job
105
- update_job_next_time(name, rufus_job.next_time)
106
-
107
- interval_defined = true
108
-
109
- break
110
- end
111
- end
112
-
113
- unless interval_defined
114
- logger.info "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
115
- end
116
- end
117
- end
118
-
119
- # Pushes the job into Sidekiq if not already pushed for the given time
120
- #
121
- # @param [String] job_name The job's name
122
- # @param [Time] time The time when the job got cleared for triggering
123
- # @param [Hash] config Job's config hash
124
- def idempotent_job_enqueue(job_name, time, config)
125
- registered = register_job_instance(job_name, time)
126
-
127
- if registered
128
- logger.info "queueing #{config['class']} (#{job_name})"
129
-
130
- handle_errors { enqueue_job(config, time) }
131
-
132
- remove_elder_job_instances(job_name)
133
- else
134
- logger.debug { "Ignoring #{job_name} job as it has been already enqueued" }
135
- end
136
- end
137
-
138
- # Pushes job's next time execution
139
- #
140
- # @param [String] name The job's name
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
149
-
150
- # Pushes job's last execution time
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
157
-
158
- # Returns true if the given schedule config hash matches the current
159
- # ENV['RAILS_ENV']
160
- def rails_env_matches?(config)
161
- config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/, '').split(',').include?(ENV['RAILS_ENV'])
162
- end
163
-
164
- def handle_errors
165
- begin
166
- yield
167
- rescue StandardError => e
168
- logger.info "#{e.class.name}: #{e.message}"
169
- end
170
- end
171
-
172
- # Enqueue a job based on a config hash
173
- #
174
- # @param job_config [Hash] the job configuration
175
- # @param time [Time] time the job is enqueued
176
- def enqueue_job(job_config, time=Time.now)
177
- config = prepare_arguments(job_config.dup)
178
-
179
- if config.delete('include_metadata')
180
- config['args'] = arguments_with_metadata(config['args'], scheduled_at: time.to_f)
181
- end
182
-
183
- if active_job_enqueue?(config['class'])
184
- enqueue_with_active_job(config)
185
- else
186
- enqueue_with_sidekiq(config)
187
- end
188
- end
189
-
190
- def rufus_scheduler_options
191
- @rufus_scheduler_options ||= {}
192
- end
193
-
194
- def rufus_scheduler_options=(options)
195
- @rufus_scheduler_options = options
196
- end
197
-
198
- def rufus_scheduler
199
- @rufus_scheduler ||= new_rufus_scheduler
200
- end
201
-
202
- # Stops old rufus scheduler and creates a new one. Returns the new
203
- # rufus scheduler
204
- def clear_schedule!
205
- rufus_scheduler.stop
206
- @rufus_scheduler = nil
207
- @@scheduled_jobs = {}
208
- rufus_scheduler
209
- end
210
-
211
- def reload_schedule!
212
- if enabled
213
- logger.info 'Reloading Schedule'
214
- clear_schedule!
215
- load_schedule!
216
- else
217
- logger.info 'SidekiqScheduler is disabled'
218
- end
219
- end
220
-
221
- def update_schedule
222
- last_changed_score, @current_changed_score = @current_changed_score, Time.now.to_f
223
- schedule_changes = SidekiqScheduler::RedisManager.get_schedule_changes(last_changed_score, @current_changed_score)
224
-
225
- if schedule_changes.size > 0
226
- logger.info 'Updating schedule'
227
- Sidekiq.reload_schedule!
228
- schedule_changes.each do |schedule_name|
229
- if Sidekiq.schedule.keys.include?(schedule_name)
230
- unschedule_job(schedule_name)
231
- load_schedule_job(schedule_name, Sidekiq.schedule[schedule_name])
232
- else
233
- unschedule_job(schedule_name)
234
- end
235
- end
236
- logger.info 'Schedule updated'
237
- end
238
- end
239
-
240
- def unschedule_job(name)
241
- if scheduled_jobs[name]
242
- logger.debug "Removing schedule #{name}"
243
- scheduled_jobs[name].unschedule
244
- scheduled_jobs.delete(name)
245
- end
246
- end
247
-
248
- def enqueue_with_active_job(config)
249
- options = {
250
- queue: config['queue']
251
- }.keep_if { |_, v| !v.nil? }
252
-
253
- initialize_active_job(config['class'], config['args']).enqueue(options)
254
- end
255
-
256
- def enqueue_with_sidekiq(config)
257
- Sidekiq::Client.push(sanitize_job_config(config))
258
- end
259
-
260
- def initialize_active_job(klass, args)
261
- if args.is_a?(Array)
262
- klass.new(*args)
263
- else
264
- klass.new(args)
265
- end
266
- end
267
-
268
- # Returns true if the enqueuing needs to be done for an ActiveJob
269
- # class false otherwise.
270
- #
271
- # @param [Class] klass the class to check is decendant from ActiveJob
272
- #
273
- # @return [Boolean]
274
- def active_job_enqueue?(klass)
275
- klass.is_a?(Class) && defined?(ActiveJob::Enqueuing) &&
276
- klass.included_modules.include?(ActiveJob::Enqueuing)
277
- end
278
-
279
- # Convert the given arguments in the format expected to be enqueued.
280
- #
281
- # @param [Hash] config the options to be converted
282
- # @option config [String] class the job class
283
- # @option config [Hash/Array] args the arguments to be passed to the job
284
- # class
285
- #
286
- # @return [Hash]
287
- def prepare_arguments(config)
288
- config['class'] = try_to_constantize(config['class'])
289
-
290
- if config['args'].is_a?(Hash)
291
- config['args'].symbolize_keys! if config['args'].respond_to?(:symbolize_keys!)
292
- else
293
- config['args'] = Array(config['args'])
294
- end
295
-
296
- config
297
- end
298
-
299
- def try_to_constantize(klass)
300
- klass.is_a?(String) ? klass.constantize : klass
301
- rescue NameError
302
- klass
303
- end
304
-
305
- # Returns true if a job's queue is included in the array of queues
306
- #
307
- # If queues are empty, returns true.
308
- #
309
- # @param [String] job_queue Job's queue name
310
- # @param [Array<String>] queues
311
- #
312
- # @return [Boolean]
313
- def enabled_queue?(job_queue, queues)
314
- queues.empty? || queues.include?(job_queue)
315
- end
316
-
317
- # Registers a queued job instance
318
- #
319
- # @param [String] job_name The job's name
320
- # @param [Time] time Time at which the job was cleared by the scheduler
321
- #
322
- # @return [Boolean] true if the job was registered, false when otherwise
323
- def register_job_instance(job_name, time)
324
- SidekiqScheduler::RedisManager.register_job_instance(job_name, time)
325
- end
326
-
327
- def remove_elder_job_instances(job_name)
328
- SidekiqScheduler::RedisManager.remove_elder_job_instances(job_name)
329
- end
330
-
331
- def job_enabled?(name)
332
- job = Sidekiq.schedule[name]
333
- schedule_state(name).fetch('enabled', job.fetch('enabled', true)) if job
334
- end
335
-
336
- def toggle_job_enabled(name)
337
- state = schedule_state(name)
338
- state['enabled'] = !job_enabled?(name)
339
- set_schedule_state(name, state)
340
- end
341
-
342
- private
343
-
344
- def new_rufus_scheduler
345
- Rufus::Scheduler.new(rufus_scheduler_options).tap do |scheduler|
346
- scheduler.define_singleton_method(:on_post_trigger) do |job, triggered_time|
347
- Sidekiq::Scheduler.update_job_last_time(job.tags[0], triggered_time)
348
- Sidekiq::Scheduler.update_job_next_time(job.tags[0], job.next_time)
349
- end
350
- end
351
- end
352
-
353
- def new_job(name, interval_type, config, schedule, options)
354
- options = options.merge({ :job => true, :tags => [name] })
355
-
356
- rufus_scheduler.send(interval_type, schedule, options) do |job, time|
357
- idempotent_job_enqueue(name, time, sanitize_job_config(config)) if job_enabled?(name)
358
- end
359
- end
360
-
361
- def sanitize_job_config(config)
362
- config.reject { |k, _| RUFUS_METADATA_KEYS.include?(k) }
363
- end
364
-
365
- # Retrieves a schedule state
366
- #
367
- # @param name [String] with the schedule's name
368
- # @return [Hash] with the schedule's state
369
- def schedule_state(name)
370
- state = SidekiqScheduler::RedisManager.get_job_state(name)
371
-
372
- state ? JSON.parse(state) : {}
373
- end
374
-
375
- # Saves a schedule state
376
- #
377
- # @param name [String] with the schedule's name
378
- # @param name [Hash] with the schedule's state
379
- def set_schedule_state(name, state)
380
- SidekiqScheduler::RedisManager.set_job_state(name, state)
381
- end
382
-
383
- # Adds a Hash with schedule metadata as the last argument to call the worker.
384
- # It currently returns the schedule time as a Float number representing the milisencods
385
- # since epoch.
386
- #
387
- # @example with hash argument
388
- # arguments_with_metadata({value: 1}, scheduled_at: Time.now)
389
- # #=> [{value: 1}, {scheduled_at: <miliseconds since epoch>}]
390
- #
391
- # @param args [Array|Hash]
392
- # @param metadata [Hash]
393
- # @return [Array] arguments with added metadata
394
- def arguments_with_metadata(args, metadata)
395
- if args.is_a? Array
396
- [*args, metadata]
397
- else
398
- [args, metadata]
399
- end
400
- end
401
-
402
- def sidekiq_queues
403
- Sidekiq.options[:queues].map(&:to_s)
404
- end
405
- end
406
- end
407
- end
3
+ Sidekiq::Scheduler = SidekiqScheduler::Scheduler
@@ -0,0 +1,14 @@
1
+ nl:
2
+ recurring_jobs: Herhalende taken
3
+ name: Naam
4
+ description: Beschrijving
5
+ interval: Interval
6
+ class: Klasse
7
+ queue: Wachtrij
8
+ arguments: Argumenten
9
+ enqueue_now: Nu toevoegen
10
+ last_time: Laatste keer
11
+ next_time: Volgende keer
12
+ no_next_time: Geen volgende keer voor deze taak
13
+ disable: Uitzetten
14
+ enable: Aanzetten
@@ -0,0 +1,14 @@
1
+ ru:
2
+ recurring_jobs: Расписание задач
3
+ name: Название
4
+ description: Описание
5
+ interval: Интервал
6
+ class: Класс
7
+ queue: Очередь
8
+ arguments: Аргументы
9
+ enqueue_now: Поставить в очередь
10
+ last_time: Время последнего выполнения
11
+ next_time: Время следующего выполнения
12
+ no_next_time: выполнение задачи не запланировано
13
+ disable: Выключить
14
+ enable: Включить
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: 2.1.10
4
+ version: 2.2.1
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: 2017-10-06 00:00:00.000000000 Z
12
+ date: 2018-01-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sidekiq
@@ -228,7 +228,7 @@ dependencies:
228
228
  - !ruby/object:Gem::Version
229
229
  version: '0'
230
230
  description: Light weight job scheduling extension for Sidekiq that adds support for
231
- queueinga jobs in a recurring way.
231
+ queueing jobs in a recurring way.
232
232
  email:
233
233
  - sidekiq-scheduler@moove-it.com
234
234
  executables: []
@@ -239,11 +239,14 @@ files:
239
239
  - README.md
240
240
  - Rakefile
241
241
  - lib/sidekiq-scheduler.rb
242
+ - lib/sidekiq-scheduler/extensions/schedule.rb
243
+ - lib/sidekiq-scheduler/extensions/web.rb
242
244
  - lib/sidekiq-scheduler/job_presenter.rb
243
245
  - lib/sidekiq-scheduler/manager.rb
244
246
  - lib/sidekiq-scheduler/redis_manager.rb
245
247
  - lib/sidekiq-scheduler/rufus_utils.rb
246
248
  - lib/sidekiq-scheduler/schedule.rb
249
+ - lib/sidekiq-scheduler/scheduler.rb
247
250
  - lib/sidekiq-scheduler/utils.rb
248
251
  - lib/sidekiq-scheduler/version.rb
249
252
  - lib/sidekiq-scheduler/web.rb
@@ -254,6 +257,8 @@ files:
254
257
  - web/locales/es.yml
255
258
  - web/locales/fr.yml
256
259
  - web/locales/it.yml
260
+ - web/locales/nl.yml
261
+ - web/locales/ru.yml
257
262
  - web/locales/sv.yml
258
263
  - web/locales/zh-cn.yml
259
264
  - web/views/recurring_jobs.erb