resque-scheduler 2.5.5 → 3.0.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.

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