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

@@ -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