resque 2.7.0 → 3.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7419ce3cc8734b05527a47b0c5fb4a9eea0a5d33265a6e3214bb429980c37681
4
- data.tar.gz: 4216e18e121883e0350275e00c19e1d6c6f0965915d4b48a8a0a29f07e652317
3
+ metadata.gz: 6029ead32e829bcc359edabd6540a827fc8dff70768f26dbc6756041c98a6691
4
+ data.tar.gz: c6a568f692f54fd91cd8371b029babaf70c096ccb8b020cd3a76eb16f6e0ab56
5
5
  SHA512:
6
- metadata.gz: 7301e154b368d012161ae22d69cb8e4400977a3099392f7789369ca37b04a413d0abec092e60dd9dddee01bda81c55dbf182da5cede4a8c2beff94f8ed44bc94
7
- data.tar.gz: 85d0c05d98502b0da28ae60ca6117d3b4a47bbfc981b48c4168f6bf727bac8c4c325dcebf604f8916d0e04c97bf933651b6077b55ead117e7ecb1cc2cf4655f0
6
+ metadata.gz: bc4575c01a0ad8e803a7a21b707bdcfca8d7a4cbab3d19f80476fce6b28e900857c3a19271358805b16f99bf59e666ff3e7bfb61911ec4271014d4c41aa53322
7
+ data.tar.gz: 2af75588506a9e8d54dbe74d6a9f3c5ef96d1daccf3d76b4e2d820bbad388730fe69f829bc3077f06d609c50f131edf9c0b102271ebd70ec8e2143df9e0c4a12
data/HISTORY.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## 3.0.0
2
+
3
+ ### Breaking Changes
4
+
5
+ * **Minimum Ruby version is now 3.0.0** - Ruby 2.x is no longer supported
6
+ * **Minimum Sinatra version is now 2.0** - old Sinatra versions (0.9-1.x) are no longer supported
7
+ * **Rack 1.x is no longer supported** - Resque now requires Rack 2.x or greater
8
+
9
+ ### Added
10
+
11
+ * Rack 3 support and switched default Rack handler to Puma (#1912)
12
+ * Ruby 3.4 and Rails 8.1 support (#1912 and #1906)
13
+ * Upstream Active Job adapter from Rails (#1906)
14
+ * Explicit `redis` (>= 4.0) and `base64` (~> 0.1) gem dependencies (#1922)
15
+ * Added `shutdown` hooks - allows plugins and adapters to know when a worker is shutting down, enabling features like Active Job Continuations (#1916, #1924)
16
+ * Added support for dynamic interval sleep - workers can now use a faster polling interval that automatically backs off when idle, reducing Redis load (#1920)
17
+
18
+ ### Fixed
19
+
20
+ * Fix circular require from Rails ActiveJob adapter (#1906)
21
+ * Fix failed job `retried_at` timezone data (#1918)
22
+ * Fix double Redis get when checking for paused workers (#1919)
23
+ * Documentation cleanup (#1903, #1917)
24
+
1
25
  ## 2.7.0
2
26
 
3
27
  ### Fixed
data/README.markdown CHANGED
@@ -39,15 +39,23 @@ The Resque frontend tells you what workers are doing, what workers are
39
39
  not doing, what queues you're using, what's in those queues, provides
40
40
  general usage stats, and helps you track failures.
41
41
 
42
- Resque now supports Ruby 2.3.0 and above.
43
- We will also only be supporting Redis 3.0 and above going forward.
42
+ Resque 3.0 requires Ruby 3.0.0 or newer.
44
43
 
45
- ### Note on the future of Resque
44
+ **Version Support:**
45
+ - Ruby: 3.0, 3.1, 3.2, 3.3, 3.4+
46
+ - Redis gem: 4.0+
47
+ - Rack: 2.x or 3.x
48
+ - Rails (for ActiveJob): 7.2+ (requires Ruby 3.1+ for Rails 8.0+)
46
49
 
47
- Would you like to be involved in Resque? Do you have thoughts about what
48
- Resque should be and do going forward? There's currently an [open discussion here](https://github.com/resque/resque/issues/1759)
49
- on just that topic, so please feel free to join in. We'd love to hear your thoughts
50
- and/or have people volunteer to be a part of the project!
50
+ ### Resque 3.0
51
+
52
+ Resque 3.0 is a major release that modernizes the codebase with updated dependency requirements:
53
+ - Requires Ruby 3.0+ (drops support for Ruby 2.x)
54
+ - Requires redis gem 4.0+ (tested with redis gem 4.x and 5.x)
55
+ - Requires Sinatra 2.0+ (with Rack 2.x or 3.x support via appropriate Sinatra version)
56
+ - Maintains compatibility with Rails 7.2+ and ActiveJob
57
+
58
+ If you need Ruby 2.x support, please use Resque 2.x.
51
59
 
52
60
  Example
53
61
  -------
@@ -133,15 +141,6 @@ require 'your/app' # Include this line if you want your workers to have access t
133
141
 
134
142
  #### Rails
135
143
 
136
- ##### Rails 7.1
137
-
138
- If you're using Rails 7.1.x, use [rack-session version 1.0.2](https://rubygems.org/gems/rack-session/versions/1.0.2). This is because resque-web's dependency, [Sinatra, isn't compatible with rack 3.0](https://github.com/sinatra/sinatra/issues/1797), which is [required by rack-session 2.0.0](https://rubygems.org/gems/rack-session/versions/2.0.0).
139
-
140
- ```
141
- gem 'rails', '~> 7.1'
142
- gem 'rack-session', '~> 1.0', '>= 1.0.2'
143
- ```
144
-
145
144
  To make resque specific changes, you can override the `resque:setup` job in `lib/tasks` (ex: `lib/tasks/resque.rake`). GitHub's setup task looks like this:
146
145
 
147
146
  ``` ruby
data/Rakefile CHANGED
@@ -28,7 +28,33 @@ Rake::TestTask.new do |test|
28
28
  test.verbose = true
29
29
  test.libs << "test"
30
30
  test.libs << "lib"
31
- test.test_files = FileList['test/**/*_test.rb']
31
+ test.test_files = FileList['test/**/*_test.rb'].exclude('test/active_job/**/*')
32
+ end
33
+
34
+ Rake::TestTask.new('test:activejob') do |test|
35
+ test.verbose = true
36
+ test.libs << "test"
37
+ test.libs << "lib"
38
+ test.libs << "test/active_job"
39
+ test.test_files = FileList['test/active_job/cases/*_test.rb']
40
+ end
41
+
42
+ task "env:aj_integration" do
43
+ ENV["AJ_INTEGRATION_TESTS"] = "1"
44
+ end
45
+
46
+ Rake::TestTask.new('test:activejob:integration' => 'env:aj_integration') do |t|
47
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.1")
48
+ puts "Integration tests require Ruby 3.1 or later"
49
+ exit 0
50
+ end
51
+
52
+ t.description = "Run integration tests for Resque::ActiveJob::Adapter"
53
+ t.libs << "test"
54
+ t.libs << "test/active_job"
55
+ t.test_files = FileList["test/active_job/integration/**/*_test.rb"]
56
+ t.verbose = true
57
+ t.warning = true
32
58
  end
33
59
 
34
60
  if command? :kicker
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/enumerable"
4
+ require "active_support/core_ext/array/access"
5
+
6
+ begin
7
+ require "resque-scheduler"
8
+ rescue LoadError
9
+ begin
10
+ require "resque_scheduler"
11
+ rescue LoadError
12
+ false
13
+ end
14
+ end
15
+
16
+ module ActiveJob
17
+ module QueueAdapters
18
+ remove_const(:ResqueAdapter) if defined?(ResqueAdapter)
19
+
20
+ # = Resque adapter for Active Job
21
+ #
22
+ # Resque (pronounced like "rescue") is a Redis-backed library for creating
23
+ # background jobs, placing those jobs on multiple queues, and processing
24
+ # them later.
25
+ #
26
+ # Read more about Resque {here}[https://github.com/resque/resque].
27
+ #
28
+ # To use Resque set the queue_adapter config to +:resque+.
29
+ #
30
+ # Rails.application.config.active_job.queue_adapter = :resque
31
+ class ResqueAdapter < ::ActiveJob::QueueAdapters::AbstractAdapter
32
+ def enqueue(job) # :nodoc:
33
+ JobWrapper.instance_variable_set(:@queue, job.queue_name)
34
+ Resque.enqueue_to job.queue_name, JobWrapper, job.serialize
35
+ end
36
+
37
+ def enqueue_at(job, timestamp) # :nodoc:
38
+ unless Resque.respond_to?(:enqueue_at_with_queue)
39
+ raise NotImplementedError, "To be able to schedule jobs with Resque you need the " \
40
+ "resque-scheduler gem. Please add it to your Gemfile and run bundle install"
41
+ end
42
+ Resque.enqueue_at_with_queue job.queue_name, timestamp, JobWrapper, job.serialize
43
+ end
44
+
45
+ class JobWrapper # :nodoc:
46
+ class << self
47
+ def perform(job_data)
48
+ ::ActiveJob::Base.execute job_data
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -78,7 +78,7 @@ module Resque
78
78
  def self.requeue(id, queue = nil)
79
79
  check_queue(queue)
80
80
  item = all(id)
81
- item['retried_at'] = Time.now.strftime("%Y/%m/%d %H:%M:%S")
81
+ item['retried_at'] = UTF8Util.clean(Time.now.strftime("%Y/%m/%d %H:%M:%S %Z"))
82
82
  data_store.update_item_in_failed_queue(id,Resque.encode(item))
83
83
  Job.create(item['queue'], item['payload']['class'], *item['payload']['args'])
84
84
  end
@@ -6,5 +6,11 @@ module Resque
6
6
  # redefine ths task to load the rails env
7
7
  task "resque:setup" => :environment
8
8
  end
9
+
10
+ initializer "resque.active_job" do
11
+ ActiveSupport.on_load(:active_job) do
12
+ require "active_job/queue_adapters/resque_adapter"
13
+ end
14
+ end
9
15
  end
10
16
  end
data/lib/resque/tasks.rb CHANGED
@@ -17,7 +17,11 @@ namespace :resque do
17
17
 
18
18
  worker.prepare
19
19
  worker.log "Starting worker #{worker}"
20
- worker.work(ENV['INTERVAL'] || 5) # interval, will block
20
+ # will block until a shutdown signal is received
21
+ worker.work(ENV["INTERVAL"],
22
+ max_interval: ENV['MAX_INTERVAL'],
23
+ min_interval: ENV['MIN_INTERVAL'],
24
+ backoff_interval: ENV['BACKOFF_INTERVAL'])
21
25
  end
22
26
 
23
27
  desc "Start multiple Resque workers. Should only be used in dev mode."
@@ -1,3 +1,3 @@
1
1
  module Resque
2
- VERSION = '2.7.0'
2
+ VERSION = '3.0.0'
3
3
  end
@@ -3,6 +3,10 @@ require 'logger'
3
3
  require 'optparse'
4
4
  require 'fileutils'
5
5
  require 'rack'
6
+ begin
7
+ require 'rackup'
8
+ rescue LoadError
9
+ end
6
10
  require 'resque/server'
7
11
 
8
12
  # only used with `bin/resque-web`
@@ -30,7 +34,7 @@ module Resque
30
34
 
31
35
  @args = load_options(runtime_args)
32
36
 
33
- @rack_handler = (s = options[:rack_handler]) ? Rack::Handler.get(s) : setup_rack_handler
37
+ @rack_handler = (s = options[:rack_handler]) ? self.class.get_rackup_or_rack_handler.get(s) : setup_rack_handler
34
38
 
35
39
  case option_parser.command
36
40
  when :help
@@ -270,6 +274,10 @@ module Resque
270
274
  self.class.logger
271
275
  end
272
276
 
277
+ def self.get_rackup_or_rack_handler
278
+ defined?(::Rackup::Handler) && ::Rack.release >= '3' ? ::Rackup::Handler : ::Rack::Handler
279
+ end
280
+
273
281
  private
274
282
  def setup_rack_handler
275
283
  # First try to set Rack handler via a special hook we honor
@@ -284,24 +292,25 @@ module Resque
284
292
  handler = nil
285
293
  @app.server.each do |server|
286
294
  begin
287
- handler = Rack::Handler.get(server)
295
+ handler = self.class.get_rackup_or_rack_handler.get(server)
288
296
  break
289
297
  rescue LoadError, NameError
290
298
  next
291
299
  end
292
300
  end
293
- raise 'No available Rack handler (e.g. WEBrick, Thin, Puma, etc.) was found.' if handler.nil?
301
+ raise 'No available Rack handler (e.g. WEBrick, Puma, etc.) was found.' if handler.nil?
294
302
 
295
303
  handler
296
304
 
297
305
  # :server might be set explicitly to a single option like "mongrel"
298
306
  else
299
- Rack::Handler.get(@app.server)
307
+ self.class.get_rackup_or_rack_handler.get(@app.server)
300
308
  end
301
309
 
302
- # If all else fails, we'll use Thin
310
+ # If all else fails, we'll use Puma
303
311
  else
304
- JRUBY ? Rack::Handler::WEBrick : Rack::Handler::Thin
312
+ rack_server = JRUBY ? 'webrick' : 'puma'
313
+ self.class.get_rackup_or_rack_handler.get(rack_server)
305
314
  end
306
315
  end
307
316
 
data/lib/resque/worker.rb CHANGED
@@ -143,6 +143,7 @@ module Resque
143
143
  def initialize(*queues)
144
144
  @shutdown = nil
145
145
  @paused = nil
146
+ @cached_pause_value = nil
146
147
  @before_first_fork_hook_ran = false
147
148
 
148
149
  @heartbeat_thread = nil
@@ -237,20 +238,43 @@ module Resque
237
238
  # The default is 5 seconds, but for a semi-active site you may
238
239
  # want to use a smaller value.
239
240
  #
241
+ # Can also be passed 3 interval floats: max, min, backoff.
242
+ # The actual sleep amount will float between these min/max
243
+ # bounds.
244
+ #
245
+ # If a job is picked up we sleep for the minimum amount of
246
+ # time, but as the queues empty we increase the backoff to the
247
+ # max interval. This prevents idle workers from hammering the
248
+ # redis server with lpop requests.
249
+ #
240
250
  # Also accepts a block which will be passed the job as soon as it
241
251
  # has completed processing. Useful for testing.
242
- def work(interval = 5.0, &block)
243
- interval = Float(interval)
252
+ def work(interval = 5.0,
253
+ min_interval: nil, # defaults to interval
254
+ max_interval: nil, # defaults to interval
255
+ backoff_interval: nil, # defaults to 0.1
256
+ &block)
257
+ interval = Float(interval || 5.0)
258
+ max_interval = Float(max_interval || interval)
259
+ min_interval = Float(min_interval || interval).clamp(nil, max_interval)
260
+ backoff_interval = Float(backoff_interval || 0.1).clamp(nil, max_interval)
261
+ interval = interval.clamp(min_interval, max_interval)
244
262
  startup
245
263
 
246
264
  loop do
247
265
  break if shutdown?
248
266
 
249
- unless work_one_job(&block)
267
+ if work_one_job(&block)
268
+ interval = min_interval
269
+ else
250
270
  state_change
251
271
  break if interval.zero?
272
+
273
+ interval = (interval + backoff_interval)
274
+ .clamp(nil, max_interval)
275
+
252
276
  log_with_severity :debug, "Sleeping for #{interval} seconds"
253
- procline paused? ? "Paused" : "Waiting for #{queues.join(',')}"
277
+ procline @cached_pause_value ? "Paused" : "Waiting for #{queues.join(',')}"
254
278
  sleep interval
255
279
  end
256
280
  end
@@ -260,6 +284,7 @@ module Resque
260
284
  rescue Exception => exception
261
285
  return if exception.class == SystemExit && !@child && run_at_exit_hooks
262
286
  log_with_severity :error, "Failed to start worker : #{exception.inspect}"
287
+ log_with_severity :error, exception.backtrace.join("\n")
263
288
  unregister_worker(exception)
264
289
  run_hook :worker_exit
265
290
  end
@@ -441,6 +466,8 @@ module Resque
441
466
  def shutdown
442
467
  log_with_severity :info, 'Exiting...'
443
468
  @shutdown = true
469
+ run_hook :shutdown
470
+ true
444
471
  end
445
472
 
446
473
  # Kill the child and shutdown immediately.
@@ -579,7 +606,7 @@ module Resque
579
606
 
580
607
  # are we paused?
581
608
  def paused?
582
- @paused || redis.get('pause-all-workers').to_s.strip.downcase == 'true'
609
+ @cached_pause_value = @paused || redis.get('pause-all-workers').to_s.strip.downcase == 'true'
583
610
  end
584
611
 
585
612
  # Stop processing jobs after the current one has completed (if we're
data/lib/resque.rb CHANGED
@@ -316,6 +316,20 @@ module Resque
316
316
  register_hook(:worker_exit, block)
317
317
  end
318
318
 
319
+ # The `shutdown` hook will be run in the **child** process
320
+ # when the worker has received a signal to stop processing
321
+ #
322
+ # Call with a block to register a hook.
323
+ # Call with no arguments to return all registered hooks.
324
+ def shutdown(&block)
325
+ block ? register_hook(:shutdown, block) : hooks(:shutdown)
326
+ end
327
+
328
+ # Register a shutdown proc.
329
+ def shutdown=(block)
330
+ register_hook(:shutdown, block)
331
+ end
332
+
319
333
  def to_s
320
334
  "Resque Client connected to #{redis_id}"
321
335
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Wanstrath
@@ -11,8 +11,36 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2024-12-30 00:00:00.000000000 Z
14
+ date: 2026-01-12 00:00:00.000000000 Z
15
15
  dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: base64
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - "~>"
21
+ - !ruby/object:Gem::Version
22
+ version: '0.1'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.1'
30
+ - !ruby/object:Gem::Dependency
31
+ name: logger
32
+ requirement: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
16
44
  - !ruby/object:Gem::Dependency
17
45
  name: redis-namespace
18
46
  requirement: !ruby/object:Gem::Requirement
@@ -27,20 +55,34 @@ dependencies:
27
55
  - - "~>"
28
56
  - !ruby/object:Gem::Version
29
57
  version: '1.6'
58
+ - !ruby/object:Gem::Dependency
59
+ name: redis
60
+ requirement: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '4.0'
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '4.0'
30
72
  - !ruby/object:Gem::Dependency
31
73
  name: sinatra
32
74
  requirement: !ruby/object:Gem::Requirement
33
75
  requirements:
34
76
  - - ">="
35
77
  - !ruby/object:Gem::Version
36
- version: 0.9.2
78
+ version: '2.0'
37
79
  type: :runtime
38
80
  prerelease: false
39
81
  version_requirements: !ruby/object:Gem::Requirement
40
82
  requirements:
41
83
  - - ">="
42
84
  - !ruby/object:Gem::Version
43
- version: 0.9.2
85
+ version: '2.0'
44
86
  - !ruby/object:Gem::Dependency
45
87
  name: multi_json
46
88
  requirement: !ruby/object:Gem::Requirement
@@ -70,7 +112,7 @@ dependencies:
70
112
  - !ruby/object:Gem::Version
71
113
  version: '1'
72
114
  - !ruby/object:Gem::Dependency
73
- name: thin
115
+ name: puma
74
116
  requirement: !ruby/object:Gem::Requirement
75
117
  requirements:
76
118
  - - ">="
@@ -127,6 +169,7 @@ files:
127
169
  - Rakefile
128
170
  - bin/resque
129
171
  - bin/resque-web
172
+ - lib/active_job/queue_adapters/resque_adapter.rb
130
173
  - lib/resque.rb
131
174
  - lib/resque/data_store.rb
132
175
  - lib/resque/errors.rb
@@ -195,7 +238,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
195
238
  requirements:
196
239
  - - ">="
197
240
  - !ruby/object:Gem::Version
198
- version: 2.3.0
241
+ version: 3.0.0
199
242
  required_rubygems_version: !ruby/object:Gem::Requirement
200
243
  requirements:
201
244
  - - ">="