Skip to content

Commit ffd5686

Browse files
HanaPearlmanp
authored andcommitted
RUBY-1896 Raise an actionable error message when retryWrites fails due to using an unsupported storage engine (#1481)
* initial actionable error message and tests * integration test for mmapv1 retryable write * remove unsupported error check from legacy retry writes, as it is not subject to current spec * implemented feedback
1 parent 106be4b commit ffd5686

File tree

4 files changed

+73
-1
lines changed

4 files changed

+73
-1
lines changed

lib/mongo/error/operation_failure.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,18 @@ def wtimeout?
252252
def max_time_ms_expired?
253253
code == 50 # MaxTimeMSExpired
254254
end
255+
256+
# Whether the error is caused by an attempted retryable write
257+
# on a storage engine that does not support retryable writes.
258+
#
259+
# @return [ true | false ] Whether the error is caused by an attempted
260+
# retryable write on a storage engine that does not support retryable writes.
261+
#
262+
# @since 2.11.0
263+
def unsupported_retryable_write?
264+
# code 20 is IllegalOperation
265+
code == 20 && message.start_with?("Transaction numbers")
266+
end
255267
end
256268
end
257269
end

lib/mongo/retryable.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,12 @@ def write_with_retry(session, write_concern, ending_transaction = false, &block)
215215
end
216216
retry_write(e, session, txn_num, &block)
217217
rescue Error::OperationFailure => e
218-
if (session.in_transaction? && !ending_transaction) || !e.write_retryable?
218+
if e.unsupported_retryable_write?
219+
raise_unsupported_error(e)
220+
elsif (session.in_transaction? && !ending_transaction) || !e.write_retryable?
219221
raise
220222
end
223+
221224
retry_write(e, session, txn_num, &block)
222225
end
223226
end
@@ -403,5 +406,15 @@ def log_retry(e, options = nil)
403406
end
404407
Logger.logger.warn "#{message} due to: #{e.class.name} #{e.message}"
405408
end
409+
410+
# Retry writes on MMAPv1 should raise an actionable error; append actionable
411+
# information to the error message and preserve the backtrace.
412+
def raise_unsupported_error(e)
413+
new_error = Error::OperationFailure.new("#{e.class}: #{e} "\
414+
"This MongoDB deployment does not support retryable writes. Please add "\
415+
"retryWrites=false to your connection string or use the retry_writes: false Ruby client option")
416+
new_error.set_backtrace(e.backtrace)
417+
raise new_error
418+
end
406419
end
407420
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
require 'spec_helper'
2+
3+
describe 'Retryable writes errors tests' do
4+
describe 'when the storage engine does not support retryable writes but the server does' do
5+
require_mmapv1
6+
min_server_fcv '3.6'
7+
require_topology :replica_set, :sharded
8+
9+
before do
10+
collection.delete_many
11+
end
12+
13+
let(:collection) do
14+
client[authorized_collection.name]
15+
end
16+
17+
let(:client) do
18+
authorized_client.with(retry_writes: true)
19+
end
20+
21+
after do
22+
client.close(true)
23+
end
24+
25+
context 'when a retryable write is attempted' do
26+
it 'raises an actionable error message' do
27+
expect {
28+
collection.insert_one(a:1)
29+
}.to raise_error(Mongo::Error::OperationFailure, /This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string or use the retry_writes: false Ruby client option/)
30+
expect(collection.find.count).to eq(0)
31+
end
32+
end
33+
end
34+
end

spec/mongo/retryable_spec.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,5 +591,18 @@ class RetryableHost
591591
end
592592
end
593593

594+
context 'when an error due to using an unsupported storage engine occurs' do
595+
before do
596+
expect(operation).to receive(:execute).and_raise(
597+
Mongo::Error::OperationFailure.new('Transaction numbers are only allowed on...',
598+
nil, :code=>20)).ordered
599+
end
600+
601+
it 'raises an exception with the correct error message' do
602+
expect {
603+
retryable.write
604+
}.to raise_error(Mongo::Error::OperationFailure, /This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string or use the retry_writes: false Ruby client option/)
605+
end
606+
end
594607
end
595608
end

0 commit comments

Comments
 (0)