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

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: