resque-scheduler 4.8.0 → 4.10.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,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd9d3813b4dfbf31849dd4cc5866f8039face2673ec14c69906b90bb40f2208e
4
- data.tar.gz: f3f4c9bfb5cb1a68c11a7284b8820a4231640a1ccf18c2a661777f51b2dc4f6b
3
+ metadata.gz: e3fea20c1e19d0477785e35cd8e568ed5edb31a1b3e5cb8b50b3bf6ceb8ed417
4
+ data.tar.gz: a015f8b83e75f674c2c74b477a656233a19ecc69ae10bbd8b1eca0a7d6328f23
5
5
  SHA512:
6
- metadata.gz: 8ce3e99af96433cbc202163b2bea73ec9ee54a4f3691e987582bb110cfece6be0f53935e755d95d6113024e73cacaa23ae397dc4178e8e5b452553752add531f
7
- data.tar.gz: b782b127a76f17c22c3299f90d57812eb966e9a7e7bcd1372d44d0204fbbab86c3f70e0fea1abb9181f8fb825f99bded51e6ff9f0c7358c1e59b6cfc04b1a604
6
+ metadata.gz: 2847caf20385af6e97a6316d4e1dc9c69841948a43d913cfe430c7a45786924315e9cf1b8b57c5bd37aa26e68f34fcfd35c6289fd9d63e233bbc0071ce962251
7
+ data.tar.gz: 8d612a72c7a3b671125e03d2dd6738691e5a9abce0d2b9ef8060f312b5793570ccc6052541740a07e0b67760359c8a72c4fe5690cca8ae9216322eb65fbabe9c
@@ -14,7 +14,7 @@ jobs:
14
14
  matrix:
15
15
  os: [ubuntu-latest]
16
16
  ruby: [
17
- 2.4
17
+ 2.7
18
18
  ]
19
19
 
20
20
  steps:
@@ -26,6 +26,7 @@ jobs:
26
26
  - 2.7
27
27
  - "3.0"
28
28
  - 3.1
29
+ - 3.2
29
30
  resque-version:
30
31
  - "master"
31
32
  - "~> 2.4.0"
@@ -41,6 +42,8 @@ jobs:
41
42
  exclude:
42
43
  - ruby-version: head
43
44
  rufus-scheduler: 3.2
45
+ - ruby-version: 3.2
46
+ rufus-scheduler: 3.2
44
47
 
45
48
  - ruby-version: 2.3
46
49
  resque-version: "~> 1.27"
data/AUTHORS.md CHANGED
@@ -29,6 +29,7 @@ Resque Scheduler authors
29
29
  - Henrik Nyh
30
30
  - Hormoz Kheradmand
31
31
  - Ian Davies
32
+ - Irving Reid
32
33
  - James Le Cuirot
33
34
  - Jarkko Mönkkönen
34
35
  - Jimmy Chao
@@ -87,4 +88,5 @@ Resque Scheduler authors
87
88
  - malomalo
88
89
  - sawanoboly
89
90
  - serek
90
- - iloveitaly
91
+ - iloveitaly
92
+ - treacher
data/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  **ATTN**: This project uses [semantic versioning](http://semver.org/).
4
4
 
5
+ ## [4.10.0] - 2023-08-20
6
+ ### Added
7
+ * Add logfmt option for logging output (#763)
8
+
9
+ ### Fixed
10
+ * Rubocop Fixes (#771)
11
+
12
+ ## [4.9.0] - 2023-05-31
13
+ ### Changed
14
+ - Adding batching to re-queuing for timestamp by @brennen-stripe in #767
15
+ - Use non-deprecated form of Redis.sadd/srem by @irvingreid in #752
16
+ - Prompt for confirmation on 'Clear Delayed Jobs' by @mishina2228 in #754
17
+ - Address some deprecation warnings in the test suite and with srem/lrem pipelined usage by @PatrickTulskie in #770
18
+
19
+ ### Fixed
20
+ - Update CI matrix and fix tests by @mishina2228 in #766
21
+
5
22
  ## [4.8.0] - 2023-27-1
6
23
  - Replace deprecated Socket.gethostname with Addrinfo.getaddrinfo to fix deprecation warnings (#753)
7
24
 
data/Gemfile CHANGED
@@ -24,4 +24,6 @@ else
24
24
  gem 'redis', redis_version
25
25
  end
26
26
 
27
+ gem 'sinatra', '> 2.0'
28
+
27
29
  gemspec
data/README.md CHANGED
@@ -139,7 +139,7 @@ requiring `resque` and `resque/scheduler` (default empty).
139
139
  * `RESQUE_SCHEDULER_INTERVAL` - Interval in seconds for checking if a
140
140
  scheduled job must run (coerced with `Kernel#Float()`) (default `5`)
141
141
  * `LOGFILE` - Log file name (default empty, meaning `$stdout`)
142
- * `LOGFORMAT` - Log output format to use (either `'text'` or `'json'`,
142
+ * `LOGFORMAT` - Log output format to use (either `'text'`, `'json'` or `'logfmt'`,
143
143
  default `'text'`)
144
144
  * `PIDFILE` - If non-empty, write process PID to file (default empty)
145
145
  * `QUIET` - Silence most output if non-empty (equivalent to a level of
@@ -663,7 +663,7 @@ are toggled by environment variables:
663
663
  - `QUIET` will stop logging anything. Completely silent.
664
664
  - `VERBOSE` opposite of 'QUIET'; will log even debug information
665
665
  - `LOGFILE` specifies the file to write logs to. (default standard output)
666
- - `LOGFORMAT` specifies either "text" or "json" output format
666
+ - `LOGFORMAT` specifies either "text", "json" or "logfmt" output format
667
667
  (default "text")
668
668
 
669
669
  All of these variables are optional and will be given the following default
@@ -44,7 +44,7 @@ module Resque
44
44
  @logfile ||= environment['LOGFILE']
45
45
  end
46
46
 
47
- # Sets whether to log in 'text' or 'json'
47
+ # Sets whether to log in 'text', 'json' or 'logfmt'
48
48
  attr_writer :logformat
49
49
 
50
50
  def logformat
@@ -65,6 +65,12 @@ module Resque
65
65
  @app_name ||= environment['APP_NAME']
66
66
  end
67
67
 
68
+ def delayed_requeue_batch_size
69
+ @delayed_requeue_batch_size ||= \
70
+ ENV['DELAYED_REQUEUE_BATCH_SIZE'].to_i if environment['DELAYED_REQUEUE_BATCH_SIZE']
71
+ @delayed_requeue_batch_size ||= 100
72
+ end
73
+
68
74
  # Amount of time in seconds to sleep between polls of the delayed
69
75
  # queue. Defaults to 5
70
76
  attr_writer :poll_sleep_amount
@@ -90,7 +90,7 @@ module Resque
90
90
  redis.rpush("delayed:#{timestamp.to_i}", encode(item))
91
91
 
92
92
  # Store the timestamps at with this item occurs
93
- redis.sadd("timestamps:#{encode(item)}", "delayed:#{timestamp.to_i}")
93
+ redis.sadd("timestamps:#{encode(item)}", ["delayed:#{timestamp.to_i}"])
94
94
 
95
95
  # Now, add this timestamp to the zsets. The score and the value are
96
96
  # the same since we'll be querying by timestamp, and we don't have
@@ -140,7 +140,7 @@ module Resque
140
140
  key = "delayed:#{timestamp.to_i}"
141
141
 
142
142
  encoded_item = redis.lpop(key)
143
- redis.srem("timestamps:#{encoded_item}", key)
143
+ redis.srem("timestamps:#{encoded_item}", [key])
144
144
  item = decode(encoded_item)
145
145
 
146
146
  # If the list is empty, remove it.
@@ -257,7 +257,7 @@ module Resque
257
257
  key = "delayed:#{timestamp.to_i}"
258
258
  encoded_job = encode(job_to_hash(klass, args))
259
259
 
260
- redis.srem("timestamps:#{encoded_job}", key)
260
+ redis.srem("timestamps:#{encoded_job}", [key])
261
261
  count = redis.lrem(key, 0, encoded_job)
262
262
  clean_up_timestamp(key, timestamp)
263
263
 
@@ -297,6 +297,22 @@ module Resque
297
297
  redis.hget('delayed:last_enqueued_at', job_name)
298
298
  end
299
299
 
300
+ def clean_up_timestamp(key, timestamp)
301
+ # Use a watch here to ensure nobody adds jobs to this delayed
302
+ # queue while we're removing it.
303
+ redis.watch(key) do
304
+ if redis.llen(key).to_i == 0
305
+ # If the list is empty, remove it.
306
+ redis.multi do |transaction|
307
+ transaction.del(key)
308
+ transaction.zrem(:delayed_queue_schedule, timestamp.to_i)
309
+ end
310
+ else
311
+ redis.redis.unwatch
312
+ end
313
+ end
314
+ end
315
+
300
316
  private
301
317
 
302
318
  def job_to_hash(klass, args)
@@ -317,7 +333,7 @@ module Resque
317
333
  replies = redis.pipelined do |pipeline|
318
334
  timestamps.each do |key|
319
335
  pipeline.lrem(key, 0, encoded_job)
320
- pipeline.srem("timestamps:#{encoded_job}", key)
336
+ pipeline.srem("timestamps:#{encoded_job}", [key])
321
337
  end
322
338
  end
323
339
 
@@ -328,22 +344,6 @@ module Resque
328
344
  replies.each_slice(2).map(&:first).inject(:+)
329
345
  end
330
346
 
331
- def clean_up_timestamp(key, timestamp)
332
- # Use a watch here to ensure nobody adds jobs to this delayed
333
- # queue while we're removing it.
334
- redis.watch(key) do
335
- if redis.llen(key).to_i == 0
336
- # If the list is empty, remove it.
337
- redis.multi do |transaction|
338
- transaction.del(key)
339
- transaction.zrem(:delayed_queue_schedule, timestamp.to_i)
340
- end
341
- else
342
- redis.redis.unwatch
343
- end
344
- end
345
- end
346
-
347
347
  def search_first_delayed_timestamp_in_range(start_at, stop_at)
348
348
  start_at = start_at.nil? ? '-inf' : start_at.to_i
349
349
  stop_at = stop_at.nil? ? '+inf' : stop_at.to_i
@@ -15,7 +15,7 @@ module Resque
15
15
  # - :quiet if logger needs to be silent for all levels. Default - false
16
16
  # - :verbose if there is a need in debug messages. Default - false
17
17
  # - :log_dev to output logs into a desired file. Default - STDOUT
18
- # - :format log format, either 'text' or 'json'. Default - 'text'
18
+ # - :format log format, either 'text', 'json' or 'logfmt'. Default - 'text'
19
19
  #
20
20
  # Example:
21
21
  #
@@ -32,6 +32,7 @@ module Resque
32
32
  # Returns an instance of MonoLogger
33
33
  def build
34
34
  logger = MonoLogger.new(@log_dev)
35
+ logger.progname = 'resque-scheduler'.freeze
35
36
  logger.level = level
36
37
  logger.formatter = send(:"#{@format}_formatter")
37
38
  logger
@@ -50,8 +51,8 @@ module Resque
50
51
  end
51
52
 
52
53
  def text_formatter
53
- proc do |severity, datetime, _progname, msg|
54
- "resque-scheduler: [#{severity}] #{datetime.iso8601}: #{msg}\n"
54
+ proc do |severity, datetime, progname, msg|
55
+ "#{progname}: [#{severity}] #{datetime.iso8601}: #{msg}\n"
55
56
  end
56
57
  end
57
58
 
@@ -59,7 +60,7 @@ module Resque
59
60
  proc do |severity, datetime, progname, msg|
60
61
  require 'json'
61
62
  JSON.dump(
62
- name: 'resque-scheduler',
63
+ name: progname,
63
64
  progname: progname,
64
65
  level: severity,
65
66
  timestamp: datetime.iso8601,
@@ -67,6 +68,15 @@ module Resque
67
68
  ) + "\n"
68
69
  end
69
70
  end
71
+
72
+ def logfmt_formatter
73
+ proc do |severity, datetime, progname, msg|
74
+ "timestamp=\"#{datetime.iso8601}\" " \
75
+ "level=\"#{severity}\" " \
76
+ "progname=\"#{progname}\" " \
77
+ "msg=\"#{msg}\"\n"
78
+ end
79
+ end
70
80
  end
71
81
  end
72
82
  end
@@ -91,7 +91,7 @@ module Resque
91
91
  non_persistent_schedules[name] = decode(encode(config))
92
92
  end
93
93
 
94
- redis.sadd(:schedules_changed, name)
94
+ redis.sadd(:schedules_changed, [name])
95
95
  reload_schedule! if reload
96
96
  end
97
97
 
@@ -105,7 +105,7 @@ module Resque
105
105
  def remove_schedule(name, reload = true)
106
106
  non_persistent_schedules.delete(name)
107
107
  redis.hdel(:persistent_schedules, name)
108
- redis.sadd(:schedules_changed, name)
108
+ redis.sadd(:schedules_changed, [name])
109
109
 
110
110
  reload_schedule! if reload
111
111
  end
@@ -16,6 +16,14 @@
16
16
  Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%= size %></b> timestamps
17
17
  </p>
18
18
 
19
+ <% if size > 0 %>
20
+ <div style="padding-bottom: 10px">
21
+ <form method="POST" action="<%= u 'delayed/clear' %>" class='clear-delayed confirmSubmission'>
22
+ <input type='submit' name='' value='Clear Delayed Jobs' />
23
+ </form>
24
+ </div>
25
+ <% end %>
26
+
19
27
  <table>
20
28
  <tr>
21
29
  <th></th>
@@ -27,7 +35,7 @@
27
35
  </tr>
28
36
  <% resque.delayed_queue_peek(start, 20).each do |timestamp| %>
29
37
  <tr>
30
- <td>
38
+ <td style="padding-top: 12px; padding-bottom: 2px; width: 10px">
31
39
  <form action="<%= u "/delayed/queue_now" %>" method="post">
32
40
  <input type="hidden" name="timestamp" value="<%= timestamp.to_i %>">
33
41
  <input type="submit" value="Queue now">
@@ -53,11 +61,4 @@
53
61
  <% end %>
54
62
  </table>
55
63
 
56
- <% if size > 0 %>
57
- <br>
58
- <form method="POST" action="<%= u 'delayed/clear' %>" class='clear-delayed'>
59
- <input type='submit' name='' value='Clear Delayed Jobs' />
60
- </form>
61
- <% end %>
62
-
63
64
  <%= partial :next_more, :start => start, :size => size %>
@@ -13,13 +13,13 @@
13
13
  </tr>
14
14
  <% delayed.each do |job| %>
15
15
  <tr>
16
- <td>
16
+ <td style="padding-top: 12px; padding-bottom: 2px; width: 10px">
17
17
  <form action="<%= u "/delayed/queue_now" %>" method="post">
18
18
  <input type="hidden" name="timestamp" value="<%= job['timestamp'].to_i %>">
19
19
  <input type="submit" value="Queue now">
20
20
  </form>
21
21
  </td>
22
- <td>
22
+ <td style="padding-top: 12px; padding-bottom: 2px; width: 10px">
23
23
  <form action="<%= u "/delayed/cancel_now" %>" method="post">
24
24
  <input type="hidden" name="timestamp" value="<%= job['timestamp'].to_i %>">
25
25
  <input type="hidden" name="klass" value="<%= job['class'] %>">
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Resque
4
4
  module Scheduler
5
- VERSION = '4.8.0'.freeze
5
+ VERSION = '4.10.0'.freeze
6
6
  end
7
7
  end
@@ -206,16 +206,80 @@ module Resque
206
206
 
207
207
  # Enqueues all delayed jobs for a timestamp
208
208
  def enqueue_delayed_items_for_timestamp(timestamp)
209
- item = nil
209
+ count = 0
210
+ batch_size = delayed_requeue_batch_size
211
+ actual_batch_size = nil
212
+
213
+ log "Processing delayed items for timestamp #{timestamp}, in batches of #{batch_size}"
214
+
210
215
  loop do
211
216
  handle_shutdown do
212
217
  # Continually check that it is still the master
213
- item = enqueue_next_item(timestamp) if am_master
218
+ if am_master
219
+ actual_batch_size = enqueue_items_in_batch_for_timestamp(timestamp,
220
+ batch_size)
221
+ end
214
222
  end
215
- # continue processing until there are no more ready items in this
216
- # timestamp
217
- break if item.nil?
223
+
224
+ count += actual_batch_size
225
+ log "queued #{count} jobs" if actual_batch_size != -1
226
+
227
+ # continue processing until there are no more items in this
228
+ # timestamp. If we don't have a full batch, this is the last one.
229
+ # This also breaks us in the event of a redis transaction failure
230
+ # i.e. enqueue_items_in_batch_for_timestamp returned -1
231
+ break if actual_batch_size < batch_size
218
232
  end
233
+
234
+ log "finished queueing #{count} total jobs for timestamp #{timestamp}" if count != -1
235
+ end
236
+
237
+ def timestamp_key(timestamp)
238
+ "delayed:#{timestamp.to_i}"
239
+ end
240
+
241
+ def enqueue_items_in_batch_for_timestamp(timestamp, batch_size)
242
+ timestamp_bucket_key = timestamp_key(timestamp)
243
+
244
+ encoded_jobs_to_requeue = Resque.redis.lrange(timestamp_bucket_key, 0, batch_size - 1)
245
+
246
+ # Watch is used to ensure that the timestamp bucket we are operating on
247
+ # is not altered by any other clients between the watch call and when we call exec
248
+ # (to execute the multi block). We should error catch on the redis.exec return value
249
+ # as that will indicate if the entire transaction was aborted or not. Though we should
250
+ # be safe as our ltrim is inside the multi block and therefore also would have been
251
+ # aborted. So nothing would have been queued, but also nothing lost from the bucket.
252
+ watch_result = Resque.redis.watch(timestamp_bucket_key) do
253
+ Resque.redis.multi do |pipeline|
254
+ encoded_jobs_to_requeue.each do |encoded_job|
255
+ pipeline.srem("timestamps:#{encoded_job}", timestamp_bucket_key)
256
+
257
+ decoded_job = Resque.decode(encoded_job)
258
+ enqueue(decoded_job)
259
+ end
260
+
261
+ pipeline.ltrim(timestamp_bucket_key, batch_size, -1)
262
+ end
263
+ end
264
+
265
+ # Did the multi block successfully remove from this timestamp and enqueue the jobs?
266
+ success = !watch_result.nil?
267
+
268
+ # If this was the last batch in this timestamp bucket, clean up
269
+ if success && encoded_jobs_to_requeue.count < batch_size
270
+ Resque.clean_up_timestamp(timestamp_bucket_key, timestamp)
271
+ end
272
+
273
+ unless success
274
+ # Our batched transaction failed in Redis due to the timestamp_bucket_key value
275
+ # being modified while we built our multi block. We return -1 to ensure we break
276
+ # out of the loop iterating on this timestamp so it can be re-processed via the
277
+ # loop in handle_delayed_items.
278
+ return -1
279
+ end
280
+
281
+ # will return 0 if none were left to batch
282
+ encoded_jobs_to_requeue.count
219
283
  end
220
284
 
221
285
  def enqueue(config)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-scheduler
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.8.0
4
+ version: 4.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben VandenBos
@@ -9,10 +9,10 @@ authors:
9
9
  - Ryan Biesemeyer
10
10
  - Dan Buch
11
11
  - Michael Bianco
12
- autorequire:
12
+ autorequire:
13
13
  bindir: exe
14
14
  cert_chain: []
15
- date: 2023-01-27 00:00:00.000000000 Z
15
+ date: 2023-08-20 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: bundler
@@ -307,7 +307,7 @@ licenses:
307
307
  - MIT
308
308
  metadata:
309
309
  rubygems_mfa_required: 'true'
310
- post_install_message:
310
+ post_install_message:
311
311
  rdoc_options: []
312
312
  require_paths:
313
313
  - lib
@@ -322,8 +322,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
322
322
  - !ruby/object:Gem::Version
323
323
  version: '0'
324
324
  requirements: []
325
- rubygems_version: 3.1.2
326
- signing_key:
325
+ rubygems_version: 3.0.3.1
326
+ signing_key:
327
327
  specification_version: 4
328
328
  summary: Light weight job scheduling on top of Resque
329
329
  test_files: []