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
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at resque@librelist.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile CHANGED
@@ -1,4 +1,12 @@
1
1
  # vim:fileencoding=utf-8
2
2
  source 'https://rubygems.org'
3
3
 
4
+ case req = ENV['RESQUE']
5
+ when nil
6
+ when 'master'
7
+ gem 'resque', git: 'https://github.com/resque/resque'
8
+ else
9
+ gem 'resque', req
10
+ end
11
+
4
12
  gemspec
data/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  resque-scheduler
2
2
  ================
3
3
 
4
- [![Dependency Status](https://gemnasium.com/resque/resque-scheduler.png)](https://gemnasium.com/resque/resque-scheduler)
5
- [![Gem Version](https://badge.fury.io/rb/resque-scheduler.png)](http://badge.fury.io/rb/resque-scheduler)
6
- [![Build Status](https://travis-ci.org/resque/resque-scheduler.png?branch=master)](https://travis-ci.org/resque/resque-scheduler)
7
- [![Code Climate](https://codeclimate.com/github/resque/resque-scheduler.png)](https://codeclimate.com/github/resque/resque-scheduler)
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/resque-scheduler.svg)](https://badge.fury.io/rb/resque-scheduler)
6
+ [![Ruby specs](https://github.com/resque/resque-scheduler/actions/workflows/ruby.yml/badge.svg)](https://github.com/resque/resque-scheduler/actions)
7
+ [![Code Climate](https://codeclimate.com/github/resque/resque-scheduler/badges/gpa.svg)](https://codeclimate.com/github/resque/resque-scheduler)
8
8
 
9
9
  ### Description
10
10
 
11
11
  Resque-scheduler is an extension to [Resque](http://github.com/resque/resque)
12
12
  that adds support for queueing items in the future.
13
13
 
14
- Job scheduling is supported in two different way: Recurring (scheduled) and
14
+ Job scheduling is supported in two different ways: Recurring (scheduled) and
15
15
  Delayed.
16
16
 
17
17
  Scheduled jobs are like cron jobs, recurring on a regular basis. Delayed
@@ -158,7 +158,7 @@ following task to wherever tasks are kept, such as
158
158
  ```ruby
159
159
  task 'resque:pool:setup' do
160
160
  Resque::Pool.after_prefork do |job|
161
- Resque.redis.client.reconnect
161
+ Resque.redis._client.reconnect
162
162
  end
163
163
  end
164
164
  ```
@@ -233,6 +233,17 @@ Resque.remove_delayed_selection { |args| args[0]['account_id'] == current_accoun
233
233
  Resque.remove_delayed_selection { |args| args[0]['user_id'] == current_user.id }
234
234
  ```
235
235
 
236
+ If you need to cancel a delayed job based on some matching arguments AND by which class the job is, but don't wish to specify each argument from when the job was created, you can do like so:
237
+
238
+ ``` ruby
239
+ # after you've enqueued a job like:
240
+ Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :account_id => current_account.id, :user_id => current_user.id)
241
+ # remove jobs matching just the account and that were of the class SendFollowUpEmail:
242
+ Resque.remove_delayed_selection(SendFollowUpEmail) { |args| args[0]['account_id'] == current_account.id }
243
+ # or remove jobs matching just the user and that were of the class SendFollowUpEmail:
244
+ Resque.remove_delayed_selection(SendFollowUpEmail) { |args| args[0]['user_id'] == current_user.id }
245
+ ```
246
+
236
247
  If you need to enqueue immediately a delayed job based on some matching arguments, but don't wish to specify each argument from when the job was created, you can do like so:
237
248
 
238
249
  ``` ruby
@@ -289,6 +300,19 @@ clear_leaderboards_contributors:
289
300
  description: "This job resets the weekly leaderboard for contributions"
290
301
  ```
291
302
 
303
+ If you would like to setup a job that is executed manually you can configure like this in your YAML file.
304
+
305
+ ```yaml
306
+ ImportOrdersManual:
307
+ description: 'Import Amazon Orders Manual'
308
+ custom_job_class: 'AmazonMws::ImportOrdersJob'
309
+ never: "* * * * *"
310
+ queue: high
311
+ description: "This is a manual job for importing orders."
312
+ args:
313
+ days_in_arrears: 7
314
+ ```
315
+
292
316
  The queue value is optional, but if left unspecified resque-scheduler will
293
317
  attempt to get the queue from the job class, which means it needs to be
294
318
  defined. If you're getting "uninitialized constant" errors, you probably
@@ -323,8 +347,8 @@ for handling the heavy lifting of the actual scheduling engine.
323
347
  #### Dynamic schedules
324
348
 
325
349
  Dynamic schedules are programmatically set on a running `resque-scheduler`.
326
- All [rufus-scheduler](http://github.com/jmettraux/rufus-scheduler) options are supported
327
- when setting schedules.
350
+ Most [rufus-scheduler](http://github.com/jmettraux/rufus-scheduler) options are supported
351
+ when setting schedules. Specifically the `overlap` option will not work.
328
352
 
329
353
  Dynamic schedules are not enabled by default. To be able to dynamically set schedules, you
330
354
  must pass the following to `resque-scheduler` initialization (see *Installation* above for a more complete example):
@@ -501,7 +525,7 @@ RESQUE_SCHEDULER_MASTER_LOCK_PREFIX=MyApp: rake resque:scheduler
501
525
 
502
526
  ### resque-web Additions
503
527
 
504
- Resque-scheduler also adds to tabs to the resque-web UI. One is for viewing
528
+ Resque-scheduler also adds two tabs to the resque-web UI. One is for viewing
505
529
  (and manually queueing) the schedule and one is for viewing pending jobs in
506
530
  the delayed queue.
507
531
 
@@ -535,11 +559,7 @@ require 'resque/scheduler/server'
535
559
 
536
560
  That should make the scheduler tabs show up in `resque-web`.
537
561
 
538
- #### Changes as of 2.0.0
539
-
540
- As of resque-scheduler 2.0.0, it's no longer necessary to have the resque-web
541
- process aware of the schedule because it reads it from redis. But prior to
542
- 2.0, you'll want to make sure you load the schedule in this file as well.
562
+ You'll want to make sure you load the schedule in this file as well.
543
563
  Something like this:
544
564
 
545
565
  ```ruby
@@ -608,18 +628,18 @@ with resque could easily work on resque-scheduler.
608
628
 
609
629
  Working on resque-scheduler requires the following:
610
630
 
611
- * A relatively modern Ruby interpreter (MRI 1.9+ is what's tested)
631
+ * A relatively modern Ruby interpreter
612
632
  * bundler
613
633
 
614
634
  The development setup looks like this, which is roughly the same thing
615
- that happens on Travis CI:
635
+ that happens on Travis CI and Appveyor:
616
636
 
617
637
  ``` bash
618
638
  # Install everything
619
639
  bundle install
620
640
 
621
641
  # Make sure tests are green before you change stuff
622
- bundle exec rake
642
+ bundle exec rubocop && bundle exec rake
623
643
  # Change stuff
624
644
  # Repeat
625
645
  ```
data/Rakefile CHANGED
@@ -1,13 +1,9 @@
1
1
  # vim:fileencoding=utf-8
2
2
  require 'bundler/gem_tasks'
3
3
  require 'rake/testtask'
4
- require 'rubocop/rake_task'
5
4
  require 'yard'
6
5
 
7
- task default: [:rubocop, :test] unless RUBY_PLATFORM =~ /java/
8
- task default: [:test] if RUBY_PLATFORM =~ /java/
9
-
10
- RuboCop::RakeTask.new
6
+ task default: :test
11
7
 
12
8
  Rake::TestTask.new do |t|
13
9
  t.libs << 'test'
File without changes
@@ -128,6 +128,7 @@ module Resque
128
128
  def option_parser
129
129
  OptionParser.new do |opts|
130
130
  opts.banner = BANNER
131
+ opts.version = Resque::Scheduler::VERSION
131
132
  OPTIONS.each do |opt|
132
133
  opts.on(*opt[:args], &opt[:callback].call(options))
133
134
  end
@@ -24,7 +24,7 @@ module Resque
24
24
  def enqueue_at_with_queue(queue, timestamp, klass, *args)
25
25
  return false unless plugin.run_before_schedule_hooks(klass, *args)
26
26
 
27
- if Resque.inline? || timestamp.to_i < Time.now.to_i
27
+ if Resque.inline? || timestamp.to_i <= Time.now.to_i
28
28
  # Just create the job and let resque perform it right away with
29
29
  # inline. If the class is a custom job class, call self#scheduled
30
30
  # on it. This allows you to do things like
@@ -33,7 +33,7 @@ module Resque
33
33
  if klass.respond_to?(:scheduled)
34
34
  klass.scheduled(queue, klass.to_s, *args)
35
35
  else
36
- Resque::Job.create(queue, klass, *args)
36
+ Resque.enqueue_to(queue, klass, *args)
37
37
  end
38
38
  else
39
39
  delayed_push(timestamp, job_to_hash_with_queue(queue, klass, args))
@@ -45,6 +45,9 @@ module Resque
45
45
  # Identical to enqueue_at but takes number_of_seconds_from_now
46
46
  # instead of a timestamp.
47
47
  def enqueue_in(number_of_seconds_from_now, klass, *args)
48
+ unless number_of_seconds_from_now.is_a?(Numeric)
49
+ raise ArgumentError, 'Please supply a numeric number of seconds'
50
+ end
48
51
  enqueue_at(Time.now + number_of_seconds_from_now, klass, *args)
49
52
  end
50
53
 
@@ -53,14 +56,17 @@ module Resque
53
56
  # number of seconds has passed.
54
57
  def enqueue_in_with_queue(queue, number_of_seconds_from_now,
55
58
  klass, *args)
59
+ unless number_of_seconds_from_now.is_a?(Numeric)
60
+ raise ArgumentError, 'Please supply a numeric number of seconds'
61
+ end
56
62
  enqueue_at_with_queue(queue, Time.now + number_of_seconds_from_now,
57
63
  klass, *args)
58
64
  end
59
65
 
60
66
  # Used internally to stuff the item into the schedule sorted list.
61
- # +timestamp+ can be either in seconds or a datetime object Insertion
62
- # if O(log(n)). Returns true if it's the first job to be scheduled at
63
- # that time, else false
67
+ # +timestamp+ can be either in seconds or a datetime object. The
68
+ # insertion time complexity is O(log(n)). Returns true if it's
69
+ # the first job to be scheduled at that time, else false.
64
70
  def delayed_push(timestamp, item)
65
71
  # First add this item to the list for this timestamp
66
72
  redis.rpush("delayed:#{timestamp.to_i}", encode(item))
@@ -82,6 +88,7 @@ module Resque
82
88
  end
83
89
 
84
90
  # Returns the size of the delayed queue schedule
91
+ # this does not represent the number of items in the queue to be scheduled
85
92
  def delayed_queue_schedule_size
86
93
  redis.zcard :delayed_queue_schedule
87
94
  end
@@ -143,6 +150,11 @@ module Resque
143
150
  remove_delayed_job(search)
144
151
  end
145
152
 
153
+ def remove_delayed_in_queue(klass, queue, *args)
154
+ search = encode(job_to_hash_with_queue(queue, klass, args))
155
+ remove_delayed_job(search)
156
+ end
157
+
146
158
  # Given an encoded item, enqueue it now
147
159
  def enqueue_delayed(klass, *args)
148
160
  hash = job_to_hash(klass, args)
@@ -151,6 +163,13 @@ module Resque
151
163
  end
152
164
  end
153
165
 
166
+ def enqueue_delayed_with_queue(klass, queue, *args)
167
+ hash = job_to_hash_with_queue(queue, klass, args)
168
+ remove_delayed_in_queue(klass, queue, *args).times do
169
+ Resque::Scheduler.enqueue_from_config(hash)
170
+ end
171
+ end
172
+
154
173
  # Given a block, remove jobs that return true from a block
155
174
  #
156
175
  # This allows for removal of delayed jobs that have arguments matching
@@ -175,7 +194,15 @@ module Resque
175
194
  found_jobs.reduce(0) do |sum, encoded_job|
176
195
  decoded_job = decode(encoded_job)
177
196
  klass = Util.constantize(decoded_job['class'])
178
- sum + enqueue_delayed(klass, *decoded_job['args'])
197
+ queue = decoded_job['queue']
198
+
199
+ if queue
200
+ jobs_queued = enqueue_delayed_with_queue(klass, queue, *decoded_job['args'])
201
+ else
202
+ jobs_queued = enqueue_delayed(klass, *decoded_job['args'])
203
+ end
204
+
205
+ jobs_queued + sum
179
206
  end
180
207
  end
181
208
 
@@ -265,6 +292,8 @@ module Resque
265
292
  { class: klass.to_s, args: args, queue: queue }
266
293
  end
267
294
 
295
+ # Removes a job from the queue, but not modify the timestamp schedule. This method
296
+ # will not effect the output of `delayed_queue_schedule_size`
268
297
  def remove_delayed_job(encoded_job)
269
298
  return 0 if Resque.inline?
270
299
 
@@ -277,6 +306,9 @@ module Resque
277
306
  end
278
307
  end
279
308
 
309
+ # timestamp key is not removed from the schedule, this is done later
310
+ # by the scheduler loop
311
+
280
312
  return 0 if replies.nil? || replies.empty?
281
313
  replies.each_slice(2).map(&:first).inject(:+)
282
314
  end
@@ -32,14 +32,18 @@ module Resque
32
32
 
33
33
  # Need to set this here for conditional Process.daemon redirect of
34
34
  # stderr/stdout to /dev/null
35
- Resque::Scheduler.quiet = !!options[:quiet]
35
+ Resque::Scheduler.quiet = if options.key?(:quiet)
36
+ !!options[:quiet]
37
+ else
38
+ true
39
+ end
36
40
 
37
41
  unless Process.respond_to?('daemon')
38
42
  abort 'background option is set, which requires ruby >= 1.9'
39
43
  end
40
44
 
41
45
  Process.daemon(true, !Resque::Scheduler.quiet)
42
- Resque.redis.client.reconnect
46
+ Resque.redis._client.reconnect
43
47
  end
44
48
 
45
49
  def setup_pid_file
@@ -60,13 +64,13 @@ module Resque
60
64
 
61
65
  c.dynamic = !!options[:dynamic] if options.key?(:dynamic)
62
66
 
63
- c.env = options[:env] if options.key(:env)
67
+ c.env = options[:env] if options.key?(:env)
64
68
 
65
69
  c.logfile = options[:logfile] if options.key?(:logfile)
66
70
 
67
71
  c.logformat = options[:logformat] if options.key?(:logformat)
68
72
 
69
- if psleep = options[:poll_sleep_amount] && !psleep.nil?
73
+ if (psleep = options[:poll_sleep_amount]) && !psleep.nil?
70
74
  c.poll_sleep_amount = Float(psleep)
71
75
  end
72
76
 
@@ -6,19 +6,11 @@ module Resque
6
6
  module Lock
7
7
  class Resilient < Base
8
8
  def acquire!
9
- Resque.redis.evalsha(
10
- acquire_sha,
11
- keys: [key],
12
- argv: [value]
13
- ).to_i == 1
9
+ evalsha(:acquire, [key], [value]).to_i == 1
14
10
  end
15
11
 
16
12
  def locked?
17
- Resque.redis.evalsha(
18
- locked_sha,
19
- keys: [key],
20
- argv: [value]
21
- ).to_i == 1
13
+ evalsha(:locked, [key], [value]).to_i == 1
22
14
  end
23
15
 
24
16
  def timeout=(seconds)
@@ -32,11 +24,26 @@ module Resque
32
24
 
33
25
  private
34
26
 
27
+ def evalsha(script, keys, argv, refresh: false)
28
+ sha_method_name = "#{script}_sha"
29
+ Resque.redis.evalsha(
30
+ send(sha_method_name, refresh),
31
+ keys: keys,
32
+ argv: argv
33
+ )
34
+ rescue Redis::CommandError => e
35
+ if e.message =~ /NOSCRIPT/
36
+ refresh = true
37
+ retry
38
+ end
39
+ raise
40
+ end
41
+
35
42
  def locked_sha(refresh = false)
36
43
  @locked_sha = nil if refresh
37
44
 
38
45
  @locked_sha ||=
39
- Resque.redis.script(:load, <<-EOF.gsub(/^ {14}/, ''))
46
+ Resque.data_store.redis.script(:load, <<-EOF.gsub(/^ {14}/, ''))
40
47
  if redis.call('GET', KEYS[1]) == ARGV[1]
41
48
  then
42
49
  redis.call('EXPIRE', KEYS[1], #{timeout})
@@ -55,7 +62,7 @@ module Resque
55
62
  @acquire_sha = nil if refresh
56
63
 
57
64
  @acquire_sha ||=
58
- Resque.redis.script(:load, <<-EOF.gsub(/^ {14}/, ''))
65
+ Resque.data_store.redis.script(:load, <<-EOF.gsub(/^ {14}/, ''))
59
66
  if redis.call('SETNX', KEYS[1], ARGV[1]) == 1
60
67
  then
61
68
  redis.call('EXPIRE', KEYS[1], #{timeout})
@@ -76,7 +76,7 @@ module Resque
76
76
 
77
77
  def release_master_lock
78
78
  master_lock.release
79
- rescue Errno::EAGAIN, Errno::ECONNRESET, Redis::CannotConnectError
79
+ rescue *INTERMITTENT_ERRORS
80
80
  @master_lock = nil
81
81
  end
82
82
 
@@ -97,7 +97,7 @@ module Resque
97
97
  end
98
98
 
99
99
  def redis_master_version
100
- Resque.redis.info['redis_version'].to_f
100
+ Resque.data_store.redis.info['redis_version'].to_f
101
101
  end
102
102
  end
103
103
  end
@@ -36,7 +36,7 @@ module Resque
36
36
  # :args can be any yaml which will be converted to a ruby literal and
37
37
  # passed in a params. (optional)
38
38
  #
39
- # :rails_envs is the list of envs where the job gets loaded. Envs are
39
+ # :rails_env is the list of envs where the job gets loaded. Envs are
40
40
  # comma separated (optional)
41
41
  #
42
42
  # :description is just that, a description of the job (optional). If
@@ -101,12 +101,13 @@ module Resque
101
101
  end
102
102
 
103
103
  # remove a given schedule by name
104
- def remove_schedule(name)
104
+ # Preventing a reload is optional and available to batch operations
105
+ def remove_schedule(name, reload = true)
105
106
  non_persistent_schedules.delete(name)
106
107
  redis.hdel(:persistent_schedules, name)
107
108
  redis.sadd(:schedules_changed, name)
108
109
 
109
- reload_schedule!
110
+ reload_schedule! if reload
110
111
  end
111
112
 
112
113
  private
@@ -46,7 +46,7 @@
46
46
  <td><%= h(show_job_arguments(job['args'])) if job && delayed_timestamp_size == 1 %></td>
47
47
  <td>
48
48
  <% if job %>
49
- <a href="<%=u URI("/delayed/jobs/#{job['class']}?args=" + URI.encode(job['args'].to_json)) %>">All schedules</a>
49
+ <a href="<%=u URI("/delayed/jobs/#{CGI.escape(job['class'])}?args=" + CGI.escape(job['args'].to_json)) %>">All schedules</a>
50
50
  <% end %>
51
51
  </td>
52
52
  </tr>
@@ -1,5 +1,5 @@
1
1
  <form method="POST" action="<%= u 'delayed/search' %>">
2
- <input type='input' name='search' value="<%= params[:search] %>"/>
2
+ <input type='input' name='search' value="<%= h params[:search] %>"/>
3
3
  <input type='submit' value='Search'/>
4
4
  </form>
5
5
 
@@ -87,7 +87,7 @@ module Resque
87
87
  def delayed_jobs_klass
88
88
  begin
89
89
  klass = Resque::Scheduler::Util.constantize(params[:klass])
90
- @args = JSON.load(URI.decode(params[:args]))
90
+ @args = JSON.load(CGI.unescape(params[:args]))
91
91
  @timestamps = Resque.scheduled_at(klass, *@args)
92
92
  rescue
93
93
  @timestamps = []
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Resque
4
4
  module Scheduler
5
- VERSION = '4.2.1'.freeze
5
+ VERSION = '4.5.0'.freeze
6
6
  end
7
7
  end
@@ -1,5 +1,6 @@
1
1
  # vim:fileencoding=utf-8
2
2
 
3
+ require 'redis/errors'
3
4
  require 'rufus/scheduler'
4
5
  require_relative 'scheduler/configuration'
5
6
  require_relative 'scheduler/locking'
@@ -13,6 +14,9 @@ module Resque
13
14
  autoload :Extension, 'resque/scheduler/extension'
14
15
  autoload :Util, 'resque/scheduler/util'
15
16
  autoload :VERSION, 'resque/scheduler/version'
17
+ INTERMITTENT_ERRORS = [
18
+ Errno::EAGAIN, Errno::ECONNRESET, Redis::CannotConnectError, Redis::TimeoutError
19
+ ].freeze
16
20
 
17
21
  private
18
22
 
@@ -44,13 +48,7 @@ module Resque
44
48
  $stdout.sync = true
45
49
  $stderr.sync = true
46
50
 
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
51
+ was_master = nil
54
52
 
55
53
  begin
56
54
  @th = Thread.current
@@ -58,11 +56,21 @@ module Resque
58
56
  # Now start the scheduling part of the loop.
59
57
  loop do
60
58
  begin
61
- if master?
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'
63
+
64
+ # Load schedule because changed
65
+ reload_schedule!
66
+ end
67
+
68
+ if am_master
62
69
  handle_delayed_items
63
70
  update_schedule if dynamic
64
71
  end
65
- rescue Errno::EAGAIN, Errno::ECONNRESET, Redis::CannotConnectError => e
72
+ was_master = am_master
73
+ rescue *INTERMITTENT_ERRORS => e
66
74
  log! e.message
67
75
  release_master_lock
68
76
  end
@@ -99,7 +107,7 @@ module Resque
99
107
  Resque.schedule.each do |name, config|
100
108
  load_schedule_job(name, config)
101
109
  end
102
- Resque.redis.del(:schedules_changed)
110
+ Resque.redis.del(:schedules_changed) if am_master && dynamic
103
111
  procline 'Schedules Loaded'
104
112
  end
105
113
 
@@ -141,11 +149,7 @@ module Resque
141
149
  args = [args, nil, job: true] if args.is_a?(::String)
142
150
 
143
151
  job = rufus_scheduler.send(interval_type, *args) do
144
- if master?
145
- log! "queueing #{config['class']} (#{name})"
146
- Resque.last_enqueued_at(name, Time.now.to_s)
147
- enqueue(config)
148
- end
152
+ enqueue_recurring(name, config)
149
153
  end
150
154
  @scheduled_jobs[name] = job
151
155
  interval_defined = true
@@ -206,7 +210,7 @@ module Resque
206
210
  loop do
207
211
  handle_shutdown do
208
212
  # Continually check that it is still the master
209
- item = enqueue_next_item(timestamp) if master?
213
+ item = enqueue_next_item(timestamp) if am_master
210
214
  end
211
215
  # continue processing until there are no more ready items in this
212
216
  # timestamp
@@ -371,7 +375,13 @@ module Resque
371
375
  true
372
376
  end
373
377
 
378
+ def stop_rufus_scheduler
379
+ rufus_scheduler.shutdown(:wait)
380
+ rufus_scheduler.join
381
+ end
382
+
374
383
  def before_shutdown
384
+ stop_rufus_scheduler
375
385
  release_master_lock
376
386
  end
377
387
 
@@ -417,6 +427,14 @@ module Resque
417
427
 
418
428
  private
419
429
 
430
+ def enqueue_recurring(name, config)
431
+ if am_master
432
+ log! "queueing #{config['class']} (#{name})"
433
+ enqueue(config)
434
+ Resque.last_enqueued_at(name, Time.now.to_s)
435
+ end
436
+ end
437
+
420
438
  def app_str
421
439
  app_name ? "[#{app_name}]" : ''
422
440
  end
@@ -432,6 +450,11 @@ module Resque
432
450
  def internal_name
433
451
  "resque-scheduler-#{Resque::Scheduler::VERSION}"
434
452
  end
453
+
454
+ def am_master
455
+ @am_master = master? unless defined?(@am_master)
456
+ @am_master
457
+ end
435
458
  end
436
459
  end
437
460
  end