mongo 2.7.0 → 2.7.1

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.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +39 -14
  5. data/Rakefile +1 -0
  6. data/lib/mongo/auth.rb +4 -1
  7. data/lib/mongo/client.rb +4 -3
  8. data/lib/mongo/cluster.rb +1 -1
  9. data/lib/mongo/collection/view/readable.rb +5 -2
  10. data/lib/mongo/database.rb +1 -0
  11. data/lib/mongo/error/invalid_server_preference.rb +1 -0
  12. data/lib/mongo/error/operation_failure.rb +10 -0
  13. data/lib/mongo/error/parser.rb +10 -0
  14. data/lib/mongo/event.rb +1 -2
  15. data/lib/mongo/operation/result.rb +4 -1
  16. data/lib/mongo/operation/shared/executable_transaction_label.rb +3 -1
  17. data/lib/mongo/operation/shared/sessions_supported.rb +1 -1
  18. data/lib/mongo/protocol/msg.rb +1 -1
  19. data/lib/mongo/protocol/query.rb +2 -2
  20. data/lib/mongo/retryable.rb +20 -6
  21. data/lib/mongo/server.rb +6 -0
  22. data/lib/mongo/server/connection.rb +4 -4
  23. data/lib/mongo/server/monitor/connection.rb +9 -5
  24. data/lib/mongo/server_selector/selectable.rb +86 -32
  25. data/lib/mongo/session.rb +98 -15
  26. data/lib/mongo/version.rb +1 -1
  27. data/spec/README.md +85 -0
  28. data/spec/integration/bulk_insert_spec.rb +1 -1
  29. data/spec/integration/change_stream_examples_spec.rb +3 -1
  30. data/spec/integration/change_stream_spec.rb +10 -14
  31. data/spec/integration/client_construction_spec.rb +1 -0
  32. data/spec/integration/command_monitoring_spec.rb +37 -1
  33. data/spec/integration/command_spec.rb +141 -0
  34. data/spec/integration/connection_spec.rb +4 -2
  35. data/spec/integration/cursor_reaping_spec.rb +1 -1
  36. data/spec/integration/docs_examples_spec.rb +1 -1
  37. data/spec/integration/retryable_writes_spec.rb +33 -42
  38. data/spec/integration/server_description_spec.rb +3 -3
  39. data/spec/integration/server_selector_spec.rb +79 -0
  40. data/spec/lite_spec_helper.rb +4 -2
  41. data/spec/mongo/address_spec.rb +8 -0
  42. data/spec/mongo/auth/cr_spec.rb +5 -2
  43. data/spec/mongo/auth/invalid_mechanism_spec.rb +11 -0
  44. data/spec/mongo/auth/scram/conversation_spec.rb +3 -2
  45. data/spec/mongo/auth/scram/negotiation_spec.rb +1 -2
  46. data/spec/mongo/auth/scram_spec.rb +11 -6
  47. data/spec/mongo/auth/user/view_spec.rb +13 -6
  48. data/spec/mongo/bulk_write_spec.rb +81 -104
  49. data/spec/mongo/client_construction_spec.rb +18 -7
  50. data/spec/mongo/client_spec.rb +11 -7
  51. data/spec/mongo/cluster_spec.rb +30 -1
  52. data/spec/mongo/collection/view/aggregation_spec.rb +18 -10
  53. data/spec/mongo/collection/view/change_stream_spec.rb +28 -8
  54. data/spec/mongo/collection/view/map_reduce_spec.rb +24 -10
  55. data/spec/mongo/collection/view/readable_spec.rb +37 -19
  56. data/spec/mongo/collection/view/writable_spec.rb +64 -32
  57. data/spec/mongo/collection/view_spec.rb +4 -2
  58. data/spec/mongo/collection_spec.rb +163 -73
  59. data/spec/mongo/cursor_spec.rb +5 -2
  60. data/spec/mongo/database_spec.rb +41 -19
  61. data/spec/mongo/error/no_server_available_spec.rb +1 -1
  62. data/spec/mongo/error/parser_spec.rb +29 -0
  63. data/spec/mongo/grid/stream/write_spec.rb +2 -1
  64. data/spec/mongo/index/view_spec.rb +42 -24
  65. data/spec/mongo/operation/delete/op_msg_spec.rb +11 -7
  66. data/spec/mongo/operation/insert/op_msg_spec.rb +10 -6
  67. data/spec/mongo/operation/update/op_msg_spec.rb +10 -6
  68. data/spec/mongo/protocol/compressed_spec.rb +1 -1
  69. data/spec/mongo/protocol/msg_spec.rb +1 -1
  70. data/spec/mongo/server/app_metadata_spec.rb +2 -1
  71. data/spec/mongo/server/connection_auth_spec.rb +1 -1
  72. data/spec/mongo/server/monitor/connection_spec.rb +42 -0
  73. data/spec/mongo/server_selector_spec.rb +17 -0
  74. data/spec/mongo/server_spec.rb +110 -0
  75. data/spec/mongo/session/session_pool_spec.rb +1 -1
  76. data/spec/mongo/session_spec.rb +1 -1
  77. data/spec/mongo/session_transaction_spec.rb +162 -1
  78. data/spec/mongo/socket/ssl_spec.rb +14 -7
  79. data/spec/mongo/uri/srv_protocol_spec.rb +41 -34
  80. data/spec/spec_helper.rb +3 -191
  81. data/spec/spec_tests/change_streams_spec.rb +3 -6
  82. data/spec/spec_tests/data/transactions/abort.yml +3 -1
  83. data/spec/spec_tests/data/transactions/commit.yml +4 -3
  84. data/spec/spec_tests/data/transactions/error-labels.yml +17 -13
  85. data/spec/spec_tests/data/transactions/read-concern.yml +611 -0
  86. data/spec/spec_tests/data/transactions/retryable-commit.yml +126 -21
  87. data/spec/spec_tests/data/transactions_api/callback-aborts.yml +42 -39
  88. data/spec/spec_tests/data/transactions_api/callback-commits.yml +52 -50
  89. data/spec/spec_tests/data/transactions_api/callback-retry.yml +33 -31
  90. data/spec/spec_tests/data/transactions_api/commit-retry.yml +42 -39
  91. data/spec/spec_tests/data/transactions_api/commit-transienttransactionerror-4.2.yml +13 -12
  92. data/spec/spec_tests/data/transactions_api/commit-transienttransactionerror.yml +15 -26
  93. data/spec/spec_tests/data/transactions_api/commit-writeconcernerror.yml +25 -33
  94. data/spec/spec_tests/data/transactions_api/commit.yml +44 -42
  95. data/spec/spec_tests/data/transactions_api/transaction-options.yml +25 -23
  96. data/spec/spec_tests/max_staleness_spec.rb +2 -0
  97. data/spec/spec_tests/retryable_writes_spec.rb +2 -6
  98. data/spec/spec_tests/sdam_spec.rb +2 -0
  99. data/spec/spec_tests/server_selection_spec.rb +3 -0
  100. data/spec/spec_tests/transactions_api_spec.rb +7 -1
  101. data/spec/spec_tests/transactions_spec.rb +6 -0
  102. data/spec/spec_tests/uri_options_spec.rb +4 -26
  103. data/spec/support/certificates/ca.pem +21 -16
  104. data/spec/support/certificates/client.pem +90 -90
  105. data/spec/support/certificates/client_cert.pem +21 -20
  106. data/spec/support/certificates/client_key.pem +27 -28
  107. data/spec/support/certificates/client_key_encrypted.pem +26 -26
  108. data/spec/support/certificates/crl.pem +10 -8
  109. data/spec/support/certificates/crl_client_revoked.pem +11 -10
  110. data/spec/support/certificates/server.pem +48 -33
  111. data/spec/support/change_streams.rb +12 -32
  112. data/spec/support/client_registry.rb +12 -1
  113. data/spec/support/cluster_config.rb +48 -2
  114. data/spec/support/common_shortcuts.rb +73 -7
  115. data/spec/support/connection_string.rb +0 -3
  116. data/spec/support/constraints.rb +87 -22
  117. data/spec/support/crud.rb +2 -1
  118. data/spec/support/shared/server_selector.rb +0 -28
  119. data/spec/support/shared/session.rb +25 -14
  120. data/spec/support/transactions.rb +4 -8
  121. data/spec/support/transactions/operation.rb +26 -4
  122. data/spec/support/transactions/verifier.rb +5 -2
  123. metadata +496 -488
  124. metadata.gz.sig +5 -2
  125. data/spec/support/certificates/password_protected.pem +0 -51
@@ -89,7 +89,7 @@ describe Mongo::Operation::Insert::OpMsg do
89
89
  describe '#message' do
90
90
 
91
91
  context 'when the server supports OP_MSG' do
92
- min_server_version '3.6'
92
+ min_server_fcv '3.6'
93
93
 
94
94
  let(:documents) do
95
95
  [ { foo: 1 }, { bar: 2 }]
@@ -119,7 +119,7 @@ describe Mongo::Operation::Insert::OpMsg do
119
119
  end
120
120
 
121
121
  context 'when the topology is replica set or sharded' do
122
- min_server_version '3.6'
122
+ min_server_fcv '3.6'
123
123
  require_topology :replica_set, :sharded
124
124
 
125
125
  let(:expected_global_args) do
@@ -136,7 +136,9 @@ describe Mongo::Operation::Insert::OpMsg do
136
136
  end
137
137
  end
138
138
 
139
- context 'when the topology is standalone', if: standalone? && sessions_enabled? do
139
+ context 'when the topology is standalone' do
140
+ min_server_fcv '3.6'
141
+ require_topology :single
140
142
 
141
143
  let(:expected_global_args) do
142
144
  global_args
@@ -192,7 +194,7 @@ describe Mongo::Operation::Insert::OpMsg do
192
194
  end
193
195
 
194
196
  context 'when the topology is replica set or sharded' do
195
- min_server_version '3.6'
197
+ min_server_fcv '3.6'
196
198
  require_topology :replica_set, :sharded
197
199
 
198
200
  let(:expected_global_args) do
@@ -212,7 +214,9 @@ describe Mongo::Operation::Insert::OpMsg do
212
214
  end
213
215
  end
214
216
 
215
- context 'when the topology is standalone', if: standalone? && sessions_enabled? do
217
+ context 'when the topology is standalone' do
218
+ min_server_fcv '3.6'
219
+ require_topology :single
216
220
 
217
221
  let(:expected_global_args) do
218
222
  global_args.dup.tap do |args|
@@ -232,7 +236,7 @@ describe Mongo::Operation::Insert::OpMsg do
232
236
  end
233
237
 
234
238
  context 'when the session is explicit' do
235
- min_server_version '3.6'
239
+ min_server_fcv '3.6'
236
240
  require_topology :replica_set, :sharded
237
241
 
238
242
  let(:session) do
@@ -95,7 +95,7 @@ describe Mongo::Operation::Update::OpMsg do
95
95
  describe '#message' do
96
96
 
97
97
  context 'when the server supports OP_MSG' do
98
- min_server_version '3.6'
98
+ min_server_fcv '3.6'
99
99
 
100
100
  let(:global_args) do
101
101
  {
@@ -121,7 +121,7 @@ describe Mongo::Operation::Update::OpMsg do
121
121
  end
122
122
 
123
123
  context 'when the topology is replica set or sharded' do
124
- min_server_version '3.6'
124
+ min_server_fcv '3.6'
125
125
  require_topology :replica_set, :sharded
126
126
 
127
127
  let(:expected_global_args) do
@@ -135,7 +135,9 @@ describe Mongo::Operation::Update::OpMsg do
135
135
  end
136
136
  end
137
137
 
138
- context 'when the topology is standalone', if: standalone? && sessions_enabled? do
138
+ context 'when the topology is standalone' do
139
+ min_server_fcv '3.6'
140
+ require_topology :single
139
141
 
140
142
  let(:expected_global_args) do
141
143
  global_args
@@ -184,7 +186,7 @@ describe Mongo::Operation::Update::OpMsg do
184
186
  end
185
187
 
186
188
  context 'when the topology is replica set or sharded' do
187
- min_server_version '3.6'
189
+ min_server_fcv '3.6'
188
190
  require_topology :replica_set, :sharded
189
191
 
190
192
  let(:expected_global_args) do
@@ -201,7 +203,9 @@ describe Mongo::Operation::Update::OpMsg do
201
203
  end
202
204
  end
203
205
 
204
- context 'when the topology is standalone', if: standalone? && sessions_enabled? do
206
+ context 'when the topology is standalone' do
207
+ min_server_fcv '3.6'
208
+ require_topology :single
205
209
 
206
210
  let(:expected_global_args) do
207
211
  global_args.dup.tap do |args|
@@ -218,7 +222,7 @@ describe Mongo::Operation::Update::OpMsg do
218
222
  end
219
223
 
220
224
  context 'when the session is explicit' do
221
- min_server_version '3.6'
225
+ min_server_fcv '3.6'
222
226
  require_topology :replica_set, :sharded
223
227
 
224
228
  let(:session) do
@@ -53,7 +53,7 @@ describe Mongo::Protocol::Compressed do
53
53
  end
54
54
 
55
55
  context 'when the original message is not replyable' do
56
- min_server_version '3.6'
56
+ min_server_fcv '3.6'
57
57
 
58
58
  let(:original_message) do
59
59
  Mongo::Protocol::Msg.new([:more_to_come], {}, { ping: 1 })
@@ -441,7 +441,7 @@ describe Mongo::Protocol::Msg do
441
441
  context 'when the msg only contains a payload type 0' do
442
442
 
443
443
  it 'creates a payload with the command' do
444
- expect(message.payload[:command_name]).to eq(:ping)
444
+ expect(message.payload[:command_name]).to eq('ping')
445
445
  expect(message.payload[:database_name]).to eq(SpecConfig.instance.test_db)
446
446
  expect(message.payload[:command]).to eq('ping' => 1)
447
447
  expect(message.payload[:request_id]).to eq(message.request_id)
@@ -106,12 +106,13 @@ describe Mongo::Server::AppMetadata do
106
106
  end
107
107
 
108
108
  context 'when the driver info is too long' do
109
+ require_no_compression
109
110
 
110
111
  before do
111
112
  allow(app_metadata).to receive(:driver_doc).and_return('x'*500)
112
113
  end
113
114
 
114
- it 'truncates the document to be just an ismaster command and the compressors', unless: compression_enabled? do
115
+ it 'truncates the document to be just an ismaster command and the compressors' do
115
116
  # Because we sometimes request that the server provide a list of valid auth mechanisms for
116
117
  # the user, we need to conditionally add the length of that metadata to the expected
117
118
  # length of the isMaster document.
@@ -55,7 +55,7 @@ describe Mongo::Server::Connection, retry: 3 do
55
55
  end
56
56
 
57
57
  context 'when the ismaster response includes saslSupportedMechs' do
58
- min_server_version '4.0'
58
+ min_server_fcv '4.0'
59
59
 
60
60
  let(:server_options) do
61
61
  SpecConfig.instance.test_options.merge(
@@ -116,4 +116,46 @@ describe Mongo::Server::Monitor::Connection do
116
116
  end
117
117
  end
118
118
  end
119
+
120
+ describe '#ismaster' do
121
+ let(:options) do
122
+ SpecConfig.instance.test_options
123
+ end
124
+
125
+ let(:result) { connection.ismaster }
126
+
127
+ it 'returns a hash' do
128
+ expect(result).to be_a(Hash)
129
+ end
130
+
131
+ it 'is successful' do
132
+ expect(result['ok']).to eq(1.0)
133
+ end
134
+
135
+ context 'network error during ismaster' do
136
+ let(:result) do
137
+ connection
138
+
139
+ socket = connection.send(:socket).send(:socket)
140
+ expect([Socket, OpenSSL::SSL::SSLSocket]).to include(socket.class)
141
+
142
+ expect(socket).to receive(:write).and_raise(IOError)
143
+ expect(socket).to receive(:write).and_call_original
144
+
145
+ connection.ismaster
146
+ end
147
+
148
+ it 'retries ismaster and is successful' do
149
+ expect(result).to be_a(Hash)
150
+ expect(result['ok']).to eq(1.0)
151
+ end
152
+
153
+ it 'logs the retry' do
154
+ expect(Mongo::Logger.logger).to receive(:warn) do |msg|
155
+ expect(msg).to match(/Retrying ismaster on #{connection.address}/)
156
+ end
157
+ expect(result).to be_a(Hash)
158
+ end
159
+ end
160
+ end
119
161
  end
@@ -206,9 +206,13 @@ describe Mongo::ServerSelector do
206
206
 
207
207
  let(:cluster) do
208
208
  double('cluster').tap do |c|
209
+ allow(c).to receive(:connected?).and_return(true)
209
210
  allow(c).to receive(:summary)
210
211
  allow(c).to receive(:topology).and_return(topology)
211
212
  allow(c).to receive(:servers).and_return(servers)
213
+ allow(c).to receive(:servers_list).and_return(servers)
214
+ allow(c).to receive(:addresses).and_return(servers.map(&:address))
215
+ allow(c).to receive(:replica_set?).and_return(true)
212
216
  allow(c).to receive(:single?).and_return(false)
213
217
  allow(c).to receive(:sharded?).and_return(false)
214
218
  allow(c).to receive(:unknown?).and_return(false)
@@ -238,9 +242,13 @@ describe Mongo::ServerSelector do
238
242
 
239
243
  let(:cluster) do
240
244
  double('cluster').tap do |c|
245
+ allow(c).to receive(:connected?).and_return(true)
241
246
  allow(c).to receive(:summary)
242
247
  allow(c).to receive(:topology).and_return(topology)
243
248
  allow(c).to receive(:servers).and_return(servers)
249
+ allow(c).to receive(:servers_list).and_return(servers)
250
+ allow(c).to receive(:addresses).and_return(servers.map(&:address))
251
+ allow(c).to receive(:replica_set?).and_return(true)
244
252
  allow(c).to receive(:single?).and_return(false)
245
253
  allow(c).to receive(:sharded?).and_return(false)
246
254
  allow(c).to receive(:unknown?).and_return(false)
@@ -284,9 +292,12 @@ describe Mongo::ServerSelector do
284
292
 
285
293
  let(:cluster) do
286
294
  double('cluster').tap do |c|
295
+ allow(c).to receive(:connected?).and_return(true)
287
296
  allow(c).to receive(:summary)
288
297
  allow(c).to receive(:topology).and_return(topology)
289
298
  allow(c).to receive(:servers).and_return(servers)
299
+ allow(c).to receive(:addresses).and_return(servers.map(&:address))
300
+ allow(c).to receive(:replica_set?).and_return(true)
290
301
  allow(c).to receive(:single?).and_return(false)
291
302
  allow(c).to receive(:sharded?).and_return(false)
292
303
  allow(c).to receive(:unknown?).and_return(false)
@@ -311,9 +322,12 @@ describe Mongo::ServerSelector do
311
322
 
312
323
  let(:cluster) do
313
324
  double('cluster').tap do |c|
325
+ allow(c).to receive(:connected?).and_return(true)
314
326
  allow(c).to receive(:summary)
315
327
  allow(c).to receive(:topology).and_return(topology)
316
328
  allow(c).to receive(:servers).and_return([server])
329
+ allow(c).to receive(:addresses).and_return([server.address])
330
+ allow(c).to receive(:replica_set?).and_return(true)
317
331
  allow(c).to receive(:single?).and_return(false)
318
332
  allow(c).to receive(:sharded?).and_return(false)
319
333
  allow(c).to receive(:unknown?).and_return(false)
@@ -348,9 +362,12 @@ describe Mongo::ServerSelector do
348
362
 
349
363
  let(:cluster) do
350
364
  double('cluster').tap do |c|
365
+ allow(c).to receive(:connected?).and_return(true)
351
366
  allow(c).to receive(:summary)
352
367
  allow(c).to receive(:topology).and_return(topology)
353
368
  allow(c).to receive(:servers).and_return(servers)
369
+ allow(c).to receive(:addresses).and_return([])
370
+ allow(c).to receive(:replica_set?).and_return(!single && !sharded)
354
371
  allow(c).to receive(:single?).and_return(single)
355
372
  allow(c).to receive(:sharded?).and_return(sharded)
356
373
  allow(c).to receive(:unknown?).and_return(false)
@@ -265,4 +265,114 @@ describe Mongo::Server do
265
265
  end
266
266
  end
267
267
  end
268
+
269
+ describe '#summary' do
270
+ context 'server is primary' do
271
+ let(:server) do
272
+ make_server(:primary)
273
+ end
274
+
275
+ before do
276
+ expect(server).to be_primary
277
+ end
278
+
279
+ it 'includes its status' do
280
+ expect(server.summary).to match(/PRIMARY/)
281
+ end
282
+
283
+ it 'includes replica set name' do
284
+ expect(server.summary).to match(/replica_set=mongodb_set/)
285
+ end
286
+ end
287
+
288
+ context 'server is secondary' do
289
+ let(:server) do
290
+ make_server(:secondary)
291
+ end
292
+
293
+ before do
294
+ expect(server).to be_secondary
295
+ end
296
+
297
+ it 'includes its status' do
298
+ expect(server.summary).to match(/SECONDARY/)
299
+ end
300
+
301
+ it 'includes replica set name' do
302
+ expect(server.summary).to match(/replica_set=mongodb_set/)
303
+ end
304
+ end
305
+
306
+ context 'server is arbiter' do
307
+ let(:server) do
308
+ make_server(:arbiter)
309
+ end
310
+
311
+ before do
312
+ expect(server).to be_arbiter
313
+ end
314
+
315
+ it 'includes its status' do
316
+ expect(server.summary).to match(/ARBITER/)
317
+ end
318
+
319
+ it 'includes replica set name' do
320
+ expect(server.summary).to match(/replica_set=mongodb_set/)
321
+ end
322
+ end
323
+
324
+ context 'server is ghost' do
325
+ let(:server) do
326
+ make_server(:ghost)
327
+ end
328
+
329
+ before do
330
+ expect(server).to be_ghost
331
+ end
332
+
333
+ it 'includes its status' do
334
+ expect(server.summary).to match(/GHOST/)
335
+ end
336
+
337
+ it 'does not include replica set name' do
338
+ expect(server.summary).not_to include('replica_set')
339
+ end
340
+ end
341
+
342
+ context 'server is other' do
343
+ let(:server) do
344
+ make_server(:other)
345
+ end
346
+
347
+ before do
348
+ expect(server).to be_other
349
+ end
350
+
351
+ it 'includes its status' do
352
+ expect(server.summary).to match(/OTHER/)
353
+ end
354
+
355
+ it 'includes replica set name' do
356
+ expect(server.summary).to match(/replica_set=mongodb_set/)
357
+ end
358
+ end
359
+
360
+ context 'server is unknown' do
361
+ let(:server) do
362
+ described_class.new(address, cluster, monitoring, listeners, SpecConfig.instance.test_options)
363
+ end
364
+
365
+ before do
366
+ expect(server).to be_unknown
367
+ end
368
+
369
+ it 'includes unknown status' do
370
+ expect(server.summary).to match(/UNKNOWN/)
371
+ end
372
+
373
+ it 'does not include replica set name' do
374
+ expect(server.summary).not_to include('replica_set')
375
+ end
376
+ end
377
+ end
268
378
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Mongo::Session::SessionPool do
4
- min_server_version '3.6'
4
+ min_server_fcv '3.6'
5
5
  require_topology :replica_set, :sharded
6
6
 
7
7
  let(:cluster) do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Mongo::Session do
4
- min_server_version '3.6'
4
+ min_server_fcv '3.6'
5
5
  require_topology :replica_set, :sharded
6
6
 
7
7
  let(:session) do
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  class SessionTransactionSpecError < StandardError; end
4
4
 
5
5
  describe Mongo::Session do
6
- min_server_version '4.0'
6
+ min_server_fcv '4.0'
7
7
  require_topology :replica_set, :sharded
8
8
 
9
9
  let(:session) do
@@ -22,6 +22,54 @@ describe Mongo::Session do
22
22
  collection.delete_many
23
23
  end
24
24
 
25
+ describe '#abort_transaction' do
26
+ require_topology :replica_set
27
+
28
+ context 'when a non-Mongo error is raised' do
29
+ before do
30
+ collection.insert_one({foo: 1})
31
+ end
32
+
33
+ it 'propagates the exception and sets state to transaction aborted' do
34
+ session.start_transaction
35
+ collection.insert_one({foo: 1}, session: session)
36
+ expect(session).to receive(:write_with_retry).and_raise(SessionTransactionSpecError)
37
+ expect do
38
+ session.abort_transaction
39
+ end.to raise_error(SessionTransactionSpecError)
40
+ expect(session.send(:within_states?, Mongo::Session::TRANSACTION_ABORTED_STATE)).to be true
41
+
42
+ # Since we failed abort_transaction call, the transaction is still
43
+ # outstanding. It will cause subsequent tests to stall until it times
44
+ # out on the server side. End the session to force the server
45
+ # to close the transaction.
46
+ kill_all_server_sessions
47
+ end
48
+ end
49
+
50
+ context 'when a Mongo error is raised' do
51
+ before do
52
+ collection.insert_one({foo: 1})
53
+ end
54
+
55
+ it 'swallows the exception and sets state to transaction aborted' do
56
+ session.start_transaction
57
+ collection.insert_one({foo: 1}, session: session)
58
+ expect(session).to receive(:write_with_retry).and_raise(Mongo::Error::SocketError)
59
+ expect do
60
+ session.abort_transaction
61
+ end.not_to raise_error
62
+ expect(session.send(:within_states?, Mongo::Session::TRANSACTION_ABORTED_STATE)).to be true
63
+
64
+ # Since we failed abort_transaction call, the transaction is still
65
+ # outstanding. It will cause subsequent tests to stall until it times
66
+ # out on the server side. End the session to force the server
67
+ # to close the transaction.
68
+ kill_all_server_sessions
69
+ end
70
+ end
71
+ end
72
+
25
73
  describe '#with_transaction' do
26
74
  context 'callback successful' do
27
75
  it 'commits' do
@@ -60,5 +108,118 @@ describe Mongo::Session do
60
108
  expect(rv).to eq(42)
61
109
  end
62
110
  end
111
+
112
+ context 'timeout with callback raising TransientTransactionError' do
113
+ max_example_run_time 7
114
+
115
+ after do
116
+ Timecop.return
117
+ end
118
+
119
+ it 'times out' do
120
+ warp = Time.now + 200
121
+ entered = false
122
+
123
+ Thread.new do
124
+ until entered
125
+ sleep 0.1
126
+ end
127
+ Timecop.travel warp
128
+ end
129
+
130
+ expect do
131
+ session.with_transaction do
132
+ entered = true
133
+
134
+ # This sleep is to give the interrupting thread a chance to run,
135
+ # it significantly affects how much time is burned in this
136
+ # looping thread
137
+ sleep 0.1
138
+
139
+ exc = Mongo::Error::OperationFailure.new('timeout test')
140
+ exc.send(:add_label, Mongo::Error::TRANSIENT_TRANSACTION_ERROR_LABEL)
141
+ raise exc
142
+ end
143
+ end.to raise_error(Mongo::Error::OperationFailure, 'timeout test')
144
+ end
145
+ end
146
+
147
+ %w(UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL TRANSIENT_TRANSACTION_ERROR_LABEL).each do |label|
148
+ context "timeout with commit raising with #{label}" do
149
+ max_example_run_time 7
150
+
151
+ after do
152
+ Timecop.return
153
+ end
154
+
155
+ before do
156
+ # create collection if it does not exist
157
+ collection.insert_one(a: 1)
158
+ end
159
+
160
+ it 'times out' do
161
+ warp = Time.now + 200
162
+ entered = false
163
+
164
+ Thread.new do
165
+ until entered
166
+ sleep 0.1
167
+ end
168
+ Timecop.travel warp
169
+ end
170
+
171
+ exc = Mongo::Error::OperationFailure.new('timeout test')
172
+ exc.send(:add_label, Mongo::Error.const_get(label))
173
+
174
+ expect(session).to receive(:commit_transaction).and_raise(exc).at_least(:once)
175
+
176
+ expect do
177
+ session.with_transaction do
178
+ entered = true
179
+
180
+ # This sleep is to give the interrupting thread a chance to run,
181
+ # it significantly affects how much time is burned in this
182
+ # looping thread
183
+ sleep 0.1
184
+
185
+ collection.insert_one(a: 2)
186
+ end
187
+ end.to raise_error(Mongo::Error::OperationFailure, 'timeout test')
188
+ end
189
+ end
190
+ end
191
+
192
+ context 'callback breaks out of with_tx loop' do
193
+ it 'aborts transaction' do
194
+ expect(session).to receive(:start_transaction).and_call_original
195
+ expect(session).to receive(:abort_transaction).and_call_original
196
+ expect(session).to receive(:log_warn).and_call_original
197
+
198
+ session.with_transaction do
199
+ break
200
+ end
201
+ end
202
+ end
203
+
204
+ context 'application timeout around with_tx' do
205
+ it 'keeps session in a working state' do
206
+ session
207
+ collection.insert_one(a: 1)
208
+
209
+ expect do
210
+ Timeout.timeout(1, SessionTransactionSpecError) do
211
+ session.with_transaction do
212
+ sleep 2
213
+ end
214
+ end
215
+ end.to raise_error(SessionTransactionSpecError)
216
+
217
+ session.with_transaction do
218
+ collection.insert_one(timeout_around_with_tx: 2)
219
+ end
220
+
221
+ expect(collection.find(timeout_around_with_tx: 2).first).not_to be nil
222
+ end
223
+ end
63
224
  end
64
225
  end