mongo 2.10.0.rc0 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
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