Skip to content

Commit 1887917

Browse files
p-mongop
authored andcommitted
Fix RUBY-1911 Average round trip time may become nil during server selection (#1517)
1 parent 39b0359 commit 1887917

File tree

3 files changed

+82
-4
lines changed

3 files changed

+82
-4
lines changed

lib/mongo/server_selector/selectable.rb

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@ def select_server(cluster, ping = nil, session = nil)
193193
servers = candidates(cluster)
194194
if Lint.enabled?
195195
servers.each do |server|
196+
# It is possible for a server to have a nil average RTT here
197+
# because the ARTT comes from description which may be updated
198+
# by a background thread while server selection is running.
199+
# Currently lint mode is not a public feature, if/when this
200+
# changes (https://jira.mongodb.org/browse/RUBY-1576) the
201+
# requirement for ARTT to be not nil would need to be removed.
196202
if server.average_round_trip_time.nil?
197203
raise Error::LintError, "Server #{server.address} has nil average rtt"
198204
end
@@ -361,11 +367,34 @@ def secondaries(candidates)
361367
# @since 2.0.0
362368
def near_servers(candidates = [], local_threshold = nil)
363369
return candidates if candidates.empty?
364-
nearest_server = candidates.min_by(&:average_round_trip_time)
370+
371+
# Average RTT on any server may change at any time by the server
372+
# monitor's background thread. ARTT may also become nil if the
373+
# server is marked unknown. Take a snapshot of ARTTs for the duration
374+
# of this method.
375+
376+
candidates = candidates.map do |server|
377+
{server: server, artt: server.average_round_trip_time}
378+
end.reject do |candidate|
379+
candidate[:artt].nil?
380+
end
381+
382+
return candidates if candidates.empty?
383+
384+
nearest_candidate = candidates.min_by do |candidate|
385+
candidate[:artt]
386+
end
387+
365388
# Default for legacy signarure
366389
local_threshold ||= self.local_threshold
367-
threshold = nearest_server.average_round_trip_time + local_threshold
368-
candidates.select { |server| server.average_round_trip_time <= threshold }.shuffle!
390+
391+
threshold = nearest_candidate[:artt] + local_threshold
392+
393+
candidates.select do |candidate|
394+
candidate[:artt] <= threshold
395+
end.map do |candidate|
396+
candidate[:server]
397+
end.shuffle!
369398
end
370399

371400
# Select the servers matching the defined tag sets.

spec/mongo/server_selector_spec.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,4 +564,49 @@ def make_server_with_staleness(last_write_date)
564564
it_behaves_like 'staleness filter'
565565
end
566566
end
567+
568+
describe '#candidates' do
569+
let(:selector) { Mongo::ServerSelector::Primary.new(options) }
570+
571+
let(:cluster) { double('cluster') }
572+
573+
let(:options) { {} }
574+
575+
context 'sharded' do
576+
let(:servers) do
577+
[make_server(:mongos)]
578+
end
579+
580+
before do
581+
allow(cluster).to receive(:single?).and_return(false)
582+
allow(cluster).to receive(:sharded?).and_return(true)
583+
allow(cluster).to receive(:options).and_return({})
584+
allow(cluster).to receive(:servers).and_return(servers)
585+
end
586+
587+
it 'returns the servers' do
588+
expect(selector.candidates(cluster)).to eq(servers)
589+
end
590+
591+
context 'with local threshold' do
592+
let(:options) do
593+
{local_threshold: 1}
594+
end
595+
596+
it 'returns the servers' do
597+
expect(selector.candidates(cluster)).to eq(servers)
598+
end
599+
600+
context 'when servers become unknown' do
601+
let(:servers) do
602+
[make_server(:unknown)]
603+
end
604+
605+
it 'returns an empty list' do
606+
expect(selector.candidates(cluster)).to eq([])
607+
end
608+
end
609+
end
610+
end
611+
end
567612
end

spec/support/common_shortcuts.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ def wait_for_all_servers(cluster)
7474

7575
def make_server(mode, options = {})
7676
tags = options[:tags] || {}
77-
average_round_trip_time = options[:average_round_trip_time] || 0
77+
average_round_trip_time = if mode == :unknown
78+
nil
79+
else
80+
options[:average_round_trip_time] || 0
81+
end
7882

7983
if mode == :unknown
8084
ismaster = {}

0 commit comments

Comments
 (0)