Skip to content

Commit 0098735

Browse files
p-mongop
authored andcommitted
RUBY-1986 Retry reads in legacy mode on OperationFailure errors that modern mode retries on (#1562)
1 parent cd6a6e6 commit 0098735

File tree

4 files changed

+66
-56
lines changed

4 files changed

+66
-56
lines changed

lib/mongo/error/operation_failure.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ class OperationFailure < Error
8080
# @since 2.6.0
8181
attr_reader :code_name
8282

83-
# Whether the error is a retryable error according to the legacy read retry
84-
# logic.
83+
# Whether the error is a retryable error according to the legacy
84+
# read retry logic.
8585
#
8686
# @return [ true, false ]
8787
#
8888
# @since 2.1.1
8989
# @deprecated
9090
def retryable?
91-
RETRY_MESSAGES.any?{ |m| message.include?(m) }
91+
write_retryable? || RETRY_MESSAGES.any?{ |m| message.include?(m) }
9292
end
9393

9494
# Whether the error is a retryable error according to the modern retryable

lib/mongo/retryable.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ def legacy_read_with_retry(session, server_selector)
333333
retry
334334
rescue Error::OperationFailure => e
335335
e.add_note("attempt #{attempt + 1}")
336-
if cluster.sharded? && e.retryable? && !(session && session.in_transaction?)
336+
if e.retryable? && !(session && session.in_transaction?)
337337
if attempt > client.max_read_retries
338338
raise e
339339
end

spec/mongo/retryable_spec.rb

Lines changed: 31 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -175,83 +175,62 @@ class RetryableHost
175175

176176
context 'when an operation failure occurs' do
177177

178-
context 'when the cluster is not a mongos' do
178+
context 'when the operation failure is not retryable' do
179+
180+
let(:error) do
181+
Mongo::Error::OperationFailure.new('not authorized')
182+
end
179183

180184
before do
181-
expect(operation).to receive(:execute).and_raise(Mongo::Error::OperationFailure).ordered
182-
expect(cluster).to receive(:sharded?).and_return(false)
185+
expect(operation).to receive(:execute).and_raise(error).ordered
183186
end
184187

185-
it 'raises an exception' do
188+
it 'raises the exception' do
186189
expect {
187190
read_operation
188191
}.to raise_error(Mongo::Error::OperationFailure)
189192
end
190193
end
191194

192-
context 'when the cluster is a mongos' do
195+
context 'when the operation failure is retryable' do
193196

194-
context 'when the operation failure is not retryable' do
197+
let(:error) do
198+
Mongo::Error::OperationFailure.new('not master')
199+
end
195200

196-
let(:error) do
197-
Mongo::Error::OperationFailure.new('not authorized')
198-
end
201+
context 'when the retry succeeds' do
199202

200203
before do
204+
expect(retryable).to receive(:select_server).ordered
201205
expect(operation).to receive(:execute).and_raise(error).ordered
202-
expect(cluster).to receive(:sharded?).and_return(true)
206+
expect(client).to receive(:read_retry_interval).and_return(0.1).ordered
207+
expect(retryable).to receive(:select_server).ordered
208+
expect(operation).to receive(:execute).and_return(true).ordered
203209
end
204210

205-
it 'raises the exception' do
206-
expect {
207-
read_operation
208-
}.to raise_error(Mongo::Error::OperationFailure)
211+
it 'returns the result' do
212+
expect(read_operation).to be true
209213
end
210214
end
211215

212-
context 'when the operation failure is retryable' do
216+
context 'when the retry fails once and then succeeds' do
217+
let(:max_read_retries) { 2 }
213218

214-
let(:error) do
215-
Mongo::Error::OperationFailure.new('not master')
216-
end
217-
218-
context 'when the retry succeeds' do
219+
before do
220+
expect(retryable).to receive(:select_server).ordered
221+
expect(operation).to receive(:execute).and_raise(error).ordered
219222

220-
before do
221-
expect(retryable).to receive(:select_server).ordered
222-
expect(operation).to receive(:execute).and_raise(error).ordered
223-
expect(cluster).to receive(:sharded?).and_return(true)
224-
expect(client).to receive(:read_retry_interval).and_return(0.1).ordered
225-
expect(retryable).to receive(:select_server).ordered
226-
expect(operation).to receive(:execute).and_return(true).ordered
227-
end
223+
expect(client).to receive(:read_retry_interval).and_return(0.1).ordered
224+
expect(retryable).to receive(:select_server).ordered
225+
expect(operation).to receive(:execute).and_raise(error).ordered
228226

229-
it 'returns the result' do
230-
expect(read_operation).to be true
231-
end
227+
expect(client).to receive(:read_retry_interval).and_return(0.1).ordered
228+
expect(retryable).to receive(:select_server).ordered
229+
expect(operation).to receive(:execute).and_return(true).ordered
232230
end
233231

234-
context 'when the retry fails once and then succeeds' do
235-
let(:max_read_retries) { 2 }
236-
237-
before do
238-
expect(retryable).to receive(:select_server).ordered
239-
expect(operation).to receive(:execute).and_raise(error).ordered
240-
241-
expect(cluster).to receive(:sharded?).and_return(true)
242-
expect(client).to receive(:read_retry_interval).and_return(0.1).ordered
243-
expect(retryable).to receive(:select_server).ordered
244-
expect(operation).to receive(:execute).and_raise(error).ordered
245-
246-
expect(cluster).to receive(:sharded?).and_return(true)
247-
expect(client).to receive(:read_retry_interval).and_return(0.1).ordered
248-
expect(retryable).to receive(:select_server).ordered
249-
expect(operation).to receive(:execute).and_return(true).ordered
250-
end
251-
252-
it 'returns the result' do
253-
expect(read_operation).to be true
254-
end
232+
it 'returns the result' do
233+
expect(read_operation).to be true
255234
end
256235
end
257236
end

spec/spec_tests/retryable_reads_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,34 @@
1212
end
1313
end
1414
end
15+
16+
describe 'Retryable reads spec tests - legacy' do
17+
require_no_multi_shard
18+
19+
define_crud_spec_tests(RETRYABLE_READS_TESTS) do |spec, req, test|
20+
let(:client_options) do
21+
{
22+
max_read_retries: 1,
23+
read_retry_interval: 0,
24+
retry_reads: false,
25+
}.update(test.client_options)
26+
end
27+
28+
let(:client) do
29+
authorized_client.with(client_options).tap do |client|
30+
client.subscribe(Mongo::Monitoring::COMMAND, event_subscriber)
31+
end
32+
end
33+
34+
around do |example|
35+
desc = example.full_description
36+
# Skip tests that disable modern retryable reads because they expect
37+
# no retries - and since legacy retryable reads are used, the tests
38+
# will fail.
39+
if desc =~/retryReads is false|fails on first attempt/
40+
skip 'Test not applicable to legacy read retries'
41+
end
42+
example.run
43+
end
44+
end
45+
end

0 commit comments

Comments
 (0)