sidekiq-scheduler 2.1.10 → 3.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a3f7cb5ee802d2e3b5c89b308fbcaff612911217
4
- data.tar.gz: e6b488b10334b3af01c9d2521e31ec3c53f0ef20
2
+ SHA256:
3
+ metadata.gz: 7c9e10cdd3a2bfc144f07860038c95ef459bb2bf8800d763ea90379b6c709472
4
+ data.tar.gz: 8f4be75200f8a5c44f53227ba73586e5e6ac9e8885972163382b372372086af0
5
5
  SHA512:
6
- metadata.gz: b56413d0c141f2ae15bfacb255a8eed2550b6b0f899703f5fd08d67539794878e411d9a5a48086f4fd9d7f542f6b78fe9a016b7edf32a78819b93c7860ecf2a2
7
- data.tar.gz: 6b8524661830f24e9233a95ec94a18ad31eac1f0880a63a3bc48f728e802f8f5ffe79437bfd5d17bdc3ec07a79d142b0d991b33a5a6363f79a4e6f1c387ee2c2
6
+ metadata.gz: e8dc47d6600676d0de6cac9b508026315c2eefcf6a055b496abd84ed663ec9bb9eddc267246ba877588ec4abb67d79a773664238d1028e23ca2859869e76dedd
7
+ data.tar.gz: 78764a88d67d2c501a4df10a8d7b79331146912a4d5fa2c83a04e1b06165bd45ceec0a11232e8e841fc452d6fd79e985142ad72a945e4fc04a8d79aa7090f79c
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
@@ -180,6 +182,8 @@ Cron, every, and interval types push jobs into sidekiq in a recurrent manner.
180
182
  `interval` is similar to `every`, the difference between them is that `interval` type schedules the
181
183
  next execution after the interval has elapsed counting from its last job enqueue.
182
184
 
185
+ Note that `every` and `interval` count from when the Sidekiq process (re)starts. So `every: '48h'` will never run if the Sidekiq process is restarted daily, for example. You can do `every: ['48h', first_in: '0s']` to make the job run immediately after a restart, and then have the worker check when it was last run.
186
+
183
187
  At, and in types push jobs only once. `at` schedules in a point in time:
184
188
  ``` yaml
185
189
  at: '3001/01/01'
@@ -223,12 +227,12 @@ To load the schedule:
223
227
 
224
228
  ``` ruby
225
229
  require 'sidekiq'
226
- require 'sidekiq/scheduler'
230
+ require 'sidekiq-scheduler'
227
231
 
228
232
  Sidekiq.configure_server do |config|
229
233
  config.on(:startup) do
230
234
  Sidekiq.schedule = YAML.load_file(File.expand_path('../../sidekiq_scheduler.yml', __FILE__))
231
- Sidekiq::Scheduler.reload_schedule!
235
+ SidekiqScheduler::Scheduler.instance.reload_schedule!
232
236
  end
233
237
  end
234
238
  ```
@@ -256,7 +260,7 @@ If `:dynamic` flag is set to `false`, you'll have to reload the schedule manuall
256
260
  side:
257
261
 
258
262
  ``` ruby
259
- Sidekiq::Scheduler.reload_schedule!
263
+ SidekiqScheduler::Scheduler.instance.reload_schedule!
260
264
  ```
261
265
 
262
266
  Invoke `Sidekiq.get_schedule` to obtain the current schedule:
@@ -268,8 +272,9 @@ Sidekiq.get_schedule
268
272
 
269
273
  ## Time zones
270
274
 
271
- Note that if you use the cron syntax, this will be interpreted as in the server time zone
272
- rather than the `config.time_zone` specified in Rails.
275
+ Note that if you use the cron syntax and are not running a Rails app, this will be interpreted in the server time zone.
276
+
277
+ In a Rails app, [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) (>= 3.3.3) will use the `config.time_zone` specified in Rails.
273
278
 
274
279
  You can explicitly specify the time zone that rufus-scheduler will use:
275
280
 
@@ -285,11 +290,27 @@ from the `config.time_zone` value, make sure it's the right format, e.g. with:
285
290
  ActiveSupport::TimeZone.find_tzinfo(Rails.configuration.time_zone).name
286
291
  ```
287
292
 
293
+ ## Notes about connection pooling
294
+
295
+ If you're configuring your own Redis connection pool, you need to make sure the size is adequate to be inclusive of both Sidekiq's own connection pool and Rufus Scheduler's.
296
+
297
+ That's a minimum of `concurrency` + 5 (per the [Sidekiq wiki](https://github.com/mperham/sidekiq/wiki/Using-Redis#complete-control)) + `Rufus::Scheduler::MAX_WORK_THREADS` (28 as of this writing; per the [Rufus README](https://github.com/jmettraux/rufus-scheduler#max_work_threads)), for a total of 58 with the default `concurrency` of 25.
298
+
299
+ You can also override the thread pool size in Rufus Scheduler by setting e.g.:
300
+
301
+ ```
302
+ SidekiqScheduler::Scheduler.instance.rufus_scheduler_options = { max_work_threads: 5 }
303
+ ```
304
+
288
305
  ## Notes about running on Multiple Hosts
289
306
 
290
- `cron` and `at` jobs are pushed once regardless of the number of `sidekiq-scheduler` running instances,
307
+ Under normal conditions, `cron` and `at` jobs are pushed once regardless of the number of `sidekiq-scheduler` running instances,
291
308
  assumming that time deltas between hosts is less than 24 hours.
292
309
 
310
+ Non-normal conditions that could push a specific job multiple times are:
311
+ - high cpu load + a high number of jobs scheduled at the same time, like 100 jobs
312
+ - 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
313
+
293
314
  `every`, `interval` and `in` jobs will be pushed once per host.
294
315
 
295
316
  ## Sidekiq Web Integration
@@ -307,6 +328,18 @@ run Sidekiq::Web
307
328
 
308
329
  ![Sidekiq Web Integration](https://github.com/moove-it/sidekiq-scheduler/raw/master/images/recurring-jobs-ui-tab.png)
309
330
 
331
+ ## ActiveJob integration
332
+
333
+ When using sidekiq-scheduler with ActiveJob your jobs can just extend `ApplicationJob` as usual, without the `require` and `include` boilerplate. Under the hood Rails will load up the scheduler and include the worker module for you.
334
+
335
+ ```rb
336
+ class HelloWorld < ApplicationJob
337
+ def perform
338
+ puts 'Hello world'
339
+ end
340
+ end
341
+ ```
342
+
310
343
  ## The Spring preloader and Testing your initializer via Rails console
311
344
 
312
345
  If you're pulling in your schedule from a YML file via an initializer as shown, be aware that the Spring application preloader included with Rails will interefere with testing via the Rails console.
@@ -327,7 +360,8 @@ something like this in an initializer:
327
360
 
328
361
  ``` ruby
329
362
  # config/initializers/sidekiq_scheduler.rb
330
- require 'sidekiq/scheduler'
363
+ require 'sidekiq'
364
+ require 'sidekiq-scheduler'
331
365
 
332
366
  puts "Sidekiq.server? is #{Sidekiq.server?.inspect}"
333
367
  puts "defined?(Rails::Server) is #{defined?(Rails::Server).inspect}"
@@ -338,12 +372,12 @@ if Rails.env == 'production' && (defined?(Rails::Server) || defined?(Unicorn))
338
372
 
339
373
  config.on(:startup) do
340
374
  Sidekiq.schedule = YAML.load_file(File.expand_path('../../scheduler.yml', __FILE__))
341
- Sidekiq::Scheduler.reload_schedule!
375
+ SidekiqScheduler::Scheduler.instance.reload_schedule!
342
376
  end
343
377
  end
344
378
  else
345
- Sidekiq::Scheduler.enabled = false
346
- puts "Sidekiq::Scheduler.enabled is #{Sidekiq::Scheduler.enabled.inspect}"
379
+ SidekiqScheduler::Scheduler.instance.enabled = false
380
+ puts "SidekiqScheduler::Scheduler.instance.enabled is #{SidekiqScheduler::Scheduler.instance.enabled.inspect}"
347
381
  end
348
382
  ```
349
383
 
@@ -353,6 +387,6 @@ MIT License
353
387
 
354
388
  ## Copyright
355
389
 
356
- Copyright 2013 - 2017 Moove-IT.
390
+ Copyright 2013 - 2018 Moove-IT.
357
391
  Copyright 2012 Morton Jonuschat.
358
392
  Some parts copyright 2010 Ben VandenBos.
@@ -1,47 +1,24 @@
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
 
10
12
  config.on(:startup) do
11
- dynamic = Sidekiq::Scheduler.dynamic
12
- dynamic = dynamic.nil? ? config.options.fetch(:dynamic, false) : dynamic
13
-
14
- dynamic_every = Sidekiq::Scheduler.dynamic_every
15
- dynamic_every = dynamic_every.nil? ? config.options.fetch(:dynamic_every, '5s') : dynamic_every
16
-
17
- enabled = Sidekiq::Scheduler.enabled
18
- enabled = enabled.nil? ? config.options.fetch(:enabled, true) : enabled
19
-
20
- scheduler = config.options.fetch(:scheduler, {})
21
-
22
- listened_queues_only = Sidekiq::Scheduler.listened_queues_only
23
- listened_queues_only = listened_queues_only.nil? ? scheduler[:listened_queues_only] : listened_queues_only
24
-
25
- schedule = Sidekiq.schedule
26
- schedule ||= config.options[:schedule] || {}
27
-
28
- scheduler_options = {
29
- dynamic: dynamic,
30
- dynamic_every: dynamic_every,
31
- enabled: enabled,
32
- schedule: schedule,
33
- listened_queues_only: listened_queues_only
34
- }
35
-
36
13
  # schedules_changed's type was changed from SET to ZSET, so we remove old versions at startup
37
14
  SidekiqScheduler::RedisManager.clean_schedules_changed
38
15
 
39
- schedule_manager = SidekiqScheduler::Manager.new(scheduler_options)
16
+ schedule_manager = SidekiqScheduler::Manager.new(config.options)
40
17
  config.options[:schedule_manager] = schedule_manager
41
18
  config.options[:schedule_manager].start
42
19
  end
43
20
 
44
- config.on(:shutdown) do
21
+ config.on(:quiet) do
45
22
  config.options[:schedule_manager].stop
46
23
  end
47
24
 
@@ -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
@@ -66,7 +66,7 @@ module SidekiqScheduler
66
66
  def self.build_collection(schedule_hash)
67
67
  schedule_hash ||= {}
68
68
 
69
- schedule_hash.map do |name, job_spec|
69
+ schedule_hash.sort.map do |name, job_spec|
70
70
  new(name, job_spec)
71
71
  end
72
72
  end
@@ -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
 
@@ -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
- 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
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
- Sidekiq::Scheduler.clear_schedule!
33
+ @scheduler_instance.clear_schedule!
27
34
  end
28
35
 
29
36
  def start
30
- Sidekiq::Scheduler.load_schedule!
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
- end
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
@@ -96,7 +96,13 @@ module SidekiqScheduler
96
96
  #
97
97
  # @return [Boolean] true if the schedules key is set, false otherwise
98
98
  def self.schedule_exist?
99
- Sidekiq.redis { |r| r.exists(:schedules) }
99
+ Sidekiq.redis do |r|
100
+ if r.respond_to?(:exists?)
101
+ r.exists?(:schedules)
102
+ else
103
+ !!r.exists(:schedules)
104
+ end
105
+ end
100
106
  end
101
107
 
102
108
  # Returns all the schedule changes for a given time range.
@@ -152,5 +152,3 @@ module SidekiqScheduler
152
152
  end
153
153
  end
154
154
  end
155
-
156
- Sidekiq.extend SidekiqScheduler::Schedule
@@ -0,0 +1,357 @@
1
+ require 'rufus/scheduler'
2
+ require 'thwait'
3
+ require 'sidekiq/util'
4
+ require 'json'
5
+ require 'sidekiq-scheduler/rufus_utils'
6
+ require 'sidekiq-scheduler/redis_manager'
7
+
8
+ module SidekiqScheduler
9
+ class Scheduler
10
+ extend Sidekiq::Util
11
+
12
+ # We expect rufus jobs to have #params
13
+ Rufus::Scheduler::Job.module_eval do
14
+ alias_method :params, :opts
15
+ end
16
+
17
+ # Set to enable or disable the scheduler.
18
+ attr_accessor :enabled
19
+
20
+ # Set to update the schedule in runtime in a given time period.
21
+ attr_accessor :dynamic
22
+
23
+ # Set to update the schedule in runtime dynamically per this period.
24
+ attr_accessor :dynamic_every
25
+
26
+ # Set to schedule jobs only when will be pushed to queues listened by sidekiq
27
+ attr_accessor :listened_queues_only
28
+
29
+ class << self
30
+
31
+ def instance
32
+ @instance = new unless @instance
33
+ @instance
34
+ end
35
+
36
+ def instance=(value)
37
+ @instance = value
38
+ end
39
+
40
+ def method_missing(method, *arguments, &block)
41
+ instance_methods.include?(method) ? instance.public_send(method, *arguments) : super
42
+ end
43
+ end
44
+
45
+ def initialize(options = {})
46
+ self.enabled = options[:enabled]
47
+ self.dynamic = options[:dynamic]
48
+ self.dynamic_every = options[:dynamic_every]
49
+ self.listened_queues_only = options[:listened_queues_only]
50
+ end
51
+
52
+ # the Rufus::Scheduler jobs that are scheduled
53
+ def scheduled_jobs
54
+ @scheduled_jobs
55
+ end
56
+
57
+ def print_schedule
58
+ if rufus_scheduler
59
+ Sidekiq.logger.info "Scheduling Info\tLast Run"
60
+ scheduler_jobs = rufus_scheduler.all_jobs
61
+ scheduler_jobs.each_value do |v|
62
+ Sidekiq.logger.info "#{v.t}\t#{v.last}\t"
63
+ end
64
+ end
65
+ end
66
+
67
+ # Pulls the schedule from Sidekiq.schedule and loads it into the
68
+ # rufus scheduler instance
69
+ def load_schedule!
70
+ if enabled
71
+ Sidekiq.logger.info 'Loading Schedule'
72
+
73
+ # Load schedule from redis for the first time if dynamic
74
+ if dynamic
75
+ Sidekiq.reload_schedule!
76
+ @current_changed_score = Time.now.to_f
77
+ rufus_scheduler.every(dynamic_every) do
78
+ update_schedule
79
+ end
80
+ end
81
+
82
+ Sidekiq.logger.info 'Schedule empty! Set Sidekiq.schedule' if Sidekiq.schedule.empty?
83
+
84
+ @scheduled_jobs = {}
85
+ queues = sidekiq_queues
86
+
87
+ Sidekiq.schedule.each do |name, config|
88
+ if !listened_queues_only || enabled_queue?(config['queue'].to_s, queues)
89
+ load_schedule_job(name, config)
90
+ else
91
+ Sidekiq.logger.info { "Ignoring #{name}, job's queue is not enabled." }
92
+ end
93
+ end
94
+
95
+ Sidekiq.logger.info 'Schedules Loaded'
96
+ else
97
+ Sidekiq.logger.info 'SidekiqScheduler is disabled'
98
+ end
99
+ end
100
+
101
+ # Loads a job schedule into the Rufus::Scheduler and stores it in @scheduled_jobs
102
+ def load_schedule_job(name, config)
103
+ # If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
104
+ # required for the jobs to be scheduled. If rails_env is missing, the
105
+ # job should be scheduled regardless of what ENV['RAILS_ENV'] is set
106
+ # to.
107
+ if config['rails_env'].nil? || rails_env_matches?(config)
108
+ Sidekiq.logger.info "Scheduling #{name} #{config}"
109
+ interval_defined = false
110
+ interval_types = %w(cron every at in interval)
111
+ interval_types.each do |interval_type|
112
+ config_interval_type = config[interval_type]
113
+
114
+ if !config_interval_type.nil? && config_interval_type.length > 0
115
+
116
+ schedule, options = SidekiqScheduler::RufusUtils.normalize_schedule_options(config_interval_type)
117
+
118
+ rufus_job = new_job(name, interval_type, config, schedule, options)
119
+ @scheduled_jobs[name] = rufus_job
120
+ SidekiqScheduler::Utils.update_job_next_time(name, rufus_job.next_time)
121
+
122
+ interval_defined = true
123
+
124
+ break
125
+ end
126
+ end
127
+
128
+ unless interval_defined
129
+ Sidekiq.logger.info "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
130
+ end
131
+ end
132
+ end
133
+
134
+ # Pushes the job into Sidekiq if not already pushed for the given time
135
+ #
136
+ # @param [String] job_name The job's name
137
+ # @param [Time] time The time when the job got cleared for triggering
138
+ # @param [Hash] config Job's config hash
139
+ def idempotent_job_enqueue(job_name, time, config)
140
+ registered = SidekiqScheduler::RedisManager.register_job_instance(job_name, time)
141
+
142
+ if registered
143
+ Sidekiq.logger.info "queueing #{config['class']} (#{job_name})"
144
+
145
+ handle_errors { enqueue_job(config, time) }
146
+
147
+ SidekiqScheduler::RedisManager.remove_elder_job_instances(job_name)
148
+ else
149
+ Sidekiq.logger.debug { "Ignoring #{job_name} job as it has been already enqueued" }
150
+ end
151
+ end
152
+
153
+ # Enqueue a job based on a config hash
154
+ #
155
+ # @param job_config [Hash] the job configuration
156
+ # @param time [Time] time the job is enqueued
157
+ def enqueue_job(job_config, time = Time.now)
158
+ config = prepare_arguments(job_config.dup)
159
+
160
+ if config.delete('include_metadata')
161
+ config['args'] = arguments_with_metadata(config['args'], scheduled_at: time.to_f)
162
+ end
163
+
164
+ if active_job_enqueue?(config['class'])
165
+ SidekiqScheduler::Utils.enqueue_with_active_job(config)
166
+ else
167
+ SidekiqScheduler::Utils.enqueue_with_sidekiq(config)
168
+ end
169
+ end
170
+
171
+ def rufus_scheduler_options
172
+ @rufus_scheduler_options ||= {}
173
+ end
174
+
175
+ def rufus_scheduler_options=(options)
176
+ @rufus_scheduler_options = options
177
+ end
178
+
179
+ def rufus_scheduler
180
+ @rufus_scheduler ||= SidekiqScheduler::Utils.new_rufus_scheduler(rufus_scheduler_options)
181
+ end
182
+
183
+ # Stops old rufus scheduler and creates a new one. Returns the new
184
+ # rufus scheduler
185
+ #
186
+ # @param [Symbol] stop_option The option to be passed to Rufus::Scheduler#stop
187
+ def clear_schedule!(stop_option = :wait)
188
+ if @rufus_scheduler
189
+ @rufus_scheduler.stop(stop_option)
190
+ @rufus_scheduler = nil
191
+ end
192
+
193
+ @@scheduled_jobs = {}
194
+
195
+ rufus_scheduler
196
+ end
197
+
198
+ def reload_schedule!
199
+ if enabled
200
+ Sidekiq.logger.info 'Reloading Schedule'
201
+ clear_schedule!
202
+ load_schedule!
203
+ else
204
+ Sidekiq.logger.info 'SidekiqScheduler is disabled'
205
+ end
206
+ end
207
+
208
+ def update_schedule
209
+ last_changed_score, @current_changed_score = @current_changed_score, Time.now.to_f
210
+ schedule_changes = SidekiqScheduler::RedisManager.get_schedule_changes(last_changed_score, @current_changed_score)
211
+
212
+ if schedule_changes.size > 0
213
+ Sidekiq.logger.info 'Updating schedule'
214
+
215
+ Sidekiq.reload_schedule!
216
+ schedule_changes.each do |schedule_name|
217
+ if Sidekiq.schedule.keys.include?(schedule_name)
218
+ unschedule_job(schedule_name)
219
+ load_schedule_job(schedule_name, Sidekiq.schedule[schedule_name])
220
+ else
221
+ unschedule_job(schedule_name)
222
+ end
223
+ end
224
+ Sidekiq.logger.info 'Schedule updated'
225
+ end
226
+ end
227
+
228
+ def job_enabled?(name)
229
+ job = Sidekiq.schedule[name]
230
+ schedule_state(name).fetch('enabled', job.fetch('enabled', true)) if job
231
+ end
232
+
233
+ def toggle_job_enabled(name)
234
+ state = schedule_state(name)
235
+ state['enabled'] = !job_enabled?(name)
236
+ set_schedule_state(name, state)
237
+ end
238
+
239
+ private
240
+
241
+ def new_job(name, interval_type, config, schedule, options)
242
+ options = options.merge({ :job => true, :tags => [name] })
243
+
244
+ rufus_scheduler.send(interval_type, schedule, options) do |job, time|
245
+ idempotent_job_enqueue(name, time, SidekiqScheduler::Utils.sanitize_job_config(config)) if job_enabled?(name)
246
+ end
247
+ end
248
+
249
+ def unschedule_job(name)
250
+ if scheduled_jobs[name]
251
+ Sidekiq.logger.debug "Removing schedule #{name}"
252
+ scheduled_jobs[name].unschedule
253
+ scheduled_jobs.delete(name)
254
+ end
255
+ end
256
+
257
+ # Retrieves a schedule state
258
+ #
259
+ # @param name [String] with the schedule's name
260
+ # @return [Hash] with the schedule's state
261
+ def schedule_state(name)
262
+ state = SidekiqScheduler::RedisManager.get_job_state(name)
263
+
264
+ state ? JSON.parse(state) : {}
265
+ end
266
+
267
+ # Saves a schedule state
268
+ #
269
+ # @param name [String] with the schedule's name
270
+ # @param name [Hash] with the schedule's state
271
+ def set_schedule_state(name, state)
272
+ SidekiqScheduler::RedisManager.set_job_state(name, state)
273
+ end
274
+
275
+ # Adds a Hash with schedule metadata as the last argument to call the worker.
276
+ # It currently returns the schedule time as a Float number representing the milisencods
277
+ # since epoch.
278
+ #
279
+ # @example with hash argument
280
+ # arguments_with_metadata({value: 1}, scheduled_at: Time.now)
281
+ # #=> [{value: 1}, {scheduled_at: <miliseconds since epoch>}]
282
+ #
283
+ # @param args [Array|Hash]
284
+ # @param metadata [Hash]
285
+ # @return [Array] arguments with added metadata
286
+ def arguments_with_metadata(args, metadata)
287
+ if args.is_a? Array
288
+ [*args, metadata]
289
+ else
290
+ [args, metadata]
291
+ end
292
+ end
293
+
294
+ def sidekiq_queues
295
+ Sidekiq.options[:queues].map(&:to_s)
296
+ end
297
+
298
+ # Returns true if a job's queue is included in the array of queues
299
+ #
300
+ # If queues are empty, returns true.
301
+ #
302
+ # @param [String] job_queue Job's queue name
303
+ # @param [Array<String>] queues
304
+ #
305
+ # @return [Boolean]
306
+ def enabled_queue?(job_queue, queues)
307
+ queues.empty? || queues.include?(job_queue)
308
+ end
309
+
310
+ # Returns true if the enqueuing needs to be done for an ActiveJob
311
+ # class false otherwise.
312
+ #
313
+ # @param [Class] klass the class to check is decendant from ActiveJob
314
+ #
315
+ # @return [Boolean]
316
+ def active_job_enqueue?(klass)
317
+ klass.is_a?(Class) && defined?(ActiveJob::Enqueuing) &&
318
+ klass.included_modules.include?(ActiveJob::Enqueuing)
319
+ end
320
+
321
+ # Convert the given arguments in the format expected to be enqueued.
322
+ #
323
+ # @param [Hash] config the options to be converted
324
+ # @option config [String] class the job class
325
+ # @option config [Hash/Array] args the arguments to be passed to the job
326
+ # class
327
+ #
328
+ # @return [Hash]
329
+ def prepare_arguments(config)
330
+ config['class'] = SidekiqScheduler::Utils.try_to_constantize(config['class'])
331
+
332
+ if config['args'].is_a?(Hash)
333
+ config['args'].symbolize_keys! if config['args'].respond_to?(:symbolize_keys!)
334
+ else
335
+ config['args'] = Array(config['args'])
336
+ end
337
+
338
+ config
339
+ end
340
+
341
+ # Returns true if the given schedule config hash matches the current ENV['RAILS_ENV']
342
+ # @param [Hash] config The schedule job configuration
343
+ #
344
+ # @return [Boolean] true if the schedule config matches the current ENV['RAILS_ENV']
345
+ def rails_env_matches?(config)
346
+ config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/, '').split(',').include?(ENV['RAILS_ENV'])
347
+ end
348
+
349
+ def handle_errors
350
+ begin
351
+ yield
352
+ rescue StandardError => e
353
+ Sidekiq.logger.info "#{e.class.name}: #{e.message}"
354
+ end
355
+ end
356
+ end
357
+ end