resque-scheduler 4.2.1 → 4.5.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 (60) hide show
  1. checksums.yaml +5 -5
  2. data/.github/dependabot.yml +12 -0
  3. data/.github/workflows/rubocop.yml +27 -0
  4. data/.github/workflows/ruby.yml +48 -0
  5. data/AUTHORS.md +7 -0
  6. data/CHANGELOG.md +495 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +8 -0
  9. data/README.md +37 -17
  10. data/Rakefile +1 -5
  11. data/{bin → exe}/resque-scheduler +0 -0
  12. data/lib/resque/scheduler/cli.rb +1 -0
  13. data/lib/resque/scheduler/delaying_extensions.rb +38 -6
  14. data/lib/resque/scheduler/env.rb +8 -4
  15. data/lib/resque/scheduler/lock/resilient.rb +19 -12
  16. data/lib/resque/scheduler/locking.rb +2 -2
  17. data/lib/resque/scheduler/scheduling_extensions.rb +4 -3
  18. data/lib/resque/scheduler/server/views/delayed.erb +1 -1
  19. data/lib/resque/scheduler/server/views/search_form.erb +1 -1
  20. data/lib/resque/scheduler/server.rb +1 -1
  21. data/lib/resque/scheduler/version.rb +1 -1
  22. data/lib/resque/scheduler.rb +39 -16
  23. data/resque-scheduler.gemspec +29 -10
  24. metadata +62 -66
  25. data/.gitignore +0 -17
  26. data/.rubocop.yml +0 -18
  27. data/.rubocop_todo.yml +0 -71
  28. data/.simplecov +0 -3
  29. data/.travis.yml +0 -26
  30. data/.vagrant-provision-as-vagrant.sh +0 -15
  31. data/.vagrant-provision.sh +0 -23
  32. data/.vagrant-skel/bash_profile +0 -7
  33. data/.vagrant-skel/bashrc +0 -7
  34. data/HISTORY.md +0 -303
  35. data/Vagrantfile +0 -14
  36. data/examples/Rakefile +0 -2
  37. data/examples/config/initializers/resque-web.rb +0 -37
  38. data/examples/dynamic-scheduling/README.md +0 -28
  39. data/examples/dynamic-scheduling/app/jobs/fix_schedules_job.rb +0 -52
  40. data/examples/dynamic-scheduling/app/jobs/send_email_job.rb +0 -9
  41. data/examples/dynamic-scheduling/app/models/user.rb +0 -16
  42. data/examples/dynamic-scheduling/config/resque.yml +0 -4
  43. data/examples/dynamic-scheduling/config/static_schedule.yml +0 -7
  44. data/examples/dynamic-scheduling/lib/tasks/resque.rake +0 -48
  45. data/examples/run-resque-web +0 -3
  46. data/script/migrate_to_timestamps_set.rb +0 -16
  47. data/tasks/resque_scheduler.rake +0 -2
  48. data/test/cli_test.rb +0 -231
  49. data/test/delayed_queue_test.rb +0 -925
  50. data/test/env_test.rb +0 -47
  51. data/test/multi_process_test.rb +0 -125
  52. data/test/resque-web_test.rb +0 -364
  53. data/test/scheduler_args_test.rb +0 -222
  54. data/test/scheduler_hooks_test.rb +0 -55
  55. data/test/scheduler_locking_test.rb +0 -316
  56. data/test/scheduler_setup_test.rb +0 -141
  57. data/test/scheduler_task_test.rb +0 -72
  58. data/test/scheduler_test.rb +0 -473
  59. data/test/test_helper.rb +0 -147
  60. data/test/util_test.rb +0 -17
data/test/env_test.rb DELETED
@@ -1,47 +0,0 @@
1
- # vim:fileencoding=utf-8
2
- require_relative 'test_helper'
3
-
4
- context 'Env' do
5
- def new_env(options = {})
6
- Resque::Scheduler::Env.new(options)
7
- end
8
-
9
- test 'daemonizes when background is true' do
10
- Process.expects(:daemon)
11
- env = new_env(background: true)
12
- env.setup
13
- end
14
-
15
- test 'reconnects redis when background is true' do
16
- Process.stubs(:daemon)
17
- mock_redis_client = mock('redis_client')
18
- mock_redis = mock('redis')
19
- mock_redis.expects(:client).returns(mock_redis_client)
20
- mock_redis_client.expects(:reconnect)
21
- Resque.expects(:redis).returns(mock_redis)
22
- env = new_env(background: true)
23
- env.setup
24
- end
25
-
26
- test 'aborts when background is given and Process does not support daemon' do
27
- Process.stubs(:daemon)
28
- Process.expects(:respond_to?).with('daemon').returns(false)
29
- env = new_env(background: true)
30
- env.expects(:abort)
31
- env.setup
32
- end
33
-
34
- test 'keep set config if no option given' do
35
- Resque::Scheduler.configure { |c| c.dynamic = true }
36
- env = new_env
37
- env.setup
38
- assert_equal(true, Resque::Scheduler.dynamic)
39
- end
40
-
41
- test 'override config if option given' do
42
- Resque::Scheduler.configure { |c| c.dynamic = true }
43
- env = new_env(dynamic: false)
44
- env.setup
45
- assert_equal(false, Resque::Scheduler.dynamic)
46
- end
47
- end
@@ -1,125 +0,0 @@
1
- # vim:fileencoding=utf-8
2
- require_relative 'test_helper'
3
-
4
- context 'Multi Process' do
5
- test 'setting schedule= from many process does not corrupt the schedules' do
6
- # more info on why we're not using threads:
7
- # https://github.com/resque/resque-scheduler/pull/439#discussion_r16788812
8
- omit('forking is not supported by jruby but this behaviour' \
9
- ' is best tested using forks') if RUBY_ENGINE == 'jruby'
10
- schedules_1 = {}
11
- schedules_2 = {}
12
- schedules = []
13
- pids = []
14
-
15
- # This number may need to be increased if this test is not failing
16
- processes = 100
17
-
18
- schedule_count = 300
19
-
20
- schedule_count.times do |n|
21
- schedules_1["1_job_#{n}"] = { cron: '0 1 0 0 0' }
22
- schedules_2["2_job_#{n}"] = { cron: '0 1 0 0 0' }
23
- end
24
-
25
- processes.times do |n|
26
- pids << fork_with_marshalled_pipe_and_result do
27
- sleep n * 0.1
28
- Resque.schedule = n.even? ? schedules_2 : schedules_1
29
- Resque.schedule
30
- end
31
- end
32
-
33
- schedules += get_results_from_children(pids)
34
-
35
- assert_equal processes, schedules.size,
36
- 'missing some schedules, did a process die?'
37
- schedules.each_with_index do |schedule, i|
38
- assert_equal schedule_count, schedule.size,
39
- "schedule count is incorrect (schedule[#{i}]: #{schedule})"
40
- end
41
- end
42
-
43
- test 'concurrent shutdowns and startups do not corrupt the schedule' do
44
- omit('forking is not supported by jruby but this behaviour' \
45
- ' is best tested using forks') if RUBY_ENGINE == 'jruby'
46
- counts = []
47
- children = []
48
-
49
- processes = 40
50
-
51
- schedules = {}
52
- schedule_count = 300
53
- schedule_count.times do |n|
54
- schedules["job_#{n}"] = { 'cron' => '0 1 0 0 0' }
55
- end
56
-
57
- Resque.schedule = schedules
58
-
59
- processes.times do |n|
60
- children << fork_with_marshalled_pipe_and_result do
61
- sleep Random.rand(3) * 0.1
62
- if n.even?
63
- Resque.schedule = schedules
64
- Resque.schedule.size
65
- else
66
- Resque::Scheduler.before_shutdown
67
- nil
68
- end
69
- end
70
- end
71
-
72
- counts += get_results_from_children(children).compact
73
-
74
- assert_equal processes / 2, counts.size,
75
- 'missing some counts, did a process die?'
76
- counts.each_with_index do |c, i|
77
- assert_equal schedule_count, c, "schedule count is incorrect (c: #{i})"
78
- end
79
- end
80
-
81
- private
82
-
83
- def fork_with_marshalled_pipe_and_result
84
- pipe_read, pipe_write = IO.pipe
85
- pid = fork do
86
- pipe_read.close
87
- result = begin
88
- [yield, nil]
89
- rescue StandardError => exc
90
- [nil, exc]
91
- end
92
- pipe_write.syswrite(Marshal.dump(result))
93
- # exit true the process to get around fork issues on minitest 5
94
- # see https://github.com/seattlerb/minitest/issues/467
95
- Process.exit!(true)
96
- end
97
- pipe_write.close
98
-
99
- [pid, pipe_read]
100
- end
101
-
102
- def get_results_from_children(children)
103
- results = []
104
- children.each do |pid, pipe|
105
- wait_for_child_process_to_terminate(pid)
106
-
107
- raise "forked process failed with #{$CHILD_STATUS}" unless $CHILD_STATUS.success?
108
- result, exc = Marshal.load(pipe.read)
109
- raise exc if exc
110
- results << result
111
- end
112
- results
113
- end
114
-
115
- def wait_for_child_process_to_terminate(pid = -1, timeout = 30)
116
- Timeout.timeout(timeout) do
117
- Process.wait(pid)
118
- end
119
- rescue Timeout::Error
120
- Process.kill('KILL', pid)
121
- # collect status so it doesn't stick around as zombie process
122
- Process.wait(pid)
123
- flunk 'Child process did not terminate in time.'
124
- end
125
- end
@@ -1,364 +0,0 @@
1
- # vim:fileencoding=utf-8
2
- require_relative 'test_helper'
3
-
4
- require 'resque/server/test_helper'
5
-
6
- context 'on GET to /schedule' do
7
- setup { get '/schedule' }
8
-
9
- test('is 200') { assert last_response.ok? }
10
- end
11
-
12
- context 'on GET to /schedule with scheduled jobs' do
13
- setup do
14
- Resque::Scheduler.env = 'production'
15
- Resque.schedule = {
16
- 'some_ivar_job' => {
17
- 'cron' => '* * * * *',
18
- 'class' => 'SomeIvarJob',
19
- 'args' => '/tmp',
20
- 'rails_env' => 'production'
21
- },
22
- 'some_other_job' => {
23
- 'every' => ['1m', ['1h']],
24
- 'queue' => 'high',
25
- 'custom_job_class' => 'SomeOtherJob',
26
- 'args' => {
27
- 'b' => 'blah'
28
- }
29
- },
30
- 'some_fancy_job' => {
31
- 'every' => ['1m'],
32
- 'queue' => 'fancy',
33
- 'class' => 'SomeFancyJob',
34
- 'args' => 'sparkles',
35
- 'rails_env' => 'fancy'
36
- },
37
- 'shared_env_job' => {
38
- 'cron' => '* * * * *',
39
- 'class' => 'SomeSharedEnvJob',
40
- 'args' => '/tmp',
41
- 'rails_env' => 'fancy, production'
42
- }
43
- }
44
- Resque::Scheduler.load_schedule!
45
- get '/schedule'
46
- end
47
-
48
- test('is 200') { assert last_response.ok? }
49
-
50
- test 'see the scheduled job' do
51
- assert last_response.body.include?('SomeIvarJob')
52
- end
53
-
54
- test 'include(highlight) jobs for other envs' do
55
- assert last_response.body.include?('SomeFancyJob')
56
- end
57
-
58
- test 'includes job used in multiple environments' do
59
- assert last_response.body.include?('SomeSharedEnvJob')
60
- end
61
-
62
- test 'allows delete when dynamic' do
63
- Resque::Scheduler.stubs(:dynamic).returns(true)
64
- get '/schedule'
65
-
66
- assert last_response.body.include?('Delete')
67
- end
68
-
69
- test "doesn't allow delete when static" do
70
- Resque::Scheduler.stubs(:dynamic).returns(false)
71
- get '/schedule'
72
-
73
- assert !last_response.body.include?('Delete')
74
- end
75
- end
76
-
77
- context 'on GET to /delayed' do
78
- setup { get '/delayed' }
79
-
80
- test('is 200') { assert last_response.ok? }
81
- end
82
-
83
- context 'on GET to /delayed/jobs/:klass' do
84
- setup do
85
- @t = Time.now + 3600
86
- Resque.enqueue_at(@t, SomeIvarJob, 'foo', 'bar')
87
- get(
88
- URI('/delayed/jobs/SomeIvarJob?args=' <<
89
- URI.encode(%w(foo bar).to_json)).to_s
90
- )
91
- end
92
-
93
- test('is 200') { assert last_response.ok? }
94
-
95
- test 'see the scheduled job' do
96
- assert last_response.body.include?(@t.to_s)
97
- end
98
-
99
- context 'with a namespaced class' do
100
- setup do
101
- @t = Time.now + 3600
102
- module Foo
103
- class Bar
104
- def self.queue
105
- 'bar'
106
- end
107
- end
108
- end
109
- Resque.enqueue_at(@t, Foo::Bar, 'foo', 'bar')
110
- get(
111
- URI('/delayed/jobs/Foo::Bar?args=' <<
112
- URI.encode(%w(foo bar).to_json)).to_s
113
- )
114
- end
115
-
116
- test('is 200') { assert last_response.ok? }
117
-
118
- test 'see the scheduled job' do
119
- assert last_response.body.include?(@t.to_s)
120
- end
121
- end
122
- end
123
-
124
- module Test
125
- RESQUE_SCHEDULE = {
126
- 'job_without_params' => {
127
- 'cron' => '* * * * *',
128
- 'class' => 'JobWithoutParams',
129
- 'args' => {
130
- 'host' => 'localhost'
131
- },
132
- 'rails_env' => 'production'
133
- },
134
- 'job_with_params' => {
135
- 'every' => '1m',
136
- 'class' => 'JobWithParams',
137
- 'args' => {
138
- 'host' => 'localhost'
139
- },
140
- 'parameters' => {
141
- 'log_level' => {
142
- 'description' => 'The level of logging',
143
- 'default' => 'warn'
144
- }
145
- }
146
- }
147
- }.freeze
148
- end
149
-
150
- context 'POST /schedule/requeue' do
151
- setup do
152
- Resque.schedule = Test::RESQUE_SCHEDULE
153
- Resque::Scheduler.load_schedule!
154
- end
155
-
156
- test 'job without params' do
157
- # Regular jobs without params should redirect to /overview
158
- job_name = 'job_without_params'
159
- Resque::Scheduler.stubs(:enqueue_from_config)
160
- .once.with(Resque.schedule[job_name])
161
-
162
- post '/schedule/requeue', 'job_name' => job_name
163
- follow_redirect!
164
- assert_equal 'http://example.org/overview', last_request.url
165
- assert last_response.ok?
166
- end
167
-
168
- test 'job with params' do
169
- # If a job has params defined,
170
- # it should render the template with a form for the job params
171
- job_name = 'job_with_params'
172
- post '/schedule/requeue', 'job_name' => job_name
173
-
174
- assert last_response.ok?, last_response.errors
175
- assert last_response.body.include?('This job requires parameters')
176
- assert last_response.body.include?(
177
- %(<input type="hidden" name="job_name" value="#{job_name}">)
178
- )
179
-
180
- Resque.schedule[job_name]['parameters'].each do |_param_name, param_config|
181
- assert last_response.body.include?(
182
- '<span style="border-bottom:1px dotted;" ' <<
183
- %[title="#{param_config['description']}">(?)</span>]
184
- )
185
- assert last_response.body.include?(
186
- '<input type="text" name="log_level" ' <<
187
- %(value="#{param_config['default']}">)
188
- )
189
- end
190
- end
191
- end
192
-
193
- context 'POST /schedule/requeue_with_params' do
194
- setup do
195
- Resque.schedule = Test::RESQUE_SCHEDULE
196
- Resque::Scheduler.load_schedule!
197
- end
198
-
199
- test 'job with params' do
200
- job_name = 'job_with_params'
201
- log_level = 'error'
202
-
203
- job_config = Resque.schedule[job_name]
204
- args = job_config['args'].merge('log_level' => log_level)
205
- job_config = job_config.merge('args' => args)
206
-
207
- Resque::Scheduler.stubs(:enqueue_from_config).once.with(job_config)
208
-
209
- post '/schedule/requeue_with_params',
210
- 'job_name' => job_name,
211
- 'log_level' => log_level
212
-
213
- follow_redirect!
214
- assert_equal 'http://example.org/overview', last_request.url
215
-
216
- assert last_response.ok?, last_response.errors
217
- end
218
- end
219
-
220
- context 'on POST to /delayed/search' do
221
- setup do
222
- t = Time.now + 60
223
- Resque.enqueue_at(t, SomeIvarJob, 'string arg')
224
- Resque.enqueue(SomeQuickJob)
225
- end
226
-
227
- test 'should find matching scheduled job' do
228
- post '/delayed/search', 'search' => 'ivar'
229
- assert last_response.status == 200
230
- assert last_response.body.include?('SomeIvarJob')
231
- end
232
-
233
- test 'the form should encode string params' do
234
- post '/delayed/search', 'search' => 'ivar'
235
- assert_match('value="[&quot;string arg&quot;]', last_response.body)
236
- end
237
-
238
- test 'should find matching queued job' do
239
- post '/delayed/search', 'search' => 'quick'
240
- assert last_response.status == 200
241
- assert last_response.body.include?('SomeQuickJob')
242
- end
243
- end
244
-
245
- context 'on POST to /delayed/cancel_now' do
246
- setup do
247
- Resque.reset_delayed_queue
248
- Resque.enqueue_at(Time.now + 10, SomeIvarJob, 'arg')
249
- Resque.enqueue_at(Time.now + 100, SomeQuickJob)
250
- end
251
-
252
- test 'removes the specified job' do
253
- job_timestamp, *remaining = Resque.delayed_queue_peek(0, 10)
254
- assert_equal 1, remaining.size
255
-
256
- post '/delayed/cancel_now',
257
- 'timestamp' => job_timestamp,
258
- 'klass' => SomeIvarJob.name,
259
- 'args' => Resque.encode(['arg'])
260
-
261
- assert_equal 302, last_response.status
262
- assert_equal remaining, Resque.delayed_queue_peek(0, 10)
263
- end
264
-
265
- test 'does not remove the job if the params do not match' do
266
- timestamps = Resque.delayed_queue_peek(0, 10)
267
-
268
- post '/delayed/cancel_now',
269
- 'timestamp' => timestamps.first,
270
- 'klass' => SomeIvarJob.name
271
-
272
- assert_equal 302, last_response.status
273
- assert_equal timestamps, Resque.delayed_queue_peek(0, 10)
274
- end
275
-
276
- test 'redirects to overview' do
277
- post '/delayed/cancel_now'
278
- assert last_response.status == 302
279
- assert last_response.header['Location'].include? '/delayed'
280
- end
281
- end
282
-
283
- context 'on POST to /delayed/clear' do
284
- setup { post '/delayed/clear' }
285
-
286
- test 'redirects to delayed' do
287
- assert last_response.status == 302
288
- assert last_response.header['Location'].include? '/delayed'
289
- end
290
- end
291
-
292
- context 'on POST to /delayed/queue_now' do
293
- setup { post '/delayed/queue_now', timestamp: 0 }
294
-
295
- test 'returns ok status' do
296
- assert last_response.status == 200
297
- end
298
- end
299
-
300
- context 'on GET to /delayed/:timestamp' do
301
- setup { get '/delayed/1234567890' }
302
-
303
- test 'shows delayed_timestamp view' do
304
- assert last_response.status == 200
305
- end
306
- end
307
-
308
- context 'DELETE /schedule when dynamic' do
309
- setup do
310
- Resque.schedule = Test::RESQUE_SCHEDULE
311
- Resque::Scheduler.load_schedule!
312
- Resque::Scheduler.stubs(:dynamic).returns(true)
313
- end
314
-
315
- test 'redirects to schedule page' do
316
- delete '/schedule'
317
-
318
- status = last_response.status
319
- redirect_location = last_response.original_headers['Location']
320
- response_status_msg = "Expected response to be a 302, but was a #{status}."
321
- redirect_msg = "Redirect to #{redirect_location} instead of /schedule."
322
-
323
- assert status == 302, response_status_msg
324
- assert_match %r{/schedule/?$}, redirect_location, redirect_msg
325
- end
326
-
327
- test 'does not show the deleted job' do
328
- delete '/schedule', job_name: 'job_with_params'
329
- follow_redirect!
330
-
331
- msg = 'The job should not have been shown on the /schedule page.'
332
- assert !last_response.body.include?('job_with_params'), msg
333
- end
334
-
335
- test 'removes job from redis' do
336
- delete '/schedule', job_name: 'job_with_params'
337
-
338
- msg = 'The job was not deleted from redis.'
339
- assert_nil Resque.fetch_schedule('job_with_params'), msg
340
- end
341
- end
342
-
343
- context 'DELETE /schedule when static' do
344
- setup do
345
- Resque.schedule = Test::RESQUE_SCHEDULE
346
- Resque::Scheduler.load_schedule!
347
- Resque::Scheduler.stubs(:dynamic).returns(false)
348
- end
349
-
350
- test 'does not remove the job from the UI' do
351
- delete '/schedule', job_name: 'job_with_params'
352
- follow_redirect!
353
-
354
- msg = 'The job should not have been removed from the /schedule page.'
355
- assert last_response.body.include?('job_with_params'), msg
356
- end
357
-
358
- test 'does not remove job from redis' do
359
- delete '/schedule', job_name: 'job_with_params'
360
-
361
- msg = 'The job should not have been deleted from redis.'
362
- assert Resque.fetch_schedule('job_with_params'), msg
363
- end
364
- end