Skip to content

Commit 5e50c41

Browse files
committed
Introduce poll-interval-variance option
1 parent 51403c0 commit 5e50c41

11 files changed

Lines changed: 115 additions & 63 deletions

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ group :test do
2626

2727
gem 'pry'
2828
gem 'pg_examiner', '~> 0.5.2'
29+
30+
gem 'timecop', '~> 0.9.10'
2931
end
3032

3133
gemspec

lib/que/command_line_interface.rb

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ def parse(
1818
default_require_file: RAILS_ENVIRONMENT_FILE
1919
)
2020

21-
options = {}
22-
queues = []
23-
log_level = 'info'
24-
log_internals = false
25-
poll_interval = 5
26-
connection_url = nil
27-
worker_count = nil
28-
worker_priorities = nil
21+
options = {}
22+
queues = []
23+
log_level = 'info'
24+
log_internals = false
25+
poll_interval = 5
26+
poll_interval_variance = 0
27+
connection_url = nil
28+
worker_count = nil
29+
worker_priorities = nil
2930

3031
parser =
3132
OptionParser.new do |opts|
@@ -50,6 +51,15 @@ def parse(
5051
poll_interval = i
5152
end
5253

54+
opts.on(
55+
'-j',
56+
'--poll-interval-variance [INTERVAL]',
57+
Float,
58+
"Set maximum variance in poll interval, in seconds (default: 0)",
59+
) do |j|
60+
poll_interval_variance = j.to_f
61+
end
62+
5363
opts.on(
5464
'--listen [LISTEN]',
5565
String,
@@ -232,7 +242,8 @@ def parse(
232242
options[:queues] = queues_hash
233243
end
234244

235-
options[:poll_interval] = poll_interval
245+
options[:poll_interval] = poll_interval
246+
options[:poll_interval_variance] = poll_interval_variance
236247

237248
locker =
238249
begin

lib/que/locker.rb

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class << self
3333
}
3434

3535
class Locker
36-
attr_reader :thread, :workers, :job_buffer, :locks, :queues, :poll_interval
36+
attr_reader :thread, :workers, :job_buffer, :locks, :queues, :poll_interval, :poll_interval_variance
3737

3838
MESSAGE_RESOLVERS = {}
3939
RESULT_RESOLVERS = {}
@@ -47,22 +47,24 @@ class Locker
4747
RESULT_RESOLVERS[:job_finished] =
4848
-> (messages) { finish_jobs(messages.map{|m| m.fetch(:metajob)}) }
4949

50-
DEFAULT_POLL_INTERVAL = 5.0
51-
DEFAULT_WAIT_PERIOD = 50
52-
DEFAULT_MAXIMUM_BUFFER_SIZE = 8
53-
DEFAULT_WORKER_PRIORITIES = [10, 30, 50, nil, nil, nil].freeze
50+
DEFAULT_POLL_INTERVAL = 5.0
51+
DEFAULT_POLL_INTERVAL_VARIANCE = 0.0
52+
DEFAULT_WAIT_PERIOD = 50
53+
DEFAULT_MAXIMUM_BUFFER_SIZE = 8
54+
DEFAULT_WORKER_PRIORITIES = [10, 30, 50, nil, nil, nil].freeze
5455

5556
def initialize(
56-
queues: [Que.default_queue],
57-
connection_url: nil,
58-
listen: true,
59-
poll: true,
60-
poll_interval: DEFAULT_POLL_INTERVAL,
61-
wait_period: DEFAULT_WAIT_PERIOD,
62-
maximum_buffer_size: DEFAULT_MAXIMUM_BUFFER_SIZE,
63-
worker_priorities: DEFAULT_WORKER_PRIORITIES,
64-
on_worker_start: nil,
65-
pidfile: nil
57+
queues: [Que.default_queue],
58+
connection_url: nil,
59+
listen: true,
60+
poll: true,
61+
poll_interval: DEFAULT_POLL_INTERVAL,
62+
poll_interval_variance: DEFAULT_POLL_INTERVAL_VARIANCE,
63+
wait_period: DEFAULT_WAIT_PERIOD,
64+
maximum_buffer_size: DEFAULT_MAXIMUM_BUFFER_SIZE,
65+
worker_priorities: DEFAULT_WORKER_PRIORITIES,
66+
on_worker_start: nil,
67+
pidfile: nil
6668
)
6769

6870
# Sanity-check all our arguments, since some users may instantiate Locker
@@ -71,6 +73,7 @@ def initialize(
7173
Que.assert [TrueClass, FalseClass], poll
7274

7375
Que.assert Numeric, poll_interval
76+
Que.assert Numeric, poll_interval_variance
7477
Que.assert Numeric, wait_period
7578

7679
Que.assert Array, worker_priorities
@@ -94,20 +97,22 @@ def initialize(
9497

9598
Que.internal_log :locker_instantiate, self do
9699
{
97-
queues: queues,
98-
listen: listen,
99-
poll: poll,
100-
poll_interval: poll_interval,
101-
wait_period: wait_period,
102-
maximum_buffer_size: maximum_buffer_size,
103-
worker_priorities: worker_priorities,
100+
queues: queues,
101+
listen: listen,
102+
poll: poll,
103+
poll_interval: poll_interval,
104+
poll_interval_variance: poll_interval_variance,
105+
wait_period: wait_period,
106+
maximum_buffer_size: maximum_buffer_size,
107+
worker_priorities: worker_priorities,
104108
}
105109
end
106110

107111
# Local cache of which advisory locks are held by this connection.
108112
@locks = Set.new
109113

110114
@poll_interval = poll_interval
115+
@poll_interval_variance = poll_interval_variance
111116

112117
if queues.is_a?(Hash)
113118
@queue_names = queues.keys
@@ -204,9 +209,10 @@ def initialize(
204209
if poll
205210
@queues.map do |queue_name, interval|
206211
Poller.new(
207-
connection: @connection,
208-
queue: queue_name,
209-
poll_interval: interval,
212+
connection: @connection,
213+
queue: queue_name,
214+
poll_interval: interval,
215+
poll_interval_variance: poll_interval_variance,
210216
)
211217
end
212218
end

lib/que/poller.rb

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -116,25 +116,32 @@ class Poller
116116
:connection,
117117
:queue,
118118
:poll_interval,
119+
:poll_interval_variance,
119120
:last_polled_at,
120-
:last_poll_satisfied
121+
:last_poll_satisfied,
122+
:next_poll_at
121123

122124
def initialize(
123125
connection:,
124126
queue:,
125-
poll_interval:
127+
poll_interval:,
128+
poll_interval_variance:
126129
)
127-
@connection = connection
128-
@queue = queue
129-
@poll_interval = poll_interval
130+
@connection = connection
131+
@queue = queue
132+
@poll_interval = poll_interval
133+
@poll_interval_variance = poll_interval_variance
134+
130135
@last_polled_at = nil
131136
@last_poll_satisfied = nil
137+
@next_poll_at = Time.now
132138

133139
Que.internal_log :poller_instantiate, self do
134140
{
135-
backend_pid: connection.backend_pid,
136-
queue: queue,
137-
poll_interval: poll_interval,
141+
backend_pid: connection.backend_pid,
142+
queue: queue,
143+
poll_interval: poll_interval,
144+
poll_interval_variance: poll_interval_variance,
138145
}
139146
end
140147
end
@@ -158,31 +165,36 @@ def poll(
158165

159166
@last_polled_at = Time.now
160167
@last_poll_satisfied = poll_satisfied?(priorities, jobs)
168+
@next_poll_at = last_polled_at +
169+
poll_interval +
170+
rand(-poll_interval_variance..poll_interval_variance)
161171

162172
Que.internal_log :poller_polled, self do
163173
{
164-
queue: @queue,
165-
locked: jobs.count,
166-
priorities: priorities,
167-
held_locks: held_locks.to_a,
168-
newly_locked: jobs.map { |key| key.fetch(:id) },
174+
queue: @queue,
175+
locked: jobs.count,
176+
priorities: priorities,
177+
held_locks: held_locks.to_a,
178+
newly_locked: jobs.map { |key| key.fetch(:id) },
179+
last_polled_at: last_polled_at,
180+
last_poll_satisfied: last_poll_satisfied,
181+
next_poll_at: next_poll_at,
169182
}
170183
end
171184

172185
jobs.map! { |job| Metajob.new(job) }
173186
end
174187

175188
def should_poll?
189+
# polling is disabled for this queue
190+
return false if poll_interval.nil?
191+
176192
# Never polled before?
177193
last_poll_satisfied.nil? ||
178194
# Plenty of jobs were available last time?
179195
last_poll_satisfied == true ||
180-
poll_interval_elapsed?
181-
end
182-
183-
def poll_interval_elapsed?
184-
return unless interval = poll_interval
185-
(Time.now - last_polled_at) > interval
196+
# It's due time to poll again regardless of the last poll's results?
197+
next_poll_at < Time.now
186198
end
187199

188200
class << self

spec/gemfiles/Gemfile-rails-6.0

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ group :test do
2020
gem 'minitest-hooks', '1.4.0'
2121
gem 'pry'
2222
gem 'pg_examiner', '~> 0.5.2'
23+
gem 'timecop', '~> 0.9.10'
2324
end

spec/gemfiles/Gemfile-rails-6.1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ group :test do
2020
gem 'minitest-hooks', '1.4.0'
2121
gem 'pry'
2222
gem 'pg_examiner', '~> 0.5.2'
23+
gem 'timecop', '~> 0.9.10'
2324
end

spec/gemfiles/Gemfile-rails-7.0

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ group :test do
2020
gem 'minitest-hooks', '1.4.0'
2121
gem 'pry'
2222
gem 'pg_examiner', '~> 0.5.2'
23+
gem 'timecop', '~> 0.9.10'
2324
end

spec/gemfiles/Gemfile-rails-7.1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ group :test do
2020
gem 'minitest-hooks', '1.4.0'
2121
gem 'pry'
2222
gem 'pg_examiner', '~> 0.5.2'
23+
gem 'timecop', '~> 0.9.10'
2324
end

spec/que/command_line_interface_spec.rb

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ def write_file
188188
def assert_locker_instantiated(
189189
worker_priorities: [10, 30, 50, nil, nil, nil],
190190
poll_interval: 5,
191+
poll_interval_variance: 0.0,
191192
listen: true,
192193
wait_period: 50,
193194
queues: ['default'],
@@ -199,13 +200,14 @@ def assert_locker_instantiated(
199200

200201
locker_instantiate = locker_instantiates.first
201202

202-
assert_equal listen, locker_instantiate[:listen]
203-
assert_equal true, locker_instantiate[:poll]
204-
assert_equal queues, locker_instantiate[:queues]
205-
assert_equal poll_interval, locker_instantiate[:poll_interval]
206-
assert_equal wait_period, locker_instantiate[:wait_period]
207-
assert_equal maximum_buffer_size, locker_instantiate[:maximum_buffer_size]
208-
assert_equal worker_priorities, locker_instantiate[:worker_priorities]
203+
assert_equal listen, locker_instantiate[:listen]
204+
assert_equal true, locker_instantiate[:poll]
205+
assert_equal queues, locker_instantiate[:queues]
206+
assert_equal poll_interval, locker_instantiate[:poll_interval]
207+
assert_equal poll_interval_variance, locker_instantiate[:poll_interval_variance]
208+
assert_equal wait_period, locker_instantiate[:wait_period]
209+
assert_equal maximum_buffer_size, locker_instantiate[:maximum_buffer_size]
210+
assert_equal worker_priorities, locker_instantiate[:worker_priorities]
209211
end
210212

211213
def assert_locker_started(
@@ -258,6 +260,14 @@ def assert_locker_started(
258260
end
259261
end
260262

263+
["-j", "--poll-interval-variance"].each do |command|
264+
it "with #{command} to configure the poll interval variance" do
265+
assert_successful_invocation "./#{filename} #{command} 5"
266+
assert_locker_instantiated(poll_interval_variance: 5)
267+
assert_locker_started
268+
end
269+
end
270+
261271
it "with --listen false to disable listen mode" do
262272
assert_successful_invocation "./#{filename} --listen false"
263273
assert_locker_instantiated(listen: false)

spec/que/poller_spec.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def poll(
4444
connection: override_connection || connection,
4545
queue: queue_name,
4646
poll_interval: 5,
47+
poll_interval_variance: 0,
4748
)
4849

4950
Que::Poller.setup(override_connection || connection)
@@ -245,6 +246,7 @@ def assert_poll(priorities:, locked:)
245246
connection: connection,
246247
queue: 'default',
247248
poll_interval: 5,
249+
poll_interval_variance: 0,
248250
)
249251
end
250252

@@ -275,14 +277,15 @@ def assert_poll(priorities:, locked:)
275277
assert_equal false, poller.should_poll?
276278
end
277279

278-
it "should be false if the number of jobs returned from the last poll was less than the lowest priority request, but the poll_interval has elapsed" do
280+
it "should be true if the number of jobs returned from the last poll was less than the lowest priority request, but the poll_interval has elapsed" do
279281
job_ids = 5.times.map { Que::Job.enqueue.que_attrs[:id] }
280282

281283
result = poller.poll(priorities: { 500 => 7 }, held_locks: Set.new)
282284
assert_equal job_ids, result.map(&:id)
283285

284-
poller.instance_variable_set(:@last_polled_at, Time.now - 30)
285-
assert_equal true, poller.should_poll?
286+
Timecop.freeze(Time.now + 30) do
287+
assert_equal true, poller.should_poll?
288+
end
286289
end
287290
end
288291
end

0 commit comments

Comments
 (0)