mongo 2.10.0.rc0 → 2.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08673c7408cfc3fce9f682577a1156fe312c5f6806b4ea4ecef3d946dd7b0d3b'
4
- data.tar.gz: 4811269ce3f01bd7508de797bf32c68bc7f6e858ca3949d12fa604dd011e41ab
3
+ metadata.gz: 1a1e6b0cb6b8548601a5ad4c4758361f9815d65750b52fef6b2393eb09ab5ae4
4
+ data.tar.gz: '084a8d5a64bba3ffaab853919918f9cb214754edbaec4ac22eeaee5e9954f39b'
5
5
  SHA512:
6
- metadata.gz: fdfddf7f78f3eabb7d2c0249680ad0bacbfac5425f2ed8719a9dad96fc308cb969ee51edf290df7394a161819bb9c55f5180c765a2edf3f03ef3976c04f2be78
7
- data.tar.gz: fe9b0f8aa59ce594ffef9da66a107e99f2d41d50ba29ae8fd318f15e53c9b574411d73c5d12450bdf4be7b6490820c847367a55157acdbbd8f59124cb74c075f
6
+ metadata.gz: 19fc6811951a53cfef86d647849337fb56014e97d2cf9aec0ea0de58f3525632a96e04c1f5a6ecd4f6de8f7a853faded939e59f716d9fa4a6c9ac40a7e742ce9
7
+ data.tar.gz: '08ff4b626997aa23fb9db94893818e2326cc2c13032ea4dabf512628b436597f13a2c69a2693d57fd58d60208c384660b4c811ca98b8aeb1a47994b212a0dbd5'
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -252,6 +252,18 @@ module Mongo
252
252
  def max_time_ms_expired?
253
253
  code == 50 # MaxTimeMSExpired
254
254
  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.10.0
263
+ def unsupported_retryable_write?
264
+ # code 20 is IllegalOperation
265
+ code == 20 && message.start_with?("Transaction numbers")
266
+ end
255
267
  end
256
268
  end
257
269
  end
@@ -215,9 +215,12 @@ module Mongo
215
215
  end
216
216
  retry_write(e, session, txn_num, &block)
217
217
  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?
219
221
  raise
220
222
  end
223
+
221
224
  retry_write(e, session, txn_num, &block)
222
225
  end
223
226
  end
@@ -403,5 +406,15 @@ module Mongo
403
406
  end
404
407
  Logger.logger.warn "#{message} due to: #{e.class.name} #{e.message}"
405
408
  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
406
419
  end
407
420
  end
@@ -20,6 +20,13 @@ module Mongo
20
20
  # Represents a description of the server, populated by the result of the
21
21
  # ismaster command.
22
22
  #
23
+ # Note: Unknown servers do not have wire versions, but for legacy reasons
24
+ # we return 0 for min_wire_version and max_wire_version of any server that does
25
+ # not have them. Presently the driver sometimes constructs commands when the
26
+ # server is unknown, so references to min_wire_version and max_wire_version
27
+ # should not be nil. When driver behavior is changed
28
+ # (https://jira.mongodb.org/browse/RUBY-1805), this may no longer be necessary.
29
+ #
23
30
  # @since 2.0.0
24
31
  class Description
25
32
 
@@ -192,9 +199,7 @@ module Mongo
192
199
  def initialize(address, config = {}, average_round_trip_time = nil)
193
200
  @address = address
194
201
  @config = config
195
- unless unknown?
196
- @features = Features.new(wire_versions, me || @address.to_s)
197
- end
202
+ @features = Features.new(wire_versions, me || @address.to_s)
198
203
  @average_round_trip_time = average_round_trip_time
199
204
  @last_update_time = Time.now.dup.freeze
200
205
 
@@ -216,9 +221,6 @@ module Mongo
216
221
 
217
222
  # @return [ Features ] features The features for the server.
218
223
  def features
219
- if unknown?
220
- return Features.new(0..0, address.to_s)
221
- end
222
224
  @features
223
225
  end
224
226
 
@@ -333,7 +335,7 @@ module Mongo
333
335
  config[MAX_WRITE_BATCH_SIZE] || DEFAULT_MAX_WRITE_BATCH_SIZE
334
336
  end
335
337
 
336
- # Get the maximum wire version.
338
+ # Get the maximum wire version. Defaults to zero.
337
339
  #
338
340
  # @example Get the max wire version.
339
341
  # description.max_wire_version
@@ -342,10 +344,10 @@ module Mongo
342
344
  #
343
345
  # @since 2.0.0
344
346
  def max_wire_version
345
- config[MAX_WIRE_VERSION]
347
+ config[MAX_WIRE_VERSION] || 0
346
348
  end
347
349
 
348
- # Get the minimum wire version.
350
+ # Get the minimum wire version. Defaults to zero.
349
351
  #
350
352
  # @example Get the min wire version.
351
353
  # description.min_wire_version
@@ -354,7 +356,7 @@ module Mongo
354
356
  #
355
357
  # @since 2.0.0
356
358
  def min_wire_version
357
- config[MIN_WIRE_VERSION]
359
+ config[MIN_WIRE_VERSION] || 0
358
360
  end
359
361
 
360
362
  # Get the me field value.
@@ -299,7 +299,7 @@ module Mongo
299
299
  if within_states?(TRANSACTION_IN_PROGRESS_STATE)
300
300
  begin
301
301
  abort_transaction
302
- rescue Mongo::Error
302
+ rescue Mongo::Error, Error::AuthError
303
303
  end
304
304
  end
305
305
  @client.cluster.session_pool.checkin(@server_session)
@@ -429,6 +429,9 @@ module Mongo
429
429
  transaction_in_progress = false
430
430
  raise
431
431
  end
432
+ rescue Error::AuthError
433
+ transaction_in_progress = false
434
+ raise
432
435
  end
433
436
  end
434
437
  end
@@ -117,7 +117,7 @@ module Mongo
117
117
  :selector => {endSessions: @queue.shift(10_000).collect { |s| s.session_id }},
118
118
  :db_name => Database::ADMIN).execute(server)
119
119
  end
120
- rescue Mongo::Error
120
+ rescue Mongo::Error, Error::AuthError
121
121
  end
122
122
 
123
123
  private
@@ -49,6 +49,9 @@ module Mongo
49
49
  # @return [ Hash ] The options.
50
50
  attr_reader :options
51
51
 
52
+ # @return [ Float ] timeout The socket timeout.
53
+ attr_reader :timeout
54
+
52
55
  # Is the socket connection alive?
53
56
  #
54
57
  # @example Is the socket alive?
@@ -123,10 +126,14 @@ module Mongo
123
126
  def read(length)
124
127
  handle_errors do
125
128
  data = read_from_socket(length)
126
- raise IOError unless (data.length > 0 || length == 0)
129
+ unless (data.length > 0 || length == 0)
130
+ raise IOError, "Expected to read > 0 bytes but read 0 bytes"
131
+ end
127
132
  while data.length < length
128
133
  chunk = read_from_socket(length - data.length)
129
- raise IOError unless (chunk.length > 0 || length == 0)
134
+ unless (chunk.length > 0 || length == 0)
135
+ raise IOError, "Expected to read > 0 bytes but read 0 bytes"
136
+ end
130
137
  data << chunk
131
138
  end
132
139
  data
@@ -185,7 +192,9 @@ module Mongo
185
192
  return ''.force_encoding('BINARY')
186
193
  end
187
194
 
188
- deadline = (Time.now + timeout) if timeout
195
+ if _timeout = self.timeout
196
+ deadline = Time.now + _timeout
197
+ end
189
198
 
190
199
  # We want to have a fixed and reasonably small size buffer for reads
191
200
  # because, for example, OpenSSL reads in 16 kb chunks max.
@@ -232,10 +241,22 @@ module Mongo
232
241
  data[retrieved, chunk.length] = chunk
233
242
  retrieved += chunk.length
234
243
  end
235
- rescue IO::WaitReadable
236
- select_timeout = (deadline - Time.now) if deadline
237
- if (select_timeout && select_timeout <= 0) || !Kernel::select([@socket], nil, [@socket], select_timeout)
238
- raise Timeout::Error.new("Took more than #{timeout} seconds to receive data.")
244
+ # As explained in https://ruby-doc.com/core-trunk/IO.html#method-c-select,
245
+ # reading from a TLS socket may require writing which may raise WaitWritable
246
+ rescue IO::WaitReadable, IO::WaitWritable => exc
247
+ if deadline
248
+ select_timeout = deadline - Time.now
249
+ if select_timeout <= 0
250
+ raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data"
251
+ end
252
+ end
253
+ if exc.is_a?(IO::WaitReadable)
254
+ select_args = [[@socket], nil, [@socket], select_timeout]
255
+ else
256
+ select_args = [nil, [@socket], [@socket], select_timeout]
257
+ end
258
+ unless Kernel::select(*select_args)
259
+ raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data"
239
260
  end
240
261
  retry
241
262
  end
@@ -33,9 +33,6 @@ module Mongo
33
33
  # @return [ Integer ] port The port to connect to.
34
34
  attr_reader :port
35
35
 
36
- # @return [ Float ] timeout The socket timeout.
37
- attr_reader :timeout
38
-
39
36
  # Establishes a socket connection.
40
37
  #
41
38
  # @example Connect the socket.
@@ -26,9 +26,6 @@ module Mongo
26
26
  # @return [ Integer ] port The port to connect to.
27
27
  attr_reader :port
28
28
 
29
- # @return [ Float ] timeout The socket timeout.
30
- attr_reader :timeout
31
-
32
29
  # Establishes a socket connection.
33
30
  #
34
31
  # @example Connect the socket.
@@ -23,9 +23,6 @@ module Mongo
23
23
  # @return [ String ] path The path to connect to.
24
24
  attr_reader :path
25
25
 
26
- # @return [ Float ] timeout The socket timeout.
27
- attr_reader :timeout
28
-
29
26
  # Initializes a new Unix socket.
30
27
  #
31
28
  # @example Create the Unix socket.
@@ -17,5 +17,5 @@ module Mongo
17
17
  # The current version of the driver.
18
18
  #
19
19
  # @since 2.0.0
20
- VERSION = '2.10.0.rc0'.freeze
20
+ VERSION = '2.10.0'.freeze
21
21
  end
@@ -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
@@ -591,5 +591,18 @@ describe Mongo::Retryable do
591
591
  end
592
592
  end
593
593
 
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
594
607
  end
595
608
  end
@@ -757,26 +757,30 @@ describe Mongo::Server::Connection, retry: 3 do
757
757
  end
758
758
  end
759
759
 
760
- context 'when a socket timeout is set' do
760
+ context 'when a socket timeout is set on client' do
761
761
 
762
762
  let(:connection) do
763
763
  described_class.new(server, socket_timeout: 10)
764
764
  end
765
765
 
766
- it 'sets the timeout' do
766
+ it 'is propagated to connection timeout' do
767
767
  expect(connection.timeout).to eq(10)
768
768
  end
769
+ end
769
770
 
771
+ context 'when an operation never completes' do
770
772
  let(:client) do
771
- authorized_client.with(socket_timeout: 1.5)
773
+ authorized_client.with(socket_timeout: 1.5,
774
+ # Read retries would cause the reads to be attempted twice,
775
+ # thus making the find take twice as long to time out.
776
+ retry_reads: false, max_read_retries: 0)
772
777
  end
773
778
 
774
779
  before do
775
- authorized_collection.delete_many
776
- authorized_collection.insert_one(a: 1)
780
+ client.cluster.next_primary
777
781
  end
778
782
 
779
- it 'raises a timeout when it expires' do
783
+ it 'times out and raises SocketTimeoutError' do
780
784
  start = Time.now
781
785
  begin
782
786
  Timeout::timeout(1.5 + 15) do
@@ -784,11 +788,11 @@ describe Mongo::Server::Connection, retry: 3 do
784
788
  end
785
789
  rescue => ex
786
790
  end_time = Time.now
787
- expect(ex).to be_a(Timeout::Error)
788
- expect(ex.message).to eq("Took more than 1.5 seconds to receive data.")
791
+ expect(ex).to be_a(Mongo::Error::SocketTimeoutError)
792
+ expect(ex.message).to match(/Took more than 1.5 seconds to receive data/)
789
793
  end
790
- # Account for wait queue timeout (2s) and rescue
791
- expect(end_time - start).to be_within(2.5).of(1.5)
794
+ # allow 1.5 seconds +- 0.5 seconds
795
+ expect(end_time - start).to be_within(1).of(2)
792
796
  end
793
797
 
794
798
  context 'when the socket_timeout is negative' do
@@ -816,7 +820,7 @@ describe Mongo::Server::Connection, retry: 3 do
816
820
  it 'raises a timeout error' do
817
821
  expect {
818
822
  reply
819
- }.to raise_exception(Timeout::Error)
823
+ }.to raise_exception(Mongo::Error::SocketTimeoutError)
820
824
  end
821
825
  end
822
826
  end
@@ -21,7 +21,7 @@ describe Mongo::Server::Description do
21
21
  'maxMessageSizeBytes' => 48000000,
22
22
  'maxWriteBatchSize' => 1000,
23
23
  'maxWireVersion' => 2,
24
- 'minWireVersion' => 0,
24
+ 'minWireVersion' => 1,
25
25
  'localTime' => Time.now,
26
26
  'lastWrite' => { 'lastWriteDate' => Time.now },
27
27
  'logicalSessionTimeoutMinutes' => 7,
@@ -194,7 +194,7 @@ describe Mongo::Server::Description do
194
194
  end
195
195
 
196
196
  it 'returns the default' do
197
- expect(description.max_wire_version).to be nil
197
+ expect(description.max_wire_version).to eq(0)
198
198
  end
199
199
  end
200
200
  end
@@ -208,7 +208,7 @@ describe Mongo::Server::Description do
208
208
  end
209
209
 
210
210
  it 'returns the value' do
211
- expect(description.min_wire_version).to eq(0)
211
+ expect(description.min_wire_version).to eq(1)
212
212
  end
213
213
  end
214
214
 
@@ -219,7 +219,7 @@ describe Mongo::Server::Description do
219
219
  end
220
220
 
221
221
  it 'returns the default' do
222
- expect(description.min_wire_version).to be nil
222
+ expect(description.min_wire_version).to eq(0)
223
223
  end
224
224
  end
225
225
  end
@@ -1,4 +1,4 @@
1
- require 'lite_spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe Mongo::Socket do
4
4
 
@@ -51,4 +51,66 @@ describe Mongo::Socket do
51
51
  end.to raise_error(Mongo::Error::SocketError, 'OpenSSL::SSL::SSLError: Test error (for fake-address) (MongoDB may not be configured with SSL support)')
52
52
  end
53
53
  end
54
+
55
+ describe '#read' do
56
+ let(:target_host) do
57
+ host = ClusterConfig.instance.primary_address_host
58
+ # Take ipv4 address
59
+ Socket.getaddrinfo(host, 0).detect { |ai| ai.first == 'AF_INET' }[3]
60
+ end
61
+
62
+ let(:socket) do
63
+ Mongo::Socket::TCP.new(target_host, ClusterConfig.instance.primary_address_port, 1, Socket::PF_INET)
64
+ end
65
+
66
+ let(:raw_socket) { socket.instance_variable_get('@socket') }
67
+
68
+ let(:wait_readable_class) do
69
+ Class.new(Exception) do
70
+ include IO::WaitReadable
71
+ end
72
+ end
73
+
74
+ context 'timeout' do
75
+ clean_slate_for_all
76
+
77
+ shared_examples_for 'times out' do
78
+ it 'times out' do
79
+ expect(socket).to receive(:timeout).at_least(:once).and_return(0.2)
80
+ # When we raise WaitWritable, the socket object is ready for
81
+ # writing which makes the read method invoke read_nonblock many times
82
+ expect(raw_socket).to receive(:read_nonblock).at_least(:once) do |len, buf|
83
+ sleep 0.01
84
+ raise exception_class
85
+ end
86
+
87
+ expect do
88
+ socket.read(10)
89
+ end.to raise_error(Mongo::Error::SocketTimeoutError, /Took more than .* seconds to receive data \(for /)
90
+ end
91
+ end
92
+
93
+ context 'with WaitReadable' do
94
+
95
+ let(:exception_class) do
96
+ Class.new(Exception) do
97
+ include IO::WaitReadable
98
+ end
99
+ end
100
+
101
+ it_behaves_like 'times out'
102
+ end
103
+
104
+ context 'with WaitWritable' do
105
+
106
+ let(:exception_class) do
107
+ Class.new(Exception) do
108
+ include IO::WaitWritable
109
+ end
110
+ end
111
+
112
+ it_behaves_like 'times out'
113
+ end
114
+ end
115
+ end
54
116
  end
@@ -74,6 +74,20 @@ class ClusterConfig
74
74
  end
75
75
  end
76
76
 
77
+ def primary_address_str
78
+ primary_address
79
+ end
80
+
81
+ def primary_address_host
82
+ both = primary_address_str
83
+ both.split(':').first
84
+ end
85
+
86
+ def primary_address_port
87
+ both = primary_address_str
88
+ both.split(':')[1] || 27017
89
+ end
90
+
77
91
  # Try running a command on the admin database to see if the mongod was
78
92
  # started with auth.
79
93
  def auth_enabled?
@@ -337,7 +337,6 @@ EOT
337
337
  {
338
338
  max_pool_size: 1,
339
339
  heartbeat_frequency: 20,
340
- max_read_retries: 5,
341
340
  # The test suite seems to perform a number of operations
342
341
  # requiring server selection. Hence a timeout of 1 here,
343
342
  # together with e.g. a misconfigured replica set,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongo
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.10.0.rc0
4
+ version: 2.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Brock
@@ -31,7 +31,7 @@ cert_chain:
31
31
  bMYVwXXhV8czdzgkQB/ZPWHSbEWXnmkze1mzvqWBCPOVXYrcnL9cnEl/RoxtS1hr
32
32
  Db6Ac6mCUSYfYHBWpWqxjc45n70i5Xi1
33
33
  -----END CERTIFICATE-----
34
- date: 2019-08-06 00:00:00.000000000 Z
34
+ date: 2019-08-13 00:00:00.000000000 Z
35
35
  dependencies:
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: bson
@@ -456,6 +456,7 @@ files:
456
456
  - spec/integration/read_concern_spec.rb
457
457
  - spec/integration/read_preference_spec.rb
458
458
  - spec/integration/reconnect_spec.rb
459
+ - spec/integration/retryable_writes_errors_spec.rb
459
460
  - spec/integration/retryable_writes_spec.rb
460
461
  - spec/integration/sdam_error_handling_spec.rb
461
462
  - spec/integration/sdam_events_spec.rb
@@ -1110,9 +1111,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
1110
1111
  version: '0'
1111
1112
  required_rubygems_version: !ruby/object:Gem::Requirement
1112
1113
  requirements:
1113
- - - ">"
1114
+ - - ">="
1114
1115
  - !ruby/object:Gem::Version
1115
- version: 1.3.1
1116
+ version: '0'
1116
1117
  requirements: []
1117
1118
  rubygems_version: 3.0.3
1118
1119
  signing_key:
@@ -1216,6 +1217,7 @@ test_files:
1216
1217
  - spec/integration/time_zone_querying_spec.rb
1217
1218
  - spec/integration/mongos_pinning_spec.rb
1218
1219
  - spec/integration/heartbeat_events_spec.rb
1220
+ - spec/integration/retryable_writes_errors_spec.rb
1219
1221
  - spec/integration/docs_examples_spec.rb
1220
1222
  - spec/integration/sdam_error_handling_spec.rb
1221
1223
  - spec/mongo/cursor_spec.rb
metadata.gz.sig CHANGED
Binary file