resque_admin-scheduler 1.1.6 → 1.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/bin/migrate_to_timestamps_set.rb +16 -0
  3. data/lib/resque_admin-scheduler.rb +4 -0
  4. data/lib/resque_admin/scheduler.rb +447 -0
  5. data/lib/resque_admin/scheduler/cli.rb +147 -0
  6. data/lib/resque_admin/scheduler/configuration.rb +73 -0
  7. data/lib/resque_admin/scheduler/delaying_extensions.rb +324 -0
  8. data/lib/resque_admin/scheduler/env.rb +89 -0
  9. data/lib/resque_admin/scheduler/extension.rb +13 -0
  10. data/lib/resque_admin/scheduler/failure_handler.rb +11 -0
  11. data/lib/resque_admin/scheduler/lock.rb +4 -0
  12. data/lib/resque_admin/scheduler/lock/base.rb +61 -0
  13. data/lib/resque_admin/scheduler/lock/basic.rb +27 -0
  14. data/lib/resque_admin/scheduler/lock/resilient.rb +78 -0
  15. data/lib/resque_admin/scheduler/locking.rb +104 -0
  16. data/lib/resque_admin/scheduler/logger_builder.rb +72 -0
  17. data/lib/resque_admin/scheduler/plugin.rb +31 -0
  18. data/lib/resque_admin/scheduler/scheduling_extensions.rb +141 -0
  19. data/lib/resque_admin/scheduler/server.rb +268 -0
  20. data/lib/resque_admin/scheduler/server/views/delayed.erb +63 -0
  21. data/lib/resque_admin/scheduler/server/views/delayed_schedules.erb +20 -0
  22. data/lib/resque_admin/scheduler/server/views/delayed_timestamp.erb +26 -0
  23. data/lib/resque_admin/scheduler/server/views/requeue-params.erb +23 -0
  24. data/lib/resque_admin/scheduler/server/views/scheduler.erb +58 -0
  25. data/lib/resque_admin/scheduler/server/views/search.erb +72 -0
  26. data/lib/resque_admin/scheduler/server/views/search_form.erb +8 -0
  27. data/lib/resque_admin/scheduler/signal_handling.rb +40 -0
  28. data/lib/resque_admin/scheduler/tasks.rb +25 -0
  29. data/lib/resque_admin/scheduler/util.rb +39 -0
  30. data/lib/resque_admin/scheduler/version.rb +7 -0
  31. data/tasks/resque_admin_scheduler.rake +2 -0
  32. metadata +84 -146
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 48985d8f5b83239c1b5ba0186bca448e8485f249
4
- data.tar.gz: 6137f2722e91a9a0c7bda08977ad60be25641882
3
+ metadata.gz: 504a3f974c00e057d1301dd2d9569258cf077b6b
4
+ data.tar.gz: 674a66930efa2d6c88ff6af4152935eae7bc2bd9
5
5
  SHA512:
6
- metadata.gz: 08bc91b1e2629eee50e5d00edd03d1985da8a265304bdb4fd81cae0f83b5c46291cdd6422564543e518c6573bf374826d86ed2bcdb1754d1cfca43e4505d1eed
7
- data.tar.gz: 0a2e74709c83046519a3dd77a00d87585d9220729f3f7521fd818f1e9e32226c349359dfcec0187bbf0c209df8635193fc032be4813bff7ac27a7d5d17b2488e
6
+ metadata.gz: e0eee0df86a771ff90aa2cf510e70af2bad988c80cabc5689e34d27ab25a81c018cb540c209acbef7ce58e3daf34cb81bf19cc98bb32922cd8db7f58cb261013
7
+ data.tar.gz: 9b781e8d53530107b8afc51e5abe7ccb89f42c5763a6d67413bc13f3b3b847e75332cd999d379213c873b2ebc687287710def25a2dc0a2f3471bfd7a2498f16b
@@ -0,0 +1,16 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ require 'redis'
4
+ require 'resque_admin'
5
+
6
+ if ARGV.size != 1
7
+ puts 'migrate_to_timestamps_set.rb <redis-host:redis-port>'
8
+ exit
9
+ end
10
+
11
+ ResqueAdmin.redis = ARGV[0]
12
+ redis = ResqueAdmin.redis
13
+ Array(redis.keys('delayed:*')).each do |key|
14
+ jobs = redis.lrange(key, 0, -1)
15
+ jobs.each { |job| redis.sadd("timestamps:#{job}", key) }
16
+ end
@@ -0,0 +1,4 @@
1
+ # vim:fileencoding=utf-8
2
+ require_relative 'resque_admin/scheduler'
3
+
4
+ ResqueAdmin.extend ResqueAdmin::Scheduler::Extension
@@ -0,0 +1,447 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ require 'rufus/scheduler'
4
+ require_relative 'scheduler/configuration'
5
+ require_relative 'scheduler/locking'
6
+ require_relative 'scheduler/logger_builder'
7
+ require_relative 'scheduler/signal_handling'
8
+ require_relative 'scheduler/failure_handler'
9
+
10
+ module ResqueAdmin
11
+ module Scheduler
12
+ autoload :Cli, 'resque_admin/scheduler/cli'
13
+ autoload :Extension, 'resque_admin/scheduler/extension'
14
+ autoload :Util, 'resque_admin/scheduler/util'
15
+ autoload :VERSION, 'resque_admin/scheduler/version'
16
+
17
+ private
18
+
19
+ extend ResqueAdmin::Scheduler::Locking
20
+ extend ResqueAdmin::Scheduler::Configuration
21
+ extend ResqueAdmin::Scheduler::SignalHandling
22
+
23
+ public
24
+
25
+ class << self
26
+ attr_writer :logger
27
+
28
+ # the Rufus::Scheduler jobs that are scheduled
29
+ attr_reader :scheduled_jobs
30
+
31
+ # allow user to set an additional failure handler
32
+ attr_writer :failure_handler
33
+
34
+ # Schedule all jobs and continually look for delayed jobs (never returns)
35
+ def run
36
+ procline 'Starting'
37
+
38
+ # trap signals
39
+ register_signal_handlers
40
+
41
+ # Quote from the resque_admin/worker.
42
+ # Fix buffering so we can `rake resque_admin:scheduler > scheduler.log` and
43
+ # get output from the child in there.
44
+ $stdout.sync = true
45
+ $stderr.sync = true
46
+
47
+ # Load the schedule into rufus
48
+ # If dynamic is set, load that schedule otherwise use normal load
49
+ if dynamic
50
+ reload_schedule!
51
+ else
52
+ load_schedule!
53
+ end
54
+
55
+ begin
56
+ @th = Thread.current
57
+
58
+ # Now start the scheduling part of the loop.
59
+ loop do
60
+ begin
61
+ if master?
62
+ handle_delayed_items
63
+ update_schedule if dynamic
64
+ end
65
+ rescue Errno::EAGAIN, Errno::ECONNRESET, Redis::CannotConnectError => e
66
+ log! e.message
67
+ release_master_lock
68
+ end
69
+ poll_sleep
70
+ end
71
+
72
+ rescue Interrupt
73
+ log 'Exiting'
74
+ end
75
+ end
76
+
77
+ def print_schedule
78
+ if rufus_scheduler
79
+ log! "Scheduling Info\tLast Run"
80
+ scheduler_jobs = rufus_scheduler.jobs
81
+ scheduler_jobs.each do |_k, v|
82
+ log! "#{v.t}\t#{v.last}\t"
83
+ end
84
+ end
85
+ end
86
+
87
+ # Pulls the schedule from ResqueAdmin.schedule and loads it into the
88
+ # rufus scheduler instance
89
+ def load_schedule!
90
+ procline 'Loading Schedule'
91
+
92
+ # Need to load the schedule from redis for the first time if dynamic
93
+ ResqueAdmin.reload_schedule! if dynamic
94
+
95
+ log! 'Schedule empty! Set ResqueAdmin.schedule' if ResqueAdmin.schedule.empty?
96
+
97
+ @scheduled_jobs = {}
98
+
99
+ ResqueAdmin.schedule.each do |name, config|
100
+ load_schedule_job(name, config)
101
+ end
102
+ ResqueAdmin.redis.del(:schedules_changed)
103
+ procline 'Schedules Loaded'
104
+ end
105
+
106
+ # modify interval type value to value with options if options available
107
+ def optionizate_interval_value(value)
108
+ args = value
109
+ if args.is_a?(::Array)
110
+ return args.first if args.size > 2 || !args.last.is_a?(::Hash)
111
+ # symbolize keys of hash for options
112
+ args[2] = args[1].reduce({}) do |m, i|
113
+ key, value = i
114
+ m[(key.respond_to?(:to_sym) ? key.to_sym : key) || key] = value
115
+ m
116
+ end
117
+
118
+ args[2][:job] = true
119
+ args[1] = nil
120
+ end
121
+ args
122
+ end
123
+
124
+ # Loads a job schedule into the Rufus::Scheduler and stores it
125
+ # in @scheduled_jobs
126
+ def load_schedule_job(name, config)
127
+ # If `rails_env` or `env` is set in the config, load jobs only if they
128
+ # are meant to be loaded in `ResqueAdmin::Scheduler.env`. If `rails_env` or
129
+ # `env` is missing, the job should be scheduled regardless of the value
130
+ # of `ResqueAdmin::Scheduler.env`.
131
+
132
+ configured_env = config['rails_env'] || config['env']
133
+
134
+ if configured_env.nil? || env_matches?(configured_env)
135
+ log! "Scheduling #{name} "
136
+ interval_defined = false
137
+ interval_types = %w(cron every)
138
+ interval_types.each do |interval_type|
139
+ next unless !config[interval_type].nil? && !config[interval_type].empty?
140
+ args = optionizate_interval_value(config[interval_type])
141
+ args = [args, nil, job: true] if args.is_a?(::String)
142
+
143
+ job = rufus_scheduler.send(interval_type, *args) do
144
+ enqueue_recurring(name, config)
145
+ end
146
+ @scheduled_jobs[name] = job
147
+ interval_defined = true
148
+ break
149
+ end
150
+ unless interval_defined
151
+ log! "no #{interval_types.join(' / ')} found for " \
152
+ "#{config['class']} (#{name}) - skipping"
153
+ end
154
+ else
155
+ log "Skipping schedule of #{name} because configured " \
156
+ "env #{configured_env.inspect} does not match current " \
157
+ "env #{env.inspect}"
158
+ end
159
+ end
160
+
161
+ # Returns true if the given schedule config hash matches the current env
162
+ def rails_env_matches?(config)
163
+ warn '`ResqueAdmin::Scheduler.rails_env_matches?` is deprecated. ' \
164
+ 'Please use `ResqueAdmin::Scheduler.env_matches?` instead.'
165
+ config['rails_env'] && env &&
166
+ config['rails_env'].split(/[\s,]+/).include?(env)
167
+ end
168
+
169
+ # Returns true if the current env is non-nil and the configured env
170
+ # (which is a comma-split string) includes the current env.
171
+ def env_matches?(configured_env)
172
+ env && configured_env.split(/[\s,]+/).include?(env)
173
+ end
174
+
175
+ # Handles queueing delayed items
176
+ # at_time - Time to start scheduling items (default: now).
177
+ def handle_delayed_items(at_time = nil)
178
+ timestamp = ResqueAdmin.next_delayed_timestamp(at_time)
179
+ if timestamp
180
+ procline 'Processing Delayed Items'
181
+ until timestamp.nil?
182
+ enqueue_delayed_items_for_timestamp(timestamp)
183
+ timestamp = ResqueAdmin.next_delayed_timestamp(at_time)
184
+ end
185
+ end
186
+ end
187
+
188
+ def enqueue_next_item(timestamp)
189
+ item = ResqueAdmin.next_item_for_timestamp(timestamp)
190
+
191
+ if item
192
+ log "queuing #{item['class']} [delayed]"
193
+ enqueue(item)
194
+ end
195
+
196
+ item
197
+ end
198
+
199
+ # Enqueues all delayed jobs for a timestamp
200
+ def enqueue_delayed_items_for_timestamp(timestamp)
201
+ item = nil
202
+ loop do
203
+ handle_shutdown do
204
+ # Continually check that it is still the master
205
+ item = enqueue_next_item(timestamp) if master?
206
+ end
207
+ # continue processing until there are no more ready items in this
208
+ # timestamp
209
+ break if item.nil?
210
+ end
211
+ end
212
+
213
+ def enqueue(config)
214
+ enqueue_from_config(config)
215
+ rescue => e
216
+ ResqueAdmin::Scheduler.failure_handler.on_enqueue_failure(config, e)
217
+ end
218
+
219
+ def handle_shutdown
220
+ exit if @shutdown
221
+ yield
222
+ exit if @shutdown
223
+ end
224
+
225
+ # Enqueues a job based on a config hash
226
+ def enqueue_from_config(job_config)
227
+ args = job_config['args'] || job_config[:args]
228
+
229
+ klass_name = job_config['class'] || job_config[:class]
230
+ begin
231
+ klass = ResqueAdmin::Scheduler::Util.constantize(klass_name)
232
+ rescue NameError
233
+ klass = klass_name
234
+ end
235
+
236
+ params = args.is_a?(Hash) ? [args] : Array(args)
237
+ queue = job_config['queue'] ||
238
+ job_config[:queue] ||
239
+ ResqueAdmin.queue_from_class(klass)
240
+ # Support custom job classes like those that inherit from
241
+ # ResqueAdmin::JobWithStatus (resque_admin-status)
242
+ job_klass = job_config['custom_job_class']
243
+ if job_klass && job_klass != 'ResqueAdmin::Job'
244
+ # The custom job class API must offer a static "scheduled" method. If
245
+ # the custom job class can not be constantized (via a requeue call
246
+ # from the web perhaps), fall back to enqueing normally via
247
+ # ResqueAdmin::Job.create.
248
+ begin
249
+ ResqueAdmin::Scheduler::Util.constantize(job_klass).scheduled(
250
+ queue, klass_name, *params
251
+ )
252
+ rescue NameError
253
+ # Note that the custom job class (job_config['custom_job_class'])
254
+ # is the one enqueued
255
+ ResqueAdmin::Job.create(queue, job_klass, *params)
256
+ end
257
+ else
258
+ # Hack to avoid havoc for people shoving stuff into queues
259
+ # for non-existent classes (for example: running scheduler in
260
+ # one app that schedules for another.
261
+ if Class === klass
262
+ ResqueAdmin::Scheduler::Plugin.run_before_delayed_enqueue_hooks(
263
+ klass, *params
264
+ )
265
+
266
+ # If the class is a custom job class, call self#scheduled on it.
267
+ # This allows you to do things like ResqueAdmin.enqueue_at(timestamp,
268
+ # CustomJobClass). Otherwise, pass off to ResqueAdmin.
269
+ if klass.respond_to?(:scheduled)
270
+ klass.scheduled(queue, klass_name, *params)
271
+ else
272
+ ResqueAdmin.enqueue_to(queue, klass, *params)
273
+ end
274
+ else
275
+ # This will not run the before_hooks in rescue, but will at least
276
+ # queue the job.
277
+ ResqueAdmin::Job.create(queue, klass, *params)
278
+ end
279
+ end
280
+ end
281
+
282
+ def rufus_scheduler
283
+ @rufus_scheduler ||= Rufus::Scheduler.new
284
+ end
285
+
286
+ # Stops old rufus scheduler and creates a new one. Returns the new
287
+ # rufus scheduler
288
+ def clear_schedule!
289
+ rufus_scheduler.stop
290
+ @rufus_scheduler = nil
291
+ @scheduled_jobs = {}
292
+ rufus_scheduler
293
+ end
294
+
295
+ def reload_schedule!
296
+ procline 'Reloading Schedule'
297
+ clear_schedule!
298
+ load_schedule!
299
+ end
300
+
301
+ def update_schedule
302
+ if ResqueAdmin.redis.scard(:schedules_changed) > 0
303
+ procline 'Updating schedule'
304
+ loop do
305
+ schedule_name = ResqueAdmin.redis.spop(:schedules_changed)
306
+ break unless schedule_name
307
+ ResqueAdmin.reload_schedule!
308
+ if ResqueAdmin.schedule.keys.include?(schedule_name)
309
+ unschedule_job(schedule_name)
310
+ load_schedule_job(schedule_name, ResqueAdmin.schedule[schedule_name])
311
+ else
312
+ unschedule_job(schedule_name)
313
+ end
314
+ end
315
+ procline 'Schedules Loaded'
316
+ end
317
+ end
318
+
319
+ def unschedule_job(name)
320
+ if scheduled_jobs[name]
321
+ log "Removing schedule #{name}"
322
+ scheduled_jobs[name].unschedule
323
+ @scheduled_jobs.delete(name)
324
+ end
325
+ end
326
+
327
+ # Sleeps and returns true
328
+ def poll_sleep
329
+ handle_shutdown do
330
+ begin
331
+ poll_sleep_loop
332
+ ensure
333
+ @sleeping = false
334
+ end
335
+ end
336
+ true
337
+ end
338
+
339
+ def poll_sleep_loop
340
+ @sleeping = true
341
+ if poll_sleep_amount > 0
342
+ start = Time.now
343
+ loop do
344
+ elapsed_sleep = (Time.now - start)
345
+ remaining_sleep = poll_sleep_amount - elapsed_sleep
346
+ @do_break = false
347
+ if remaining_sleep <= 0
348
+ @do_break = true
349
+ else
350
+ @do_break = handle_signals_with_operation do
351
+ sleep(remaining_sleep)
352
+ end
353
+ end
354
+ break if @do_break
355
+ end
356
+ else
357
+ handle_signals_with_operation
358
+ end
359
+ end
360
+
361
+ def handle_signals_with_operation
362
+ yield if block_given?
363
+ handle_signals
364
+ false
365
+ rescue Interrupt
366
+ before_shutdown if @shutdown
367
+ true
368
+ end
369
+
370
+ def stop_rufus_scheduler
371
+ rufus_scheduler.shutdown(:wait)
372
+ rufus_scheduler.join
373
+ end
374
+
375
+ def before_shutdown
376
+ stop_rufus_scheduler
377
+ release_master_lock
378
+ end
379
+
380
+ # Sets the shutdown flag, clean schedules and exits if sleeping
381
+ def shutdown
382
+ return if @shutdown
383
+ @shutdown = true
384
+ log!('Shutting down')
385
+ @th.raise Interrupt if @sleeping
386
+ end
387
+
388
+ def log!(msg)
389
+ logger.info { msg }
390
+ end
391
+
392
+ def log_error(msg)
393
+ logger.error { msg }
394
+ end
395
+
396
+ def log(msg)
397
+ logger.debug { msg }
398
+ end
399
+
400
+ def procline(string)
401
+ log! string
402
+ argv0 = build_procline(string)
403
+ log "Setting procline #{argv0.inspect}"
404
+ $0 = argv0
405
+ end
406
+
407
+ def failure_handler
408
+ @failure_handler ||= ResqueAdmin::Scheduler::FailureHandler
409
+ end
410
+
411
+ def logger
412
+ @logger ||= ResqueAdmin::Scheduler::LoggerBuilder.new(
413
+ quiet: quiet,
414
+ verbose: verbose,
415
+ log_dev: logfile,
416
+ format: logformat
417
+ ).build
418
+ end
419
+
420
+ private
421
+
422
+ def enqueue_recurring(name, config)
423
+ if master?
424
+ log! "queueing #{config['class']} (#{name})"
425
+ ResqueAdmin.last_enqueued_at(name, Time.now.to_s)
426
+ enqueue(config)
427
+ end
428
+ end
429
+
430
+ def app_str
431
+ app_name ? "[#{app_name}]" : ''
432
+ end
433
+
434
+ def env_str
435
+ env ? "[#{env}]" : ''
436
+ end
437
+
438
+ def build_procline(string)
439
+ "#{internal_name}#{app_str}#{env_str}: #{string}"
440
+ end
441
+
442
+ def internal_name
443
+ "resque_admin-scheduler-#{ResqueAdmin::Scheduler::VERSION}"
444
+ end
445
+ end
446
+ end
447
+ end