sidekiq-scheduler 2.1.10 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
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