mongo 2.5.0.beta → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/lib/mongo/address.rb +1 -1
- data/lib/mongo/address/unix.rb +1 -1
- data/lib/mongo/auth/user.rb +0 -5
- data/lib/mongo/auth/user/view.rb +4 -4
- data/lib/mongo/bulk_write.rb +60 -32
- data/lib/mongo/client.rb +44 -8
- data/lib/mongo/cluster.rb +14 -12
- data/lib/mongo/cluster/periodic_executor.rb +106 -0
- data/lib/mongo/cluster/{cursor_reaper.rb → reapers/cursor_reaper.rb} +5 -37
- data/lib/mongo/cluster/reapers/socket_reaper.rb +59 -0
- data/lib/mongo/collection.rb +9 -6
- data/lib/mongo/collection/view.rb +2 -2
- data/lib/mongo/collection/view/builder/aggregation.rb +2 -1
- data/lib/mongo/collection/view/builder/find_command.rb +1 -1
- data/lib/mongo/collection/view/change_stream.rb +14 -1
- data/lib/mongo/collection/view/map_reduce.rb +30 -13
- data/lib/mongo/collection/view/readable.rb +5 -5
- data/lib/mongo/collection/view/writable.rb +98 -51
- data/lib/mongo/error.rb +3 -0
- data/lib/mongo/error/invalid_txt_record.rb +27 -0
- data/lib/mongo/error/invalid_uri.rb +7 -6
- data/lib/mongo/error/mismatched_domain.rb +27 -0
- data/lib/mongo/error/no_srv_records.rb +26 -0
- data/lib/mongo/error/unsupported_features.rb +0 -18
- data/lib/mongo/index/view.rb +2 -2
- data/lib/mongo/operation.rb +1 -0
- data/lib/mongo/operation/causally_consistent.rb +33 -0
- data/lib/mongo/operation/commands.rb +2 -1
- data/lib/mongo/operation/commands/aggregate.rb +2 -7
- data/lib/mongo/operation/commands/count.rb +27 -0
- data/lib/mongo/operation/commands/distinct.rb +27 -0
- data/lib/mongo/operation/commands/find.rb +3 -1
- data/lib/mongo/operation/commands/map_reduce.rb +1 -0
- data/lib/mongo/operation/commands/parallel_scan.rb +1 -0
- data/lib/mongo/operation/specifiable.rb +12 -0
- data/lib/mongo/operation/uses_command_op_msg.rb +36 -5
- data/lib/mongo/operation/write.rb +0 -5
- data/lib/mongo/operation/write/bulk/bulkable.rb +4 -8
- data/lib/mongo/operation/write/bulk/mergable.rb +2 -0
- data/lib/mongo/operation/write/command/create_index.rb +19 -0
- data/lib/mongo/operation/write/command/create_user.rb +19 -0
- data/lib/mongo/operation/write/command/delete.rb +1 -2
- data/lib/mongo/operation/write/command/drop_index.rb +19 -0
- data/lib/mongo/operation/write/command/insert.rb +1 -2
- data/lib/mongo/operation/write/command/remove_user.rb +19 -0
- data/lib/mongo/operation/write/command/update.rb +1 -2
- data/lib/mongo/operation/write/command/update_user.rb +19 -0
- data/lib/mongo/operation/write/write_command_enabled.rb +1 -3
- data/lib/mongo/protocol/compressed.rb +2 -1
- data/lib/mongo/protocol/serializers.rb +6 -6
- data/lib/mongo/retryable.rb +48 -5
- data/lib/mongo/server.rb +15 -0
- data/lib/mongo/server/connection.rb +21 -1
- data/lib/mongo/server/connection_pool.rb +3 -0
- data/lib/mongo/server/connection_pool/queue.rb +50 -5
- data/lib/mongo/server/description.rb +11 -3
- data/lib/mongo/server/description/features.rb +26 -7
- data/lib/mongo/session.rb +133 -6
- data/lib/mongo/session/server_session.rb +30 -0
- data/lib/mongo/session/session_pool.rb +20 -20
- data/lib/mongo/uri.rb +88 -44
- data/lib/mongo/uri/srv_protocol.rb +158 -0
- data/lib/mongo/version.rb +1 -1
- data/lib/mongo/write_concern/normalizable.rb +12 -0
- data/mongo.gemspec +1 -2
- data/spec/mongo/address_spec.rb +12 -0
- data/spec/mongo/auth/user/view_spec.rb +1 -5
- data/spec/mongo/bulk_write_spec.rb +232 -401
- data/spec/mongo/change_stream_examples_spec.rb +150 -0
- data/spec/mongo/client_spec.rb +142 -2
- data/spec/mongo/cluster/cursor_reaper_spec.rb +0 -70
- data/spec/mongo/cluster/socket_reaper_spec.rb +32 -0
- data/spec/mongo/cluster_spec.rb +11 -7
- data/spec/mongo/collection/view/aggregation_spec.rb +46 -1
- data/spec/mongo/collection/view/builder/find_command_spec.rb +15 -0
- data/spec/mongo/collection/view/change_stream_spec.rb +79 -12
- data/spec/mongo/collection/view/map_reduce_spec.rb +120 -4
- data/spec/mongo/collection/view/readable_spec.rb +23 -5
- data/spec/mongo/collection_spec.rb +292 -102
- data/spec/mongo/command_monitoring_spec.rb +26 -32
- data/spec/mongo/crud_spec.rb +1 -1
- data/spec/mongo/cursor_spec.rb +2 -3
- data/spec/mongo/database_spec.rb +30 -14
- data/spec/mongo/dns_seedlist_discovery_spec.rb +94 -0
- data/spec/mongo/grid/fs_bucket_spec.rb +1 -1
- data/spec/mongo/grid/stream/write_spec.rb +1 -1
- data/spec/mongo/index/view_spec.rb +8 -46
- data/spec/mongo/operation/write/bulk/delete_spec.rb +2 -2
- data/spec/mongo/operation/write/bulk/insert_spec.rb +2 -10
- data/spec/mongo/operation/write/{create_index_spec.rb → command/create_index_spec.rb} +2 -6
- data/spec/mongo/operation/write/command/delete_spec.rb +35 -7
- data/spec/mongo/operation/write/{drop_index_spec.rb → command/drop_index_spec.rb} +1 -1
- data/spec/mongo/operation/write/command/insert_spec.rb +37 -6
- data/spec/mongo/operation/write/{remove_user_spec.rb → command/remove_user_spec.rb} +2 -6
- data/spec/mongo/operation/write/command/update_spec.rb +34 -7
- data/spec/mongo/operation/write/{update_user_spec.rb → command/update_user_spec.rb} +1 -1
- data/spec/mongo/operation/write/create_user_spec.rb +1 -1
- data/spec/mongo/operation/write/delete_spec.rb +1 -1
- data/spec/mongo/operation/write/insert_spec.rb +2 -10
- data/spec/mongo/operation/write/update_spec.rb +3 -15
- data/spec/mongo/retryable_spec.rb +1 -1
- data/spec/mongo/retryable_writes_spec.rb +815 -0
- data/spec/mongo/server/connection_pool/queue_spec.rb +35 -2
- data/spec/mongo/server/connection_pool_spec.rb +234 -1
- data/spec/mongo/server/connection_spec.rb +10 -6
- data/spec/mongo/server/description/features_spec.rb +51 -37
- data/spec/mongo/server/description_spec.rb +6 -3
- data/spec/mongo/server_spec.rb +87 -0
- data/spec/mongo/session/server_session_spec.rb +43 -0
- data/spec/mongo/session/session_pool_spec.rb +63 -27
- data/spec/mongo/session_spec.rb +247 -0
- data/spec/mongo/shell_examples_spec.rb +2 -2
- data/spec/mongo/uri/srv_protocol_spec.rb +933 -0
- data/spec/mongo/uri_spec.rb +42 -3
- data/spec/mongo/write_concern/acknowledged_spec.rb +11 -0
- data/spec/mongo/write_concern/unacknowledged_spec.rb +11 -0
- data/spec/spec_helper.rb +11 -25
- data/spec/support/authorization.rb +2 -1
- data/spec/support/connection_string.rb +8 -4
- data/spec/support/crud.rb +38 -24
- data/spec/support/crud/write.rb +30 -3
- data/spec/support/crud_tests/read/aggregate-out.yml +21 -0
- data/spec/support/crud_tests/write/bulkWrite-arrayFilters.yml +44 -0
- data/spec/support/crud_tests/write/findOneAndUpdate-arrayFilters.yml +1 -1
- data/spec/support/crud_tests/write/insertMany.yml +1 -3
- data/spec/support/crud_tests/write/replaceOne.yml +1 -1
- data/spec/support/crud_tests/write/updateMany-arrayFilters.yml +1 -1
- data/spec/support/crud_tests/write/updateOne-arrayFilters.yml +1 -1
- data/spec/support/dns_seedlist_discovery_tests/longer-parent-in-return.yml +11 -0
- data/spec/support/dns_seedlist_discovery_tests/misformatted-option.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/no-results.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/not-enough-parts.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/one-result-default-port.yml +10 -0
- data/spec/support/dns_seedlist_discovery_tests/one-txt-record-multiple-strings.yml +10 -0
- data/spec/support/dns_seedlist_discovery_tests/one-txt-record.yml +11 -0
- data/spec/support/dns_seedlist_discovery_tests/parent-part-mismatch1.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/parent-part-mismatch2.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/parent-part-mismatch3.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/parent-part-mismatch4.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/parent-part-mismatch5.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/returned-parent-too-short.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/returned-parent-wrong.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/two-results-default-port.yml +11 -0
- data/spec/support/dns_seedlist_discovery_tests/two-results-nonstandard-port.yml +11 -0
- data/spec/support/dns_seedlist_discovery_tests/two-txt-records.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/txt-record-not-allowed-option.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/txt-record-with-overridden-ssl-option.yml +11 -0
- data/spec/support/dns_seedlist_discovery_tests/txt-record-with-overridden-uri-option.yml +11 -0
- data/spec/support/dns_seedlist_discovery_tests/txt-record-with-unallowed-option.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/uri-with-port.yml +5 -0
- data/spec/support/dns_seedlist_discovery_tests/uri-with-two-hosts.yml +5 -0
- data/spec/support/retryable_writes_tests/bulkWrite.yml +305 -0
- data/spec/support/retryable_writes_tests/deleteOne.yml +51 -0
- data/spec/support/retryable_writes_tests/findOneAndDelete.yml +52 -0
- data/spec/support/retryable_writes_tests/findOneAndReplace.yml +57 -0
- data/spec/support/retryable_writes_tests/findOneAndUpdate.yml +56 -0
- data/spec/support/retryable_writes_tests/insertMany.yml +72 -0
- data/spec/support/retryable_writes_tests/insertOne.yml +55 -0
- data/spec/support/retryable_writes_tests/replaceOne.yml +60 -0
- data/spec/support/retryable_writes_tests/updateOne.yml +120 -0
- data/spec/support/shared/session.rb +525 -24
- metadata +437 -350
- metadata.gz.sig +0 -0
- data/lib/mongo/operation/commands/user_query.rb +0 -72
- data/lib/mongo/operation/write/create_index.rb +0 -67
- data/lib/mongo/operation/write/create_user.rb +0 -50
- data/lib/mongo/operation/write/drop_index.rb +0 -63
- data/lib/mongo/operation/write/remove_user.rb +0 -48
- data/lib/mongo/operation/write/update_user.rb +0 -50
@@ -0,0 +1,815 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Retryable Writes' do
|
4
|
+
|
5
|
+
RETRYABLE_WRITES_TESTS.each do |file|
|
6
|
+
|
7
|
+
spec = Mongo::CRUD::Spec.new(file)
|
8
|
+
|
9
|
+
context(spec.description) do
|
10
|
+
|
11
|
+
spec.tests.each do |test|
|
12
|
+
|
13
|
+
context(test.description) do
|
14
|
+
|
15
|
+
let(:collection) do
|
16
|
+
client[TEST_COLL]
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:client) do
|
20
|
+
authorized_client.with(heartbeat_frequency: 100, retry_writes: true).tap do |cl|
|
21
|
+
cl.subscribe(Mongo::Monitoring::COMMAND, subscriber)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:subscriber) do
|
26
|
+
EventSubscriber.new
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
test.setup_test(collection)
|
31
|
+
end
|
32
|
+
|
33
|
+
after do
|
34
|
+
test.clear_fail_point(collection)
|
35
|
+
collection.delete_many
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:results) do
|
39
|
+
if test.error?
|
40
|
+
error = nil
|
41
|
+
begin; test.run(collection); rescue => e; error = e; end
|
42
|
+
error
|
43
|
+
else
|
44
|
+
test.run(collection)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'has the correct data in the collection', if: (sessions_enabled? && replica_set? && test.outcome_collection_data) do
|
49
|
+
skip 'Test cannot be run on this server version' unless spec.server_version_satisfied?(client)
|
50
|
+
results
|
51
|
+
expect(collection.find.to_a).to match_collection_data(test)
|
52
|
+
end
|
53
|
+
|
54
|
+
if test.error?
|
55
|
+
it 'raises an error', if: sessions_enabled? && replica_set? do
|
56
|
+
skip 'Test cannot be run on this server version' unless spec.server_version_satisfied?(client)
|
57
|
+
expect(results).to be_a(Mongo::Error)
|
58
|
+
end
|
59
|
+
else
|
60
|
+
it 'returns the correct result', if: sessions_enabled? && replica_set? do
|
61
|
+
skip 'Test cannot be run on this server version' unless spec.server_version_satisfied?(client)
|
62
|
+
expect(results).to match_operation_result(test)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'Retryable writes integration tests' do
|
71
|
+
|
72
|
+
let(:primary) do
|
73
|
+
primary = client.cluster.next_primary
|
74
|
+
end
|
75
|
+
|
76
|
+
let(:primary_connection) do
|
77
|
+
connection = primary.pool.checkout
|
78
|
+
connection.connect!
|
79
|
+
primary.pool.checkin(connection)
|
80
|
+
connection
|
81
|
+
end
|
82
|
+
|
83
|
+
let(:primary_socket) do
|
84
|
+
primary_connection.send(:socket)
|
85
|
+
end
|
86
|
+
|
87
|
+
after do
|
88
|
+
authorized_collection.delete_many
|
89
|
+
end
|
90
|
+
|
91
|
+
shared_examples_for 'an operation that is retried' do
|
92
|
+
|
93
|
+
context 'when the operation fails on the first attempt' do
|
94
|
+
|
95
|
+
before do
|
96
|
+
# Note that for writes, server.connectable? is called, refreshing the socket
|
97
|
+
allow(primary).to receive(:connectable?).and_return(true)
|
98
|
+
expect(primary_socket).to receive(:write).and_raise(error)
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when the error is retryable' do
|
102
|
+
|
103
|
+
before do
|
104
|
+
expect(Mongo::Logger.logger).to receive(:warn).once.and_call_original
|
105
|
+
expect(client.cluster).to receive(:scan!)
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'when the error is a SocketError' do
|
109
|
+
|
110
|
+
let(:error) do
|
111
|
+
Mongo::Error::SocketError
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'retries writes' do
|
115
|
+
operation
|
116
|
+
expect(expectation).to eq(successful_retry_value)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'when the error is a SocketTimeoutError' do
|
121
|
+
|
122
|
+
let(:error) do
|
123
|
+
Mongo::Error::SocketTimeoutError
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'retries writes' do
|
127
|
+
operation
|
128
|
+
expect(expectation).to eq(successful_retry_value)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'when the error is a retryable OperationFailure' do
|
133
|
+
|
134
|
+
let(:error) do
|
135
|
+
Mongo::Error::OperationFailure.new('not master')
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'retries writes' do
|
139
|
+
operation
|
140
|
+
expect(expectation).to eq(successful_retry_value)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'when the error is not retryable' do
|
146
|
+
|
147
|
+
context 'when the error is a non-retryable OperationFailure' do
|
148
|
+
|
149
|
+
let(:error) do
|
150
|
+
Mongo::Error::OperationFailure.new('other error')
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'does not retry writes' do
|
154
|
+
expect {
|
155
|
+
operation
|
156
|
+
}.to raise_error(error)
|
157
|
+
expect(expectation).to eq(unsuccessful_retry_value)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'when the operation fails on the first attempt and again on the second attempt' do
|
164
|
+
|
165
|
+
before do
|
166
|
+
# Note that for writes, server.connectable? is called, refreshing the socket
|
167
|
+
allow(primary).to receive(:connectable?).and_return(true)
|
168
|
+
allow(primary_socket).to receive(:write).and_raise(error)
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'when the selected server does not support retryable writes' do
|
172
|
+
|
173
|
+
before do
|
174
|
+
legacy_primary = double('legacy primary', :'retry_writes?' => false)
|
175
|
+
allow(client.cluster).to receive(:next_primary).and_return(primary, legacy_primary)
|
176
|
+
expect(primary_socket).to receive(:write).and_raise(error)
|
177
|
+
end
|
178
|
+
|
179
|
+
context 'when the error is a SocketError' do
|
180
|
+
|
181
|
+
let(:error) do
|
182
|
+
Mongo::Error::SocketError
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'does not retry writes and raises the original error' do
|
186
|
+
expect {
|
187
|
+
operation
|
188
|
+
}.to raise_error(error)
|
189
|
+
expect(expectation).to eq(unsuccessful_retry_value)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context 'when the error is a SocketTimeoutError' do
|
194
|
+
|
195
|
+
let(:error) do
|
196
|
+
Mongo::Error::SocketTimeoutError
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'does not retry writes and raises the original error' do
|
200
|
+
expect {
|
201
|
+
operation
|
202
|
+
}.to raise_error(error)
|
203
|
+
expect(expectation).to eq(unsuccessful_retry_value)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'when the error is a retryable OperationFailure' do
|
208
|
+
|
209
|
+
let(:error) do
|
210
|
+
Mongo::Error::OperationFailure.new('not master')
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'does not retry writes and raises the original error' do
|
214
|
+
expect {
|
215
|
+
operation
|
216
|
+
}.to raise_error(error)
|
217
|
+
expect(expectation).to eq(unsuccessful_retry_value)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
[Mongo::Error::SocketError,
|
223
|
+
Mongo::Error::SocketTimeoutError,
|
224
|
+
Mongo::Error::OperationFailure.new('not master')].each do |retryable_error|
|
225
|
+
|
226
|
+
context "when the first error is a #{retryable_error}" do
|
227
|
+
|
228
|
+
let(:error) do
|
229
|
+
retryable_error
|
230
|
+
end
|
231
|
+
|
232
|
+
before do
|
233
|
+
bad_socket = primary_connection.address.socket(primary_connection.socket_timeout,
|
234
|
+
primary_connection.send(:ssl_options))
|
235
|
+
good_socket = primary_connection.address.socket(primary_connection.socket_timeout,
|
236
|
+
primary_connection.send(:ssl_options))
|
237
|
+
allow(bad_socket).to receive(:write).and_raise(second_error)
|
238
|
+
allow(primary_connection.address).to receive(:socket).and_return(bad_socket, good_socket)
|
239
|
+
end
|
240
|
+
|
241
|
+
context 'when the second error is a SocketError' do
|
242
|
+
|
243
|
+
let(:second_error) do
|
244
|
+
Mongo::Error::SocketError
|
245
|
+
end
|
246
|
+
|
247
|
+
before do
|
248
|
+
expect(client.cluster).to receive(:scan!).twice
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'does not retry writes and raises the second error' do
|
252
|
+
expect {
|
253
|
+
operation
|
254
|
+
}.to raise_error(second_error)
|
255
|
+
expect(expectation).to eq(unsuccessful_retry_value)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
context 'when the second error is a SocketTimeoutError' do
|
260
|
+
|
261
|
+
before do
|
262
|
+
expect(client.cluster).to receive(:scan!).twice
|
263
|
+
end
|
264
|
+
|
265
|
+
let(:second_error) do
|
266
|
+
Mongo::Error::SocketTimeoutError
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'does not retry writes and raises the second error' do
|
270
|
+
expect {
|
271
|
+
operation
|
272
|
+
}.to raise_error(second_error)
|
273
|
+
expect(expectation).to eq(unsuccessful_retry_value)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
context 'when the second error is a retryable OperationFailure' do
|
278
|
+
|
279
|
+
before do
|
280
|
+
expect(client.cluster).to receive(:scan!).twice
|
281
|
+
end
|
282
|
+
|
283
|
+
let(:second_error) do
|
284
|
+
Mongo::Error::OperationFailure.new('not master')
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'does not retry writes and raises the second error' do
|
288
|
+
expect {
|
289
|
+
operation
|
290
|
+
}.to raise_error(second_error)
|
291
|
+
expect(expectation).to eq(unsuccessful_retry_value)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
context 'when the second error is a non-retryable OperationFailure' do
|
296
|
+
|
297
|
+
before do
|
298
|
+
expect(client.cluster).to receive(:scan!).once
|
299
|
+
end
|
300
|
+
|
301
|
+
let(:second_error) do
|
302
|
+
Mongo::Error::OperationFailure.new('other error')
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'does not retry writes and raises the first error' do
|
306
|
+
expect {
|
307
|
+
operation
|
308
|
+
}.to raise_error(error)
|
309
|
+
expect(expectation).to eq(unsuccessful_retry_value)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
context 'when the second error is a another error' do
|
314
|
+
|
315
|
+
let(:second_error) do
|
316
|
+
StandardError
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'does not retry writes and raises the first error' do
|
320
|
+
expect {
|
321
|
+
operation
|
322
|
+
}.to raise_error(error)
|
323
|
+
expect(expectation).to eq(unsuccessful_retry_value)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
shared_examples_for 'an operation that is not retried' do
|
332
|
+
|
333
|
+
before do
|
334
|
+
# Note that for writes, server.connectable? is called, refreshing the socket
|
335
|
+
allow(primary).to receive(:connectable?).and_return(true)
|
336
|
+
expect(primary_socket).to receive(:write).and_raise(Mongo::Error::SocketError)
|
337
|
+
expect(client.cluster).not_to receive(:scan!)
|
338
|
+
end
|
339
|
+
|
340
|
+
it 'does not retry writes' do
|
341
|
+
expect {
|
342
|
+
operation
|
343
|
+
}.to raise_error(Mongo::Error::SocketError)
|
344
|
+
expect(expectation).to eq(unsuccessful_retry_value)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
shared_examples_for 'an operation that does not support retryable writes' do
|
349
|
+
|
350
|
+
let!(:client) do
|
351
|
+
authorized_client.with(retry_writes: true)
|
352
|
+
end
|
353
|
+
|
354
|
+
let!(:collection) do
|
355
|
+
client[TEST_COLL, write: WRITE_CONCERN]
|
356
|
+
end
|
357
|
+
|
358
|
+
before do
|
359
|
+
# Note that for writes, server.connectable? is called, refreshing the socket
|
360
|
+
allow(primary).to receive(:connectable?).and_return(true)
|
361
|
+
expect(primary_socket).to receive(:write).and_raise(Mongo::Error::SocketError)
|
362
|
+
expect(client.cluster).not_to receive(:scan!)
|
363
|
+
end
|
364
|
+
|
365
|
+
it 'does not retry writes' do
|
366
|
+
expect {
|
367
|
+
operation
|
368
|
+
}.to raise_error(Mongo::Error::SocketError)
|
369
|
+
expect(expectation).to eq(unsuccessful_retry_value)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
shared_examples_for 'supported retryable writes' do
|
374
|
+
|
375
|
+
context 'when the client has retry_writes set to true' do
|
376
|
+
|
377
|
+
let!(:client) do
|
378
|
+
authorized_client.with(retry_writes: true)
|
379
|
+
end
|
380
|
+
|
381
|
+
context 'when the collection has write concern acknowledged' do
|
382
|
+
|
383
|
+
let!(:collection) do
|
384
|
+
client[TEST_COLL, write: WRITE_CONCERN]
|
385
|
+
end
|
386
|
+
|
387
|
+
context 'when the server supports retryable writes' do
|
388
|
+
|
389
|
+
before do
|
390
|
+
allow(primary).to receive(:retry_writes?).and_return(true)
|
391
|
+
end
|
392
|
+
|
393
|
+
if standalone? && sessions_enabled?
|
394
|
+
it_behaves_like 'an operation that is not retried'
|
395
|
+
elsif sessions_enabled?
|
396
|
+
it_behaves_like 'an operation that is retried'
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
context 'when the server does not support retryable writes' do
|
401
|
+
|
402
|
+
before do
|
403
|
+
allow(primary).to receive(:retry_writes?).and_return(false)
|
404
|
+
end
|
405
|
+
|
406
|
+
it_behaves_like 'an operation that is not retried'
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
context 'when the collection has write concern unacknowledged' do
|
411
|
+
|
412
|
+
let!(:collection) do
|
413
|
+
client[TEST_COLL, write: { w: 0 }]
|
414
|
+
end
|
415
|
+
|
416
|
+
it_behaves_like 'an operation that is not retried'
|
417
|
+
end
|
418
|
+
|
419
|
+
context 'when the collection has write concern not set' do
|
420
|
+
|
421
|
+
let!(:collection) do
|
422
|
+
client[TEST_COLL]
|
423
|
+
end
|
424
|
+
|
425
|
+
context 'when the server supports retryable writes' do
|
426
|
+
|
427
|
+
before do
|
428
|
+
allow(primary).to receive(:retry_writes?).and_return(true)
|
429
|
+
end
|
430
|
+
|
431
|
+
if standalone? && sessions_enabled?
|
432
|
+
it_behaves_like 'an operation that is not retried'
|
433
|
+
elsif sessions_enabled?
|
434
|
+
it_behaves_like 'an operation that is retried'
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
context 'when the server does not support retryable writes' do
|
439
|
+
|
440
|
+
before do
|
441
|
+
allow(primary).to receive(:retry_writes?).and_return(false)
|
442
|
+
end
|
443
|
+
|
444
|
+
it_behaves_like 'an operation that is not retried'
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
context 'when the client has retry_writes set to false' do
|
450
|
+
|
451
|
+
let!(:client) do
|
452
|
+
authorized_client.with(retry_writes: false)
|
453
|
+
end
|
454
|
+
|
455
|
+
context 'when the collection has write concern acknowledged' do
|
456
|
+
|
457
|
+
let!(:collection) do
|
458
|
+
client[TEST_COLL, write: WRITE_CONCERN]
|
459
|
+
end
|
460
|
+
|
461
|
+
it_behaves_like 'an operation that is not retried'
|
462
|
+
end
|
463
|
+
|
464
|
+
context 'when the collection has write concern unacknowledged' do
|
465
|
+
|
466
|
+
let!(:collection) do
|
467
|
+
client[TEST_COLL, write: { w: 0 }]
|
468
|
+
end
|
469
|
+
|
470
|
+
it_behaves_like 'an operation that is not retried'
|
471
|
+
end
|
472
|
+
|
473
|
+
context 'when the collection has write concern not set' do
|
474
|
+
|
475
|
+
let!(:collection) do
|
476
|
+
client[TEST_COLL]
|
477
|
+
end
|
478
|
+
|
479
|
+
it_behaves_like 'an operation that is not retried'
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
context 'when the client has retry_writes not set' do
|
484
|
+
|
485
|
+
let!(:client) do
|
486
|
+
authorized_client
|
487
|
+
end
|
488
|
+
|
489
|
+
context 'when the collection has write concern acknowledged' do
|
490
|
+
|
491
|
+
let!(:collection) do
|
492
|
+
client[TEST_COLL, write: WRITE_CONCERN]
|
493
|
+
end
|
494
|
+
|
495
|
+
it_behaves_like 'an operation that is not retried'
|
496
|
+
end
|
497
|
+
|
498
|
+
context 'when the collection has write concern unacknowledged' do
|
499
|
+
|
500
|
+
let!(:collection) do
|
501
|
+
client[TEST_COLL, write: { w: 0 }]
|
502
|
+
end
|
503
|
+
|
504
|
+
it_behaves_like 'an operation that is not retried'
|
505
|
+
end
|
506
|
+
|
507
|
+
context 'when the collection has write concern not set' do
|
508
|
+
|
509
|
+
let!(:collection) do
|
510
|
+
client[TEST_COLL]
|
511
|
+
end
|
512
|
+
|
513
|
+
it_behaves_like 'an operation that is not retried'
|
514
|
+
end
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
context 'when the operation is insert_one' do
|
519
|
+
|
520
|
+
let(:operation) do
|
521
|
+
collection.insert_one(a:1)
|
522
|
+
end
|
523
|
+
|
524
|
+
let(:expectation) do
|
525
|
+
collection.find(a: 1).count
|
526
|
+
end
|
527
|
+
|
528
|
+
let(:successful_retry_value) do
|
529
|
+
1
|
530
|
+
end
|
531
|
+
|
532
|
+
let(:unsuccessful_retry_value) do
|
533
|
+
0
|
534
|
+
end
|
535
|
+
|
536
|
+
it_behaves_like 'supported retryable writes'
|
537
|
+
end
|
538
|
+
|
539
|
+
context 'when the operation is update_one' do
|
540
|
+
|
541
|
+
before do
|
542
|
+
# Account for when the collection has unacknowledged write concern and use authorized_collection here.
|
543
|
+
authorized_collection.insert_one(a:0)
|
544
|
+
end
|
545
|
+
|
546
|
+
let(:operation) do
|
547
|
+
collection.update_one({ a: 0 }, { '$set' => { a: 1 } })
|
548
|
+
end
|
549
|
+
|
550
|
+
let(:expectation) do
|
551
|
+
collection.find(a: 1).count
|
552
|
+
end
|
553
|
+
|
554
|
+
let(:successful_retry_value) do
|
555
|
+
1
|
556
|
+
end
|
557
|
+
|
558
|
+
let(:unsuccessful_retry_value) do
|
559
|
+
0
|
560
|
+
end
|
561
|
+
|
562
|
+
it_behaves_like 'supported retryable writes'
|
563
|
+
end
|
564
|
+
|
565
|
+
context 'when the operation is replace_one' do
|
566
|
+
|
567
|
+
before do
|
568
|
+
# Account for when the collection has unacknowledged write concern and use authorized_collection here.
|
569
|
+
authorized_collection.insert_one(a:0)
|
570
|
+
end
|
571
|
+
|
572
|
+
let(:operation) do
|
573
|
+
collection.replace_one({ a: 0 }, { a: 1 })
|
574
|
+
end
|
575
|
+
|
576
|
+
let(:expectation) do
|
577
|
+
collection.find(a: 1).count
|
578
|
+
end
|
579
|
+
|
580
|
+
let(:successful_retry_value) do
|
581
|
+
1
|
582
|
+
end
|
583
|
+
|
584
|
+
let(:unsuccessful_retry_value) do
|
585
|
+
0
|
586
|
+
end
|
587
|
+
|
588
|
+
it_behaves_like 'supported retryable writes'
|
589
|
+
end
|
590
|
+
|
591
|
+
context 'when the operation is delete_one' do
|
592
|
+
|
593
|
+
before do
|
594
|
+
# Account for when the collection has unacknowledged write concern and use authorized_collection here.
|
595
|
+
authorized_collection.insert_one(a:1)
|
596
|
+
end
|
597
|
+
|
598
|
+
let(:operation) do
|
599
|
+
collection.delete_one(a:1)
|
600
|
+
end
|
601
|
+
|
602
|
+
let(:expectation) do
|
603
|
+
collection.find(a: 1).count
|
604
|
+
end
|
605
|
+
|
606
|
+
let(:successful_retry_value) do
|
607
|
+
0
|
608
|
+
end
|
609
|
+
|
610
|
+
let(:unsuccessful_retry_value) do
|
611
|
+
1
|
612
|
+
end
|
613
|
+
|
614
|
+
it_behaves_like 'supported retryable writes'
|
615
|
+
end
|
616
|
+
|
617
|
+
context 'when the operation is find_one_and_update' do
|
618
|
+
|
619
|
+
before do
|
620
|
+
# Account for when the collection has unacknowledged write concern and use authorized_collection here.
|
621
|
+
authorized_collection.insert_one(a:0)
|
622
|
+
end
|
623
|
+
|
624
|
+
let(:operation) do
|
625
|
+
collection.find_one_and_update({ a: 0 }, { '$set' => { a: 1 } })
|
626
|
+
end
|
627
|
+
|
628
|
+
let(:expectation) do
|
629
|
+
collection.find(a: 1).count
|
630
|
+
end
|
631
|
+
|
632
|
+
let(:successful_retry_value) do
|
633
|
+
1
|
634
|
+
end
|
635
|
+
|
636
|
+
let(:unsuccessful_retry_value) do
|
637
|
+
0
|
638
|
+
end
|
639
|
+
|
640
|
+
it_behaves_like 'supported retryable writes'
|
641
|
+
end
|
642
|
+
|
643
|
+
context 'when the operation is find_one_and_replace' do
|
644
|
+
|
645
|
+
before do
|
646
|
+
# Account for when the collection has unacknowledged write concern and use authorized_collection here.
|
647
|
+
authorized_collection.insert_one(a:0)
|
648
|
+
end
|
649
|
+
|
650
|
+
let(:operation) do
|
651
|
+
collection.find_one_and_replace({ a: 0 }, { a: 3 })
|
652
|
+
end
|
653
|
+
|
654
|
+
let(:expectation) do
|
655
|
+
collection.find(a: 3).count
|
656
|
+
end
|
657
|
+
|
658
|
+
let(:successful_retry_value) do
|
659
|
+
1
|
660
|
+
end
|
661
|
+
|
662
|
+
let(:unsuccessful_retry_value) do
|
663
|
+
0
|
664
|
+
end
|
665
|
+
|
666
|
+
it_behaves_like 'supported retryable writes'
|
667
|
+
end
|
668
|
+
|
669
|
+
context 'when the operation is find_one_and_delete' do
|
670
|
+
|
671
|
+
before do
|
672
|
+
# Account for when the collection has unacknowledged write concern and use authorized_collection here.
|
673
|
+
authorized_collection.insert_one(a:1)
|
674
|
+
end
|
675
|
+
|
676
|
+
let(:operation) do
|
677
|
+
collection.find_one_and_delete({ a: 1 })
|
678
|
+
end
|
679
|
+
|
680
|
+
let(:expectation) do
|
681
|
+
collection.find(a: 1).count
|
682
|
+
end
|
683
|
+
|
684
|
+
let(:successful_retry_value) do
|
685
|
+
0
|
686
|
+
end
|
687
|
+
|
688
|
+
let(:unsuccessful_retry_value) do
|
689
|
+
1
|
690
|
+
end
|
691
|
+
|
692
|
+
it_behaves_like 'supported retryable writes'
|
693
|
+
end
|
694
|
+
|
695
|
+
context 'when the operation is update_many' do
|
696
|
+
|
697
|
+
before do
|
698
|
+
# Account for when the collection has unacknowledged write concern and use authorized_collection here.
|
699
|
+
authorized_collection.insert_one(a:0)
|
700
|
+
authorized_collection.insert_one(a:0)
|
701
|
+
end
|
702
|
+
|
703
|
+
let(:operation) do
|
704
|
+
collection.update_many({ a: 0 }, { '$set' => { a: 1 } })
|
705
|
+
end
|
706
|
+
|
707
|
+
let(:expectation) do
|
708
|
+
collection.find(a: 1).count
|
709
|
+
end
|
710
|
+
|
711
|
+
let(:unsuccessful_retry_value) do
|
712
|
+
0
|
713
|
+
end
|
714
|
+
|
715
|
+
it_behaves_like 'an operation that does not support retryable writes'
|
716
|
+
end
|
717
|
+
|
718
|
+
context 'when the operation is delete_many' do
|
719
|
+
|
720
|
+
before do
|
721
|
+
# Account for when the collection has unacknowledged write concern and use authorized_collection here.
|
722
|
+
authorized_collection.insert_one(a:1)
|
723
|
+
authorized_collection.insert_one(a:1)
|
724
|
+
end
|
725
|
+
|
726
|
+
let(:operation) do
|
727
|
+
collection.delete_many(a: 1)
|
728
|
+
end
|
729
|
+
|
730
|
+
let(:expectation) do
|
731
|
+
collection.find(a: 1).count
|
732
|
+
end
|
733
|
+
|
734
|
+
let(:unsuccessful_retry_value) do
|
735
|
+
2
|
736
|
+
end
|
737
|
+
|
738
|
+
it_behaves_like 'an operation that does not support retryable writes'
|
739
|
+
end
|
740
|
+
|
741
|
+
context 'when the operation is a bulk write' do
|
742
|
+
|
743
|
+
before do
|
744
|
+
# Account for when the collection has unacknowledged write concern and use authorized_collection here.
|
745
|
+
authorized_collection.insert_one(a: 1)
|
746
|
+
end
|
747
|
+
|
748
|
+
let(:operation) do
|
749
|
+
collection.bulk_write([{ delete_one: { filter: { a: 1 } } },
|
750
|
+
{ insert_one: { a: 1 } },
|
751
|
+
{ insert_one: { a: 1 } }])
|
752
|
+
end
|
753
|
+
|
754
|
+
let(:expectation) do
|
755
|
+
collection.find(a: 1).count
|
756
|
+
end
|
757
|
+
|
758
|
+
let(:successful_retry_value) do
|
759
|
+
2
|
760
|
+
end
|
761
|
+
|
762
|
+
let(:unsuccessful_retry_value) do
|
763
|
+
1
|
764
|
+
end
|
765
|
+
|
766
|
+
it_behaves_like 'supported retryable writes'
|
767
|
+
end
|
768
|
+
|
769
|
+
context 'when the operation is bulk write including delete_many' do
|
770
|
+
|
771
|
+
before do
|
772
|
+
# Account for when the collection has unacknowledged write concern and use authorized_collection here.
|
773
|
+
authorized_collection.insert_one(a:1)
|
774
|
+
authorized_collection.insert_one(a:1)
|
775
|
+
end
|
776
|
+
|
777
|
+
let(:operation) do
|
778
|
+
collection.bulk_write([{ delete_many: { filter: { a: 1 } } }])
|
779
|
+
end
|
780
|
+
|
781
|
+
let(:expectation) do
|
782
|
+
collection.find(a: 1).count
|
783
|
+
end
|
784
|
+
|
785
|
+
let(:unsuccessful_retry_value) do
|
786
|
+
2
|
787
|
+
end
|
788
|
+
|
789
|
+
it_behaves_like 'an operation that does not support retryable writes'
|
790
|
+
end
|
791
|
+
|
792
|
+
context 'when the operation is bulk write including update_many' do
|
793
|
+
|
794
|
+
before do
|
795
|
+
# Account for when the collection has unacknowledged write concern and use authorized_collection here.
|
796
|
+
authorized_collection.insert_one(a:0)
|
797
|
+
authorized_collection.insert_one(a:0)
|
798
|
+
end
|
799
|
+
|
800
|
+
let(:operation) do
|
801
|
+
collection.bulk_write([{ update_many: { filter: { a: 0 }, update: { a: 1 } } }])
|
802
|
+
end
|
803
|
+
|
804
|
+
let(:expectation) do
|
805
|
+
collection.find(a: 1).count
|
806
|
+
end
|
807
|
+
|
808
|
+
let(:unsuccessful_retry_value) do
|
809
|
+
0
|
810
|
+
end
|
811
|
+
|
812
|
+
it_behaves_like 'an operation that does not support retryable writes'
|
813
|
+
end
|
814
|
+
end
|
815
|
+
end
|