resque-scheduler 4.8.0 → 4.10.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,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: []