resque-scheduler 5.0.0 → 5.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e23e91d3c9920396d8feaad257fe4a27f4ff269f6cd0d6f9756016599d7cdf27
4
- data.tar.gz: 7c1c242fd5c2ed74f8c15924d476775f76ed4929fb65d31b965e708c5949a601
3
+ metadata.gz: 046040c8fa18b0cb7aabc7fdc46ff008f7b0c30f9aa0500137f5c6c987bdbabc
4
+ data.tar.gz: b3e243c3c9ccefa0c1282b492b70a9b26677fa2b4be6ead096e339c721d9f86a
5
5
  SHA512:
6
- metadata.gz: c90721652b59bdc1f2572975d3ef93ed9aa420f36a6a8f3a0c5a35f17a3e5145ebdd30bfacf7d62e4903f9477319282c8a33d55080f5c870c84161f4933e79d4
7
- data.tar.gz: 061fc1d3dfea48a20db7e8c11b31fe0b2e6594d9be0ed5c202c38f304941bceb8c1f15497d0c0ec886c0ddd37fc867b6dd1b5e143e9bb5961cd01b41f73d2e5e
6
+ metadata.gz: 8b6c3caa8b47c24aee900d2c348cabb20d1a32a5fce88e02a974649ca6c711b1703562393902c7638e14c1a11180b1901b126480e905c7cca8b2dbd47d27daa3
7
+ data.tar.gz: 7b2665804103943b3bf9a98b84134ba71a0ac00ace8e0c2ed7c765873445c801bea14c8ae9082fec4133e6f3be0314bdf1485ecd47c7bab8aba6d8d85ef36ec8
data/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  **ATTN**: This project uses [semantic versioning](http://semver.org/).
4
4
 
5
+ ## [5.0.1] - 2026-10-11
6
+ ### Changed
7
+ * Pipeline `count_all_scheduled_jobs` for significantly faster execution by @top-sigrid in #822
8
+ * Avoid unnecessary reloads in static mode by @ekmartin in #823
9
+
10
+ ### Fixed
11
+ * Skip invalid schedules without interrupting scheduler loading by @taf2 in #543
12
+ * Encode a delayed job once so its timestamps key can be cleaned up by @kamaljoshi in #827 (originally #599)
13
+ * Log to the logfile when backgrounded by @meatballhat in #828 (originally #568)
14
+ * Require redis before using its error classes by @neuromanskie in #829
15
+ * Fix flaky test by using strings instead of symbols for JSON-serialized args by @top-sigrid in #813
16
+
17
+ ### Security
18
+ * Escape job class and queue names in the scheduler web UI by @PatrickTulskie in #830
19
+ * Escape schedule names and parameters on the requeue params page by @PatrickTulskie in #831
20
+ * Escape the scheduler environment and master lock holder on the schedule page by @PatrickTulskie in #832
21
+
5
22
  ## [5.0.0] - 2026-01-15
6
23
  ### Breaking Changes
7
24
  * Ruby requirement: `>= 2.3.0` → `>= 3.0.0`
@@ -32,7 +49,7 @@
32
49
  * Fixed test compatibility with Rack 3 by @PatrickTulskie in #809
33
50
  * Fixed race condition in scheduler task tests by @PatrickTulskie in #809
34
51
 
35
- ## [4.10.3] - 2024-12-30
52
+ ## [4.11.0] - 2024-12-30
36
53
  ### Added
37
54
  * Expose timeout for lock via environment variable configuration by @pmm4654 in #786
38
55
  * Add Ruby 3.3 to the CI matrix by @santiagorodriguez96 in #797
@@ -83,11 +83,13 @@ module Resque
83
83
  # insertion time complexity is O(log(n)). Returns true if it's
84
84
  # the first job to be scheduled at that time, else false.
85
85
  def delayed_push(timestamp, item)
86
+ encoded_job = encode(item)
87
+
86
88
  # First add this item to the list for this timestamp
87
- redis.rpush("delayed:#{timestamp.to_i}", encode(item))
89
+ redis.rpush("delayed:#{timestamp.to_i}", encoded_job)
88
90
 
89
91
  # Store the timestamps at with this item occurs
90
- redis.sadd("timestamps:#{encode(item)}", ["delayed:#{timestamp.to_i}"])
92
+ redis.sadd("timestamps:#{encoded_job}", ["delayed:#{timestamp.to_i}"])
91
93
 
92
94
  # Now, add this timestamp to the zsets. The score and the value are
93
95
  # the same since we'll be querying by timestamp, and we don't have
@@ -261,12 +263,18 @@ module Resque
261
263
  count
262
264
  end
263
265
 
266
+ # Counts all jobs across all delayed timestamps.
267
+ # Uses pipelined LLEN calls in batches to avoid blocking the Redis thread
268
+ # for too long while being significantly faster than sequential calls.
264
269
  def count_all_scheduled_jobs
265
- total_jobs = 0
266
- Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |ts|
267
- total_jobs += redis.llen("delayed:#{ts}").to_i
270
+ timestamps = Array(redis.zrange(:delayed_queue_schedule, 0, -1))
271
+ return 0 if timestamps.empty?
272
+
273
+ timestamps.each_slice(10_000).sum do |batch|
274
+ redis.pipelined do |pipeline|
275
+ batch.each { |ts| pipeline.llen("delayed:#{ts}") }
276
+ end.sum
268
277
  end
269
- total_jobs
270
278
  end
271
279
 
272
280
  # Discover if a job has been delayed.
@@ -30,18 +30,18 @@ module Resque
30
30
  def setup_backgrounding
31
31
  return unless options[:background]
32
32
 
33
- # Need to set this here for conditional Process.daemon redirect of
34
- # stderr/stdout to /dev/null
35
- Resque::Scheduler.quiet = if options.key?(:quiet)
36
- !!options[:quiet]
37
- else
38
- true
39
- end
33
+ Resque::Scheduler.quiet = background_quiet?
40
34
 
41
35
  Process.daemon(true, !Resque::Scheduler.quiet)
42
36
  Resque.redis.reconnect
43
37
  end
44
38
 
39
+ def background_quiet?
40
+ return !!options[:quiet] if options.key?(:quiet)
41
+ return false if options.key?(:logfile)
42
+ !!options[:background]
43
+ end
44
+
45
45
  def setup_pid_file
46
46
  return unless options[:pidfile]
47
47
 
@@ -12,7 +12,7 @@
12
12
  <% jobs = resque.delayed_timestamp_peek(timestamp, start, 20) %>
13
13
  <% jobs.each do |job| %>
14
14
  <tr>
15
- <td class='class'><%= job['class'] %></td>
15
+ <td class='class'><%= h job['class'] %></td>
16
16
  <td class='args'><%= h show_job_arguments(job['args']) %></td>
17
17
  </tr>
18
18
  <% end %>
@@ -1,4 +1,4 @@
1
- <h1><%= @job_name %></h1>
1
+ <h1><%= h @job_name %></h1>
2
2
 
3
3
  <p class='intro'>
4
4
  This job requires parameters:
@@ -10,14 +10,14 @@
10
10
  <% value ||= {} %>
11
11
  <tr>
12
12
  <td>
13
- <%= key %>
13
+ <%= h key %>
14
14
  <% if value['description'] || value[:description] %>
15
- <span style="border-bottom:1px dotted;" title="<%=value ['description'] || value[:description] %>">(?)</span>
15
+ <span style="border-bottom:1px dotted;" title="<%= h(value['description'] || value[:description]) %>">(?)</span>
16
16
  <% end %>:
17
17
  </td>
18
- <td><input type="text" name="<%= key %>" value="<%= value['default'] || value[:default] %>"></td>
18
+ <td><input type="text" name="<%= h key %>" value="<%= h(value['default'] || value[:default]) %>"></td>
19
19
  <% end %>
20
20
  </table>
21
- <input type="hidden" name="job_name" value="<%= @job_name %>">
21
+ <input type="hidden" name="job_name" value="<%= h @job_name %>">
22
22
  <input type="submit" value="Queue now">
23
23
  </form>
@@ -4,8 +4,8 @@
4
4
  The list below contains all scheduled jobs. Click &quot;Queue now&quot; to queue
5
5
  a job immediately.
6
6
  <br/> Server local time: <%= Time.now %>
7
- <br/> Server Environment: <%= Resque::Scheduler.env %>
8
- <br/> Current master: <%= Resque.redis.get(Resque::Scheduler.master_lock.key) %>
7
+ <br/> Server Environment: <%= h Resque::Scheduler.env %>
8
+ <br/> Current master: <%= h Resque.redis.get(Resque::Scheduler.master_lock.key) %>
9
9
  </p>
10
10
  <p class='intro'>
11
11
  The highlighted jobs are skipped for current environment.
@@ -22,13 +22,13 @@
22
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
- <input type="hidden" name="klass" value="<%= job['class'] %>">
25
+ <input type="hidden" name="klass" value="<%= h job['class'] %>">
26
26
  <input type="hidden" name="args" value="<%= h(Resque.encode job['args']) %>">
27
27
  <input type="submit" value="Cancel Job">
28
28
  </form>
29
29
  </td>
30
30
  <td class='args'><%= format_time(Time.at(job['timestamp'])) %></td>
31
- <td class='class'><%= job['class'] %></td>
31
+ <td class='class'><%= h job['class'] %></td>
32
32
  <td class='args'><%= h job['args'].inspect %></td>
33
33
  </tr>
34
34
  <% end %>
@@ -44,8 +44,8 @@
44
44
  </tr>
45
45
  <% queued.each do |job| %>
46
46
  <tr>
47
- <td class='class'><%= job['queue'] %></td>
48
- <td class='class'><%= job['class'] %></td>
47
+ <td class='class'><%= h job['queue'] %></td>
48
+ <td class='class'><%= h job['class'] %></td>
49
49
  <td class='args'><%= h job['args'].inspect %></td>
50
50
  </tr>
51
51
  <% end %>
@@ -61,8 +61,8 @@
61
61
  </tr>
62
62
  <% working.each do |job| %>
63
63
  <tr>
64
- <td class='class'><%= job['queue'] %></td>
65
- <td class='class'><%= job['class'] %></td>
64
+ <td class='class'><%= h job['queue'] %></td>
65
+ <td class='class'><%= h job['class'] %></td>
66
66
  <td class='args'><%= h job['args'].inspect %></td>
67
67
  </tr>
68
68
  <% end %>
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Resque
4
4
  module Scheduler
5
- VERSION = '5.0.0'.freeze
5
+ VERSION = '5.0.1'.freeze
6
6
  end
7
7
  end
@@ -1,6 +1,6 @@
1
1
  # vim:fileencoding=utf-8
2
2
 
3
- require 'redis/errors'
3
+ require 'redis'
4
4
  require 'rufus/scheduler'
5
5
  require_relative 'scheduler/configuration'
6
6
  require_relative 'scheduler/locking'
@@ -49,6 +49,7 @@ module Resque
49
49
  $stderr.sync = true
50
50
 
51
51
  was_master = nil
52
+ schedule_loaded = false
52
53
 
53
54
  begin
54
55
  @th = Thread.current
@@ -61,8 +62,14 @@ module Resque
61
62
  if am_master != was_master
62
63
  procline am_master ? 'Master scheduler' : 'Child scheduler'
63
64
 
64
- # Load schedule because changed
65
- reload_schedule!
65
+ # The first pass always loads the schedule - load_schedule!
66
+ # reads am_master, so it cannot run before the check above.
67
+ # After that, only a dynamic schedule can have changed while
68
+ # we were not master.
69
+ if dynamic || !schedule_loaded
70
+ reload_schedule!
71
+ schedule_loaded = true
72
+ end
66
73
  end
67
74
 
68
75
  if am_master
@@ -148,8 +155,13 @@ module Resque
148
155
  args = optionizate_interval_value(config[interval_type])
149
156
  args = [args, nil, job: true] if args.is_a?(::String)
150
157
 
151
- job = rufus_scheduler.send(interval_type, *args) do
152
- enqueue_recurring(name, config)
158
+ begin
159
+ job = rufus_scheduler.send(interval_type, *args) do
160
+ enqueue_recurring(name, config)
161
+ end
162
+ rescue ArgumentError => e
163
+ log_error "Invalid #{interval_type} schedule for #{name}: #{e.message} - skipping"
164
+ return
153
165
  end
154
166
  @scheduled_jobs[name] = job
155
167
  interval_defined = true
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: 5.0.0
4
+ version: 5.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben VandenBos
@@ -10,10 +10,9 @@ authors:
10
10
  - Dan Buch
11
11
  - Michael Bianco
12
12
  - Patrick Tulskie
13
- autorequire:
14
13
  bindir: exe
15
14
  cert_chain: []
16
- date: 2026-01-15 00:00:00.000000000 Z
15
+ date: 1980-01-02 00:00:00.000000000 Z
17
16
  dependencies:
18
17
  - !ruby/object:Gem::Dependency
19
18
  name: bundler
@@ -322,7 +321,6 @@ files:
322
321
  - ".devcontainer/devcontainer.json"
323
322
  - ".devcontainer/docker-compose.yml"
324
323
  - ".github/dependabot.yml"
325
- - ".github/funding.yml"
326
324
  - ".github/workflows/codeql-analysis.yml"
327
325
  - ".github/workflows/rubocop.yml"
328
326
  - ".github/workflows/ruby.yml"
@@ -369,7 +367,6 @@ licenses:
369
367
  - MIT
370
368
  metadata:
371
369
  rubygems_mfa_required: 'true'
372
- post_install_message:
373
370
  rdoc_options: []
374
371
  require_paths:
375
372
  - lib
@@ -384,8 +381,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
384
381
  - !ruby/object:Gem::Version
385
382
  version: '0'
386
383
  requirements: []
387
- rubygems_version: 3.0.3.1
388
- signing_key:
384
+ rubygems_version: 4.0.16
389
385
  specification_version: 4
390
386
  summary: Light weight job scheduling on top of Resque
391
387
  test_files: []
data/.github/funding.yml DELETED
@@ -1,4 +0,0 @@
1
- # Docs for this file can be found here:
2
- # https://docs.github.com/en/github/administering-a-repository/displaying-a-sponsor-button-in-your-repository
3
-
4
- github: ["iloveitaly"]