resque-scheduler 2.2.0 → 4.10.2

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.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.github/dependabot.yml +12 -0
  3. data/.github/funding.yml +4 -0
  4. data/.github/workflows/codeql-analysis.yml +59 -0
  5. data/.github/workflows/rubocop.yml +27 -0
  6. data/.github/workflows/ruby.yml +81 -0
  7. data/AUTHORS.md +31 -0
  8. data/CHANGELOG.md +539 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Gemfile +26 -2
  11. data/LICENSE +1 -1
  12. data/README.md +423 -91
  13. data/Rakefile +12 -35
  14. data/exe/resque-scheduler +5 -0
  15. data/lib/resque/scheduler/cli.rb +147 -0
  16. data/lib/resque/scheduler/configuration.rb +102 -0
  17. data/lib/resque/scheduler/delaying_extensions.rb +371 -0
  18. data/lib/resque/scheduler/env.rb +85 -0
  19. data/lib/resque/scheduler/extension.rb +13 -0
  20. data/lib/resque/scheduler/failure_handler.rb +11 -0
  21. data/lib/resque/scheduler/lock/base.rb +12 -3
  22. data/lib/resque/scheduler/lock/basic.rb +4 -5
  23. data/lib/resque/scheduler/lock/resilient.rb +52 -43
  24. data/lib/resque/scheduler/lock.rb +2 -1
  25. data/lib/resque/scheduler/locking.rb +104 -0
  26. data/lib/resque/scheduler/logger_builder.rb +83 -0
  27. data/lib/resque/scheduler/plugin.rb +31 -0
  28. data/lib/resque/scheduler/scheduling_extensions.rb +142 -0
  29. data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed.erb +23 -8
  30. data/lib/resque/scheduler/server/views/delayed_schedules.erb +20 -0
  31. data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed_timestamp.erb +1 -1
  32. data/lib/resque/scheduler/server/views/scheduler.erb +58 -0
  33. data/lib/resque/scheduler/server/views/search.erb +69 -0
  34. data/lib/resque/scheduler/server/views/search_form.erb +4 -0
  35. data/lib/resque/scheduler/server.rb +268 -0
  36. data/lib/resque/scheduler/signal_handling.rb +40 -0
  37. data/lib/resque/scheduler/tasks.rb +25 -0
  38. data/lib/resque/scheduler/util.rb +39 -0
  39. data/lib/resque/scheduler/version.rb +7 -0
  40. data/lib/resque/scheduler.rb +333 -149
  41. data/lib/resque-scheduler.rb +4 -0
  42. data/resque-scheduler.gemspec +56 -20
  43. metadata +205 -104
  44. data/.gitignore +0 -8
  45. data/.rubocop.yml +0 -120
  46. data/.travis.yml +0 -10
  47. data/HISTORY.md +0 -180
  48. data/lib/resque/scheduler_locking.rb +0 -90
  49. data/lib/resque_scheduler/logger_builder.rb +0 -51
  50. data/lib/resque_scheduler/plugin.rb +0 -25
  51. data/lib/resque_scheduler/server/views/scheduler.erb +0 -44
  52. data/lib/resque_scheduler/server.rb +0 -92
  53. data/lib/resque_scheduler/tasks.rb +0 -40
  54. data/lib/resque_scheduler/version.rb +0 -3
  55. data/lib/resque_scheduler.rb +0 -355
  56. data/script/migrate_to_timestamps_set.rb +0 -14
  57. data/tasks/resque_scheduler.rake +0 -2
  58. data/test/delayed_queue_test.rb +0 -383
  59. data/test/redis-test.conf +0 -108
  60. data/test/resque-web_test.rb +0 -116
  61. data/test/scheduler_args_test.rb +0 -156
  62. data/test/scheduler_hooks_test.rb +0 -23
  63. data/test/scheduler_locking_test.rb +0 -180
  64. data/test/scheduler_setup_test.rb +0 -59
  65. data/test/scheduler_test.rb +0 -256
  66. data/test/support/redis_instance.rb +0 -129
  67. data/test/test_helper.rb +0 -92
  68. /data/lib/{resque_scheduler → resque/scheduler}/server/views/requeue-params.erb +0 -0
@@ -1,49 +1,43 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ require 'redis/errors'
1
4
  require 'rufus/scheduler'
2
- require 'resque/scheduler_locking'
3
- require 'resque_scheduler/logger_builder'
5
+ require_relative 'scheduler/configuration'
6
+ require_relative 'scheduler/locking'
7
+ require_relative 'scheduler/logger_builder'
8
+ require_relative 'scheduler/signal_handling'
9
+ require_relative 'scheduler/failure_handler'
4
10
 
5
11
  module Resque
12
+ module Scheduler
13
+ autoload :Cli, 'resque/scheduler/cli'
14
+ autoload :Extension, 'resque/scheduler/extension'
15
+ autoload :Util, 'resque/scheduler/util'
16
+ autoload :VERSION, 'resque/scheduler/version'
17
+ INTERMITTENT_ERRORS = [
18
+ Errno::EAGAIN, Errno::ECONNRESET, Redis::CannotConnectError, Redis::TimeoutError
19
+ ].freeze
6
20
 
7
- class Scheduler
8
-
9
- extend Resque::SchedulerLocking
10
-
11
- class << self
12
-
13
- # If true, logs more stuff...
14
- attr_accessor :verbose
15
-
16
- # If set, produces no output
17
- attr_accessor :mute
21
+ private
18
22
 
19
- # If set, will write messages to the file
20
- attr_accessor :logfile
23
+ extend Resque::Scheduler::Locking
24
+ extend Resque::Scheduler::Configuration
25
+ extend Resque::Scheduler::SignalHandling
21
26
 
22
- # If set, will try to update the schedule in the loop
23
- attr_accessor :dynamic
24
-
25
- # Amount of time in seconds to sleep between polls of the delayed
26
- # queue. Defaults to 5
27
- attr_writer :poll_sleep_amount
27
+ public
28
28
 
29
+ class << self
29
30
  attr_writer :logger
30
31
 
31
32
  # the Rufus::Scheduler jobs that are scheduled
32
- def scheduled_jobs
33
- @@scheduled_jobs
34
- end
33
+ attr_reader :scheduled_jobs
35
34
 
36
- def poll_sleep_amount
37
- @poll_sleep_amount ||= 5 # seconds
38
- end
39
-
40
- def logger
41
- @logger ||= ResqueScheduler::LoggerBuilder.new(:mute => mute, :verbose => verbose, :log_dev => logfile).build
42
- end
35
+ # allow user to set an additional failure handler
36
+ attr_writer :failure_handler
43
37
 
44
38
  # Schedule all jobs and continually look for delayed jobs (never returns)
45
39
  def run
46
- $0 = "resque-scheduler: Starting"
40
+ procline 'Starting'
47
41
 
48
42
  # trap signals
49
43
  register_signal_handlers
@@ -54,52 +48,45 @@ module Resque
54
48
  $stdout.sync = true
55
49
  $stderr.sync = true
56
50
 
57
- # Load the schedule into rufus
58
- # If dynamic is set, load that schedule otherwise use normal load
59
- if dynamic
60
- reload_schedule!
61
- else
62
- load_schedule!
63
- end
51
+ was_master = nil
64
52
 
65
- # Now start the scheduling part of the loop.
66
- loop do
67
- if is_master?
68
- begin
69
- handle_delayed_items
70
- update_schedule if dynamic
71
- rescue Errno::EAGAIN, Errno::ECONNRESET => e
72
- warn e.message
73
- end
74
- end
75
- poll_sleep
76
- end
53
+ begin
54
+ @th = Thread.current
77
55
 
78
- # never gets here.
79
- end
56
+ # Now start the scheduling part of the loop.
57
+ loop do
58
+ begin
59
+ # Check on changes to master/child
60
+ @am_master = master?
61
+ if am_master != was_master
62
+ procline am_master ? 'Master scheduler' : 'Child scheduler'
80
63
 
64
+ # Load schedule because changed
65
+ reload_schedule!
66
+ end
81
67
 
82
- # For all signals, set the shutdown flag and wait for current
83
- # poll/enqueing to finish (should be almost istant). In the
84
- # case of sleeping, exit immediately.
85
- def register_signal_handlers
86
- trap("TERM") { shutdown }
87
- trap("INT") { shutdown }
68
+ if am_master
69
+ handle_delayed_items
70
+ update_schedule if dynamic
71
+ end
72
+ was_master = am_master
73
+ rescue *INTERMITTENT_ERRORS => e
74
+ log! e.message
75
+ release_master_lock
76
+ end
77
+ poll_sleep
78
+ end
88
79
 
89
- begin
90
- trap('QUIT') { shutdown }
91
- trap('USR1') { print_schedule }
92
- trap('USR2') { reload_schedule! }
93
- rescue ArgumentError
94
- warn "Signals QUIT and USR1 and USR2 not supported."
80
+ rescue Interrupt
81
+ log 'Exiting'
95
82
  end
96
83
  end
97
84
 
98
85
  def print_schedule
99
86
  if rufus_scheduler
100
87
  log! "Scheduling Info\tLast Run"
101
- scheduler_jobs = rufus_scheduler.all_jobs
102
- scheduler_jobs.each do |k, v|
88
+ scheduler_jobs = rufus_scheduler.jobs
89
+ scheduler_jobs.each do |_k, v|
103
90
  log! "#{v.t}\t#{v.last}\t"
104
91
  end
105
92
  end
@@ -108,20 +95,20 @@ module Resque
108
95
  # Pulls the schedule from Resque.schedule and loads it into the
109
96
  # rufus scheduler instance
110
97
  def load_schedule!
111
- procline "Loading Schedule"
98
+ procline 'Loading Schedule'
112
99
 
113
100
  # Need to load the schedule from redis for the first time if dynamic
114
101
  Resque.reload_schedule! if dynamic
115
102
 
116
- log! "Schedule empty! Set Resque.schedule" if Resque.schedule.empty?
103
+ log! 'Schedule empty! Set Resque.schedule' if Resque.schedule.empty?
117
104
 
118
- @@scheduled_jobs = {}
105
+ @scheduled_jobs = {}
119
106
 
120
107
  Resque.schedule.each do |name, config|
121
108
  load_schedule_job(name, config)
122
109
  end
123
- Resque.redis.del(:schedules_changed)
124
- procline "Schedules Loaded"
110
+ Resque.redis.del(:schedules_changed) if am_master && dynamic
111
+ procline 'Schedules Loaded'
125
112
  end
126
113
 
127
114
  # modify interval type value to value with options if options available
@@ -130,75 +117,175 @@ module Resque
130
117
  if args.is_a?(::Array)
131
118
  return args.first if args.size > 2 || !args.last.is_a?(::Hash)
132
119
  # symbolize keys of hash for options
133
- args[1] = args[1].inject({}) do |m, i|
120
+ args[2] = args[1].reduce({}) do |m, i|
134
121
  key, value = i
135
- m[(key.to_sym rescue key) || key] = value
122
+ m[(key.respond_to?(:to_sym) ? key.to_sym : key) || key] = value
136
123
  m
137
124
  end
125
+
126
+ args[2][:job] = true
127
+ args[1] = nil
138
128
  end
139
129
  args
140
130
  end
141
131
 
142
- # Loads a job schedule into the Rufus::Scheduler and stores it in @@scheduled_jobs
132
+ # Loads a job schedule into the Rufus::Scheduler and stores it
133
+ # in @scheduled_jobs
143
134
  def load_schedule_job(name, config)
144
- # If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
145
- # required for the jobs to be scheduled. If rails_env is missing, the
146
- # job should be scheduled regardless of what ENV['RAILS_ENV'] is set
147
- # to.
148
- if config['rails_env'].nil? || rails_env_matches?(config)
135
+ # If `rails_env` or `env` is set in the config, load jobs only if they
136
+ # are meant to be loaded in `Resque::Scheduler.env`. If `rails_env` or
137
+ # `env` is missing, the job should be scheduled regardless of the value
138
+ # of `Resque::Scheduler.env`.
139
+
140
+ configured_env = config['rails_env'] || config['env']
141
+
142
+ if configured_env.nil? || env_matches?(configured_env)
149
143
  log! "Scheduling #{name} "
150
144
  interval_defined = false
151
- interval_types = %w{cron every}
145
+ interval_types = %w(cron every)
152
146
  interval_types.each do |interval_type|
153
- if !config[interval_type].nil? && config[interval_type].length > 0
154
- args = optionizate_interval_value(config[interval_type])
155
- @@scheduled_jobs[name] = rufus_scheduler.send(interval_type, *args) do
156
- if is_master?
157
- log! "queueing #{config['class']} (#{name})"
158
- handle_errors { enqueue_from_config(config) }
159
- end
160
- end
161
- interval_defined = true
162
- break
147
+ next unless !config[interval_type].nil? && !config[interval_type].empty?
148
+ args = optionizate_interval_value(config[interval_type])
149
+ args = [args, nil, job: true] if args.is_a?(::String)
150
+
151
+ job = rufus_scheduler.send(interval_type, *args) do
152
+ enqueue_recurring(name, config)
163
153
  end
154
+ @scheduled_jobs[name] = job
155
+ interval_defined = true
156
+ break
164
157
  end
165
158
  unless interval_defined
166
- log! "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
159
+ log! "no #{interval_types.join(' / ')} found for " \
160
+ "#{config['class']} (#{name}) - skipping"
167
161
  end
162
+ else
163
+ log "Skipping schedule of #{name} because configured " \
164
+ "env #{configured_env.inspect} does not match current " \
165
+ "env #{env.inspect}"
168
166
  end
169
167
  end
170
168
 
171
- # Returns true if the given schedule config hash matches the current
172
- # ENV['RAILS_ENV']
169
+ # Returns true if the given schedule config hash matches the current env
173
170
  def rails_env_matches?(config)
174
- config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/,'').split(',').include?(ENV['RAILS_ENV'])
171
+ warn '`Resque::Scheduler.rails_env_matches?` is deprecated. ' \
172
+ 'Please use `Resque::Scheduler.env_matches?` instead.'
173
+ config['rails_env'] && env &&
174
+ config['rails_env'].split(/[\s,]+/).include?(env)
175
+ end
176
+
177
+ # Returns true if the current env is non-nil and the configured env
178
+ # (which is a comma-split string) includes the current env.
179
+ def env_matches?(configured_env)
180
+ env && configured_env.split(/[\s,]+/).include?(env)
175
181
  end
176
182
 
177
183
  # Handles queueing delayed items
178
184
  # at_time - Time to start scheduling items (default: now).
179
- def handle_delayed_items(at_time=nil)
180
- if timestamp = Resque.next_delayed_timestamp(at_time)
181
- procline "Processing Delayed Items"
182
- while !timestamp.nil?
185
+ def handle_delayed_items(at_time = nil)
186
+ timestamp = Resque.next_delayed_timestamp(at_time)
187
+ if timestamp
188
+ procline 'Processing Delayed Items'
189
+ until timestamp.nil?
183
190
  enqueue_delayed_items_for_timestamp(timestamp)
184
191
  timestamp = Resque.next_delayed_timestamp(at_time)
185
192
  end
186
193
  end
187
194
  end
188
195
 
196
+ def enqueue_next_item(timestamp)
197
+ item = Resque.next_item_for_timestamp(timestamp)
198
+
199
+ if item
200
+ log "queuing #{item['class']} [delayed]"
201
+ enqueue(item)
202
+ end
203
+
204
+ item
205
+ end
206
+
189
207
  # Enqueues all delayed jobs for a timestamp
190
208
  def enqueue_delayed_items_for_timestamp(timestamp)
191
- item = nil
192
- begin
209
+ count = 0
210
+ batch_size = delayed_requeue_batch_size
211
+ actual_batch_size = nil
212
+
213
+ log "Processing delayed items for timestamp #{timestamp}, in batches of #{batch_size}"
214
+
215
+ loop do
193
216
  handle_shutdown do
194
217
  # Continually check that it is still the master
195
- if is_master? && item = Resque.next_item_for_timestamp(timestamp)
196
- log "queuing #{item['class']} [delayed]"
197
- handle_errors { enqueue_from_config(item) }
218
+ if am_master
219
+ actual_batch_size = enqueue_items_in_batch_for_timestamp(timestamp,
220
+ batch_size)
221
+ end
222
+ end
223
+
224
+ count += actual_batch_size
225
+ log "queued #{count} jobs" if actual_batch_size != -1
226
+
227
+ # continue processing until there are no more items in this
228
+ # timestamp. If we don't have a full batch, this is the last one.
229
+ # This also breaks us in the event of a redis transaction failure
230
+ # i.e. enqueue_items_in_batch_for_timestamp returned -1
231
+ break if actual_batch_size < batch_size
232
+ end
233
+
234
+ log "finished queueing #{count} total jobs for timestamp #{timestamp}" if count != -1
235
+ end
236
+
237
+ def timestamp_key(timestamp)
238
+ "delayed:#{timestamp.to_i}"
239
+ end
240
+
241
+ def enqueue_items_in_batch_for_timestamp(timestamp, batch_size)
242
+ timestamp_bucket_key = timestamp_key(timestamp)
243
+
244
+ encoded_jobs_to_requeue = Resque.redis.lrange(timestamp_bucket_key, 0, batch_size - 1)
245
+
246
+ # Watch is used to ensure that the timestamp bucket we are operating on
247
+ # is not altered by any other clients between the watch call and when we call exec
248
+ # (to execute the multi block). We should error catch on the redis.exec return value
249
+ # as that will indicate if the entire transaction was aborted or not. Though we should
250
+ # be safe as our ltrim is inside the multi block and therefore also would have been
251
+ # aborted. So nothing would have been queued, but also nothing lost from the bucket.
252
+ watch_result = Resque.redis.watch(timestamp_bucket_key) do
253
+ Resque.redis.multi do |pipeline|
254
+ encoded_jobs_to_requeue.each do |encoded_job|
255
+ pipeline.srem("timestamps:#{encoded_job}", timestamp_bucket_key)
256
+
257
+ decoded_job = Resque.decode(encoded_job)
258
+ enqueue(decoded_job)
198
259
  end
260
+
261
+ pipeline.ltrim(timestamp_bucket_key, batch_size, -1)
199
262
  end
200
- # continue processing until there are no more ready items in this timestamp
201
- end while !item.nil?
263
+ end
264
+
265
+ # Did the multi block successfully remove from this timestamp and enqueue the jobs?
266
+ success = !watch_result.nil?
267
+
268
+ # If this was the last batch in this timestamp bucket, clean up
269
+ if success && encoded_jobs_to_requeue.count < batch_size
270
+ Resque.clean_up_timestamp(timestamp_bucket_key, timestamp)
271
+ end
272
+
273
+ unless success
274
+ # Our batched transaction failed in Redis due to the timestamp_bucket_key value
275
+ # being modified while we built our multi block. We return -1 to ensure we break
276
+ # out of the loop iterating on this timestamp so it can be re-processed via the
277
+ # loop in handle_delayed_items.
278
+ return -1
279
+ end
280
+
281
+ # will return 0 if none were left to batch
282
+ encoded_jobs_to_requeue.count
283
+ end
284
+
285
+ def enqueue(config)
286
+ enqueue_from_config(config)
287
+ rescue => e
288
+ Resque::Scheduler.failure_handler.on_enqueue_failure(config, e)
202
289
  end
203
290
 
204
291
  def handle_shutdown
@@ -207,43 +294,50 @@ module Resque
207
294
  exit if @shutdown
208
295
  end
209
296
 
210
- def handle_errors
211
- begin
212
- yield
213
- rescue Exception => e
214
- log! "#{e.class.name}: #{e.message}"
215
- end
216
- end
217
-
218
297
  # Enqueues a job based on a config hash
219
298
  def enqueue_from_config(job_config)
220
299
  args = job_config['args'] || job_config[:args]
221
300
 
222
301
  klass_name = job_config['class'] || job_config[:class]
223
- klass = Resque.constantize(klass_name) rescue klass_name
302
+ begin
303
+ klass = Resque::Scheduler::Util.constantize(klass_name)
304
+ rescue NameError
305
+ klass = klass_name
306
+ end
224
307
 
225
308
  params = args.is_a?(Hash) ? [args] : Array(args)
226
- queue = job_config['queue'] || job_config[:queue] || Resque.queue_from_class(klass)
227
- # Support custom job classes like those that inherit from Resque::JobWithStatus (resque-status)
228
- if (job_klass = job_config['custom_job_class']) && (job_klass != 'Resque::Job')
229
- # The custom job class API must offer a static "scheduled" method. If the custom
230
- # job class can not be constantized (via a requeue call from the web perhaps), fall
231
- # back to enqueing normally via Resque::Job.create.
309
+ queue = job_config['queue'] ||
310
+ job_config[:queue] ||
311
+ Resque.queue_from_class(klass)
312
+ # Support custom job classes like those that inherit from
313
+ # Resque::JobWithStatus (resque-status)
314
+ job_klass = job_config['custom_job_class']
315
+ if job_klass && job_klass != 'Resque::Job'
316
+ # The custom job class API must offer a static "scheduled" method. If
317
+ # the custom job class can not be constantized (via a requeue call
318
+ # from the web perhaps), fall back to enqueuing normally via
319
+ # Resque::Job.create.
232
320
  begin
233
- Resque.constantize(job_klass).scheduled(queue, klass_name, *params)
321
+ Resque::Scheduler::Util.constantize(job_klass).scheduled(
322
+ queue, klass_name, *params
323
+ )
234
324
  rescue NameError
235
- # Note that the custom job class (job_config['custom_job_class']) is the one enqueued
325
+ # Note that the custom job class (job_config['custom_job_class'])
326
+ # is the one enqueued
236
327
  Resque::Job.create(queue, job_klass, *params)
237
328
  end
238
329
  else
239
- # hack to avoid havoc for people shoving stuff into queues
330
+ # Hack to avoid havoc for people shoving stuff into queues
240
331
  # for non-existent classes (for example: running scheduler in
241
- # one app that schedules for another
332
+ # one app that schedules for another.
242
333
  if Class === klass
243
- ResqueScheduler::Plugin.run_before_delayed_enqueue_hooks(klass, *params)
334
+ Resque::Scheduler::Plugin.run_before_delayed_enqueue_hooks(
335
+ klass, *params
336
+ )
244
337
 
245
- # If the class is a custom job class, call self#scheduled on it. This allows you to do things like
246
- # Resque.enqueue_at(timestamp, CustomJobClass). Otherwise, pass off to Resque.
338
+ # If the class is a custom job class, call self#scheduled on it.
339
+ # This allows you to do things like Resque.enqueue_at(timestamp,
340
+ # CustomJobClass). Otherwise, pass off to Resque.
247
341
  if klass.respond_to?(:scheduled)
248
342
  klass.scheduled(queue, klass_name, *params)
249
343
  else
@@ -258,7 +352,7 @@ module Resque
258
352
  end
259
353
 
260
354
  def rufus_scheduler
261
- @rufus_scheduler ||= Rufus::Scheduler.start_new
355
+ @rufus_scheduler ||= Rufus::Scheduler.new
262
356
  end
263
357
 
264
358
  # Stops old rufus scheduler and creates a new one. Returns the new
@@ -266,21 +360,23 @@ module Resque
266
360
  def clear_schedule!
267
361
  rufus_scheduler.stop
268
362
  @rufus_scheduler = nil
269
- @@scheduled_jobs = {}
363
+ @scheduled_jobs = {}
270
364
  rufus_scheduler
271
365
  end
272
366
 
273
367
  def reload_schedule!
274
- procline "Reloading Schedule"
368
+ procline 'Reloading Schedule'
275
369
  clear_schedule!
276
370
  load_schedule!
277
371
  end
278
372
 
279
373
  def update_schedule
280
374
  if Resque.redis.scard(:schedules_changed) > 0
281
- procline "Updating schedule"
282
- Resque.reload_schedule!
283
- while schedule_name = Resque.redis.spop(:schedules_changed)
375
+ procline 'Updating schedule'
376
+ loop do
377
+ schedule_name = Resque.redis.spop(:schedules_changed)
378
+ break unless schedule_name
379
+ Resque.reload_schedule!
284
380
  if Resque.schedule.keys.include?(schedule_name)
285
381
  unschedule_job(schedule_name)
286
382
  load_schedule_job(schedule_name, Resque.schedule[schedule_name])
@@ -288,7 +384,7 @@ module Resque
288
384
  unschedule_job(schedule_name)
289
385
  end
290
386
  end
291
- procline "Schedules Loaded"
387
+ procline 'Schedules Loaded'
292
388
  end
293
389
  end
294
390
 
@@ -296,44 +392,132 @@ module Resque
296
392
  if scheduled_jobs[name]
297
393
  log "Removing schedule #{name}"
298
394
  scheduled_jobs[name].unschedule
299
- @@scheduled_jobs.delete(name)
395
+ @scheduled_jobs.delete(name)
300
396
  end
301
397
  end
302
398
 
303
399
  # Sleeps and returns true
304
400
  def poll_sleep
401
+ handle_shutdown do
402
+ begin
403
+ poll_sleep_loop
404
+ ensure
405
+ @sleeping = false
406
+ end
407
+ end
408
+ true
409
+ end
410
+
411
+ def poll_sleep_loop
305
412
  @sleeping = true
306
- handle_shutdown { sleep poll_sleep_amount }
307
- @sleeping = false
413
+ if poll_sleep_amount > 0
414
+ start = Time.now
415
+ loop do
416
+ elapsed_sleep = (Time.now - start)
417
+ remaining_sleep = poll_sleep_amount - elapsed_sleep
418
+ @do_break = false
419
+ if remaining_sleep <= 0
420
+ @do_break = true
421
+ else
422
+ @do_break = handle_signals_with_operation do
423
+ sleep(remaining_sleep)
424
+ end
425
+ end
426
+ break if @do_break
427
+ end
428
+ else
429
+ handle_signals_with_operation
430
+ end
431
+ end
432
+
433
+ def handle_signals_with_operation
434
+ yield if block_given?
435
+ handle_signals
436
+ false
437
+ rescue Interrupt
438
+ before_shutdown if @shutdown
308
439
  true
309
440
  end
310
441
 
442
+ def stop_rufus_scheduler
443
+ rufus_scheduler.shutdown(:wait)
444
+ end
445
+
446
+ def before_shutdown
447
+ stop_rufus_scheduler
448
+ release_master_lock
449
+ end
450
+
311
451
  # Sets the shutdown flag, clean schedules and exits if sleeping
312
452
  def shutdown
453
+ return if @shutdown
313
454
  @shutdown = true
314
-
315
- if @sleeping
316
- Resque.clean_schedules
317
- Thread.new { release_master_lock! }
318
- exit
319
- end
455
+ log!('Shutting down')
456
+ @th.raise Interrupt if @sleeping
320
457
  end
321
458
 
322
459
  def log!(msg)
323
- logger.info msg
460
+ logger.info { msg }
461
+ end
462
+
463
+ def log_error(msg)
464
+ logger.error { msg }
324
465
  end
325
466
 
326
467
  def log(msg)
327
- logger.debug msg
468
+ logger.debug { msg }
328
469
  end
329
470
 
330
471
  def procline(string)
331
472
  log! string
332
- $0 = "resque-scheduler-#{ResqueScheduler::VERSION}: #{string}"
473
+ argv0 = build_procline(string)
474
+ log "Setting procline #{argv0.inspect}"
475
+ $0 = argv0
333
476
  end
334
477
 
335
- end
478
+ def failure_handler
479
+ @failure_handler ||= Resque::Scheduler::FailureHandler
480
+ end
336
481
 
337
- end
482
+ def logger
483
+ @logger ||= Resque::Scheduler::LoggerBuilder.new(
484
+ quiet: quiet,
485
+ verbose: verbose,
486
+ log_dev: logfile,
487
+ format: logformat
488
+ ).build
489
+ end
490
+
491
+ private
338
492
 
493
+ def enqueue_recurring(name, config)
494
+ if am_master
495
+ log! "queueing #{config['class']} (#{name})"
496
+ enqueue(config)
497
+ Resque.last_enqueued_at(name, Time.now.to_s)
498
+ end
499
+ end
500
+
501
+ def app_str
502
+ app_name ? "[#{app_name}]" : ''
503
+ end
504
+
505
+ def env_str
506
+ env ? "[#{env}]" : ''
507
+ end
508
+
509
+ def build_procline(string)
510
+ "#{internal_name}#{app_str}#{env_str}: #{string}"
511
+ end
512
+
513
+ def internal_name
514
+ "resque-scheduler-#{Resque::Scheduler::VERSION}"
515
+ end
516
+
517
+ def am_master
518
+ @am_master = master? unless defined?(@am_master)
519
+ @am_master
520
+ end
521
+ end
522
+ end
339
523
  end
@@ -0,0 +1,4 @@
1
+ # vim:fileencoding=utf-8
2
+ require_relative 'resque/scheduler'
3
+
4
+ Resque.extend Resque::Scheduler::Extension