mongo 2.7.2 → 2.8.0.rc0

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 (94) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -3
  4. data/lib/mongo/address.rb +17 -20
  5. data/lib/mongo/address/ipv4.rb +6 -3
  6. data/lib/mongo/address/ipv6.rb +6 -3
  7. data/lib/mongo/address/unix.rb +5 -2
  8. data/lib/mongo/auth.rb +15 -2
  9. data/lib/mongo/auth/cr/conversation.rb +4 -2
  10. data/lib/mongo/auth/ldap/conversation.rb +4 -2
  11. data/lib/mongo/auth/scram.rb +3 -7
  12. data/lib/mongo/auth/scram/conversation.rb +28 -19
  13. data/lib/mongo/auth/user.rb +45 -10
  14. data/lib/mongo/auth/x509/conversation.rb +4 -2
  15. data/lib/mongo/cluster.rb +9 -17
  16. data/lib/mongo/error.rb +2 -0
  17. data/lib/mongo/error/missing_password.rb +29 -0
  18. data/lib/mongo/error/operation_failure.rb +7 -3
  19. data/lib/mongo/error/parser.rb +2 -1
  20. data/lib/mongo/error/sdam_error_detection.rb +54 -0
  21. data/lib/mongo/operation/aggregate/command.rb +1 -16
  22. data/lib/mongo/operation/aggregate/op_msg.rb +1 -1
  23. data/lib/mongo/operation/collections_info.rb +2 -3
  24. data/lib/mongo/operation/delete/command.rb +2 -15
  25. data/lib/mongo/operation/delete/legacy.rb +1 -16
  26. data/lib/mongo/operation/explain/command.rb +1 -16
  27. data/lib/mongo/operation/explain/legacy.rb +1 -16
  28. data/lib/mongo/operation/find/command.rb +1 -16
  29. data/lib/mongo/operation/find/legacy.rb +1 -16
  30. data/lib/mongo/operation/get_more/command.rb +1 -16
  31. data/lib/mongo/operation/indexes/command.rb +1 -16
  32. data/lib/mongo/operation/indexes/legacy.rb +4 -16
  33. data/lib/mongo/operation/list_collections/command.rb +1 -16
  34. data/lib/mongo/operation/map_reduce/command.rb +1 -16
  35. data/lib/mongo/operation/parallel_scan/command.rb +1 -16
  36. data/lib/mongo/operation/result.rb +3 -0
  37. data/lib/mongo/operation/shared/executable.rb +4 -0
  38. data/lib/mongo/operation/shared/polymorphic_lookup.rb +1 -1
  39. data/lib/mongo/operation/shared/polymorphic_result.rb +8 -1
  40. data/lib/mongo/operation/shared/result/aggregatable.rb +0 -5
  41. data/lib/mongo/operation/update/command.rb +2 -15
  42. data/lib/mongo/operation/update/legacy.rb +1 -16
  43. data/lib/mongo/operation/users_info/command.rb +1 -16
  44. data/lib/mongo/retryable.rb +22 -10
  45. data/lib/mongo/server.rb +10 -1
  46. data/lib/mongo/server/app_metadata.rb +7 -2
  47. data/lib/mongo/server/connectable.rb +0 -6
  48. data/lib/mongo/server/connection.rb +86 -135
  49. data/lib/mongo/server/connection_base.rb +133 -0
  50. data/lib/mongo/server/connection_pool.rb +11 -24
  51. data/lib/mongo/server/connection_pool/queue.rb +41 -41
  52. data/lib/mongo/server/description.rb +1 -1
  53. data/lib/mongo/server/monitor.rb +4 -4
  54. data/lib/mongo/server/monitor/connection.rb +26 -7
  55. data/lib/mongo/server/pending_connection.rb +36 -0
  56. data/lib/mongo/server_selector/selectable.rb +9 -1
  57. data/lib/mongo/session.rb +0 -1
  58. data/lib/mongo/socket.rb +23 -6
  59. data/lib/mongo/socket/ssl.rb +11 -18
  60. data/lib/mongo/socket/tcp.rb +13 -14
  61. data/lib/mongo/socket/unix.rb +9 -27
  62. data/lib/mongo/uri.rb +1 -1
  63. data/lib/mongo/version.rb +1 -1
  64. data/spec/integration/auth_spec.rb +160 -0
  65. data/spec/integration/retryable_writes_spec.rb +55 -58
  66. data/spec/integration/sdam_error_handling_spec.rb +115 -0
  67. data/spec/mongo/address/ipv4_spec.rb +4 -0
  68. data/spec/mongo/address/ipv6_spec.rb +4 -0
  69. data/spec/mongo/auth/scram/conversation_spec.rb +6 -5
  70. data/spec/mongo/auth/scram/negotiation_spec.rb +25 -36
  71. data/spec/mongo/auth/scram_spec.rb +2 -2
  72. data/spec/mongo/auth/user_spec.rb +97 -0
  73. data/spec/mongo/client_construction_spec.rb +1 -1
  74. data/spec/mongo/error/operation_failure_spec.rb +125 -1
  75. data/spec/mongo/retryable_spec.rb +17 -8
  76. data/spec/mongo/server/connection_pool/queue_spec.rb +24 -10
  77. data/spec/mongo/server/connection_pool_spec.rb +30 -117
  78. data/spec/mongo/server/connection_spec.rb +147 -25
  79. data/spec/mongo/server/description_spec.rb +0 -14
  80. data/spec/mongo/server/monitor/connection_spec.rb +22 -0
  81. data/spec/mongo/server_selector_spec.rb +1 -0
  82. data/spec/mongo/server_spec.rb +6 -6
  83. data/spec/mongo/socket/ssl_spec.rb +48 -116
  84. data/spec/mongo/socket/tcp_spec.rb +22 -0
  85. data/spec/mongo/socket/unix_spec.rb +9 -9
  86. data/spec/mongo/socket_spec.rb +15 -3
  87. data/spec/spec_tests/server_selection_spec.rb +2 -0
  88. data/spec/support/client_registry.rb +8 -2
  89. data/spec/support/common_shortcuts.rb +20 -1
  90. data/spec/support/constraints.rb +10 -2
  91. data/spec/support/lite_constraints.rb +8 -0
  92. data/spec/support/spec_config.rb +9 -1
  93. metadata +14 -4
  94. metadata.gz.sig +0 -0
@@ -92,7 +92,10 @@ module Mongo
92
92
  # @return [ Array ] queue The underlying array of connections.
93
93
  attr_reader :queue
94
94
 
95
- # @return [ Mutex ] mutex The mutex used for synchronization.
95
+ # @return [ Mutex ] mutex The mutex used for synchronization of
96
+ # access to #queue.
97
+ #
98
+ # @api private
96
99
  attr_reader :mutex
97
100
 
98
101
  # @return [ Hash ] options The options.
@@ -104,7 +107,11 @@ module Mongo
104
107
  # Number of connections that the pool has which are ready to be
105
108
  # checked out. This is NOT the size of the connection pool (total
106
109
  # number of active connections created by the pool).
107
- def_delegators :queue, :size
110
+ def size
111
+ mutex.synchronize do
112
+ queue.size
113
+ end
114
+ end
108
115
 
109
116
  # Number of connections that the pool has which are ready to be
110
117
  # checked out.
@@ -133,14 +140,15 @@ module Mongo
133
140
  # @since 2.0.0
134
141
  def dequeue
135
142
  check_count_invariants
136
- mutex.synchronize do
137
- dequeue_connection
138
- end
143
+ dequeue_connection
139
144
  ensure
140
145
  check_count_invariants
141
146
  end
142
147
 
143
- # Disconnect all connections in the queue.
148
+ # Closes all idle connections in the queue and schedules currently
149
+ # dequeued connections to be closed when they are enqueued back into
150
+ # the queue. The queue remains operational and can create new
151
+ # connections when requested.
144
152
  #
145
153
  # @example Disconnect all connections.
146
154
  # queue.disconnect!
@@ -151,19 +159,16 @@ module Mongo
151
159
  def disconnect!
152
160
  check_count_invariants
153
161
  mutex.synchronize do
154
- queue.each{ |connection| connection.disconnect! }
155
- @pool_size -= queue.length
156
- if @pool_size < 0
157
- # This should never happen
158
- log_warn("ConnectionPool::Queue: connection accounting problem")
159
- @pool_size = 0
162
+ while connection = queue.pop
163
+ connection.disconnect!
164
+ @pool_size -= 1
165
+ if @pool_size < 0
166
+ # This should never happen
167
+ log_warn("ConnectionPool::Queue: connection accounting problem")
168
+ @pool_size = 0
169
+ end
160
170
  end
161
- queue.clear
162
171
  @generation += 1
163
- while @pool_size < min_size
164
- @pool_size += 1
165
- queue.unshift(@block.call(@generation))
166
- end
167
172
  true
168
173
  end
169
174
  ensure
@@ -290,26 +295,19 @@ module Mongo
290
295
  check_count_invariants
291
296
  return unless max_idle_time
292
297
 
293
- to_refresh = []
294
- queue.each do |connection|
295
- if last_checkin = connection.last_checkin
296
- if (Time.now - last_checkin) > max_idle_time
297
- to_refresh << connection
298
- end
299
- end
300
- end
301
-
302
298
  mutex.synchronize do
303
- num_checked_out = pool_size - queue_size
304
- min_size_delta = [(min_size - num_checked_out), 0].max
305
-
306
- to_refresh.each do |connection|
307
- if queue.include?(connection)
308
- connection.disconnect!
309
- if queue.index(connection) < min_size_delta
310
- begin; connection.connect!; rescue; end
299
+ i = 0
300
+ while i < queue.length
301
+ connection = queue[i]
302
+ if last_checkin = connection.last_checkin
303
+ if (Time.now - last_checkin) > max_idle_time
304
+ connection.disconnect!
305
+ queue.delete_at(i)
306
+ @pool_size -= 1
307
+ next
311
308
  end
312
309
  end
310
+ i += 1
313
311
  end
314
312
  end
315
313
  ensure
@@ -319,12 +317,14 @@ module Mongo
319
317
  private
320
318
 
321
319
  def dequeue_connection
322
- deadline = Time.now + wait_timeout
323
- loop do
324
- return queue.shift unless queue.empty?
325
- connection = create_connection
326
- return connection if connection
327
- wait_for_next!(deadline)
320
+ mutex.synchronize do
321
+ deadline = Time.now + wait_timeout
322
+ loop do
323
+ return queue.shift unless queue.empty?
324
+ connection = create_connection
325
+ return connection if connection
326
+ wait_for_next!(deadline)
327
+ end
328
328
  end
329
329
  end
330
330
 
@@ -345,7 +345,7 @@ module Mongo
345
345
 
346
346
  def check_count_invariants
347
347
  if Mongo::Lint.enabled?
348
- if pool_size < min_size
348
+ if pool_size < 0
349
349
  raise Error::LintError, 'connection pool queue: underflow'
350
350
  end
351
351
  if pool_size > max_size
@@ -196,7 +196,7 @@ module Mongo
196
196
  @features = Features.new(wire_versions, me || @address.to_s)
197
197
  end
198
198
  @average_round_trip_time = average_round_trip_time
199
- @last_update_time = Time.now.dup.freeze
199
+ @last_update_time = Time.now.freeze
200
200
 
201
201
  if Mongo::Lint.enabled?
202
202
  # prepopulate cache instance variables
@@ -227,7 +227,7 @@ module Mongo
227
227
  if monitoring.monitoring?
228
228
  monitoring.started(
229
229
  Monitoring::SERVER_HEARTBEAT,
230
- Monitoring::Event::ServerHeartbeatStarted.new(connection.address)
230
+ Monitoring::Event::ServerHeartbeatStarted.new(description.address)
231
231
  )
232
232
  end
233
233
 
@@ -235,11 +235,11 @@ module Mongo
235
235
  connection.ismaster
236
236
  end
237
237
  if exc
238
- log_debug("Error running ismaster on #{connection.address}: #{exc.message}")
238
+ log_debug("Error running ismaster on #{description.address}: #{exc.message}")
239
239
  if monitoring.monitoring?
240
240
  monitoring.failed(
241
241
  Monitoring::SERVER_HEARTBEAT,
242
- Monitoring::Event::ServerHeartbeatFailed.new(connection.address, rtt, exc)
242
+ Monitoring::Event::ServerHeartbeatFailed.new(description.address, rtt, exc)
243
243
  )
244
244
  end
245
245
  result = {}
@@ -247,7 +247,7 @@ module Mongo
247
247
  if monitoring.monitoring?
248
248
  monitoring.succeeded(
249
249
  Monitoring::SERVER_HEARTBEAT,
250
- Monitoring::Event::ServerHeartbeatSucceeded.new(connection.address, rtt)
250
+ Monitoring::Event::ServerHeartbeatSucceeded.new(description.address, rtt)
251
251
  )
252
252
  end
253
253
  end
@@ -73,7 +73,14 @@ module Mongo
73
73
  COMPRESSION_WARNING = 'The server has no compression algorithms in common with those requested. ' +
74
74
  'Compression will not be used.'.freeze
75
75
 
76
- # Initialize a new socket connection from the client to the server.
76
+ # Creates a new connection object to the specified target address
77
+ # with the specified options.
78
+ #
79
+ # The constructor does not perform any I/O (and thus does not create
80
+ # sockets nor handshakes); call connect! method on the connection
81
+ # object to create the network connection.
82
+ #
83
+ # @note Monitoring connections do not authenticate.
77
84
  #
78
85
  # @api private
79
86
  #
@@ -113,6 +120,12 @@ module Mongo
113
120
  @compressor = nil
114
121
  end
115
122
 
123
+ # @return [ Hash ] options The passed in options.
124
+ attr_reader :options
125
+
126
+ # @return [ Mongo::Address ] address The address to connect to.
127
+ attr_reader :address
128
+
116
129
  # The compressor, which is determined during the handshake.
117
130
  #
118
131
  # @since 2.5.0
@@ -135,7 +148,9 @@ module Mongo
135
148
  end
136
149
  end
137
150
 
138
- # Tell the underlying socket to establish a connection to the host.
151
+ # Establishes a network connection to the target address.
152
+ #
153
+ # If the connection is already established, this method does nothing.
139
154
  #
140
155
  # @example Connect to the host.
141
156
  # connection.connect!
@@ -147,10 +162,11 @@ module Mongo
147
162
  #
148
163
  # @since 2.0.0
149
164
  def connect!
150
- unless socket && socket.connectable?
151
- @socket = address.socket(socket_timeout, ssl_options)
152
- address.connect_socket!(socket)
153
- handshake!
165
+ unless @socket
166
+ socket = address.socket(socket_timeout, ssl_options,
167
+ connect_timeout: address.connect_timeout)
168
+ handshake!(socket)
169
+ @socket = socket
154
170
  end
155
171
  true
156
172
  end
@@ -204,13 +220,16 @@ module Mongo
204
220
  end
205
221
  end
206
222
 
207
- def handshake!
223
+ def handshake!(socket)
208
224
  if @app_metadata
209
225
  socket.write(@app_metadata.ismaster_bytes)
210
226
  reply = Protocol::Message.deserialize(socket, Mongo::Protocol::Message::MAX_MESSAGE_SIZE).documents[0]
211
227
  set_compressor!(reply)
212
228
  reply
213
229
  end
230
+ rescue => e
231
+ log_warn("Failed to handshake with #{address}: #{e.class}: #{e}")
232
+ raise
214
233
  end
215
234
 
216
235
  def retry_message
@@ -0,0 +1,36 @@
1
+ # Copyright (C) 2019 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ class Server
17
+
18
+ # This class encapsulates connections during handshake and authentication.
19
+ #
20
+ # @api private
21
+ class PendingConnection < ConnectionBase
22
+ extend Forwardable
23
+
24
+ def initialize(socket, server, monitoring, options = {})
25
+ @socket = socket
26
+ @options = options
27
+ @server = server
28
+ @monitoring = monitoring
29
+ end
30
+
31
+ def ensure_connected
32
+ yield @socket
33
+ end
34
+ end
35
+ end
36
+ end
@@ -124,7 +124,7 @@ module Mongo
124
124
  @local_threshold = cluster.options[:local_threshold] || LOCAL_THRESHOLD
125
125
  @server_selection_timeout = cluster.options[:server_selection_timeout] || SERVER_SELECTION_TIMEOUT
126
126
  deadline = Time.now + server_selection_timeout
127
- while (deadline - Time.now) > 0
127
+ while (time_remaining = deadline - Time.now) > 0
128
128
  servers = candidates(cluster)
129
129
  if Lint.enabled?
130
130
  servers.each do |server|
@@ -155,6 +155,14 @@ module Mongo
155
155
  return server
156
156
  end
157
157
  cluster.scan!(false)
158
+ if cluster.server_selection_semaphore
159
+ cluster.server_selection_semaphore.wait(time_remaining)
160
+ else
161
+ if Lint.enabled?
162
+ raise Error::LintError, 'Waiting for server selection without having a server selection semaphore'
163
+ end
164
+ sleep 0.25
165
+ end
158
166
  end
159
167
 
160
168
  msg = "No #{name} server is available in cluster: #{cluster.summary} " +
@@ -812,7 +812,6 @@ module Mongo
812
812
  if e.label?(Mongo::Error::UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)
813
813
  # WriteConcernFailed
814
814
  if e.is_a?(Mongo::Error::OperationFailure) && e.code == 64 && e.wtimeout?
815
- transaction_in_progress = false
816
815
  raise
817
816
  end
818
817
  if Time.now >= deadline
@@ -34,6 +34,7 @@ module Mongo
34
34
  # Error message for timeouts on socket calls.
35
35
  #
36
36
  # @since 2.0.0
37
+ # @deprecated
37
38
  TIMEOUT_ERROR = 'Socket request timed out'.freeze
38
39
 
39
40
  # The pack directive for timeouts.
@@ -47,6 +48,9 @@ module Mongo
47
48
  # @return [ Socket ] socket The wrapped socket.
48
49
  attr_reader :socket
49
50
 
51
+ # @return [ Hash ] The options.
52
+ attr_reader :options
53
+
50
54
  # Is the socket connection alive?
51
55
  #
52
56
  # @example Is the socket alive?
@@ -162,7 +166,16 @@ module Mongo
162
166
  # @since 2.0.5
163
167
  def eof?
164
168
  @socket.eof?
165
- rescue IOError, SystemCallError => _
169
+ rescue IOError, SystemCallError
170
+ true
171
+ end
172
+
173
+ # For backwards compatibilty only, do not use.
174
+ #
175
+ # @return [ true ] Always true.
176
+ #
177
+ # @deprecated
178
+ def connectable?
166
179
  true
167
180
  end
168
181
 
@@ -189,7 +202,7 @@ module Mongo
189
202
  buf_size = length
190
203
  end
191
204
 
192
- # The binary encoding is important, otherwise ruby performs encoding
205
+ # The binary encoding is important, otherwise Ruby performs encoding
193
206
  # conversions of some sort during the write into the buffer which
194
207
  # kills performance
195
208
  buf = allocate_string(buf_size)
@@ -281,13 +294,17 @@ module Mongo
281
294
  def handle_errors
282
295
  begin
283
296
  yield
284
- rescue Errno::ETIMEDOUT
285
- raise Error::SocketTimeoutError, TIMEOUT_ERROR
297
+ rescue Errno::ETIMEDOUT => e
298
+ raise Error::SocketTimeoutError, "#{e.class}: #{e} (for #{address})"
286
299
  rescue IOError, SystemCallError => e
287
- raise Error::SocketError, "#{e.class}: #{e}"
300
+ raise Error::SocketError, "#{e.class}: #{e} (for #{address})"
288
301
  rescue OpenSSL::SSL::SSLError => e
289
- raise Error::SocketError, "#{e.class}: #{e} (#{SSL_ERROR})"
302
+ raise Error::SocketError, "#{e.class}: #{e} (for #{address}) (#{SSL_ERROR})"
290
303
  end
291
304
  end
305
+
306
+ def address
307
+ raise NotImplementedError
308
+ end
292
309
  end
293
310
  end
@@ -32,9 +32,6 @@ module Mongo
32
32
  # @return [ String ] host_name The original host name.
33
33
  attr_reader :host_name
34
34
 
35
- # @return [ Hash ] The ssl options.
36
- attr_reader :options
37
-
38
35
  # @return [ Integer ] port The port to connect to.
39
36
  attr_reader :port
40
37
 
@@ -52,8 +49,8 @@ module Mongo
52
49
  # @return [ SSL ] The connected socket instance.
53
50
  #
54
51
  # @since 2.0.0
55
- def connect!(connect_timeout = nil)
56
- Timeout.timeout(connect_timeout, Error::SocketTimeoutError) do
52
+ def connect!
53
+ Timeout.timeout(options[:connect_timeout], Error::SocketTimeoutError) do
57
54
  handle_errors { @tcp_socket.connect(::Socket.pack_sockaddr_in(port, host)) }
58
55
  @socket = OpenSSL::SSL::SSLSocket.new(@tcp_socket, context)
59
56
  @socket.hostname = @host_name
@@ -63,6 +60,7 @@ module Mongo
63
60
  self
64
61
  end
65
62
  end
63
+ private :connect!
66
64
 
67
65
  # Initializes a new SSL socket.
68
66
  #
@@ -73,7 +71,9 @@ module Mongo
73
71
  # @param [ Integer ] port The port number.
74
72
  # @param [ Float ] timeout The socket timeout value.
75
73
  # @param [ Integer ] family The socket family.
76
- # @param [ Hash ] options The ssl options.
74
+ # @param [ Hash ] options The options.
75
+ #
76
+ # @option options [ Float ] :connect_timeout Connect timeout.
77
77
  #
78
78
  # @since 2.0.0
79
79
  def initialize(host, port, host_name, timeout, family, options = {})
@@ -83,6 +83,7 @@ module Mongo
83
83
  @tcp_socket = ::Socket.new(family, SOCK_STREAM, 0)
84
84
  @tcp_socket.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
85
85
  set_socket_options(@tcp_socket)
86
+ connect!
86
87
  end
87
88
 
88
89
  # Read a single byte from the socket.
@@ -100,18 +101,6 @@ module Mongo
100
101
  end
101
102
  end
102
103
 
103
- # This socket can only be used if the ssl socket (@socket) has been created.
104
- #
105
- # @example Is the socket connectable?
106
- # socket.connectable?
107
- #
108
- # @return [ true, false ] If the socket is connectable.
109
- #
110
- # @since 2.2.5
111
- def connectable?
112
- !!@socket
113
- end
114
-
115
104
  private
116
105
 
117
106
  def verify_certificate?
@@ -213,6 +202,10 @@ module Mongo
213
202
  # Capped at 16k due to https://linux.die.net/man/3/ssl_read
214
203
  16384
215
204
  end
205
+
206
+ def address
207
+ "#{host}:#{port} (#{host_name}:#{port}, TLS)"
208
+ end
216
209
  end
217
210
  end
218
211
  end