mongo 2.9.2 → 2.10.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 (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