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 +4 -4
- data/.github/workflows/rubocop.yml +1 -1
- data/.github/workflows/ruby.yml +3 -0
- data/AUTHORS.md +3 -1
- data/CHANGELOG.md +17 -0
- data/Gemfile +2 -0
- data/README.md +2 -2
- data/lib/resque/scheduler/configuration.rb +7 -1
- data/lib/resque/scheduler/delaying_extensions.rb +20 -20
- data/lib/resque/scheduler/logger_builder.rb +14 -4
- data/lib/resque/scheduler/scheduling_extensions.rb +2 -2
- data/lib/resque/scheduler/server/views/delayed.erb +9 -8
- data/lib/resque/scheduler/server/views/search.erb +2 -2
- data/lib/resque/scheduler/version.rb +1 -1
- data/lib/resque/scheduler.rb +69 -5
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3fea20c1e19d0477785e35cd8e568ed5edb31a1b3e5cb8b50b3bf6ceb8ed417
|
4
|
+
data.tar.gz: a015f8b83e75f674c2c74b477a656233a19ecc69ae10bbd8b1eca0a7d6328f23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2847caf20385af6e97a6316d4e1dc9c69841948a43d913cfe430c7a45786924315e9cf1b8b57c5bd37aa26e68f34fcfd35c6289fd9d63e233bbc0071ce962251
|
7
|
+
data.tar.gz: 8d612a72c7a3b671125e03d2dd6738691e5a9abce0d2b9ef8060f312b5793570ccc6052541740a07e0b67760359c8a72c4fe5690cca8ae9216322eb65fbabe9c
|
data/.github/workflows/ruby.yml
CHANGED
@@ -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
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 `'
|
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 "
|
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 '
|
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 '
|
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,
|
54
|
-
"
|
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:
|
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'] %>">
|
data/lib/resque/scheduler.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
218
|
+
if am_master
|
219
|
+
actual_batch_size = enqueue_items_in_batch_for_timestamp(timestamp,
|
220
|
+
batch_size)
|
221
|
+
end
|
214
222
|
end
|
215
|
-
|
216
|
-
|
217
|
-
|
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.
|
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-
|
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
|
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: []
|