resque-scheduler 4.3.1 → 4.6.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.

@@ -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,35 @@ 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
 
66
+ # Update the delayed timestamp of any matching delayed jobs or enqueue a
67
+ # new job if no matching jobs are found. Returns the number of delayed or
68
+ # enqueued jobs.
69
+ def delay_or_enqueue_at(timestamp, klass, *args)
70
+ count = remove_delayed(klass, *args)
71
+ count = 1 if count == 0
72
+
73
+ count.times do
74
+ enqueue_at(timestamp, klass, *args)
75
+ end
76
+ end
77
+
78
+ # Identical to +delay_or_enqueue_at+, except it takes
79
+ # number_of_seconds_from_now instead of a timestamp
80
+ def delay_or_enqueue_in(number_of_seconds_from_now, klass, *args)
81
+ delay_or_enqueue_at(Time.now + number_of_seconds_from_now, klass, *args)
82
+ end
83
+
60
84
  # 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
85
+ # +timestamp+ can be either in seconds or a datetime object. The
86
+ # insertion time complexity is O(log(n)). Returns true if it's
87
+ # the first job to be scheduled at that time, else false.
64
88
  def delayed_push(timestamp, item)
65
89
  # First add this item to the list for this timestamp
66
90
  redis.rpush("delayed:#{timestamp.to_i}", encode(item))
@@ -82,6 +106,7 @@ module Resque
82
106
  end
83
107
 
84
108
  # Returns the size of the delayed queue schedule
109
+ # this does not represent the number of items in the queue to be scheduled
85
110
  def delayed_queue_schedule_size
86
111
  redis.zcard :delayed_queue_schedule
87
112
  end
@@ -128,10 +153,7 @@ module Resque
128
153
  Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |item|
129
154
  key = "delayed:#{item}"
130
155
  items = redis.lrange(key, 0, -1)
131
- redis.pipelined do
132
- items.each { |ts_item| redis.del("timestamps:#{ts_item}") }
133
- end
134
- redis.del key
156
+ redis.del(key, items.map { |ts_item| "timestamps:#{ts_item}" })
135
157
  end
136
158
 
137
159
  redis.del :delayed_queue_schedule
@@ -143,6 +165,11 @@ module Resque
143
165
  remove_delayed_job(search)
144
166
  end
145
167
 
168
+ def remove_delayed_in_queue(klass, queue, *args)
169
+ search = encode(job_to_hash_with_queue(queue, klass, args))
170
+ remove_delayed_job(search)
171
+ end
172
+
146
173
  # Given an encoded item, enqueue it now
147
174
  def enqueue_delayed(klass, *args)
148
175
  hash = job_to_hash(klass, args)
@@ -151,6 +178,13 @@ module Resque
151
178
  end
152
179
  end
153
180
 
181
+ def enqueue_delayed_with_queue(klass, queue, *args)
182
+ hash = job_to_hash_with_queue(queue, klass, args)
183
+ remove_delayed_in_queue(klass, queue, *args).times do
184
+ Resque::Scheduler.enqueue_from_config(hash)
185
+ end
186
+ end
187
+
154
188
  # Given a block, remove jobs that return true from a block
155
189
  #
156
190
  # This allows for removal of delayed jobs that have arguments matching
@@ -175,7 +209,15 @@ module Resque
175
209
  found_jobs.reduce(0) do |sum, encoded_job|
176
210
  decoded_job = decode(encoded_job)
177
211
  klass = Util.constantize(decoded_job['class'])
178
- sum + enqueue_delayed(klass, *decoded_job['args'])
212
+ queue = decoded_job['queue']
213
+
214
+ if queue
215
+ jobs_queued = enqueue_delayed_with_queue(klass, queue, *decoded_job['args'])
216
+ else
217
+ jobs_queued = enqueue_delayed(klass, *decoded_job['args'])
218
+ end
219
+
220
+ jobs_queued + sum
179
221
  end
180
222
  end
181
223
 
@@ -190,9 +232,9 @@ module Resque
190
232
 
191
233
  # Beyond 100 there's almost no improvement in speed
192
234
  found = timestamps.each_slice(100).map do |ts_group|
193
- jobs = redis.pipelined do |r|
235
+ jobs = redis.pipelined do |pipeline|
194
236
  ts_group.each do |ts|
195
- r.lrange("delayed:#{ts}", 0, -1)
237
+ pipeline.lrange("delayed:#{ts}", 0, -1)
196
238
  end
197
239
  end
198
240
 
@@ -265,18 +307,23 @@ module Resque
265
307
  { class: klass.to_s, args: args, queue: queue }
266
308
  end
267
309
 
310
+ # Removes a job from the queue, but not modify the timestamp schedule. This method
311
+ # will not effect the output of `delayed_queue_schedule_size`
268
312
  def remove_delayed_job(encoded_job)
269
313
  return 0 if Resque.inline?
270
314
 
271
315
  timestamps = redis.smembers("timestamps:#{encoded_job}")
272
316
 
273
- replies = redis.pipelined do
317
+ replies = redis.pipelined do |pipeline|
274
318
  timestamps.each do |key|
275
- redis.lrem(key, 0, encoded_job)
276
- redis.srem("timestamps:#{encoded_job}", key)
319
+ pipeline.lrem(key, 0, encoded_job)
320
+ pipeline.srem("timestamps:#{encoded_job}", key)
277
321
  end
278
322
  end
279
323
 
324
+ # timestamp key is not removed from the schedule, this is done later
325
+ # by the scheduler loop
326
+
280
327
  return 0 if replies.nil? || replies.empty?
281
328
  replies.each_slice(2).map(&:first).inject(:+)
282
329
  end
@@ -287,9 +334,9 @@ module Resque
287
334
  redis.watch(key) do
288
335
  if redis.llen(key).to_i == 0
289
336
  # If the list is empty, remove it.
290
- redis.multi do
291
- redis.del(key)
292
- redis.zrem(:delayed_queue_schedule, timestamp.to_i)
337
+ redis.multi do |transaction|
338
+ transaction.del(key)
339
+ transaction.zrem(:delayed_queue_schedule, timestamp.to_i)
293
340
  end
294
341
  else
295
342
  redis.redis.unwatch
@@ -38,12 +38,8 @@ module Resque
38
38
  true
39
39
  end
40
40
 
41
- unless Process.respond_to?('daemon')
42
- abort 'background option is set, which requires ruby >= 1.9'
43
- end
44
-
45
41
  Process.daemon(true, !Resque::Scheduler.quiet)
46
- Resque.redis.client.reconnect
42
+ Resque.redis._client.reconnect
47
43
  end
48
44
 
49
45
  def setup_pid_file
@@ -64,13 +60,13 @@ module Resque
64
60
 
65
61
  c.dynamic = !!options[:dynamic] if options.key?(:dynamic)
66
62
 
67
- c.env = options[:env] if options.key(:env)
63
+ c.env = options[:env] if options.key?(:env)
68
64
 
69
65
  c.logfile = options[:logfile] if options.key?(:logfile)
70
66
 
71
67
  c.logformat = options[:logformat] if options.key?(:logformat)
72
68
 
73
- if psleep = options[:poll_sleep_amount] && !psleep.nil?
69
+ if (psleep = options[:poll_sleep_amount]) && !psleep.nil?
74
70
  c.poll_sleep_amount = Float(psleep)
75
71
  end
76
72
 
@@ -43,7 +43,7 @@ module Resque
43
43
  @locked_sha = nil if refresh
44
44
 
45
45
  @locked_sha ||=
46
- Resque.redis.script(:load, <<-EOF.gsub(/^ {14}/, ''))
46
+ Resque.data_store.redis.script(:load, <<-EOF.gsub(/^ {14}/, ''))
47
47
  if redis.call('GET', KEYS[1]) == ARGV[1]
48
48
  then
49
49
  redis.call('EXPIRE', KEYS[1], #{timeout})
@@ -62,7 +62,7 @@ module Resque
62
62
  @acquire_sha = nil if refresh
63
63
 
64
64
  @acquire_sha ||=
65
- Resque.redis.script(:load, <<-EOF.gsub(/^ {14}/, ''))
65
+ Resque.data_store.redis.script(:load, <<-EOF.gsub(/^ {14}/, ''))
66
66
  if redis.call('SETNX', KEYS[1], ARGV[1]) == 1
67
67
  then
68
68
  redis.call('EXPIRE', KEYS[1], #{timeout})
@@ -2,7 +2,7 @@
2
2
 
3
3
  # ### Locking the scheduler process
4
4
  #
5
- # There are two places in resque-scheduler that need to be synchonized in order
5
+ # There are two places in resque-scheduler that need to be synchronized in order
6
6
  # to be able to run redundant scheduler processes while ensuring jobs don't get
7
7
  # queued multiple times when the master process changes.
8
8
  #
@@ -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
@@ -1,9 +1,9 @@
1
1
  <h1>Delayed Jobs</h1>
2
- <%- size = resque.delayed_queue_schedule_size %>
2
+ <% size = resque.delayed_queue_schedule_size %>
3
3
 
4
4
  <%= scheduler_view :search_form, layout: false %>
5
5
 
6
- <p style="font-color: red; font-weight: bold;">
6
+ <p style="color: red; font-weight: bold;">
7
7
  <%= @error_message %>
8
8
  </p>
9
9
 
@@ -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>
@@ -55,7 +55,7 @@
55
55
 
56
56
  <% if size > 0 %>
57
57
  <br>
58
- <form method="POST" action="<%=u 'delayed/clear'%>" class='clear-delayed'>
58
+ <form method="POST" action="<%= u 'delayed/clear' %>" class='clear-delayed'>
59
59
  <input type='submit' name='' value='Clear Delayed Jobs' />
60
60
  </form>
61
61
  <% end %>
@@ -13,7 +13,7 @@
13
13
  <% jobs.each do |job| %>
14
14
  <tr>
15
15
  <td class='class'><%= job['class'] %></td>
16
- <td class='args'><%=h show_job_arguments(job['args']) %></td>
16
+ <td class='args'><%= h show_job_arguments(job['args']) %></td>
17
17
  </tr>
18
18
  <% end %>
19
19
  <% if jobs.empty? %>
@@ -8,7 +8,7 @@
8
8
  <br/> Current master: <%= Resque.redis.get(Resque::Scheduler.master_lock.key) %>
9
9
  </p>
10
10
  <p class='intro'>
11
- The highlighted jobs are skipped for current environment.
11
+ The highlighted jobs are skipped for current environment.
12
12
  </p>
13
13
  <div style="overflow-y: auto; width:100%; padding: 0px 5px;">
14
14
  <table>
@@ -29,7 +29,7 @@
29
29
  <% Resque.schedule.keys.sort.each_with_index do |name, index| %>
30
30
  <% config = Resque.schedule[name] %>
31
31
  <tr style="<%= scheduled_in_this_env?(name) ? '' : 'color: #9F6000;background: #FEEFB3;' %>">
32
- <td style="padding-left: 15px;"><%= index+ 1 %>.</td>
32
+ <td style="padding-left: 15px;"><%= index + 1 %>.</td>
33
33
  <% if Resque::Scheduler.dynamic %>
34
34
  <td style="padding-top: 12px; padding-bottom: 2px; width: 10px">
35
35
  <form action="<%= u "/schedule" %>" method="post" style="margin-left: 0">
@@ -33,7 +33,6 @@
33
33
  </tr>
34
34
  <% end %>
35
35
  </table>
36
- </h1>
37
36
 
38
37
  <% queued = @jobs.select { |j| j['where_at'] == 'queued' } %>
39
38
  <h1>Queued jobs</h1>
@@ -68,5 +67,3 @@
68
67
  </tr>
69
68
  <% end %>
70
69
  </table>
71
-
72
-
@@ -1,8 +1,4 @@
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
-
6
-
7
-
8
-
@@ -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 = []
@@ -10,13 +10,13 @@ module Resque
10
10
  end
11
11
 
12
12
  # For all signals, set the shutdown flag and wait for current
13
- # poll/enqueing to finish (should be almost instant). In the
13
+ # poll/enqueuing to finish (should be almost instant). In the
14
14
  # case of sleeping, exit immediately.
15
15
  def register_signal_handlers
16
16
  (Signal.list.keys & %w(INT TERM USR1 USR2 QUIT)).each do |sig|
17
17
  trap(sig) do
18
18
  signal_queue << sig
19
- # break sleep in the primary scheduler thread, alowing
19
+ # break sleep in the primary scheduler thread, allowing
20
20
  # the signal queue to get processed as soon as possible.
21
21
  @th.wakeup if @th && @th.alive?
22
22
  end
@@ -4,7 +4,7 @@ module Resque
4
4
  module Scheduler
5
5
  class Util
6
6
  # In order to upgrade to resque(1.25) which has deprecated following
7
- # methods, we just added these usefull helpers back to use in Resque
7
+ # methods, we just added these useful helpers back to use in Resque
8
8
  # Scheduler. refer to:
9
9
  # https://github.com/resque/resque-scheduler/pull/273
10
10
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Resque
4
4
  module Scheduler
5
- VERSION = '4.3.1'.freeze
5
+ VERSION = '4.6.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
 
@@ -202,7 +210,7 @@ module Resque
202
210
  loop do
203
211
  handle_shutdown do
204
212
  # Continually check that it is still the master
205
- item = enqueue_next_item(timestamp) if master?
213
+ item = enqueue_next_item(timestamp) if am_master
206
214
  end
207
215
  # continue processing until there are no more ready items in this
208
216
  # timestamp
@@ -243,7 +251,7 @@ module Resque
243
251
  if job_klass && job_klass != 'Resque::Job'
244
252
  # The custom job class API must offer a static "scheduled" method. If
245
253
  # the custom job class can not be constantized (via a requeue call
246
- # from the web perhaps), fall back to enqueing normally via
254
+ # from the web perhaps), fall back to enqueuing normally via
247
255
  # Resque::Job.create.
248
256
  begin
249
257
  Resque::Scheduler::Util.constantize(job_klass).scheduled(
@@ -369,7 +377,6 @@ module Resque
369
377
 
370
378
  def stop_rufus_scheduler
371
379
  rufus_scheduler.shutdown(:wait)
372
- rufus_scheduler.join
373
380
  end
374
381
 
375
382
  def before_shutdown
@@ -420,10 +427,10 @@ module Resque
420
427
  private
421
428
 
422
429
  def enqueue_recurring(name, config)
423
- if master?
430
+ if am_master
424
431
  log! "queueing #{config['class']} (#{name})"
425
- Resque.last_enqueued_at(name, Time.now.to_s)
426
432
  enqueue(config)
433
+ Resque.last_enqueued_at(name, Time.now.to_s)
427
434
  end
428
435
  end
429
436
 
@@ -442,6 +449,11 @@ module Resque
442
449
  def internal_name
443
450
  "resque-scheduler-#{Resque::Scheduler::VERSION}"
444
451
  end
452
+
453
+ def am_master
454
+ @am_master = master? unless defined?(@am_master)
455
+ @am_master
456
+ end
445
457
  end
446
458
  end
447
459
  end
@@ -11,12 +11,14 @@ Gem::Specification.new do |spec|
11
11
  Simon Eskildsen
12
12
  Ryan Biesemeyer
13
13
  Dan Buch
14
+ Michael Bianco
14
15
  EOF
15
16
  spec.email = %w(
16
17
  bvandenbos@gmail.com
17
18
  sirup@sirupsen.com
18
19
  ryan@yaauie.com
19
20
  dan@meatballhat.com
21
+ mike@mikebian.co
20
22
  )
21
23
  spec.summary = 'Light weight job scheduling on top of Resque'
22
24
  spec.description = <<-DESCRIPTION
@@ -24,13 +26,16 @@ Gem::Specification.new do |spec|
24
26
  Adds methods enqueue_at/enqueue_in to schedule jobs in the future.
25
27
  Also supports queueing jobs on a fixed, cron-like schedule.
26
28
  DESCRIPTION
27
- spec.homepage = 'http://github.com/resque/resque-scheduler'
29
+ spec.homepage = 'https://github.com/resque/resque-scheduler'
28
30
  spec.license = 'MIT'
31
+ spec.metadata['rubygems_mfa_required'] = 'true'
32
+
33
+ spec.required_ruby_version = '>= 2.3.0'
29
34
 
30
35
  spec.files = `git ls-files -z`.split("\0").reject do |f|
31
36
  f.match(%r{^(test|spec|features|examples|bin|tasks)/}) ||
32
- f.match(/^(Vagrantfile|Gemfile\.lock|appveyor\.yml)/) ||
33
- f.match(/^\.(rubocop|simplecov|travis|vagrant|gitignore)/)
37
+ f.match(/^(Vagrantfile|Gemfile\.lock)/) ||
38
+ f.match(/^\.(rubocop|simplecov|vagrant|gitignore)/)
34
39
  end
35
40
  spec.bindir = 'exe'
36
41
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -38,7 +43,6 @@ Gem::Specification.new do |spec|
38
43
 
39
44
  spec.add_development_dependency 'bundler'
40
45
  spec.add_development_dependency 'json'
41
- spec.add_development_dependency 'kramdown'
42
46
  spec.add_development_dependency 'minitest'
43
47
  spec.add_development_dependency 'mocha'
44
48
  spec.add_development_dependency 'pry'
@@ -47,14 +51,16 @@ Gem::Specification.new do |spec|
47
51
  spec.add_development_dependency 'simplecov'
48
52
  spec.add_development_dependency 'test-unit'
49
53
  spec.add_development_dependency 'yard'
50
- spec.add_development_dependency 'tzinfo-data'
54
+ spec.add_development_dependency 'timecop'
51
55
 
52
56
  # We pin rubocop because new cops have a tendency to result in false-y
53
57
  # positives for new contributors, which is not a nice experience.
54
58
  spec.add_development_dependency 'rubocop', '~> 0.40.0'
55
59
 
56
60
  spec.add_runtime_dependency 'mono_logger', '~> 1.0'
57
- spec.add_runtime_dependency 'redis', '>= 3.3', '< 5'
58
- spec.add_runtime_dependency 'resque', '~> 1.26'
59
- spec.add_runtime_dependency 'rufus-scheduler', '~> 3.2'
61
+ spec.add_runtime_dependency 'redis', '>= 3.3'
62
+ spec.add_runtime_dependency 'resque', '>= 1.27'
63
+ # rufus-scheduler v3.7 causes a failure in test/multi_process_test.rb
64
+ # rufus-scheduler v3.3 is missing a to_local method which fails tests
65
+ spec.add_runtime_dependency 'rufus-scheduler', '~> 3.2', '!= 3.3'
60
66
  end