resque-scheduler 2.5.5 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of resque-scheduler might be problematic. Click here for more details.

Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +12 -6
  3. data/.rubocop.yml +18 -113
  4. data/.rubocop_todo.yml +29 -0
  5. data/.simplecov +3 -1
  6. data/.travis.yml +12 -4
  7. data/.vagrant-provision-as-vagrant.sh +15 -0
  8. data/.vagrant-provision.sh +23 -0
  9. data/.vagrant-skel/bash_profile +7 -0
  10. data/.vagrant-skel/bashrc +7 -0
  11. data/AUTHORS.md +5 -0
  12. data/Gemfile +1 -2
  13. data/HISTORY.md +18 -0
  14. data/README.md +39 -11
  15. data/ROADMAP.md +0 -6
  16. data/Rakefile +11 -19
  17. data/Vagrantfile +14 -0
  18. data/bin/resque-scheduler +2 -2
  19. data/examples/Rakefile +1 -1
  20. data/examples/config/initializers/resque-web.rb +2 -2
  21. data/examples/dynamic-scheduling/app/jobs/fix_schedules_job.rb +2 -4
  22. data/examples/dynamic-scheduling/app/jobs/send_email_job.rb +1 -1
  23. data/examples/dynamic-scheduling/app/models/user.rb +2 -2
  24. data/examples/dynamic-scheduling/lib/tasks/resque.rake +2 -2
  25. data/lib/resque-scheduler.rb +3 -1
  26. data/lib/resque/scheduler.rb +112 -168
  27. data/lib/resque/scheduler/cli.rb +144 -0
  28. data/lib/resque/scheduler/configuration.rb +73 -0
  29. data/lib/resque/scheduler/delaying_extensions.rb +278 -0
  30. data/lib/resque/scheduler/env.rb +61 -0
  31. data/lib/resque/scheduler/extension.rb +13 -0
  32. data/lib/resque/scheduler/lock.rb +2 -1
  33. data/lib/resque/scheduler/lock/base.rb +6 -2
  34. data/lib/resque/scheduler/lock/basic.rb +4 -5
  35. data/lib/resque/scheduler/lock/resilient.rb +30 -37
  36. data/lib/resque/scheduler/locking.rb +94 -0
  37. data/lib/resque/scheduler/logger_builder.rb +72 -0
  38. data/lib/resque/scheduler/plugin.rb +31 -0
  39. data/lib/resque/scheduler/scheduling_extensions.rb +150 -0
  40. data/lib/resque/scheduler/server.rb +246 -0
  41. data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed.erb +2 -1
  42. data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed_schedules.erb +0 -0
  43. data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed_timestamp.erb +0 -0
  44. data/lib/{resque_scheduler → resque/scheduler}/server/views/requeue-params.erb +0 -0
  45. data/lib/{resque_scheduler → resque/scheduler}/server/views/scheduler.erb +16 -1
  46. data/lib/{resque_scheduler → resque/scheduler}/server/views/search.erb +2 -1
  47. data/lib/{resque_scheduler → resque/scheduler}/server/views/search_form.erb +0 -0
  48. data/lib/resque/scheduler/signal_handling.rb +40 -0
  49. data/lib/{resque_scheduler → resque/scheduler}/tasks.rb +3 -5
  50. data/lib/resque/scheduler/util.rb +41 -0
  51. data/lib/resque/scheduler/version.rb +7 -0
  52. data/resque-scheduler.gemspec +21 -19
  53. data/script/migrate_to_timestamps_set.rb +5 -3
  54. data/tasks/resque_scheduler.rake +1 -1
  55. data/test/cli_test.rb +26 -69
  56. data/test/delayed_queue_test.rb +262 -169
  57. data/test/env_test.rb +41 -0
  58. data/test/resque-web_test.rb +169 -48
  59. data/test/scheduler_args_test.rb +73 -41
  60. data/test/scheduler_hooks_test.rb +9 -8
  61. data/test/scheduler_locking_test.rb +55 -36
  62. data/test/scheduler_setup_test.rb +52 -15
  63. data/test/scheduler_task_test.rb +15 -10
  64. data/test/scheduler_test.rb +215 -114
  65. data/test/support/redis_instance.rb +32 -33
  66. data/test/test_helper.rb +33 -36
  67. data/test/util_test.rb +11 -0
  68. metadata +113 -57
  69. data/lib/resque/scheduler_locking.rb +0 -91
  70. data/lib/resque_scheduler.rb +0 -386
  71. data/lib/resque_scheduler/cli.rb +0 -160
  72. data/lib/resque_scheduler/logger_builder.rb +0 -72
  73. data/lib/resque_scheduler/plugin.rb +0 -28
  74. data/lib/resque_scheduler/server.rb +0 -183
  75. data/lib/resque_scheduler/util.rb +0 -34
  76. data/lib/resque_scheduler/version.rb +0 -5
  77. data/test/redis-test.conf +0 -108
@@ -0,0 +1,246 @@
1
+ # vim:fileencoding=utf-8
2
+ require 'resque-scheduler'
3
+ require 'resque/server'
4
+ require 'json'
5
+
6
+ # Extend Resque::Server to add tabs
7
+ module Resque
8
+ module Scheduler
9
+ module Server
10
+ def self.included(base)
11
+ base.class_eval do
12
+ helpers { include HelperMethods }
13
+ include ServerMethods
14
+
15
+ get('/schedule') { schedule }
16
+ post('/schedule/requeue') { schedule_requeue }
17
+ post('/schedule/requeue_with_params') do
18
+ schedule_requeue_with_params
19
+ end
20
+ delete('/schedule') { delete_schedule }
21
+ get('/delayed') { delayed }
22
+ get('/delayed/jobs/:klass') { delayed_jobs_klass }
23
+ post('/delayed/search') { delayed_search }
24
+ get('/delayed/:timestamp') { delayed_timestamp }
25
+ post('/delayed/queue_now') { delayed_queue_now }
26
+ post('/delayed/cancel_now') { delayed_cancel_now }
27
+ post('/delayed/clear') { delayed_clear }
28
+ end
29
+ end
30
+
31
+ module ServerMethods
32
+ def schedule
33
+ Resque.reload_schedule! if Resque::Scheduler.dynamic
34
+ erb scheduler_template('scheduler')
35
+ end
36
+
37
+ def schedule_requeue
38
+ @job_name = params['job_name'] || params[:job_name]
39
+ config = Resque.schedule[@job_name]
40
+ @parameters = config['parameters'] || config[:parameters]
41
+ if @parameters
42
+ erb scheduler_template('requeue-params')
43
+ else
44
+ Resque::Scheduler.enqueue_from_config(config)
45
+ redirect u('/overview')
46
+ end
47
+ end
48
+
49
+ def schedule_requeue_with_params
50
+ job_name = params['job_name'] || params[:job_name]
51
+ config = Resque.schedule[job_name]
52
+ # Build args hash from post data (removing the job name)
53
+ submitted_args = params.reject do |key, _value|
54
+ key == 'job_name' || key == :job_name
55
+ end
56
+
57
+ # Merge constructed args hash with existing args hash for
58
+ # the job, if it exists
59
+ config_args = config['args'] || config[:args] || {}
60
+ config_args = config_args.merge(submitted_args)
61
+
62
+ # Insert the args hash into config and queue the resque job
63
+ config = config.merge('args' => config_args)
64
+ Resque::Scheduler.enqueue_from_config(config)
65
+ redirect u('/overview')
66
+ end
67
+
68
+ def delete_schedule
69
+ if Resque::Scheduler.dynamic
70
+ job_name = params['job_name'] || params[:job_name]
71
+ Resque.remove_schedule(job_name)
72
+ end
73
+ redirect u('/schedule')
74
+ end
75
+
76
+ def delayed
77
+ erb scheduler_template('delayed')
78
+ end
79
+
80
+ def delayed_jobs_klass
81
+ begin
82
+ klass = Resque::Scheduler::Util.constantize(params[:klass])
83
+ @args = JSON.load(URI.decode(params[:args]))
84
+ @timestamps = Resque.scheduled_at(klass, *@args)
85
+ rescue
86
+ @timestamps = []
87
+ end
88
+
89
+ erb scheduler_template('delayed_schedules')
90
+ end
91
+
92
+ def delayed_search
93
+ @jobs = find_job(params[:search])
94
+ erb scheduler_template('search')
95
+ end
96
+
97
+ def delayed_timestamp
98
+ erb scheduler_template('delayed_timestamp')
99
+ end
100
+
101
+ def delayed_queue_now
102
+ timestamp = params['timestamp'].to_i
103
+ if timestamp > 0
104
+ Resque::Scheduler.enqueue_delayed_items_for_timestamp(timestamp)
105
+ end
106
+ redirect u('/overview')
107
+ end
108
+
109
+ def delayed_cancel_now
110
+ klass = Resque::Scheduler::Util.constantize(params['klass'])
111
+ timestamp = params['timestamp']
112
+ args = Resque.decode params['args']
113
+ Resque.remove_delayed_job_from_timestamp(timestamp, klass, *args)
114
+ redirect u('/delayed')
115
+ end
116
+
117
+ def delayed_clear
118
+ Resque.reset_delayed_queue
119
+ redirect u('delayed')
120
+ end
121
+ end
122
+
123
+ module HelperMethods
124
+ def render_partial(partial)
125
+ erb partial, layout: false
126
+ end
127
+
128
+ def format_time(t)
129
+ t.strftime('%Y-%m-%d %H:%M:%S %z')
130
+ end
131
+
132
+ def queue_from_class_name(class_name)
133
+ Resque.queue_from_class(
134
+ Resque::Scheduler::Util.constantize(class_name)
135
+ )
136
+ end
137
+
138
+ def find_job(worker)
139
+ worker = worker.downcase
140
+ results = working_jobs_for_worker(worker)
141
+
142
+ dels = delayed_jobs_for_worker(worker)
143
+ results += dels.select do |j|
144
+ j['class'].downcase.include?(worker) &&
145
+ j.merge!('where_at' => 'delayed')
146
+ end
147
+
148
+ Resque.queues.each do |queue|
149
+ queued = Resque.peek(queue, 0, Resque.size(queue))
150
+ queued = [queued] unless queued.is_a?(Array)
151
+ results += queued.select do |j|
152
+ j['class'].downcase.include?(worker) &&
153
+ j.merge!('queue' => queue, 'where_at' => 'queued')
154
+ end
155
+ end
156
+
157
+ results
158
+ end
159
+
160
+ def schedule_interval(config)
161
+ if config['every']
162
+ schedule_interval_every(config['every'])
163
+ elsif config['cron']
164
+ 'cron: ' + config['cron'].to_s
165
+ else
166
+ 'Not currently scheduled'
167
+ end
168
+ end
169
+
170
+ def schedule_interval_every(every)
171
+ every = [*every]
172
+ s = 'every: ' << every.first
173
+
174
+ return s unless every.length > 1
175
+
176
+ s << ' ('
177
+ meta = every.last.map do |key, value|
178
+ "#{key.to_s.gsub(/_/, ' ')} #{value}"
179
+ end
180
+ s << meta.join(', ') << ')'
181
+ end
182
+
183
+ def schedule_class(config)
184
+ if config['class'].nil? && !config['custom_job_class'].nil?
185
+ config['custom_job_class']
186
+ else
187
+ config['class']
188
+ end
189
+ end
190
+
191
+ def scheduler_template(name)
192
+ File.read(
193
+ File.expand_path("../server/views/#{name}.erb", __FILE__)
194
+ )
195
+ end
196
+
197
+ def scheduled_in_this_env?(name)
198
+ return true if Resque.schedule[name]['rails_env'].nil?
199
+ rails_env(name).split(/[\s,]+/).include?(Resque::Scheduler.env)
200
+ end
201
+
202
+ def rails_env(name)
203
+ Resque.schedule[name]['rails_env']
204
+ end
205
+
206
+ private
207
+
208
+ def working_jobs_for_worker(worker)
209
+ [].tap do |results|
210
+ working = [*Resque.working]
211
+ work = working.select do |w|
212
+ w.job && w.job['payload'] &&
213
+ w.job['payload']['class'].downcase.include?(worker)
214
+ end
215
+ work.each do |w|
216
+ results += [
217
+ w.job['payload'].merge(
218
+ 'queue' => w.job['queue'], 'where_at' => 'working'
219
+ )
220
+ ]
221
+ end
222
+ end
223
+ end
224
+
225
+ def delayed_jobs_for_worker(_worker)
226
+ [].tap do |dels|
227
+ schedule_size = Resque.delayed_queue_schedule_size
228
+ Resque.delayed_queue_peek(0, schedule_size).each do |d|
229
+ Resque.delayed_timestamp_peek(
230
+ d, 0, Resque.delayed_timestamp_size(d)).each do |j|
231
+ dels << j.merge!('timestamp' => d)
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ Resque::Server.tabs << 'Schedule'
242
+ Resque::Server.tabs << 'Delayed'
243
+
244
+ Resque::Server.class_eval do
245
+ include Resque::Scheduler::Server
246
+ end
@@ -1,7 +1,7 @@
1
1
  <h1>Delayed Jobs</h1>
2
2
  <%- size = resque.delayed_queue_schedule_size %>
3
3
 
4
- <%= erb File.read(File.join(File.dirname(__FILE__), 'server/views/search_form.erb')) %>
4
+ <%= render_partial File.read(File.join(File.dirname(__FILE__), 'server/views/search_form.erb')) %>
5
5
 
6
6
  <p class='intro'>
7
7
  This list below contains the timestamps for scheduled delayed jobs.
@@ -57,3 +57,4 @@
57
57
  <% end %>
58
58
 
59
59
  <%= partial :next_more, :start => start, :size => size %>
60
+
@@ -5,9 +5,12 @@
5
5
  a job immediately.
6
6
  Server local time: <%= Time.now %>
7
7
  </p>
8
-
8
+ <div style="overflow-y: auto; width:100%; padding: 0px 5px;">
9
9
  <table>
10
10
  <tr>
11
+ <% if Resque::Scheduler.dynamic %>
12
+ <th></th>
13
+ <% end %>
11
14
  <th></th>
12
15
  <th>Name</th>
13
16
  <th>Description</th>
@@ -15,10 +18,20 @@
15
18
  <th>Class</th>
16
19
  <th>Queue</th>
17
20
  <th>Arguments</th>
21
+ <th>Last Enqueued</th>
18
22
  </tr>
19
23
  <% Resque.schedule.keys.sort.select { |n| scheduled_in_this_env?(n) }.each do |name| %>
20
24
  <% config = Resque.schedule[name] %>
21
25
  <tr>
26
+ <% if Resque::Scheduler.dynamic %>
27
+ <td style="padding-top: 12px; padding-bottom: 2px; width: 10px">
28
+ <form action="<%= u "/schedule" %>" method="post" style="margin-left: 0">
29
+ <input type="hidden" name="job_name" value="<%= h name %>">
30
+ <input type="hidden" name="_method" value="delete">
31
+ <input type="submit" value="Delete">
32
+ </form>
33
+ </td>
34
+ <% end %>
22
35
  <td style="padding-top: 12px; padding-bottom: 2px; width: 10px">
23
36
  <form action="<%= u "/schedule/requeue" %>" method="post" style="margin-left: 0">
24
37
  <input type="hidden" name="job_name" value="<%= h name %>">
@@ -31,6 +44,8 @@
31
44
  <td><%= h schedule_class(config) %></td>
32
45
  <td><%= h config['queue'] || queue_from_class_name(config['class']) %></td>
33
46
  <td><%= h config['args'].inspect %></td>
47
+ <td><%= h Resque.get_last_enqueued_at(name) || 'Never' %></td>
34
48
  </tr>
35
49
  <% end %>
36
50
  </table>
51
+ </div>
@@ -1,5 +1,5 @@
1
1
  <h1>Search Results</h1>
2
- <%= erb File.read(File.join(File.dirname(__FILE__), 'server/views/search_form.erb')) %>
2
+ <%= render_partial File.read(File.join(File.dirname(__FILE__), 'server/views/search_form.erb')) %>
3
3
  <hr>
4
4
  <% delayed = @jobs.select { |j| j['where_at'] == 'delayed' } %>
5
5
  <h1>Delayed jobs</h1>
@@ -70,3 +70,4 @@
70
70
  </table>
71
71
 
72
72
 
73
+
@@ -0,0 +1,40 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ module Resque
4
+ module Scheduler
5
+ module SignalHandling
6
+ attr_writer :signal_queue
7
+
8
+ def signal_queue
9
+ @signal_queue ||= []
10
+ end
11
+
12
+ # For all signals, set the shutdown flag and wait for current
13
+ # poll/enqueing to finish (should be almost instant). In the
14
+ # case of sleeping, exit immediately.
15
+ def register_signal_handlers
16
+ %w(INT TERM USR1 USR2 QUIT).each do |sig|
17
+ trap(sig) do
18
+ signal_queue << sig
19
+ # break sleep in the primary scheduler thread, alowing
20
+ # the signal queue to get processed as soon as possible.
21
+ @th.wakeup if @th.alive?
22
+ end
23
+ end
24
+ end
25
+
26
+ def handle_signals
27
+ loop do
28
+ sig = signal_queue.shift
29
+ break unless sig
30
+ log! "Got #{sig} signal"
31
+ case sig
32
+ when 'INT', 'TERM', 'QUIT' then shutdown
33
+ when 'USR1' then print_schedule
34
+ when 'USR2' then reload_schedule!
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -2,13 +2,13 @@
2
2
 
3
3
  require 'English'
4
4
  require 'resque/tasks'
5
- require 'resque_scheduler'
5
+ require 'resque-scheduler'
6
6
 
7
7
  namespace :resque do
8
8
  task :setup
9
9
 
10
10
  def scheduler_cli
11
- @scheduler_cli ||= ResqueScheduler::Cli.new(
11
+ @scheduler_cli ||= Resque::Scheduler::Cli.new(
12
12
  %W(#{ENV['RESQUE_SCHEDULER_OPTIONS']})
13
13
  )
14
14
  end
@@ -21,8 +21,6 @@ namespace :resque do
21
21
 
22
22
  task :scheduler_setup do
23
23
  scheduler_cli.parse_options
24
- unless scheduler_cli.pre_setup
25
- Rake::Task['resque:setup'].invoke
26
- end
24
+ Rake::Task['resque:setup'].invoke unless scheduler_cli.pre_setup
27
25
  end
28
26
  end
@@ -0,0 +1,41 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ module Resque
4
+ module Scheduler
5
+ class Util
6
+ # In order to upgrade to resque(1.25) which has deprecated following
7
+ # methods, we just added these usefull helpers back to use in Resque
8
+ # Scheduler. refer to:
9
+ # https://github.com/resque/resque-scheduler/pull/273
10
+
11
+ def self.constantize(camel_cased_word)
12
+ camel_cased_word = camel_cased_word.to_s
13
+
14
+ if camel_cased_word.include?('-')
15
+ camel_cased_word = classify(camel_cased_word)
16
+ end
17
+
18
+ names = camel_cased_word.split('::')
19
+ names.shift if names.empty? || names.first.empty?
20
+
21
+ constant = Object
22
+ names.each do |name|
23
+ args = Module.method(:const_get).arity != 1 ? [false] : []
24
+
25
+ if constant.const_defined?(name, *args)
26
+ constant = constant.const_get(name)
27
+ else
28
+ constant = constant.const_missing(name)
29
+ end
30
+ end
31
+ constant
32
+ end
33
+
34
+ def self.classify(dashed_word)
35
+ dashed_word.split('-').each do|part|
36
+ part[0] = part[0].chr.upcase
37
+ end.join
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,7 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ module Resque
4
+ module Scheduler
5
+ VERSION = '3.0.0'
6
+ end
7
+ end
@@ -1,36 +1,38 @@
1
1
  # vim:fileencoding=utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'resque_scheduler/version'
4
+ require 'resque/scheduler/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = 'resque-scheduler'
8
- spec.version = ResqueScheduler::VERSION
9
- spec.authors = ['Ben VandenBos']
10
- spec.email = ['bvandenbos@gmail.com']
11
- spec.homepage = 'http://github.com/resque/resque-scheduler'
12
- spec.summary = 'Light weight job scheduling on top of Resque'
13
- spec.description = %q{Light weight job scheduling on top of Resque.
7
+ spec.name = 'resque-scheduler'
8
+ spec.version = Resque::Scheduler::VERSION
9
+ spec.authors = ['Ben VandenBos']
10
+ spec.email = ['bvandenbos@gmail.com']
11
+ spec.homepage = 'http://github.com/resque/resque-scheduler'
12
+ spec.summary = 'Light weight job scheduling on top of Resque'
13
+ spec.description = %q(Light weight job scheduling on top of Resque.
14
14
  Adds methods enqueue_at/enqueue_in to schedule jobs in the future.
15
- Also supports queueing jobs on a fixed, cron-like schedule.}
15
+ Also supports queueing jobs on a fixed, cron-like schedule.)
16
16
  spec.license = 'MIT'
17
17
 
18
- spec.files = `git ls-files`.split("\n")
19
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
- spec.test_files = spec.files.grep(%r{^test/})
18
+ spec.files = `git ls-files`.split("\n")
19
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(/^test\//)
21
21
  spec.require_paths = ['lib']
22
22
 
23
- spec.add_development_dependency 'bundler', '~> 1.3'
23
+ spec.add_development_dependency 'bundler', '~> 1.5'
24
+ spec.add_development_dependency 'json'
25
+ spec.add_development_dependency 'kramdown'
24
26
  spec.add_development_dependency 'mocha'
25
27
  spec.add_development_dependency 'pry'
26
28
  spec.add_development_dependency 'rack-test'
27
29
  spec.add_development_dependency 'rake'
28
- spec.add_development_dependency 'json' if RUBY_VERSION < '1.9'
29
- spec.add_development_dependency 'rubocop' unless RUBY_VERSION < '1.9'
30
- spec.add_development_dependency 'simplecov' unless RUBY_VERSION < '1.9'
30
+ spec.add_development_dependency 'rubocop'
31
+ spec.add_development_dependency 'simplecov'
32
+ spec.add_development_dependency 'yard'
31
33
 
32
34
  spec.add_runtime_dependency 'mono_logger', '~> 1.0'
33
- spec.add_runtime_dependency 'redis', '~> 3.0.4'
34
- spec.add_runtime_dependency 'resque', '~> 1.25.1'
35
- spec.add_runtime_dependency 'rufus-scheduler', '~> 2.0.24'
35
+ spec.add_runtime_dependency 'redis', '~> 3.0'
36
+ spec.add_runtime_dependency 'resque', '~> 1.25'
37
+ spec.add_runtime_dependency 'rufus-scheduler', '~> 2.0'
36
38
  end