delayed 0.5.5 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae36c9e652d4fb6ce28f8897b0e7b03b7777ce9907a74973924111b735bca3ae
4
- data.tar.gz: 0d4c27d0bf1f7e256fa85914086a0711c6406b4be5e88f211eccbd921c001718
3
+ metadata.gz: 56e1fb1ba1756335d42efb26a1469fecab4eb40817fb1783c5bd010db6aa799f
4
+ data.tar.gz: 003ee0881213a406571b19d293b145a48eee377f6aef1282f701d76429816e9e
5
5
  SHA512:
6
- metadata.gz: cbae79e450983c1dd968b5f9959b4497758c32cc2c21a45ecea5b41dc1d7829fac2699d0a8cea635369d276eaec5b07ce8d7fa292674578ff62690823bc163fc
7
- data.tar.gz: df37746eee87f40792499e02421f82a2fc4afa4317e168bdb440f3bed1ba737c761469092098f99e2aa3fb163f50981e6b19a83a4859e91871ac8f046363f630
6
+ metadata.gz: a2cccc74e3a2ecbb2459008fe9d399ebf2572dba782cd660bdd69bf4aafbab421a545952206fe1767edd03b9b38655f1e4a4209d9dd30060631499d3d949fba0
7
+ data.tar.gz: f92d61ae2788cc3fa6e5a1d84150571516caf7d8e520057efc90f80de24bf1d402a5e01243c18eae903d46e48849ab044809e466fc687a02ab4f07c179118ec1
data/README.md CHANGED
@@ -432,6 +432,10 @@ Delayed::Worker.read_ahead = 5
432
432
 
433
433
  # If a worker finds no jobs, it will sleep this number of seconds in between attempts:
434
434
  Delayed::Worker.sleep_delay = 5
435
+
436
+ # Until version 1.0, the worker will not sleep at all between attemps if it finds jobs.
437
+ # This can be configured by setting the minimum reserve interval:
438
+ Delayed::Worker.min_reserve_interval = 0.5 # seconds
435
439
  ```
436
440
 
437
441
  If a job fails, it will be rerun up to 25 times (with an exponential back-off). Jobs will also
@@ -1,7 +1,8 @@
1
- require 'active_support/proxy_object'
2
-
3
1
  module Delayed
4
- class DelayProxy < ActiveSupport::ProxyObject
2
+ class DelayProxy < BasicObject
3
+ undef_method :==
4
+ undef_method :equal?
5
+
5
6
  def initialize(payload_class, target, options)
6
7
  @payload_class = payload_class
7
8
  @target = target
@@ -21,7 +21,7 @@ module Delayed
21
21
  def on_exit!; end
22
22
 
23
23
  def interruptable_sleep(seconds)
24
- pipe[0].wait_readable(seconds)
24
+ pipe[0].wait_readable(seconds) if seconds.positive?
25
25
  end
26
26
 
27
27
  def stop
@@ -12,6 +12,7 @@ module Delayed
12
12
  include Runnable
13
13
 
14
14
  cattr_accessor :sleep_delay, instance_writer: false, default: 5
15
+ cattr_accessor :min_reserve_interval, instance_writer: false, default: 0
15
16
  cattr_accessor :max_attempts, instance_writer: false, default: 25
16
17
  cattr_accessor :max_claims, instance_writer: false, default: 5
17
18
  cattr_accessor :max_run_time, instance_writer: false, default: 20.minutes
@@ -92,6 +93,7 @@ module Delayed
92
93
  total = 0
93
94
 
94
95
  while total < num
96
+ start = clock_time
95
97
  jobs = reserve_jobs
96
98
  break if jobs.empty?
97
99
 
@@ -107,6 +109,9 @@ module Delayed
107
109
  pool.wait_for_termination
108
110
 
109
111
  break if stop? # leave if we're exiting
112
+
113
+ elapsed = clock_time - start
114
+ interruptable_sleep(self.class.min_reserve_interval - elapsed)
110
115
  end
111
116
 
112
117
  [success.value, total - success.value]
@@ -227,5 +232,9 @@ module Delayed
227
232
  def reload!
228
233
  Rails.application.reloader.reload! if defined?(Rails.application.reloader) && Rails.application.reloader.check!
229
234
  end
235
+
236
+ def clock_time
237
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
238
+ end
230
239
  end
231
240
  end
@@ -49,11 +49,11 @@ RSpec.describe Delayed::ActiveJobAdapter do
49
49
  / priority: ?\n/,
50
50
  " arguments: []\n",
51
51
  " executions: 0\n",
52
- (" exception_executions: {}\n" if ActiveJob::VERSION::MAJOR >= 6),
52
+ (" exception_executions: {}\n" if ActiveJob.gem_version >= Gem::Version.new('6.0')),
53
53
  " locale: en\n",
54
- (/ timezone: ?\n/ if ActiveJob::VERSION::MAJOR >= 6),
55
- (/ enqueued_at: '2023-01-20T18:52:29(\.\d+)?Z'\n/ if ActiveJob::VERSION::MAJOR >= 6),
56
- (/ scheduled_at: ?\n/ if ActiveJob::VERSION::MAJOR >= 7 && ActiveJob::VERSION::MINOR >= 1),
54
+ (/ timezone: ?\n/ if ActiveJob.gem_version >= Gem::Version.new('6.0')),
55
+ (/ enqueued_at: '2023-01-20T18:52:29(\.\d+)?Z'\n/ if ActiveJob.gem_version >= Gem::Version.new('6.0')),
56
+ (/ scheduled_at: ?\n/ if ActiveJob.gem_version >= Gem::Version.new('7.1')),
57
57
  ].compact
58
58
  end
59
59
  end
@@ -64,7 +64,7 @@ describe 'rake' do
64
64
  .to change { Delayed::Worker.min_priority }.from(nil).to(6)
65
65
  .and change { Delayed::Worker.max_priority }.from(nil).to(8)
66
66
  .and change { Delayed::Worker.queues }.from([]).to(%w(foo bar))
67
- .and change { Delayed::Worker.sleep_delay }.from(5).to(1)
67
+ .and change { Delayed::Worker.sleep_delay }.from(TEST_SLEEP_DELAY).to(1)
68
68
  .and change { Delayed::Worker.read_ahead }.from(5).to(3)
69
69
  .and change { Delayed::Worker.max_claims }.from(5).to(3)
70
70
  end
@@ -96,7 +96,7 @@ describe 'rake' do
96
96
  .to change { Delayed::Worker.min_priority }.from(nil).to(6)
97
97
  .and change { Delayed::Worker.max_priority }.from(nil).to(8)
98
98
  .and change { Delayed::Worker.queues }.from([]).to(%w(foo))
99
- .and change { Delayed::Worker.sleep_delay }.from(5).to(1)
99
+ .and change { Delayed::Worker.sleep_delay }.from(TEST_SLEEP_DELAY).to(1)
100
100
  .and change { Delayed::Worker.read_ahead }.from(5).to(3)
101
101
  .and change { Delayed::Worker.max_claims }.from(5).to(3)
102
102
  end
data/spec/helper.rb CHANGED
@@ -97,6 +97,11 @@ class SingletonClass
97
97
  include Singleton
98
98
  end
99
99
 
100
+ # Negative values are treated as sleep(0),
101
+ # so we can use different values to test the sleep behavior:
102
+ TEST_MIN_RESERVE_INTERVAL = -10
103
+ TEST_SLEEP_DELAY = -100
104
+
100
105
  RSpec.configure do |config|
101
106
  config.around(:each) do |example|
102
107
  aj_priority_was = ActiveJob::Base.priority
@@ -113,6 +118,14 @@ RSpec.configure do |config|
113
118
  queues_was = Delayed::Worker.queues
114
119
  read_ahead_was = Delayed::Worker.read_ahead
115
120
  sleep_delay_was = Delayed::Worker.sleep_delay
121
+ min_reserve_interval_was = Delayed::Worker.min_reserve_interval
122
+
123
+ if Gem.loaded_specs['delayed'].version >= Gem::Version.new('1.0') && min_reserve_interval_was.zero?
124
+ raise "Min reserve interval should be nonzero in v1.0 release"
125
+ end
126
+
127
+ Delayed::Worker.sleep_delay = TEST_SLEEP_DELAY
128
+ Delayed::Worker.min_reserve_interval = TEST_MIN_RESERVE_INTERVAL
116
129
 
117
130
  example.run
118
131
  ensure
@@ -130,6 +143,7 @@ RSpec.configure do |config|
130
143
  Delayed::Worker.queues = queues_was
131
144
  Delayed::Worker.read_ahead = read_ahead_was
132
145
  Delayed::Worker.sleep_delay = sleep_delay_was
146
+ Delayed::Worker.min_reserve_interval = min_reserve_interval_was
133
147
 
134
148
  Delayed::Job.delete_all
135
149
  end
data/spec/worker_spec.rb CHANGED
@@ -1,10 +1,6 @@
1
1
  require 'helper'
2
2
 
3
3
  describe Delayed::Worker do
4
- before do
5
- described_class.sleep_delay = 0
6
- end
7
-
8
4
  describe 'start' do
9
5
  it 'runs the :execute lifecycle hook' do
10
6
  performances = []
@@ -32,62 +28,74 @@ describe Delayed::Worker do
32
28
  allow(subject).to receive(:interruptable_sleep).and_call_original
33
29
  end
34
30
 
35
- context 'when there are no jobs' do
36
- before do
37
- allow(Delayed::Job).to receive(:reserve).and_return([])
38
- end
31
+ around do |example|
32
+ max_claims_was = described_class.max_claims
33
+ described_class.max_claims = max_claims
34
+ example.run
35
+ ensure
36
+ described_class.max_claims = max_claims_was
37
+ end
39
38
 
40
- it 'does not log and then sleeps' do
39
+ before do
40
+ allow(Delayed::Job).to receive(:reserve).and_return((0...jobs_returned).map { job }, [])
41
+ end
42
+
43
+ let(:max_claims) { 1 }
44
+ let(:jobs_returned) { 1 }
45
+ let(:job) do
46
+ instance_double(
47
+ Delayed::Job,
48
+ id: 123,
49
+ max_run_time: 10,
50
+ name: 'MyJob',
51
+ run_at: Delayed::Job.db_time_now,
52
+ created_at: Delayed::Job.db_time_now,
53
+ priority: Delayed::Priority.interactive,
54
+ queue: 'testqueue',
55
+ attempts: 0,
56
+ invoke_job: true,
57
+ destroy: true,
58
+ )
59
+ end
60
+
61
+ it 'logs the count and sleeps only within the loop' do
62
+ subject.run!
63
+ expect(Delayed.logger).to have_received(:info).with(/1 jobs processed/)
64
+ expect(subject).to have_received(:interruptable_sleep).once.with(a_value_within(1).of(TEST_MIN_RESERVE_INTERVAL))
65
+ expect(subject).not_to have_received(:interruptable_sleep).with(TEST_SLEEP_DELAY)
66
+ end
67
+
68
+ context 'when no jobs are returned' do
69
+ let(:jobs_returned) { 0 }
70
+
71
+ it 'does not log and then sleeps only outside of the loop' do
41
72
  subject.run!
42
73
  expect(Delayed.logger).not_to have_received(:info)
43
- expect(subject).to have_received(:interruptable_sleep)
74
+ expect(subject).to have_received(:interruptable_sleep).with(TEST_SLEEP_DELAY)
44
75
  end
45
76
  end
46
77
 
47
- context 'when there is a job worked off' do
48
- around do |example|
49
- max_claims_was = described_class.max_claims
50
- described_class.max_claims = max_claims
51
- example.run
52
- ensure
53
- described_class.max_claims = max_claims_was
54
- end
55
-
56
- before do
57
- allow(Delayed::Job).to receive(:reserve).and_return([job], [])
58
- end
59
-
60
- let(:max_claims) { 1 }
61
- let(:job) do
62
- instance_double(
63
- Delayed::Job,
64
- id: 123,
65
- max_run_time: 10,
66
- name: 'MyJob',
67
- run_at: Delayed::Job.db_time_now,
68
- created_at: Delayed::Job.db_time_now,
69
- priority: Delayed::Priority.interactive,
70
- queue: 'testqueue',
71
- attempts: 0,
72
- invoke_job: true,
73
- destroy: true,
74
- )
75
- end
78
+ context 'when max_claims is 3 and 3 jobs are returned' do
79
+ let(:max_claims) { 3 }
80
+ let(:jobs_returned) { 3 }
76
81
 
77
- it 'logs the count and does not sleep' do
82
+ it 'logs the count and sleeps only in the loop' do
78
83
  subject.run!
79
- expect(Delayed.logger).to have_received(:info).with(/1 jobs processed/)
80
- expect(subject).not_to have_received(:interruptable_sleep)
84
+ expect(Delayed.logger).to have_received(:info).with(/3 jobs processed/)
85
+ expect(subject).to have_received(:interruptable_sleep).once.with(a_value_within(1).of(TEST_MIN_RESERVE_INTERVAL))
86
+ expect(subject).not_to have_received(:interruptable_sleep).with(TEST_SLEEP_DELAY)
81
87
  end
88
+ end
82
89
 
83
- context 'when max_claims is 2' do
84
- let(:max_claims) { 2 }
90
+ context 'when max_claims is 3 and 2 jobs are returned' do
91
+ let(:max_claims) { 3 }
92
+ let(:jobs_returned) { 2 }
85
93
 
86
- it 'logs the count and sleeps' do
87
- subject.run!
88
- expect(Delayed.logger).to have_received(:info).with(/1 jobs processed/)
89
- expect(subject).to have_received(:interruptable_sleep)
90
- end
94
+ it 'logs the count and sleeps both in the loop and outside of the loop' do
95
+ subject.run!
96
+ expect(Delayed.logger).to have_received(:info).with(/2 jobs processed/)
97
+ expect(subject).to have_received(:interruptable_sleep).once.with(a_value_within(1).of(TEST_MIN_RESERVE_INTERVAL))
98
+ expect(subject).to have_received(:interruptable_sleep).once.with(TEST_SLEEP_DELAY)
91
99
  end
92
100
  end
93
101
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delayed
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.5
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Griffith
@@ -16,10 +16,9 @@ authors:
16
16
  - Matt Griffin
17
17
  - Steve Richert
18
18
  - Tobias Lütke
19
- autorequire:
20
19
  bindir: bin
21
20
  cert_chain: []
22
- date: 2024-08-13 00:00:00.000000000 Z
21
+ date: 2025-01-15 00:00:00.000000000 Z
23
22
  dependencies:
24
23
  - !ruby/object:Gem::Dependency
25
24
  name: activerecord
@@ -124,7 +123,6 @@ metadata:
124
123
  bug_tracker_uri: https://github.com/betterment/delayed/issues
125
124
  source_code_uri: https://github.com/betterment/delayed
126
125
  rubygems_mfa_required: 'true'
127
- post_install_message:
128
126
  rdoc_options: []
129
127
  require_paths:
130
128
  - lib
@@ -139,8 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
137
  - !ruby/object:Gem::Version
140
138
  version: '0'
141
139
  requirements: []
142
- rubygems_version: 3.3.26
143
- signing_key:
140
+ rubygems_version: 3.6.2
144
141
  specification_version: 4
145
142
  summary: a multi-threaded, SQL-driven ActiveJob backend used at Betterment to process
146
143
  millions of background jobs per day