resque-scheduler 3.0.0 → 3.1.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.

checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 315b0ea47908358f5cd357a315f9bcf958402ccb
4
- data.tar.gz: 8bcbe775f3fc7f68d47a7d0cf79f75b2f27ac0b3
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YzhjNDEyNjkyZWQ5MGY0YWUxNWNmN2JiMTAzODdhOGUyZTAyZTA1YQ==
5
+ data.tar.gz: !binary |-
6
+ NTQzOWY4MDliZjUxZDhlMTZjYTc4OWM0NWVjMTA5MTVmZmJkYzQ1ZA==
5
7
  SHA512:
6
- metadata.gz: 40b428d2bfdbd36eb3ba623812a3d06db24a674531db24069c9dd19e24af478c49eafe615238d333ae9181ecf2ca3e76a0a8f9b20f2181431a4ff147733d80f4
7
- data.tar.gz: ce2e9aee9671c7a4ffbf016934d169ee36d575543142fccea9318244c8a84c20108878ed5010ace31d67aa706bc6f4793aabc5952bf521aeecf0f33728ac18f4
8
+ metadata.gz: !binary |-
9
+ ZDE1ZTBlZjlkOWM1MDEwMjc3Y2I2MWExNTNiNjFlMDUyNjY1YzZlMmYxYWQ0
10
+ MjdkMjQ3OTA0N2FhYjk4NWI5NTdkZmM2ODYwNTMzNGM4OTExNDdkMjA5ZjI1
11
+ MWI2Y2ExOWZkM2EyZjkxNTVlNTViODExNDYwZjk4OTAyMWM5YWI=
12
+ data.tar.gz: !binary |-
13
+ YzE3OTNjODE1YWI5NWQwNmNjZDJlMDM0Yjg2ZTAwZGRkMzA2NzY3NTc4ZjI4
14
+ NGQ4Y2Q4ZDM1NWUxNDViYjk3YmI1M2E5ZDMwYjAzMTk4MWYwZjMzNGVmNmE5
15
+ YTAyYWI4NGI1Zjc1M2U1MzgxZTk4NWNiODYzZDQyODFkM2M2MWM=
data/.rubocop.yml CHANGED
@@ -7,28 +7,10 @@ AllCops:
7
7
  - resque-scheduler.gemspec
8
8
  - bin/resque-scheduler
9
9
 
10
- # Offence count: 1
11
- CaseEquality:
12
- Enabled: false
13
-
14
- # Offence count: 2
15
- # Configuration parameters: CountComments.
16
- ClassLength:
17
- Max: 130
18
-
19
- # Offence count: 3
20
- CyclomaticComplexity:
21
- Max: 21
22
-
23
- # Offence count: 29
24
10
  Documentation:
25
11
  Enabled: false
26
12
 
27
- # Offence count: 17
28
- # Configuration parameters: CountComments.
29
- MethodLength:
30
- Max: 145
31
-
32
- # Offence count: 1
33
- RescueException:
13
+ Style/DoubleNegation:
14
+ Enabled: false
15
+ Metrics/PerceivedComplexity:
34
16
  Enabled: false
data/.rubocop_todo.yml CHANGED
@@ -1,29 +1,57 @@
1
1
  # This configuration was generated by `rubocop --auto-gen-config`
2
- # on 2014-05-24 08:24:15 -0400 using RuboCop version 0.22.0.
2
+ # on 2014-12-21 12:37:35 -0500 using RuboCop version 0.28.0.
3
3
  # The point is for the user to remove these configuration records
4
4
  # one by one as the offenses are removed from the code base.
5
5
  # Note that changes in the inspected code, or installation of new
6
6
  # versions of RuboCop, may require this file to be generated again.
7
7
 
8
- # Offense count: 12
9
- DoubleNegation:
8
+ # Offense count: 2
9
+ # Configuration parameters: AllowSafeAssignment.
10
+ Lint/AssignmentInCondition:
10
11
  Enabled: false
11
12
 
13
+ # Offense count: 16
14
+ Metrics/AbcSize:
15
+ Max: 42
16
+
17
+ # Offense count: 2
18
+ # Configuration parameters: CountComments.
19
+ Metrics/ClassLength:
20
+ Max: 105
21
+
22
+ # Offense count: 4
23
+ Metrics/CyclomaticComplexity:
24
+ Max: 12
25
+
26
+ # Offense count: 18
27
+ # Configuration parameters: CountComments.
28
+ Metrics/MethodLength:
29
+ Max: 34
30
+
12
31
  # Offense count: 1
13
- EachWithObject:
32
+ Style/CaseEquality:
33
+ Enabled: false
34
+
35
+ # Offense count: 1
36
+ Style/EachWithObject:
14
37
  Enabled: false
15
38
 
16
39
  # Offense count: 3
17
40
  # Configuration parameters: Exclude.
18
- FileName:
41
+ Style/FileName:
19
42
  Enabled: false
20
43
 
21
- # Offense count: 7
44
+ # Offense count: 6
22
45
  # Configuration parameters: MinBodyLength.
23
- GuardClause:
46
+ Style/GuardClause:
47
+ Enabled: false
48
+
49
+ # Offense count: 6
50
+ # Configuration parameters: MaxLineLength.
51
+ Style/IfUnlessModifier:
24
52
  Enabled: false
25
53
 
26
54
  # Offense count: 1
27
- # Configuration parameters: EnforcedStyle, SupportedStyles.
28
- Next:
55
+ # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
56
+ Style/Next:
29
57
  Enabled: false
data/.travis.yml CHANGED
@@ -1,23 +1,31 @@
1
1
  language: ruby
2
+ sudo: false
2
3
  rvm:
3
- - '1.9.3'
4
- - '2.1'
5
- - 'jruby-19mode'
6
- - 'rbx'
4
+ - 1.9.3
5
+ - 2.1.5
6
+ - jruby-19mode
7
+ - rbx
7
8
  env:
8
9
  global:
9
10
  - RESQUE_SCHEDULER_DISABLE_TEST_REDIS_SERVER=1
10
11
  - JRUBY_OPTS='-Xcext.enabled=true'
11
12
  matrix:
12
13
  allow_failures:
13
- - rvm: 'jruby-19mode'
14
- - rvm: 'rbx'
14
+ - rvm: jruby-19mode
15
+ - rvm: rbx
15
16
  services:
16
17
  - redis-server
17
18
  notifications:
18
19
  email:
19
20
  recipients:
20
21
  - daniel.buch+resque-scheduler@gmail.com
22
+ on_success: change
23
+ on_failure: change
24
+ irc:
25
+ channels:
26
+ - 'chat.freenode.net#resque'
27
+ on_success: change
28
+ on_failure: change
21
29
  deploy:
22
30
  provider: rubygems
23
31
  api_key:
@@ -26,4 +34,5 @@ deploy:
26
34
  on:
27
35
  tags: true
28
36
  repo: resque/resque-scheduler
29
- rvm: '2.1'
37
+ rvm: 2.1.5
38
+ all_branches: true
data/HISTORY.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Resque Scheduler History / ChangeLog / Release Notes
2
2
 
3
+ ## 3.1.0 (2014-12-21)
4
+ * Note in README.md about production redis deployment configuration
5
+ * Bugfix to only override configuration options if provided
6
+ * Avoid use of redis `KEYS` command in `Resque.remove_delayed_selection`
7
+ * Better PID file cleanup
8
+ * Added option to filter by job class in `Resque.remove_delayed_selection`
9
+ * Bugfix to only release master lock if it belongs to us
10
+ * Tell-don't-ask with `Resque.schedule` to enable atomic clear & set
11
+
3
12
  ## 3.0.0 (2014-05-27)
4
13
  * The grand re-namespacing of
5
14
  `resque_scheduler/(.*)` => `resque/scheduler/\1`
data/README.md CHANGED
@@ -44,7 +44,7 @@ To install:
44
44
 
45
45
  gem install resque-scheduler
46
46
 
47
- If you use a Gemfile, you may want to specify the `:require` explicitly:
47
+ If you use a Gemfile:
48
48
 
49
49
  ```ruby
50
50
  gem 'resque-scheduler'
@@ -151,7 +151,7 @@ default `'text'`)
151
151
  of `MonoLogger::DEBUG`, default `false`)
152
152
 
153
153
 
154
- ### Resque Pool integration
154
+ ### Resque Pool integration
155
155
 
156
156
  For normal work with the
157
157
  [resque-pool](https://github.com/nevans/resque-pool) gem, add the
@@ -599,6 +599,14 @@ provisioners:
599
599
  vagrant up
600
600
  ```
601
601
 
602
+ ### Deployment Notes
603
+
604
+ It is recommended that a production deployment of resque_scheduler be hosted on
605
+ a dedicated Redis database. While making and managing scheduled tasks,
606
+ resque_scheudler currently scans the entire Redis keyspace, which may cause latency
607
+ and stability issues if resque_scheduler is hosted on a Redis instance storing a large
608
+ number of keys (such as those written by a different system hosted on the same Redis instance).
609
+
602
610
  ### Contributing
603
611
 
604
612
  See [CONTRIBUTING.md](CONTRIBUTING.md)
data/Rakefile CHANGED
@@ -7,7 +7,7 @@ require 'yard'
7
7
  task default: [:rubocop, :test] unless RUBY_PLATFORM =~ /java/
8
8
  task default: [:test] if RUBY_PLATFORM =~ /java/
9
9
 
10
- Rubocop::RakeTask.new
10
+ RuboCop::RakeTask.new
11
11
 
12
12
  Rake::TestTask.new do |t|
13
13
  t.libs << 'test'
@@ -206,7 +206,7 @@ module Resque
206
206
 
207
207
  def handle_errors
208
208
  yield
209
- rescue Exception => e
209
+ rescue => e
210
210
  log_error "#{e.class.name}: #{e.message}"
211
211
  end
212
212
 
@@ -336,7 +336,7 @@ module Resque
336
336
  rescue Interrupt
337
337
  if @shutdown
338
338
  Resque.clean_schedules
339
- release_master_lock!
339
+ release_master_lock
340
340
  end
341
341
  break
342
342
  end
@@ -105,12 +105,7 @@ module Resque
105
105
  # Returns the next delayed queue timestamp
106
106
  # (don't call directly)
107
107
  def next_delayed_timestamp(at_time = nil)
108
- items = redis.zrangebyscore(
109
- :delayed_queue_schedule, '-inf', (at_time || Time.now).to_i,
110
- limit: [0, 1]
111
- )
112
- timestamp = items.nil? ? nil : Array(items).first
113
- timestamp.to_i unless timestamp.nil?
108
+ search_first_delayed_timestamp_in_range(nil, at_time || Time.now)
114
109
  end
115
110
 
116
111
  # Returns the next item to be processed for a given timestamp, nil if
@@ -145,17 +140,7 @@ module Resque
145
140
  # Given an encoded item, remove it from the delayed_queue
146
141
  def remove_delayed(klass, *args)
147
142
  search = encode(job_to_hash(klass, args))
148
- timestamps = redis.smembers("timestamps:#{search}")
149
-
150
- replies = redis.pipelined do
151
- timestamps.each do |key|
152
- redis.lrem(key, 0, search)
153
- redis.srem("timestamps:#{search}", key)
154
- end
155
- end
156
-
157
- return 0 if replies.nil? || replies.empty?
158
- replies.each_slice(2).map(&:first).inject(:+)
143
+ remove_delayed_job(search)
159
144
  end
160
145
 
161
146
  # Given an encoded item, enqueue it now
@@ -170,21 +155,22 @@ module Resque
170
155
  #
171
156
  # This allows for removal of delayed jobs that have arguments matching
172
157
  # certain criteria
173
- def remove_delayed_selection
158
+ def remove_delayed_selection(klass = nil)
174
159
  fail ArgumentError, 'Please supply a block' unless block_given?
175
160
 
176
161
  destroyed = 0
177
- # There is no way to search Redis list entries for a partial match,
178
- # so we query for all delayed job tasks and do our matching after
179
- # decoding the payload data
180
- jobs = Resque.redis.keys('delayed:*')
181
- jobs.each do |job|
162
+ start = nil
163
+ while start = search_first_delayed_timestamp_in_range(start, nil)
164
+ job = "delayed:#{start}"
165
+ start += 1
182
166
  index = Resque.redis.llen(job) - 1
183
167
  while index >= 0
184
168
  payload = Resque.redis.lindex(job, index)
185
169
  decoded_payload = decode(payload)
186
- if yield(decoded_payload['args'])
187
- removed = redis.lrem job, 0, payload
170
+ job_class = decoded_payload['class']
171
+ relevant_class = (klass.nil? || klass.to_s == job_class)
172
+ if relevant_class && yield(decoded_payload['args'])
173
+ removed = remove_delayed_job(payload)
188
174
  destroyed += removed
189
175
  index -= removed
190
176
  else
@@ -192,6 +178,7 @@ module Resque
192
178
  end
193
179
  end
194
180
  end
181
+
195
182
  destroyed
196
183
  end
197
184
 
@@ -254,6 +241,20 @@ module Resque
254
241
  { class: klass.to_s, args: args, queue: queue }
255
242
  end
256
243
 
244
+ def remove_delayed_job(encoded_job)
245
+ timestamps = redis.smembers("timestamps:#{encoded_job}")
246
+
247
+ replies = redis.pipelined do
248
+ timestamps.each do |key|
249
+ redis.lrem(key, 0, encoded_job)
250
+ redis.srem("timestamps:#{encoded_job}", key)
251
+ end
252
+ end
253
+
254
+ return 0 if replies.nil? || replies.empty?
255
+ replies.each_slice(2).map(&:first).inject(:+)
256
+ end
257
+
257
258
  def clean_up_timestamp(key, timestamp)
258
259
  # If the list is empty, remove it.
259
260
 
@@ -270,6 +271,18 @@ module Resque
270
271
  end
271
272
  end
272
273
 
274
+ def search_first_delayed_timestamp_in_range(start_at, stop_at)
275
+ start_at = start_at.nil? ? '-inf' : start_at.to_i
276
+ stop_at = stop_at.nil? ? '+inf' : stop_at.to_i
277
+
278
+ items = redis.zrangebyscore(
279
+ :delayed_queue_schedule, start_at, stop_at,
280
+ limit: [0, 1]
281
+ )
282
+ timestamp = items.nil? ? nil : Array(items).first
283
+ timestamp.to_i unless timestamp.nil?
284
+ end
285
+
273
286
  def plugin
274
287
  Resque::Scheduler::Plugin
275
288
  end
@@ -1,10 +1,13 @@
1
1
  # vim:fileencoding=utf-8
2
2
 
3
+ require 'English' # $PROCESS_ID
4
+
3
5
  module Resque
4
6
  module Scheduler
5
7
  class Env
6
8
  def initialize(options)
7
9
  @options = options
10
+ @pidfile_path = nil
8
11
  end
9
12
 
10
13
  def setup
@@ -16,46 +19,79 @@ module Resque
16
19
  setup_scheduler_configuration
17
20
  end
18
21
 
22
+ def cleanup
23
+ cleanup_pid_file
24
+ end
25
+
19
26
  private
20
27
 
21
- attr_reader :options
28
+ attr_reader :options, :pidfile_path
22
29
 
23
30
  def setup_backgrounding
31
+ return unless options[:background]
32
+
24
33
  # Need to set this here for conditional Process.daemon redirect of
25
34
  # stderr/stdout to /dev/null
26
35
  Resque::Scheduler.quiet = !!options[:quiet]
27
36
 
28
- if options[:background]
29
- unless Process.respond_to?('daemon')
30
- abort 'background option is set, which requires ruby >= 1.9'
31
- end
32
-
33
- Process.daemon(true, !Resque::Scheduler.quiet)
34
- Resque.redis.client.reconnect
37
+ unless Process.respond_to?('daemon')
38
+ abort 'background option is set, which requires ruby >= 1.9'
35
39
  end
40
+
41
+ Process.daemon(true, !Resque::Scheduler.quiet)
42
+ Resque.redis.client.reconnect
36
43
  end
37
44
 
38
45
  def setup_pid_file
39
- File.open(options[:pidfile], 'w') do |f|
46
+ return unless options[:pidfile]
47
+
48
+ @pidfile_path = File.expand_path(options[:pidfile])
49
+
50
+ File.open(pidfile_path, 'w') do |f|
40
51
  f.puts $PROCESS_ID
41
- end if options[:pidfile]
52
+ end
53
+
54
+ at_exit { cleanup_pid_file }
42
55
  end
43
56
 
44
57
  def setup_scheduler_configuration
45
58
  Resque::Scheduler.configure do |c|
46
- # These settings are somewhat redundant given the defaults present
47
- # in the attr reader methods. They are left here for clarity and
48
- # to serve as an example of how to use `.configure`.
49
-
50
- c.app_name = options[:app_name]
51
- c.dynamic = !!options[:dynamic]
52
- c.env = options[:env]
53
- c.logfile = options[:logfile]
54
- c.logformat = options[:logformat]
55
- c.poll_sleep_amount = Float(options[:poll_sleep_amount] || '5')
56
- c.verbose = !!options[:verbose]
59
+ if options.key?(:app_name)
60
+ c.app_name = options[:app_name]
61
+ end
62
+
63
+ if options.key?(:dynamic)
64
+ c.dynamic = !!options[:dynamic]
65
+ end
66
+
67
+ if options.key(:env)
68
+ c.env = options[:env]
69
+ end
70
+
71
+ if options.key?(:logfile)
72
+ c.logfile = options[:logfile]
73
+ end
74
+
75
+ if options.key?(:logformat)
76
+ c.logformat = options[:logformat]
77
+ end
78
+
79
+ if psleep = options[:poll_sleep_amount] && !psleep.nil?
80
+ c.poll_sleep_amount = Float(psleep)
81
+ end
82
+
83
+ if options.key?(:verbose)
84
+ c.verbose = !!options[:verbose]
85
+ end
57
86
  end
58
87
  end
88
+
89
+ def cleanup_pid_file
90
+ return unless pidfile_path
91
+
92
+ File.delete(pidfile_path) if File.exist?(pidfile_path)
93
+ @pidfile_path = nil
94
+ end
59
95
  end
60
96
  end
61
97
  end
@@ -33,6 +33,11 @@ module Resque
33
33
  Resque.redis.del(key) == 1
34
34
  end
35
35
 
36
+ # Releases the lock iff we own it
37
+ def release
38
+ locked? && release!
39
+ end
40
+
36
41
  private
37
42
 
38
43
  # Extends the lock by `timeout` seconds.
@@ -67,9 +67,17 @@ module Resque
67
67
  end
68
68
 
69
69
  def release_master_lock!
70
+ warn "#{self}\#release_master_lock! is deprecated because it does " \
71
+ "not respect lock ownership. Use #{self}\#release_master_lock " \
72
+ "instead (at #{caller.first}"
73
+
70
74
  master_lock.release!
71
75
  end
72
76
 
77
+ def release_master_lock
78
+ master_lock.release
79
+ end
80
+
73
81
  private
74
82
 
75
83
  def build_master_lock
@@ -44,15 +44,33 @@ module Resque
44
44
  # param, otherwise params is passed in as the only parameter to
45
45
  # perform.
46
46
  def schedule=(schedule_hash)
47
- # clean the schedules as it exists in redis
48
- clean_schedules
47
+ # This operation tries to be as atomic as possible.
48
+ # It needs to read the existing schedules outside the transaction.
49
+ # Unlikely, but this could still cause a race condition.
50
+ #
51
+ # A more robust solution would be to SCRIPT it, but that would change
52
+ # the required version of Redis.
49
53
 
50
- schedule_hash = prepare_schedule(schedule_hash)
54
+ # select schedules to remove
55
+ if redis.exists(:schedules)
56
+ clean_keys = non_persistent_schedules
57
+ else
58
+ clean_keys = []
59
+ end
51
60
 
52
- # store all schedules in redis, so we can retrieve them back
53
- # everywhere.
54
- schedule_hash.each do |name, job_spec|
55
- set_schedule(name, job_spec)
61
+ # Start the transaction. If this is not atomic and more than one
62
+ # process is calling `schedule=` the clean_schedules might overlap a
63
+ # set_schedule and cause the schedules to become corrupt.
64
+ redis.multi do
65
+ clean_schedules(clean_keys)
66
+
67
+ schedule_hash = prepare_schedule(schedule_hash)
68
+
69
+ # store all schedules in redis, so we can retrieve them back
70
+ # everywhere.
71
+ schedule_hash.each do |name, job_spec|
72
+ set_schedule(name, job_spec)
73
+ end
56
74
  end
57
75
 
58
76
  # ensure only return the successfully saved data!
@@ -82,16 +100,18 @@ module Resque
82
100
  end
83
101
 
84
102
  # clean the schedules as it exists in redis, useful for first setup?
85
- def clean_schedules
86
- if redis.exists(:schedules)
87
- redis.hkeys(:schedules).each do |key|
88
- remove_schedule(key) unless schedule_persisted?(key)
89
- end
103
+ def clean_schedules(keys = non_persistent_schedules)
104
+ keys.each do |key|
105
+ remove_schedule(key)
90
106
  end
91
107
  @schedule = nil
92
108
  true
93
109
  end
94
110
 
111
+ def non_persistent_schedules
112
+ redis.hkeys(:schedules).select { |k| !schedule_persisted?(k) }
113
+ end
114
+
95
115
  # Create or update a schedule with the provided name and configuration.
96
116
  #
97
117
  # Note: values for class and custom_job_class need to be strings,
@@ -102,14 +122,11 @@ module Resque
102
122
  # :queue => 'high',
103
123
  # :args => '/tmp/poop'})
104
124
  def set_schedule(name, config)
105
- existing_config = fetch_schedule(name)
106
125
  persist = config.delete(:persist) || config.delete('persist')
107
- unless existing_config && existing_config == config
108
- redis.pipelined do
109
- redis.hset(:schedules, name, encode(config))
110
- redis.sadd(:schedules_changed, name)
111
- redis.sadd(:persisted_schedules, name) if persist
112
- end
126
+ redis.pipelined do
127
+ redis.hset(:schedules, name, encode(config))
128
+ redis.sadd(:schedules_changed, name)
129
+ redis.sadd(:persisted_schedules, name) if persist
113
130
  end
114
131
  config
115
132
  end
@@ -125,11 +142,9 @@ module Resque
125
142
 
126
143
  # remove a given schedule by name
127
144
  def remove_schedule(name)
128
- redis.pipelined do
129
- redis.hdel(:schedules, name)
130
- redis.srem(:persisted_schedules, name)
131
- redis.sadd(:schedules_changed, name)
132
- end
145
+ redis.hdel(:schedules, name)
146
+ redis.srem(:persisted_schedules, name)
147
+ redis.sadd(:schedules_changed, name)
133
148
  end
134
149
 
135
150
  private
@@ -142,7 +142,7 @@ module Resque
142
142
  dels = delayed_jobs_for_worker(worker)
143
143
  results += dels.select do |j|
144
144
  j['class'].downcase.include?(worker) &&
145
- j.merge!('where_at' => 'delayed')
145
+ j.merge!('where_at' => 'delayed')
146
146
  end
147
147
 
148
148
  Resque.queues.each do |queue|
@@ -150,7 +150,7 @@ module Resque
150
150
  queued = [queued] unless queued.is_a?(Array)
151
151
  results += queued.select do |j|
152
152
  j['class'].downcase.include?(worker) &&
153
- j.merge!('queue' => queue, 'where_at' => 'queued')
153
+ j.merge!('queue' => queue, 'where_at' => 'queued')
154
154
  end
155
155
  end
156
156
 
@@ -210,7 +210,7 @@ module Resque
210
210
  working = [*Resque.working]
211
211
  work = working.select do |w|
212
212
  w.job && w.job['payload'] &&
213
- w.job['payload']['class'].downcase.include?(worker)
213
+ w.job['payload']['class'].downcase.include?(worker)
214
214
  end
215
215
  work.each do |w|
216
216
  results += [
@@ -1,6 +1,5 @@
1
1
  # vim:fileencoding=utf-8
2
2
 
3
- require 'English'
4
3
  require 'resque/tasks'
5
4
  require 'resque-scheduler'
6
5
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Resque
4
4
  module Scheduler
5
- VERSION = '3.0.0'
5
+ VERSION = '3.1.0'
6
6
  end
7
7
  end
@@ -10,27 +10,32 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ['bvandenbos@gmail.com']
11
11
  spec.homepage = 'http://github.com/resque/resque-scheduler'
12
12
  spec.summary = 'Light weight job scheduling on top of Resque'
13
- spec.description = %q(Light weight job scheduling on top of Resque.
13
+ spec.description = <<-DESCRIPTION
14
+ Light weight job scheduling on top of Resque.
14
15
  Adds methods enqueue_at/enqueue_in to schedule jobs in the future.
15
- Also supports queueing jobs on a fixed, cron-like schedule.)
16
+ Also supports queueing jobs on a fixed, cron-like schedule.
17
+ DESCRIPTION
16
18
  spec.license = 'MIT'
17
19
 
18
- spec.files = `git ls-files`.split("\n")
20
+ spec.files = `git ls-files -z`.split("\0")
19
21
  spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
20
22
  spec.test_files = spec.files.grep(/^test\//)
21
23
  spec.require_paths = ['lib']
22
24
 
23
- spec.add_development_dependency 'bundler', '~> 1.5'
25
+ spec.add_development_dependency 'bundler'
24
26
  spec.add_development_dependency 'json'
25
27
  spec.add_development_dependency 'kramdown'
26
28
  spec.add_development_dependency 'mocha'
27
29
  spec.add_development_dependency 'pry'
28
30
  spec.add_development_dependency 'rack-test'
29
31
  spec.add_development_dependency 'rake'
30
- spec.add_development_dependency 'rubocop'
31
32
  spec.add_development_dependency 'simplecov'
32
33
  spec.add_development_dependency 'yard'
33
34
 
35
+ # We pin rubocop because new cops have a tendency to result in false-y
36
+ # positives for new contributors, which is not a nice experience.
37
+ spec.add_development_dependency 'rubocop', '~> 0.28.0'
38
+
34
39
  spec.add_runtime_dependency 'mono_logger', '~> 1.0'
35
40
  spec.add_runtime_dependency 'redis', '~> 3.0'
36
41
  spec.add_runtime_dependency 'resque', '~> 1.25'
data/test/cli_test.rb CHANGED
@@ -153,8 +153,8 @@ context 'Cli' do
153
153
  end
154
154
 
155
155
  test 'initializes logfile from the env' do
156
- cli = new_cli([], 'LOGFILE' => 'derp.log')
157
- assert_equal('derp.log', cli.send(:options)[:logfile])
156
+ cli = new_cli([], 'LOGFILE' => 'example.log')
157
+ assert_equal('example.log', cli.send(:options)[:logfile])
158
158
  end
159
159
 
160
160
  test 'defaults to nil logfile' do
@@ -447,6 +447,75 @@ context 'DelayedQueue' do
447
447
  assert_equal(4, Resque.count_all_scheduled_jobs)
448
448
  end
449
449
 
450
+ test 'remove_delayed_selection ignores last_enqueued_at redis key' do
451
+ t = Time.now + 120
452
+ Resque.enqueue_at(t, SomeIvarJob)
453
+ Resque.last_enqueued_at(SomeIvarJob, t)
454
+
455
+ assert_equal(0, Resque.remove_delayed_selection { |a| a.first == 'bar' })
456
+ assert_equal(t.to_s, Resque.get_last_enqueued_at(SomeIvarJob))
457
+ end
458
+
459
+ test 'remove_delayed_selection removes item by class' do
460
+ t = Time.now + 120
461
+ Resque.enqueue_at(t, SomeIvarJob, 'foo')
462
+ Resque.enqueue_at(t, SomeQuickJob, 'foo')
463
+
464
+ assert_equal(1, Resque.remove_delayed_selection(SomeIvarJob) do |a|
465
+ a.first == 'foo'
466
+ end)
467
+ assert_equal(1, Resque.count_all_scheduled_jobs)
468
+ end
469
+
470
+ test 'remove_delayed_selection removes item by class name as a string' do
471
+ t = Time.now + 120
472
+ Resque.enqueue_at(t, SomeIvarJob, 'foo')
473
+ Resque.enqueue_at(t, SomeQuickJob, 'foo')
474
+
475
+ assert_equal(1, Resque.remove_delayed_selection('SomeIvarJob') do |a|
476
+ a.first == 'foo'
477
+ end)
478
+ assert_equal(1, Resque.count_all_scheduled_jobs)
479
+ end
480
+
481
+ test 'remove_delayed_selection removes item by class name as a symbol' do
482
+ t = Time.now + 120
483
+ Resque.enqueue_at(t, SomeIvarJob, 'foo')
484
+ Resque.enqueue_at(t, SomeQuickJob, 'foo')
485
+
486
+ assert_equal(1, Resque.remove_delayed_selection(:SomeIvarJob) do |a|
487
+ a.first == 'foo'
488
+ end)
489
+ assert_equal(1, Resque.count_all_scheduled_jobs)
490
+ end
491
+
492
+ test 'remove_delayed_selection removes items only from matching job class' do
493
+ t = Time.now + 120
494
+ Resque.enqueue_at(t, SomeIvarJob, 'foo')
495
+ Resque.enqueue_at(t, SomeQuickJob, 'foo')
496
+ Resque.enqueue_at(t + 1, SomeIvarJob, 'bar')
497
+ Resque.enqueue_at(t + 1, SomeQuickJob, 'bar')
498
+ Resque.enqueue_at(t + 1, SomeIvarJob, 'foo')
499
+ Resque.enqueue_at(t + 2, SomeQuickJob, 'foo')
500
+
501
+ assert_equal(2, Resque.remove_delayed_selection(SomeIvarJob) do |a|
502
+ a.first == 'foo'
503
+ end)
504
+ assert_equal(4, Resque.count_all_scheduled_jobs)
505
+ end
506
+
507
+ test 'remove_delayed_selection removes items from matching job class ' \
508
+ 'without params' do
509
+ t = Time.now + 120
510
+ Resque.enqueue_at(t, SomeIvarJob)
511
+ Resque.enqueue_at(t + 1, SomeQuickJob)
512
+ Resque.enqueue_at(t + 2, SomeIvarJob)
513
+ Resque.enqueue_at(t + 3, SomeQuickJob)
514
+
515
+ assert_equal(2, Resque.remove_delayed_selection(SomeQuickJob) { true })
516
+ assert_equal(2, Resque.count_all_scheduled_jobs)
517
+ end
518
+
450
519
  test 'remove_delayed_job_from_timestamp removes instances of jobs ' \
451
520
  'at a given timestamp' do
452
521
  t = Time.now + 120
data/test/env_test.rb CHANGED
@@ -31,11 +31,17 @@ context 'Env' do
31
31
  env.setup
32
32
  end
33
33
 
34
- test 'writes pid to pidfile when given' do
35
- mock_pidfile = mock('pidfile')
36
- mock_pidfile.expects(:puts)
37
- File.expects(:open).with('derp.pid', 'w').yields(mock_pidfile)
38
- env = new_env(pidfile: 'derp.pid')
34
+ test 'keep set config if no option given' do
35
+ Resque::Scheduler.configure { |c| c.dynamic = true }
36
+ env = new_env
39
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)
40
46
  end
41
47
  end
@@ -0,0 +1,37 @@
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
+ schedules = {}
7
+ counts = []
8
+ threads = []
9
+
10
+ # This number may need to be increased if this test is not failing
11
+ processes = 20
12
+
13
+ schedule_count = 200
14
+
15
+ schedule_count.times do |n|
16
+ schedules["job #{n}"] = { cron: '0 1 0 0 0' }
17
+ end
18
+
19
+ processes.times do |n|
20
+ threads << Thread.new do
21
+ sleep n * 0.1
22
+ Resque.schedule = schedules
23
+ counts << Resque.schedule.size
24
+ end
25
+ end
26
+
27
+ # doing this outside the threads increases the odds of failure
28
+ Resque.schedule = schedules
29
+ counts << Resque.schedule.size
30
+
31
+ threads.each(&:join)
32
+
33
+ counts.each_with_index do |c, i|
34
+ assert_equal schedule_count, c, "schedule count is incorrect (c: #{i})"
35
+ end
36
+ end
37
+ end
@@ -174,17 +174,17 @@ context 'POST /schedule/requeue' do
174
174
  assert last_response.ok?, last_response.errors
175
175
  assert last_response.body.include?('This job requires parameters')
176
176
  assert last_response.body.include?(
177
- %Q(<input type="hidden" name="job_name" value="#{job_name}">)
177
+ %(<input type="hidden" name="job_name" value="#{job_name}">)
178
178
  )
179
179
 
180
180
  Resque.schedule[job_name]['parameters'].each do |_param_name, param_config|
181
181
  assert last_response.body.include?(
182
182
  '<span style="border-bottom:1px dotted;" ' <<
183
- %Q[title="#{param_config['description']}">(?)</span>]
183
+ %[title="#{param_config['description']}">(?)</span>]
184
184
  )
185
185
  assert last_response.body.include?(
186
186
  '<input type="text" name="log_level" ' <<
187
- %Q(value="#{param_config['default']}">)
187
+ %(value="#{param_config['default']}">)
188
188
  )
189
189
  end
190
190
  end
@@ -225,13 +225,13 @@ context 'on POST to /delayed/search' do
225
225
  end
226
226
 
227
227
  test 'should find matching scheduled job' do
228
- post '/delayed/search' , 'search' => 'ivar'
228
+ post '/delayed/search', 'search' => 'ivar'
229
229
  assert last_response.status == 200
230
230
  assert last_response.body.include?('SomeIvarJob')
231
231
  end
232
232
 
233
233
  test 'should find matching queued job' do
234
- post '/delayed/search' , 'search' => 'quick'
234
+ post '/delayed/search', 'search' => 'quick'
235
235
  assert last_response.status == 200
236
236
  assert last_response.body.include?('SomeQuickJob')
237
237
  end
@@ -113,6 +113,12 @@ context 'Resque::Scheduler::Locking' do
113
113
  end
114
114
 
115
115
  test 'release_master_lock should delegate to master_lock' do
116
+ @subject.master_lock.expects(:release)
117
+ @subject.release_master_lock
118
+ end
119
+
120
+ test 'release_master_lock! should delegate to master_lock' do
121
+ @subject.expects(:warn)
116
122
  @subject.master_lock.expects(:release!)
117
123
  @subject.release_master_lock!
118
124
  end
@@ -22,7 +22,7 @@ context 'Resque::Scheduler' do
22
22
  end
23
23
 
24
24
  test 'sending TERM to scheduler breaks out of poll_sleep' do
25
- Resque::Scheduler.expects(:release_master_lock!)
25
+ Resque::Scheduler.expects(:release_master_lock)
26
26
 
27
27
  @pid = Process.pid
28
28
  Thread.new do
@@ -34,7 +34,7 @@ context 'Resque::Scheduler' do
34
34
  Resque::Scheduler.run
35
35
  end
36
36
 
37
- Resque::Scheduler.unstub(:release_master_lock!)
38
- Resque::Scheduler.release_master_lock!
37
+ Resque::Scheduler.unstub(:release_master_lock)
38
+ Resque::Scheduler.release_master_lock
39
39
  end
40
40
  end
@@ -418,8 +418,8 @@ context 'Resque::Scheduler' do
418
418
  end
419
419
 
420
420
  test 'procline contains env when present' do
421
- Resque::Scheduler.env = 'derp'
422
- assert Resque::Scheduler.send(:build_procline, 'cage') =~ /\[derp\]: cage/
421
+ Resque::Scheduler.env = 'xyz'
422
+ assert Resque::Scheduler.send(:build_procline, 'cage') =~ /\[xyz\]: cage/
423
423
  end
424
424
 
425
425
  test 'procline omits env when absent' do
@@ -25,7 +25,7 @@ class RedisInstance
25
25
  end
26
26
 
27
27
  def stop!
28
- $stdout.puts "Sending TERM to Redis (#{pid})..."
28
+ $stdout.puts "Sending TERM to Redis (#{pid})..." if $stdout.tty?
29
29
  Process.kill('TERM', pid)
30
30
 
31
31
  @port = nil
metadata CHANGED
@@ -1,215 +1,214 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-scheduler
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben VandenBos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-27 00:00:00.000000000 Z
11
+ date: 2014-12-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ! '>='
18
18
  - !ruby/object:Gem::Version
19
- version: '1.5'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
- version: '1.5'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: json
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - ! '>='
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - ! '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: kramdown
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - ! '>='
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - ! '>='
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: mocha
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - ! '>='
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: pry
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - ! '>='
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - ! '>='
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rack-test
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - ! '>='
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - ! '>='
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ">="
101
+ - - ! '>='
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ">="
108
+ - - ! '>='
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: rubocop
112
+ name: simplecov
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ">="
115
+ - - ! '>='
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ">="
122
+ - - ! '>='
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: simplecov
126
+ name: yard
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - ">="
129
+ - - ! '>='
130
130
  - !ruby/object:Gem::Version
131
131
  version: '0'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - ">="
136
+ - - ! '>='
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  - !ruby/object:Gem::Dependency
140
- name: yard
140
+ name: rubocop
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - ">="
143
+ - - ~>
144
144
  - !ruby/object:Gem::Version
145
- version: '0'
145
+ version: 0.28.0
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - ">="
150
+ - - ~>
151
151
  - !ruby/object:Gem::Version
152
- version: '0'
152
+ version: 0.28.0
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: mono_logger
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
- - - "~>"
157
+ - - ~>
158
158
  - !ruby/object:Gem::Version
159
159
  version: '1.0'
160
160
  type: :runtime
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
- - - "~>"
164
+ - - ~>
165
165
  - !ruby/object:Gem::Version
166
166
  version: '1.0'
167
167
  - !ruby/object:Gem::Dependency
168
168
  name: redis
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
- - - "~>"
171
+ - - ~>
172
172
  - !ruby/object:Gem::Version
173
173
  version: '3.0'
174
174
  type: :runtime
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
- - - "~>"
178
+ - - ~>
179
179
  - !ruby/object:Gem::Version
180
180
  version: '3.0'
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: resque
183
183
  requirement: !ruby/object:Gem::Requirement
184
184
  requirements:
185
- - - "~>"
185
+ - - ~>
186
186
  - !ruby/object:Gem::Version
187
187
  version: '1.25'
188
188
  type: :runtime
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
- - - "~>"
192
+ - - ~>
193
193
  - !ruby/object:Gem::Version
194
194
  version: '1.25'
195
195
  - !ruby/object:Gem::Dependency
196
196
  name: rufus-scheduler
197
197
  requirement: !ruby/object:Gem::Requirement
198
198
  requirements:
199
- - - "~>"
199
+ - - ~>
200
200
  - !ruby/object:Gem::Version
201
201
  version: '2.0'
202
202
  type: :runtime
203
203
  prerelease: false
204
204
  version_requirements: !ruby/object:Gem::Requirement
205
205
  requirements:
206
- - - "~>"
206
+ - - ~>
207
207
  - !ruby/object:Gem::Version
208
208
  version: '2.0'
209
- description: |-
210
- Light weight job scheduling on top of Resque.
211
- Adds methods enqueue_at/enqueue_in to schedule jobs in the future.
212
- Also supports queueing jobs on a fixed, cron-like schedule.
209
+ description: ! " Light weight job scheduling on top of Resque.\n Adds methods
210
+ enqueue_at/enqueue_in to schedule jobs in the future.\n Also supports queueing
211
+ jobs on a fixed, cron-like schedule.\n"
213
212
  email:
214
213
  - bvandenbos@gmail.com
215
214
  executables:
@@ -217,15 +216,15 @@ executables:
217
216
  extensions: []
218
217
  extra_rdoc_files: []
219
218
  files:
220
- - ".gitignore"
221
- - ".rubocop.yml"
222
- - ".rubocop_todo.yml"
223
- - ".simplecov"
224
- - ".travis.yml"
225
- - ".vagrant-provision-as-vagrant.sh"
226
- - ".vagrant-provision.sh"
227
- - ".vagrant-skel/bash_profile"
228
- - ".vagrant-skel/bashrc"
219
+ - .gitignore
220
+ - .rubocop.yml
221
+ - .rubocop_todo.yml
222
+ - .simplecov
223
+ - .travis.yml
224
+ - .vagrant-provision-as-vagrant.sh
225
+ - .vagrant-provision.sh
226
+ - .vagrant-skel/bash_profile
227
+ - .vagrant-skel/bashrc
229
228
  - AUTHORS.md
230
229
  - CONTRIBUTING.md
231
230
  - Gemfile
@@ -279,6 +278,7 @@ files:
279
278
  - test/cli_test.rb
280
279
  - test/delayed_queue_test.rb
281
280
  - test/env_test.rb
281
+ - test/multi_process_test.rb
282
282
  - test/resque-web_test.rb
283
283
  - test/scheduler_args_test.rb
284
284
  - test/scheduler_hooks_test.rb
@@ -299,17 +299,17 @@ require_paths:
299
299
  - lib
300
300
  required_ruby_version: !ruby/object:Gem::Requirement
301
301
  requirements:
302
- - - ">="
302
+ - - ! '>='
303
303
  - !ruby/object:Gem::Version
304
304
  version: '0'
305
305
  required_rubygems_version: !ruby/object:Gem::Requirement
306
306
  requirements:
307
- - - ">="
307
+ - - ! '>='
308
308
  - !ruby/object:Gem::Version
309
309
  version: '0'
310
310
  requirements: []
311
311
  rubyforge_project:
312
- rubygems_version: 2.2.2
312
+ rubygems_version: 2.4.5
313
313
  signing_key:
314
314
  specification_version: 4
315
315
  summary: Light weight job scheduling on top of Resque
@@ -317,6 +317,7 @@ test_files:
317
317
  - test/cli_test.rb
318
318
  - test/delayed_queue_test.rb
319
319
  - test/env_test.rb
320
+ - test/multi_process_test.rb
320
321
  - test/resque-web_test.rb
321
322
  - test/scheduler_args_test.rb
322
323
  - test/scheduler_hooks_test.rb
@@ -327,4 +328,3 @@ test_files:
327
328
  - test/support/redis_instance.rb
328
329
  - test/test_helper.rb
329
330
  - test/util_test.rb
330
- has_rdoc: