mongo 2.9.2 → 2.10.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/mongo.rb +1 -0
  5. data/lib/mongo/auth/user/view.rb +4 -4
  6. data/lib/mongo/bulk_write.rb +14 -8
  7. data/lib/mongo/bulk_write/result.rb +1 -1
  8. data/lib/mongo/bulk_write/result_combiner.rb +2 -2
  9. data/lib/mongo/bulk_write/transformable.rb +17 -9
  10. data/lib/mongo/client.rb +107 -16
  11. data/lib/mongo/cluster.rb +47 -25
  12. data/lib/mongo/cluster/topology/replica_set_no_primary.rb +1 -1
  13. data/lib/mongo/cluster_time.rb +139 -0
  14. data/lib/mongo/collection.rb +84 -25
  15. data/lib/mongo/collection/view.rb +7 -3
  16. data/lib/mongo/collection/view/aggregation.rb +4 -4
  17. data/lib/mongo/collection/view/builder/aggregation.rb +31 -6
  18. data/lib/mongo/collection/view/builder/find_command.rb +4 -1
  19. data/lib/mongo/collection/view/builder/map_reduce.rb +4 -1
  20. data/lib/mongo/collection/view/change_stream.rb +54 -66
  21. data/lib/mongo/collection/view/iterable.rb +2 -2
  22. data/lib/mongo/collection/view/map_reduce.rb +6 -4
  23. data/lib/mongo/collection/view/readable.rb +36 -16
  24. data/lib/mongo/collection/view/writable.rb +68 -22
  25. data/lib/mongo/cursor.rb +87 -20
  26. data/lib/mongo/database.rb +47 -43
  27. data/lib/mongo/database/view.rb +54 -11
  28. data/lib/mongo/error.rb +13 -4
  29. data/lib/mongo/error/invalid_write_concern.rb +2 -2
  30. data/lib/mongo/error/operation_failure.rb +65 -11
  31. data/lib/mongo/error/parser.rb +41 -8
  32. data/lib/mongo/grid/fs_bucket.rb +26 -6
  33. data/lib/mongo/grid/stream/read.rb +9 -2
  34. data/lib/mongo/grid/stream/write.rb +21 -5
  35. data/lib/mongo/index/view.rb +3 -3
  36. data/lib/mongo/lint.rb +10 -3
  37. data/lib/mongo/operation.rb +2 -0
  38. data/lib/mongo/operation/aggregate/result.rb +19 -6
  39. data/lib/mongo/operation/collections_info.rb +1 -1
  40. data/lib/mongo/operation/get_more/result.rb +9 -0
  41. data/lib/mongo/operation/list_collections/command.rb +1 -3
  42. data/lib/mongo/operation/list_collections/op_msg.rb +1 -2
  43. data/lib/mongo/operation/parallel_scan/command.rb +4 -1
  44. data/lib/mongo/operation/parallel_scan/op_msg.rb +4 -1
  45. data/lib/mongo/operation/result.rb +27 -4
  46. data/lib/mongo/operation/shared/executable.rb +19 -5
  47. data/lib/mongo/operation/shared/executable_no_validate.rb +1 -2
  48. data/lib/mongo/operation/shared/executable_transaction_label.rb +0 -9
  49. data/lib/mongo/operation/shared/polymorphic_result.rb +9 -1
  50. data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
  51. data/lib/mongo/operation/shared/sessions_supported.rb +42 -32
  52. data/lib/mongo/operation/shared/specifiable.rb +40 -0
  53. data/lib/mongo/operation/shared/unpinnable.rb +39 -0
  54. data/lib/mongo/operation/shared/write.rb +1 -1
  55. data/lib/mongo/protocol/update.rb +6 -2
  56. data/lib/mongo/retryable.rb +79 -39
  57. data/lib/mongo/server/connection.rb +10 -3
  58. data/lib/mongo/server/description.rb +25 -1
  59. data/lib/mongo/server/monitor/connection.rb +1 -1
  60. data/lib/mongo/server_selector.rb +10 -0
  61. data/lib/mongo/server_selector/selectable.rb +172 -32
  62. data/lib/mongo/session.rb +654 -581
  63. data/lib/mongo/session/session_pool.rb +1 -1
  64. data/lib/mongo/socket.rb +7 -28
  65. data/lib/mongo/socket/ssl.rb +26 -1
  66. data/lib/mongo/socket/tcp.rb +3 -0
  67. data/lib/mongo/socket/unix.rb +3 -0
  68. data/lib/mongo/uri.rb +112 -265
  69. data/lib/mongo/uri/srv_protocol.rb +4 -1
  70. data/lib/mongo/version.rb +1 -1
  71. data/lib/mongo/write_concern.rb +10 -29
  72. data/lib/mongo/write_concern/acknowledged.rb +12 -0
  73. data/lib/mongo/write_concern/base.rb +17 -13
  74. data/lib/mongo/write_concern/unacknowledged.rb +12 -0
  75. data/spec/atlas/atlas_connectivity_spec.rb +7 -37
  76. data/spec/atlas/operations_spec.rb +25 -0
  77. data/spec/integration/change_stream_examples_spec.rb +45 -31
  78. data/spec/integration/change_stream_spec.rb +305 -5
  79. data/spec/integration/client_spec.rb +44 -0
  80. data/spec/integration/command_monitoring_spec.rb +1 -0
  81. data/spec/integration/command_spec.rb +7 -1
  82. data/spec/integration/mmapv1_spec.rb +28 -0
  83. data/spec/integration/mongos_pinning_spec.rb +34 -0
  84. data/spec/integration/operation_failure_code_spec.rb +2 -2
  85. data/spec/integration/{read_concern.rb → read_concern_spec.rb} +7 -1
  86. data/spec/integration/read_preference_spec.rb +485 -0
  87. data/spec/integration/retryable_writes_spec.rb +8 -19
  88. data/spec/integration/sdam_error_handling_spec.rb +1 -1
  89. data/spec/integration/sdam_events_spec.rb +2 -2
  90. data/spec/integration/server_description_spec.rb +14 -17
  91. data/spec/integration/server_selector_spec.rb +7 -3
  92. data/spec/integration/server_spec.rb +48 -0
  93. data/spec/integration/ssl_uri_options_spec.rb +1 -1
  94. data/spec/integration/step_down_spec.rb +10 -4
  95. data/spec/integration/transactions_examples_spec.rb +11 -10
  96. data/spec/lite_spec_helper.rb +19 -16
  97. data/spec/mongo/auth/scram/negotiation_spec.rb +11 -8
  98. data/spec/mongo/bulk_write/ordered_combiner_spec.rb +6 -6
  99. data/spec/mongo/bulk_write/unordered_combiner_spec.rb +4 -4
  100. data/spec/mongo/bulk_write_spec.rb +12 -2
  101. data/spec/mongo/client_construction_spec.rb +160 -8
  102. data/spec/mongo/client_spec.rb +5 -4
  103. data/spec/mongo/cluster_spec.rb +6 -6
  104. data/spec/mongo/cluster_time_spec.rb +148 -0
  105. data/spec/mongo/collection/view/aggregation_spec.rb +34 -15
  106. data/spec/mongo/collection/view/change_stream_spec.rb +62 -3
  107. data/spec/mongo/collection/view/map_reduce_spec.rb +7 -5
  108. data/spec/mongo/collection/view/readable_spec.rb +4 -4
  109. data/spec/mongo/collection_spec.rb +331 -14
  110. data/spec/mongo/cursor_spec.rb +117 -5
  111. data/spec/mongo/database_spec.rb +240 -8
  112. data/spec/mongo/error/operation_failure_spec.rb +47 -1
  113. data/spec/mongo/error/parser_spec.rb +160 -23
  114. data/spec/mongo/operation/insert/bulk_spec.rb +2 -1
  115. data/spec/mongo/operation/result_spec.rb +27 -0
  116. data/spec/mongo/operation/update/bulk_spec.rb +1 -0
  117. data/spec/mongo/retryable_spec.rb +2 -0
  118. data/spec/mongo/server/app_metadata_spec.rb +2 -2
  119. data/spec/mongo/server/connection_spec.rb +13 -17
  120. data/spec/mongo/server/monitor/connection_spec.rb +13 -10
  121. data/spec/mongo/server_selector_spec.rb +34 -2
  122. data/spec/mongo/session/session_pool_spec.rb +14 -3
  123. data/spec/mongo/session_spec.rb +3 -3
  124. data/spec/mongo/session_transaction_spec.rb +4 -3
  125. data/spec/mongo/socket/ssl_spec.rb +19 -5
  126. data/spec/mongo/socket_spec.rb +1 -62
  127. data/spec/mongo/uri/srv_protocol_spec.rb +14 -20
  128. data/spec/mongo/uri_option_parsing_spec.rb +94 -8
  129. data/spec/mongo/uri_spec.rb +23 -10
  130. data/spec/mongo/write_concern_spec.rb +56 -3
  131. data/spec/spec_tests/change_streams_spec.rb +2 -1
  132. data/spec/spec_tests/cmap_spec.rb +1 -1
  133. data/spec/spec_tests/crud_spec.rb +12 -2
  134. data/spec/spec_tests/data/change_streams/change-streams-errors.yml +24 -1
  135. data/spec/spec_tests/data/change_streams/change-streams.yml +172 -3
  136. data/spec/spec_tests/data/command_monitoring/bulkWrite.yml +1 -1
  137. data/spec/spec_tests/data/command_monitoring/updateMany.yml +0 -2
  138. data/spec/spec_tests/data/command_monitoring/updateOne.yml +0 -5
  139. data/spec/spec_tests/data/crud/read/aggregate-out.yml +0 -6
  140. data/spec/spec_tests/data/crud/read/count-empty.yml +29 -0
  141. data/spec/spec_tests/data/crud/write/bulkWrite-arrayFilters.yml +1 -0
  142. data/spec/spec_tests/data/crud/write/bulkWrite-collation.yml +101 -0
  143. data/spec/spec_tests/data/crud/write/bulkWrite.yml +401 -0
  144. data/spec/spec_tests/data/crud/write/insertMany.yml +58 -2
  145. data/spec/spec_tests/data/crud/write/updateMany-arrayFilters.yml +3 -0
  146. data/spec/spec_tests/data/crud/write/updateOne-arrayFilters.yml +6 -1
  147. data/spec/spec_tests/data/crud_v2/aggregate-merge.yml +103 -0
  148. data/spec/spec_tests/data/crud_v2/aggregate-out-readConcern.yml +110 -0
  149. data/spec/spec_tests/data/crud_v2/bulkWrite-arrayFilters.yml +81 -0
  150. data/spec/spec_tests/data/crud_v2/db-aggregate.yml +38 -0
  151. data/spec/spec_tests/data/crud_v2/updateWithPipelines.yml +92 -0
  152. data/spec/spec_tests/data/retryable_writes/insertOne-serverErrors.yml +2 -2
  153. data/spec/spec_tests/data/transactions/abort.yml +3 -0
  154. data/spec/spec_tests/data/transactions/bulk.yml +3 -8
  155. data/spec/spec_tests/data/transactions/causal-consistency.yml +3 -8
  156. data/spec/spec_tests/data/transactions/commit.yml +3 -1
  157. data/spec/spec_tests/data/transactions/count.yml +3 -0
  158. data/spec/spec_tests/data/transactions/delete.yml +3 -0
  159. data/spec/spec_tests/data/transactions/error-labels.yml +4 -1
  160. data/spec/spec_tests/data/transactions/errors-client.yml +56 -0
  161. data/spec/spec_tests/data/transactions/errors.yml +3 -0
  162. data/spec/spec_tests/data/transactions/findOneAndDelete.yml +3 -0
  163. data/spec/spec_tests/data/transactions/findOneAndReplace.yml +3 -0
  164. data/spec/spec_tests/data/transactions/findOneAndUpdate.yml +3 -0
  165. data/spec/spec_tests/data/transactions/insert.yml +3 -0
  166. data/spec/spec_tests/data/transactions/isolation.yml +3 -0
  167. data/spec/spec_tests/data/transactions/mongos-pin-auto.yml +1671 -0
  168. data/spec/spec_tests/data/transactions/mongos-recovery-token.yml +347 -0
  169. data/spec/spec_tests/data/transactions/pin-mongos.yml +557 -0
  170. data/spec/spec_tests/data/transactions/read-concern.yml +3 -0
  171. data/spec/spec_tests/data/transactions/read-pref.yml +3 -0
  172. data/spec/spec_tests/data/transactions/reads.yml +3 -0
  173. data/spec/spec_tests/data/transactions/retryable-abort.yml +5 -2
  174. data/spec/spec_tests/data/transactions/retryable-commit.yml +4 -1
  175. data/spec/spec_tests/data/transactions/retryable-writes.yml +3 -0
  176. data/spec/spec_tests/data/transactions/run-command.yml +3 -0
  177. data/spec/spec_tests/data/transactions/transaction-options.yml +6 -0
  178. data/spec/spec_tests/data/transactions/update.yml +3 -8
  179. data/spec/spec_tests/data/transactions/write-concern.yml +348 -38
  180. data/spec/spec_tests/data/transactions_api/callback-aborts.yml +6 -0
  181. data/spec/spec_tests/data/transactions_api/callback-commits.yml +5 -0
  182. data/spec/spec_tests/data/transactions_api/callback-retry.yml +7 -2
  183. data/spec/spec_tests/data/transactions_api/commit-retry.yml +70 -15
  184. data/spec/spec_tests/data/transactions_api/commit-transienttransactionerror-4.2.yml +3 -0
  185. data/spec/spec_tests/data/transactions_api/commit-transienttransactionerror.yml +3 -0
  186. data/spec/spec_tests/data/transactions_api/commit-writeconcernerror.yml +59 -109
  187. data/spec/spec_tests/data/transactions_api/commit.yml +5 -0
  188. data/spec/spec_tests/data/transactions_api/transaction-options.yml +10 -0
  189. data/spec/spec_tests/retryable_reads_spec.rb +5 -2
  190. data/spec/spec_tests/retryable_writes_spec.rb +5 -2
  191. data/spec/spec_tests/sdam_monitoring_spec.rb +3 -3
  192. data/spec/spec_tests/sdam_spec.rb +2 -2
  193. data/spec/spec_tests/transactions_api_spec.rb +1 -67
  194. data/spec/spec_tests/transactions_spec.rb +2 -66
  195. data/spec/support/authorization.rb +4 -0
  196. data/spec/support/change_streams.rb +30 -10
  197. data/spec/support/change_streams/operation.rb +27 -0
  198. data/spec/support/client_registry.rb +44 -25
  199. data/spec/support/cluster_config.rb +25 -14
  200. data/spec/support/cluster_tools.rb +32 -10
  201. data/spec/support/command_monitoring.rb +1 -1
  202. data/spec/support/common_shortcuts.rb +30 -0
  203. data/spec/support/connection_string.rb +8 -3
  204. data/spec/support/constraints.rb +34 -0
  205. data/spec/support/crud.rb +31 -16
  206. data/spec/support/crud/context.rb +23 -0
  207. data/spec/support/crud/operation.rb +311 -14
  208. data/spec/support/crud/spec.rb +2 -1
  209. data/spec/support/crud/test.rb +24 -27
  210. data/spec/support/crud/test_base.rb +22 -0
  211. data/spec/support/crud/verifier.rb +15 -1
  212. data/spec/support/event_subscriber.rb +12 -0
  213. data/spec/support/sdam_formatter_integration.rb +12 -6
  214. data/spec/support/shared/server_selector.rb +10 -0
  215. data/spec/support/shared/session.rb +13 -12
  216. data/spec/support/spec_config.rb +32 -22
  217. data/spec/support/spec_setup.rb +2 -2
  218. data/spec/support/transactions.rb +87 -0
  219. data/spec/support/transactions/context.rb +33 -0
  220. data/spec/support/transactions/operation.rb +99 -349
  221. data/spec/support/transactions/spec.rb +1 -3
  222. data/spec/support/transactions/test.rb +110 -49
  223. data/spec/support/utils.rb +74 -1
  224. metadata +52 -10
  225. metadata.gz.sig +0 -0
  226. data/spec/support/crud/read.rb +0 -265
  227. data/spec/support/crud/write.rb +0 -284
@@ -20,6 +20,7 @@ module Mongo
20
20
  #
21
21
  # @since 2.0.0
22
22
  module Specifiable
23
+ include Unpinnable
23
24
 
24
25
  # The field for database name.
25
26
  #
@@ -564,6 +565,45 @@ module Mongo
564
565
  def acknowledged_write?
565
566
  write_concern.nil? || write_concern.acknowledged?
566
567
  end
568
+
569
+ private
570
+
571
+ def validate_result(result)
572
+ unpin_maybe(session) do
573
+ add_error_labels do
574
+ result.validate!
575
+ end
576
+ end
577
+ end
578
+
579
+ # Adds error labels to exceptions raised in the yielded to block,
580
+ # which should perform MongoDB operations and raise Mongo::Errors on
581
+ # failure. This method handles network errors (Error::SocketError)
582
+ # and server-side errors (Error::OperationFailure); it does not
583
+ # handle server selection errors (Error::NoServerAvailable), for which
584
+ # labels are added in the server selection code.
585
+ def add_error_labels
586
+ begin
587
+ yield
588
+ rescue Mongo::Error::SocketError => e
589
+ if session && session.in_transaction? && !session.committing_transaction?
590
+ e.add_label('TransientTransactionError')
591
+ end
592
+ if session && session.committing_transaction?
593
+ e.add_label('UnknownTransactionCommitResult')
594
+ end
595
+ raise e
596
+ rescue Mongo::Error::OperationFailure => e
597
+ if session && session.committing_transaction?
598
+ if e.write_retryable? || e.wtimeout? || (e.write_concern_error? &&
599
+ !Session::UNLABELED_WRITE_CONCERN_CODES.include?(e.write_concern_error_code)
600
+ ) || e.max_time_ms_expired?
601
+ e.add_label('UnknownTransactionCommitResult')
602
+ end
603
+ end
604
+ raise e
605
+ end
606
+ end
567
607
  end
568
608
  end
569
609
  end
@@ -0,0 +1,39 @@
1
+ # Copyright (C) 2014-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
+ module Operation
17
+ module Unpinnable
18
+
19
+ private
20
+
21
+ # Unpins the session if the session is pinned and the yielded to block
22
+ # raises errors that are required to unpin the session.
23
+ #
24
+ # @note This method takes the session as an argument because Unpinnable
25
+ # is included in BulkWrite which does not store the session in the
26
+ # receiver (despite Specifiable doing so).
27
+ #
28
+ # @param [ Session | nil ] Session to consider.
29
+ def unpin_maybe(session)
30
+ yield
31
+ rescue Mongo::Error => e
32
+ if session
33
+ session.unpin_maybe(e)
34
+ end
35
+ raise
36
+ end
37
+ end
38
+ end
39
+ end
@@ -39,7 +39,7 @@ module Mongo
39
39
  else
40
40
  self.class::Command.new(spec).execute(server)
41
41
  end
42
- result.validate!
42
+ validate_result(result)
43
43
  end
44
44
 
45
45
  # Execute the bulk write operation.
@@ -192,8 +192,12 @@ module Mongo
192
192
  updates = BSON::Document.new
193
193
  updates.store(Message::Q, filter)
194
194
  updates.store(U, update)
195
- updates.store(MULTI, flags.include?(:multi_update))
196
- updates.store(UPSERT, flags.include?(:upsert))
195
+ if flags.include?(:multi_update)
196
+ updates.store(MULTI, true)
197
+ end
198
+ if flags.include?(:upsert)
199
+ updates.store(UPSERT, true)
200
+ end
197
201
  document.store(UPDATE, collection)
198
202
  document.store(Message::ORDERED, true)
199
203
  document.store(UPDATES, [ updates ])
@@ -118,7 +118,7 @@ module Mongo
118
118
  elsif client.max_read_retries > 0
119
119
  legacy_read_with_retry(session, server_selector, &block)
120
120
  else
121
- server = select_server(cluster, server_selector)
121
+ server = select_server(cluster, server_selector, session)
122
122
  yield server
123
123
  end
124
124
  end
@@ -200,7 +200,7 @@ module Mongo
200
200
  # If we are here, session is not nil. A session being nil would have
201
201
  # failed retry_write_allowed? check.
202
202
 
203
- server = cluster.next_primary
203
+ server = select_server(cluster, ServerSelector.primary, session)
204
204
 
205
205
  unless ending_transaction || server.retry_writes?
206
206
  return legacy_write_with_retry(server, session, &block)
@@ -213,12 +213,75 @@ module Mongo
213
213
  if session.in_transaction? && !ending_transaction
214
214
  raise
215
215
  end
216
- retry_write(e, txn_num, &block)
216
+ retry_write(e, session, txn_num, &block)
217
217
  rescue Error::OperationFailure => e
218
218
  if (session.in_transaction? && !ending_transaction) || !e.write_retryable?
219
219
  raise
220
220
  end
221
- retry_write(e, txn_num, &block)
221
+ retry_write(e, session, txn_num, &block)
222
+ end
223
+ end
224
+
225
+ # Retryable writes wrapper for operations not supporting modern retryable
226
+ # writes.
227
+ #
228
+ # If the driver is configured to use modern retryable writes, this method
229
+ # yields to the passed block exactly once, thus not retrying any writes.
230
+ #
231
+ # If the driver is configured to use legacy retryable writes, this method
232
+ # delegates to legacy_write_with_retry which performs write retries using
233
+ # legacy logic.
234
+ #
235
+ # @param [ nil | Session ] session Optional session to use with the operation.
236
+ # @param [ nil | Hash | WriteConcern::Base ] write_concern The write concern.
237
+ #
238
+ # @yieldparam [ Server ] server The server to which the write should be sent.
239
+ #
240
+ # @api private
241
+ def nro_write_with_retry(session, write_concern, &block)
242
+ if session && session.client.options[:retry_writes]
243
+ server = select_server(cluster, ServerSelector.primary, session)
244
+ yield server
245
+ else
246
+ legacy_write_with_retry(nil, session, &block)
247
+ end
248
+ end
249
+
250
+ # Implements legacy write retrying functionality by yielding to the passed
251
+ # block one or more times.
252
+ #
253
+ # This method is used for operations which are not supported by modern
254
+ # retryable writes, such as delete_many and update_many.
255
+ #
256
+ # @param [ Server ] server The server which should be used for the
257
+ # operation. If not provided, the current primary will be retrieved from
258
+ # the cluster.
259
+ # @param [ nil | Session ] session Optional session to use with the operation.
260
+ #
261
+ # @yieldparam [ Server ] server The server to which the write should be sent.
262
+ #
263
+ # @api private
264
+ def legacy_write_with_retry(server = nil, session = nil)
265
+ # This is the pre-session retry logic, and is not subject to
266
+ # current retryable write specifications.
267
+ # In particular it does not retry on SocketError and SocketTimeoutError.
268
+ attempt = 0
269
+ begin
270
+ attempt += 1
271
+ server ||= select_server(cluster, ServerSelector.primary, session)
272
+ yield server
273
+ rescue Error::OperationFailure => e
274
+ server = nil
275
+ if attempt > client.max_write_retries
276
+ raise
277
+ end
278
+ if e.write_retryable? && !(session && session.in_transaction?)
279
+ log_retry(e, message: 'Legacy write retry')
280
+ cluster.scan!(false)
281
+ retry
282
+ else
283
+ raise
284
+ end
222
285
  end
223
286
  end
224
287
 
@@ -226,25 +289,25 @@ module Mongo
226
289
 
227
290
  def modern_read_with_retry(session, server_selector, &block)
228
291
  attempt = 0
229
- server = select_server(cluster, server_selector)
292
+ server = select_server(cluster, server_selector, session)
230
293
  begin
231
294
  yield server
232
295
  rescue Error::SocketError, Error::SocketTimeoutError => e
233
296
  if session.in_transaction?
234
297
  raise
235
298
  end
236
- retry_read(e, server_selector, &block)
299
+ retry_read(e, server_selector, session, &block)
237
300
  rescue Error::OperationFailure => e
238
301
  if session.in_transaction? || !e.write_retryable?
239
302
  raise
240
303
  end
241
- retry_read(e, server_selector, &block)
304
+ retry_read(e, server_selector, session, &block)
242
305
  end
243
306
  end
244
307
 
245
308
  def legacy_read_with_retry(session, server_selector)
246
309
  attempt = 0
247
- server = select_server(cluster, server_selector)
310
+ server = select_server(cluster, server_selector, session)
248
311
  begin
249
312
  attempt += 1
250
313
  yield server
@@ -253,7 +316,7 @@ module Mongo
253
316
  raise
254
317
  end
255
318
  log_retry(e, message: 'Legacy read retry')
256
- server = select_server(cluster, server_selector)
319
+ server = select_server(cluster, server_selector, session)
257
320
  retry
258
321
  rescue Error::OperationFailure => e
259
322
  if cluster.sharded? && e.retryable? && !(session && session.in_transaction?)
@@ -262,7 +325,7 @@ module Mongo
262
325
  end
263
326
  log_retry(e, message: 'Legacy read retry')
264
327
  sleep(client.read_retry_interval)
265
- server = select_server(cluster, server_selector)
328
+ server = select_server(cluster, server_selector, session)
266
329
  retry
267
330
  else
268
331
  raise
@@ -285,9 +348,9 @@ module Mongo
285
348
  end
286
349
  end
287
350
 
288
- def retry_read(original_error, server_selector, &block)
351
+ def retry_read(original_error, server_selector, session, &block)
289
352
  begin
290
- server = select_server(cluster, server_selector)
353
+ server = select_server(cluster, server_selector, session)
291
354
  rescue
292
355
  raise original_error
293
356
  end
@@ -306,13 +369,13 @@ module Mongo
306
369
  end
307
370
  end
308
371
 
309
- def retry_write(original_error, txn_num, &block)
372
+ def retry_write(original_error, session, txn_num, &block)
310
373
  # We do not request a scan of the cluster here, because error handling
311
374
  # for the error which triggered the retry should have updated the
312
375
  # server description and/or topology as necessary (specifically,
313
376
  # a socket error or a not master error should have marked the respective
314
377
  # server unknown). Here we just need to wait for server selection.
315
- server = cluster.next_primary
378
+ server = select_server(cluster, ServerSelector.primary, session)
316
379
  raise original_error unless (server.retry_writes? && txn_num)
317
380
  log_retry(original_error, message: 'Write retry')
318
381
  yield(server, txn_num, true)
@@ -325,33 +388,10 @@ module Mongo
325
388
  raise original_error
326
389
  end
327
390
 
328
- def legacy_write_with_retry(server = nil, session = nil)
329
- # This is the pre-session retry logic, and is not subject to
330
- # current retryable write specifications.
331
- # In particular it does not retry on SocketError and SocketTimeoutError.
332
- attempt = 0
333
- begin
334
- attempt += 1
335
- yield(server || cluster.next_primary)
336
- rescue Error::OperationFailure => e
337
- server = nil
338
- if attempt > client.max_write_retries
339
- raise
340
- end
341
- if e.write_retryable? && !(session && session.in_transaction?)
342
- log_retry(e, message: 'Legacy write retry')
343
- cluster.scan!(false)
344
- retry
345
- else
346
- raise
347
- end
348
- end
349
- end
350
-
351
391
  # This is a separate method to make it possible for the test suite to
352
392
  # assert that server selection is performed during retry attempts.
353
- def select_server(cluster, server_selector)
354
- server_selector.select_server(cluster)
393
+ def select_server(cluster, server_selector, session)
394
+ server_selector.select_server(cluster, nil, session)
355
395
  end
356
396
 
357
397
  # Log a warning so that any application slow down is immediately obvious.
@@ -175,9 +175,16 @@ module Mongo
175
175
  def do_connect
176
176
  socket = address.socket(socket_timeout, ssl_options,
177
177
  connect_timeout: address.connect_timeout)
178
- handshake!(socket)
179
- pending_connection = PendingConnection.new(socket, @server, monitoring, options)
180
- authenticate!(pending_connection)
178
+
179
+ begin
180
+ handshake!(socket)
181
+ pending_connection = PendingConnection.new(socket, @server, monitoring, options)
182
+ authenticate!(pending_connection)
183
+ rescue Exception
184
+ socket.close
185
+ raise
186
+ end
187
+
181
188
  socket
182
189
  end
183
190
  private :do_connect
@@ -217,7 +217,7 @@ module Mongo
217
217
  # @return [ Features ] features The features for the server.
218
218
  def features
219
219
  if unknown?
220
- raise ArgumentError, "An unknown server's features are not known"
220
+ return Features.new(0..0, address.to_s)
221
221
  end
222
222
  @features
223
223
  end
@@ -705,6 +705,30 @@ module Mongo
705
705
  end
706
706
  alias_method :eql?, :==
707
707
 
708
+ # @api private
709
+ def server_version_gte?(version)
710
+ required_wv = case version
711
+ when '4.2'
712
+ 8
713
+ when '4.0'
714
+ 7
715
+ when '3.6'
716
+ 6
717
+ when '3.4'
718
+ 5
719
+ when '3.2'
720
+ 4
721
+ when '3.0'
722
+ 3
723
+ when '2.6'
724
+ 2
725
+ else
726
+ raise ArgumentError, "Bogus required version #{version}"
727
+ end
728
+
729
+ required_wv >= min_wire_version && required_wv <= max_wire_version
730
+ end
731
+
708
732
  private
709
733
 
710
734
  def compare_config(other)
@@ -235,7 +235,7 @@ module Mongo
235
235
  end
236
236
 
237
237
  def retry_message
238
- "Retrying ismaster on #{address}"
238
+ "Retrying ismaster in monitor for #{address}"
239
239
  end
240
240
  end
241
241
  end
@@ -74,5 +74,15 @@ module Mongo
74
74
  Mongo::Lint.validate_underscore_read_preference(preference)
75
75
  PREFERENCES.fetch((preference[:mode] || :primary).to_sym).new(preference)
76
76
  end
77
+
78
+ # Returns the primary server selector.
79
+ #
80
+ # A call to this method is equivalent to `get(mode: :primary)`, except the
81
+ # resulting server selector object is cached and not recreated each time.
82
+ #
83
+ # @api private
84
+ def primary
85
+ @primary ||= get(mode: :primary)
86
+ end
77
87
  end
78
88
  end
@@ -92,17 +92,85 @@ module Mongo
92
92
  "#<#{self.class.name}:0x#{object_id} tag_sets=#{tag_sets.inspect} max_staleness=#{max_staleness.inspect}>"
93
93
  end
94
94
 
95
- # Select a server from eligible candidates.
96
- #
97
- # @example Select a server from the cluster.
98
- # selector.select_server(cluster)
99
- #
100
- # @param [ Mongo::Cluster ] cluster The cluster from which to select an eligible server.
95
+ # Select a server from the specified cluster, taking into account
96
+ # mongos pinning for the specified session.
97
+ #
98
+ # If the session is given and has a pinned server, this server is the
99
+ # only server considered for selection. If the server is of type mongos,
100
+ # it is returned immediately; otherwise monitoring checks on this
101
+ # server are initiated to update its status, and if the server becomes
102
+ # a mongos within the server selection timeout, it is returned.
103
+ #
104
+ # If no session is given or the session does not have a pinned server,
105
+ # normal server selection process is performed among all servers in the
106
+ # specified cluster matching the preference of this server selector
107
+ # object. Monitoring checks are initiated on servers in the cluster until
108
+ # a suitable server is found, up to the server selection timeout.
109
+ #
110
+ # If a suitable server is not found within the server selection timeout,
111
+ # this method raises Error::NoServerAvailable.
112
+ #
113
+ # @param [ Mongo::Cluster ] cluster The cluster from which to select
114
+ # an eligible server.
115
+ # @param [ true, false ] ping Whether to ping the server before selection.
116
+ # Deprecated and ignored.
117
+ # @param [ Session | nil ] session Optional session to take into account
118
+ # for mongos pinning. Added in version 2.10.0.
101
119
  #
102
120
  # @return [ Mongo::Server ] A server matching the server preference.
103
121
  #
122
+ # @raise [ Error::NoServerAvailable ] No server was found matching the
123
+ # specified preference / pinning requirement in the server selection
124
+ # timeout.
125
+ # @raise [ Error::LintError ] An unexpected condition was detected, and
126
+ # lint mode is enabled.
127
+ #
104
128
  # @since 2.0.0
105
- def select_server(cluster, ping = nil)
129
+ def select_server(cluster, ping = nil, session = nil)
130
+ server_selection_timeout = cluster.options[:server_selection_timeout] || SERVER_SELECTION_TIMEOUT
131
+
132
+ # Special handling for zero timeout: if we have to select a server,
133
+ # and the timeout is zero, fail immediately (since server selection
134
+ # will take some non-zero amount of time in any case).
135
+ if server_selection_timeout == 0
136
+ msg = "Failing server selection due to zero timeout. " +
137
+ " Requested #{name} in cluster: #{cluster.summary}"
138
+ raise Error::NoServerAvailable.new(self, cluster, msg)
139
+ end
140
+
141
+ deadline = Time.now + server_selection_timeout
142
+
143
+ if session && session.pinned_server
144
+ if Mongo::Lint.enabled?
145
+ unless cluster.sharded?
146
+ raise Error::LintError, "Session has a pinned server in a non-sharded topology: #{topology}"
147
+ end
148
+ end
149
+
150
+ if !session.in_transaction?
151
+ session.unpin
152
+ end
153
+
154
+ if server = session.pinned_server
155
+ # Here we assume that a mongos stays in the topology indefinitely.
156
+ # This will no longer be the case once SRV polling is implemented.
157
+
158
+ unless server.mongos?
159
+ while (time_remaining = deadline - Time.now) > 0
160
+ wait_for_server_selection(cluster, time_remaining)
161
+ end
162
+
163
+ unless server.mongos?
164
+ msg = "The session being used is pinned to the server which is not a mongos: #{server.summary} " +
165
+ "(after #{server_selection_timeout} seconds)"
166
+ raise Error::NoServerAvailable.new(self, cluster, msg)
167
+ end
168
+ end
169
+
170
+ return server
171
+ end
172
+ end
173
+
106
174
  if cluster.replica_set?
107
175
  validate_max_staleness_value_early!
108
176
  end
@@ -121,10 +189,7 @@ module Mongo
121
189
  raise Error::NoServerAvailable.new(self, cluster, msg)
122
190
  end
123
191
  =end
124
- @local_threshold = cluster.options[:local_threshold] || LOCAL_THRESHOLD
125
- @server_selection_timeout = cluster.options[:server_selection_timeout] || SERVER_SELECTION_TIMEOUT
126
- deadline = Time.now + server_selection_timeout
127
- while (time_remaining = deadline - Time.now) > 0
192
+ loop do
128
193
  servers = candidates(cluster)
129
194
  if Lint.enabled?
130
195
  servers.each do |server|
@@ -152,36 +217,41 @@ module Mongo
152
217
  raise Error::NoServerAvailable.new(self, cluster, msg)
153
218
  end
154
219
 
220
+ if session && session.starting_transaction? && cluster.sharded?
221
+ session.pin(server)
222
+ end
223
+
155
224
  return server
156
225
  end
226
+
157
227
  cluster.scan!(false)
158
- if cluster.server_selection_semaphore
159
- cluster.server_selection_semaphore.wait(time_remaining)
228
+
229
+ time_remaining = deadline - Time.now
230
+ if time_remaining > 0
231
+ wait_for_server_selection(cluster, time_remaining)
232
+
233
+ # If we wait for server selection, perform another round of
234
+ # attempting to locate a suitable server. Otherwise server selection
235
+ # can raise NoServerAvailable message when the diagnostics
236
+ # reports an available server of the requested type.
160
237
  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
238
+ break
165
239
  end
166
240
  end
167
241
 
168
242
  msg = "No #{name} server is available in cluster: #{cluster.summary} " +
169
243
  "with timeout=#{server_selection_timeout}, " +
170
- "LT=#{local_threshold}"
171
- dead_monitors = []
172
- cluster.servers_list.each do |server|
173
- thread = server.monitor.instance_variable_get('@thread')
174
- if thread.nil? || !thread.alive?
175
- dead_monitors << server
176
- end
177
- end
178
- if dead_monitors.any?
179
- msg += ". The following servers have dead monitor threads: #{dead_monitors.map(&:summary).join(', ')}"
244
+ "LT=#{local_threshold_with_cluster(cluster)}"
245
+ msg += server_selection_diagnostic_message(cluster)
246
+ raise Error::NoServerAvailable.new(self, cluster, msg)
247
+ rescue Error::NoServerAvailable => e
248
+ if session && session.in_transaction? && !session.committing_transaction?
249
+ e.add_label('TransientTransactionError')
180
250
  end
181
- unless cluster.connected?
182
- msg += ". The cluster is disconnected (client may have been closed)"
251
+ if session && session.committing_transaction?
252
+ e.add_label('UnknownTransactionCommitResult')
183
253
  end
184
- raise Error::NoServerAvailable.new(self, cluster, msg)
254
+ raise e
185
255
  end
186
256
 
187
257
  # Get the timeout for server selection.
@@ -200,6 +270,10 @@ module Mongo
200
270
  (options[:server_selection_timeout] || ServerSelector::SERVER_SELECTION_TIMEOUT)
201
271
  end
202
272
 
273
+ def local_threshold_with_cluster(cluster)
274
+ options[:local_threshold] || cluster.options[:local_threshold] || LOCAL_THRESHOLD
275
+ end
276
+
203
277
  # Get the local threshold boundary for nearest selection in seconds.
204
278
  #
205
279
  # @example Get the local threshold.
@@ -229,7 +303,10 @@ module Mongo
229
303
  if cluster.single?
230
304
  cluster.servers.each { |server| validate_max_staleness_support!(server) }
231
305
  elsif cluster.sharded?
232
- near_servers(cluster.servers).each { |server| validate_max_staleness_support!(server) }
306
+ local_threshold = local_threshold_with_cluster(cluster)
307
+ near_servers(cluster.servers, local_threshold).each do |server|
308
+ validate_max_staleness_support!(server)
309
+ end
233
310
  else
234
311
  validate_max_staleness_value!(cluster) unless cluster.unknown?
235
312
  select(cluster.servers)
@@ -276,13 +353,17 @@ module Mongo
276
353
  #
277
354
  # @param [ Array ] candidates List of candidate servers to select the
278
355
  # near servers from.
356
+ # @param [ Integer ] local_threshold Local threshold. This parameter
357
+ # will be required in driver version 3.0.
279
358
  #
280
359
  # @return [ Array ] The near servers.
281
360
  #
282
361
  # @since 2.0.0
283
- def near_servers(candidates = [])
362
+ def near_servers(candidates = [], local_threshold = nil)
284
363
  return candidates if candidates.empty?
285
364
  nearest_server = candidates.min_by(&:average_round_trip_time)
365
+ # Default for legacy signarure
366
+ local_threshold ||= self.local_threshold
286
367
  threshold = nearest_server.average_round_trip_time + local_threshold
287
368
  candidates.select { |server| server.average_round_trip_time <= threshold }.shuffle!
288
369
  end
@@ -363,6 +444,65 @@ module Mongo
363
444
  end
364
445
  end
365
446
  end
447
+
448
+ # Waits for server state changes in the specified cluster.
449
+ #
450
+ # If the cluster has a server selection semaphore, waits on that
451
+ # semaphore up to the specified remaining time. Any change in server
452
+ # state resulting from SDAM will immediately wake up this method and
453
+ # cause it to return.
454
+ #
455
+ # If the cluster des not have a server selection semaphore, waits
456
+ # the smaller of 0.25 seconds and the specified remaining time.
457
+ # This functionality is provided for backwards compatibilty only for
458
+ # applications directly invoking the server selection process.
459
+ # If lint mode is enabled and the cluster does not have a server
460
+ # selection semaphore, Error::LintError will be raised.
461
+ #
462
+ # @param [ Cluster ] cluster The cluster to wait for.
463
+ # @param [ Numeric ] time_remaining Maximum time to wait, in seconds.
464
+ def wait_for_server_selection(cluster, time_remaining)
465
+ if cluster.server_selection_semaphore
466
+ cluster.server_selection_semaphore.wait(time_remaining)
467
+ else
468
+ if Lint.enabled?
469
+ raise Error::LintError, 'Waiting for server selection without having a server selection semaphore'
470
+ end
471
+ sleep [time_remaining, 0.25].min
472
+ end
473
+ end
474
+
475
+ # Creates a diagnostic message when server selection fails.
476
+ #
477
+ # The diagnostic message includes the following information, as applicable:
478
+ #
479
+ # - Servers having dead monitor threads
480
+ # - Cluster is disconnected
481
+ #
482
+ # If none of the conditions for diagnostic messages apply, an empty string
483
+ # is returned.
484
+ #
485
+ # @param [ Cluster ] cluster The cluster on which server selection was
486
+ # performed.
487
+ #
488
+ # @return [ String ] The diagnostic message.
489
+ def server_selection_diagnostic_message(cluster)
490
+ msg = ''
491
+ dead_monitors = []
492
+ cluster.servers_list.each do |server|
493
+ thread = server.monitor.instance_variable_get('@thread')
494
+ if thread.nil? || !thread.alive?
495
+ dead_monitors << server
496
+ end
497
+ end
498
+ if dead_monitors.any?
499
+ msg += ". The following servers have dead monitor threads: #{dead_monitors.map(&:summary).join(', ')}"
500
+ end
501
+ unless cluster.connected?
502
+ msg += ". The cluster is disconnected (client may have been closed)"
503
+ end
504
+ msg
505
+ end
366
506
  end
367
507
  end
368
508
  end