mongo 2.20.1 → 2.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -0
  3. data/Rakefile +2 -2
  4. data/lib/mongo/address.rb +22 -3
  5. data/lib/mongo/auth/aws/credentials_retriever.rb +70 -17
  6. data/lib/mongo/auth/base.rb +1 -1
  7. data/lib/mongo/bulk_write.rb +35 -2
  8. data/lib/mongo/client.rb +38 -6
  9. data/lib/mongo/client_encryption.rb +6 -3
  10. data/lib/mongo/cluster/reapers/cursor_reaper.rb +6 -1
  11. data/lib/mongo/cluster/sdam_flow.rb +20 -7
  12. data/lib/mongo/cluster.rb +14 -4
  13. data/lib/mongo/collection/helpers.rb +1 -1
  14. data/lib/mongo/collection/view/aggregation/behavior.rb +131 -0
  15. data/lib/mongo/collection/view/aggregation.rb +33 -99
  16. data/lib/mongo/collection/view/builder/aggregation.rb +1 -7
  17. data/lib/mongo/collection/view/change_stream.rb +80 -27
  18. data/lib/mongo/collection/view/iterable.rb +76 -60
  19. data/lib/mongo/collection/view/map_reduce.rb +25 -8
  20. data/lib/mongo/collection/view/readable.rb +79 -30
  21. data/lib/mongo/collection/view/writable.rb +109 -48
  22. data/lib/mongo/collection/view.rb +43 -3
  23. data/lib/mongo/collection.rb +158 -23
  24. data/lib/mongo/crypt/auto_encrypter.rb +4 -6
  25. data/lib/mongo/crypt/binding.rb +4 -4
  26. data/lib/mongo/crypt/context.rb +20 -14
  27. data/lib/mongo/crypt/encryption_io.rb +56 -26
  28. data/lib/mongo/crypt/explicit_encrypter.rb +49 -20
  29. data/lib/mongo/crypt/explicit_encryption_context.rb +17 -11
  30. data/lib/mongo/crypt/kms/azure/credentials_retriever.rb +22 -6
  31. data/lib/mongo/crypt/kms/gcp/credentials_retriever.rb +29 -4
  32. data/lib/mongo/csot_timeout_holder.rb +119 -0
  33. data/lib/mongo/cursor/kill_spec.rb +5 -2
  34. data/lib/mongo/cursor/nontailable.rb +27 -0
  35. data/lib/mongo/cursor.rb +86 -24
  36. data/lib/mongo/cursor_host.rb +82 -0
  37. data/lib/mongo/database/view.rb +81 -14
  38. data/lib/mongo/database.rb +88 -18
  39. data/lib/mongo/error/operation_failure.rb +209 -204
  40. data/lib/mongo/error/server_timeout_error.rb +12 -0
  41. data/lib/mongo/error/socket_timeout_error.rb +3 -1
  42. data/lib/mongo/error/timeout_error.rb +23 -0
  43. data/lib/mongo/error.rb +2 -0
  44. data/lib/mongo/grid/fs_bucket.rb +45 -12
  45. data/lib/mongo/grid/stream/read.rb +15 -1
  46. data/lib/mongo/grid/stream/write.rb +21 -4
  47. data/lib/mongo/index/view.rb +77 -16
  48. data/lib/mongo/operation/context.rb +40 -2
  49. data/lib/mongo/operation/create_search_indexes/op_msg.rb +2 -2
  50. data/lib/mongo/operation/delete/op_msg.rb +2 -1
  51. data/lib/mongo/operation/drop_search_index/op_msg.rb +2 -2
  52. data/lib/mongo/operation/find/op_msg.rb +45 -0
  53. data/lib/mongo/operation/get_more/op_msg.rb +33 -0
  54. data/lib/mongo/operation/insert/op_msg.rb +3 -2
  55. data/lib/mongo/operation/insert/result.rb +4 -2
  56. data/lib/mongo/operation/list_collections/result.rb +1 -1
  57. data/lib/mongo/operation/map_reduce/result.rb +1 -1
  58. data/lib/mongo/operation/op_msg_base.rb +3 -1
  59. data/lib/mongo/operation/result.rb +26 -5
  60. data/lib/mongo/operation/shared/executable.rb +12 -1
  61. data/lib/mongo/operation/shared/op_msg_executable.rb +4 -1
  62. data/lib/mongo/operation/shared/response_handling.rb +3 -3
  63. data/lib/mongo/operation/shared/sessions_supported.rb +1 -1
  64. data/lib/mongo/operation/shared/timed.rb +52 -0
  65. data/lib/mongo/operation/shared/write.rb +4 -1
  66. data/lib/mongo/operation/update/op_msg.rb +2 -1
  67. data/lib/mongo/operation/update_search_index/op_msg.rb +2 -2
  68. data/lib/mongo/operation.rb +1 -0
  69. data/lib/mongo/protocol/message.rb +1 -4
  70. data/lib/mongo/protocol/msg.rb +2 -2
  71. data/lib/mongo/retryable/read_worker.rb +69 -29
  72. data/lib/mongo/retryable/write_worker.rb +49 -18
  73. data/lib/mongo/retryable.rb +8 -2
  74. data/lib/mongo/server/connection.rb +11 -5
  75. data/lib/mongo/server/connection_base.rb +22 -2
  76. data/lib/mongo/server/connection_pool.rb +32 -14
  77. data/lib/mongo/server/description/features.rb +1 -1
  78. data/lib/mongo/server/description.rb +18 -5
  79. data/lib/mongo/server/monitor.rb +7 -4
  80. data/lib/mongo/server/pending_connection.rb +7 -3
  81. data/lib/mongo/server/{round_trip_time_averager.rb → round_trip_time_calculator.rb} +25 -7
  82. data/lib/mongo/server.rb +11 -6
  83. data/lib/mongo/server_selector/base.rb +25 -9
  84. data/lib/mongo/session.rb +78 -9
  85. data/lib/mongo/socket/ssl.rb +109 -17
  86. data/lib/mongo/socket/tcp.rb +40 -6
  87. data/lib/mongo/socket.rb +154 -25
  88. data/lib/mongo/uri/options_mapper.rb +1 -0
  89. data/lib/mongo/version.rb +1 -1
  90. data/lib/mongo.rb +1 -0
  91. data/spec/atlas/atlas_connectivity_spec.rb +4 -0
  92. data/spec/atlas/operations_spec.rb +4 -0
  93. data/spec/integration/client_side_encryption/auto_encryption_mongocryptd_spawn_spec.rb +2 -1
  94. data/spec/integration/client_side_encryption/auto_encryption_spec.rb +494 -487
  95. data/spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb +1 -1
  96. data/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb +66 -22
  97. data/spec/integration/client_side_operations_timeout/encryption_prose_spec.rb +131 -0
  98. data/spec/integration/connection_pool_populator_spec.rb +2 -0
  99. data/spec/integration/cursor_pinning_spec.rb +15 -60
  100. data/spec/integration/cursor_reaping_spec.rb +1 -1
  101. data/spec/integration/docs_examples_spec.rb +1 -1
  102. data/spec/integration/operation_failure_code_spec.rb +1 -1
  103. data/spec/integration/operation_failure_message_spec.rb +3 -3
  104. data/spec/integration/retryable_errors_spec.rb +2 -2
  105. data/spec/integration/sdam_error_handling_spec.rb +2 -1
  106. data/spec/integration/search_indexes_prose_spec.rb +4 -0
  107. data/spec/integration/server_spec.rb +4 -3
  108. data/spec/integration/transactions_api_examples_spec.rb +2 -0
  109. data/spec/kerberos/kerberos_spec.rb +4 -0
  110. data/spec/lite_spec_helper.rb +3 -1
  111. data/spec/mongo/auth/user/view_spec.rb +1 -1
  112. data/spec/mongo/caching_cursor_spec.rb +1 -1
  113. data/spec/mongo/client_encryption_spec.rb +1 -0
  114. data/spec/mongo/client_spec.rb +158 -4
  115. data/spec/mongo/collection/view/aggregation_spec.rb +14 -39
  116. data/spec/mongo/collection/view/change_stream_spec.rb +3 -3
  117. data/spec/mongo/collection_spec.rb +5 -6
  118. data/spec/mongo/crypt/auto_encrypter_spec.rb +14 -12
  119. data/spec/mongo/crypt/data_key_context_spec.rb +3 -1
  120. data/spec/mongo/crypt/explicit_encryption_context_spec.rb +2 -2
  121. data/spec/mongo/crypt/handle_spec.rb +1 -1
  122. data/spec/mongo/cursor_spec.rb +26 -9
  123. data/spec/mongo/error/operation_failure_heavy_spec.rb +2 -2
  124. data/spec/mongo/operation/context_spec.rb +79 -0
  125. data/spec/mongo/operation/create/op_msg_spec.rb +106 -110
  126. data/spec/mongo/operation/delete/op_msg_spec.rb +6 -5
  127. data/spec/mongo/operation/find/op_msg_spec.rb +66 -0
  128. data/spec/mongo/operation/get_more/op_msg_spec.rb +65 -0
  129. data/spec/mongo/operation/insert/op_msg_spec.rb +128 -131
  130. data/spec/mongo/operation/shared/csot/examples.rb +113 -0
  131. data/spec/mongo/query_cache_spec.rb +243 -225
  132. data/spec/mongo/retryable_spec.rb +1 -0
  133. data/spec/mongo/server/round_trip_time_calculator_spec.rb +120 -0
  134. data/spec/mongo/socket/ssl_spec.rb +0 -10
  135. data/spec/runners/change_streams/test.rb +2 -2
  136. data/spec/runners/crud/operation.rb +1 -1
  137. data/spec/runners/crud/verifier.rb +3 -1
  138. data/spec/runners/transactions/operation.rb +4 -6
  139. data/spec/runners/unified/ambiguous_operations.rb +13 -0
  140. data/spec/runners/unified/assertions.rb +4 -0
  141. data/spec/runners/unified/change_stream_operations.rb +14 -24
  142. data/spec/runners/unified/crud_operations.rb +82 -59
  143. data/spec/runners/unified/ddl_operations.rb +38 -7
  144. data/spec/runners/unified/grid_fs_operations.rb +37 -2
  145. data/spec/runners/unified/support_operations.rb +43 -4
  146. data/spec/runners/unified/test.rb +22 -10
  147. data/spec/runners/unified.rb +1 -1
  148. data/spec/solo/clean_exit_spec.rb +2 -0
  149. data/spec/spec_tests/client_side_operations_timeout_spec.rb +15 -0
  150. data/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml +3 -1
  151. data/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml +3 -1
  152. data/spec/spec_tests/data/change_streams_unified/change-streams-errors.yml +3 -1
  153. data/spec/spec_tests/data/change_streams_unified/change-streams-pre_and_post_images.yml +1 -1
  154. data/spec/spec_tests/data/change_streams_unified/change-streams-resume-allowlist.yml +1 -1
  155. data/spec/spec_tests/data/change_streams_unified/change-streams-resume-errorLabels.yml +1 -1
  156. data/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml +1 -1
  157. data/spec/spec_tests/data/client_side_encryption/badQueries.yml +2 -1
  158. data/spec/spec_tests/data/client_side_encryption/timeoutMS.yml +67 -0
  159. data/spec/spec_tests/data/client_side_operations_timeout/bulkWrite.yml +87 -0
  160. data/spec/spec_tests/data/client_side_operations_timeout/change-streams.yml +358 -0
  161. data/spec/spec_tests/data/client_side_operations_timeout/close-cursors.yml +129 -0
  162. data/spec/spec_tests/data/client_side_operations_timeout/command-execution.yml +250 -0
  163. data/spec/spec_tests/data/client_side_operations_timeout/convenient-transactions.yml +113 -0
  164. data/spec/spec_tests/data/client_side_operations_timeout/cursors.yml +70 -0
  165. data/spec/spec_tests/data/client_side_operations_timeout/deprecated-options.yml +3982 -0
  166. data/spec/spec_tests/data/client_side_operations_timeout/error-transformations.yml +96 -0
  167. data/spec/spec_tests/data/client_side_operations_timeout/global-timeoutMS.yml +3236 -0
  168. data/spec/spec_tests/data/client_side_operations_timeout/gridfs-advanced.yml +207 -0
  169. data/spec/spec_tests/data/client_side_operations_timeout/gridfs-delete.yml +152 -0
  170. data/spec/spec_tests/data/client_side_operations_timeout/gridfs-download.yml +182 -0
  171. data/spec/spec_tests/data/client_side_operations_timeout/gridfs-find.yml +100 -0
  172. data/spec/spec_tests/data/client_side_operations_timeout/gridfs-upload.yml +249 -0
  173. data/spec/spec_tests/data/client_side_operations_timeout/legacy-timeouts.yml +204 -0
  174. data/spec/spec_tests/data/client_side_operations_timeout/non-tailable-cursors.yml +307 -0
  175. data/spec/spec_tests/data/client_side_operations_timeout/override-collection-timeoutMS.yml +1877 -0
  176. data/spec/spec_tests/data/client_side_operations_timeout/override-operation-timeoutMS.yml +1918 -0
  177. data/spec/spec_tests/data/client_side_operations_timeout/retryability-legacy-timeouts.yml +1676 -0
  178. data/spec/spec_tests/data/client_side_operations_timeout/retryability-timeoutMS.yml +2824 -0
  179. data/spec/spec_tests/data/client_side_operations_timeout/sessions-inherit-timeoutMS.yml +168 -0
  180. data/spec/spec_tests/data/client_side_operations_timeout/sessions-override-operation-timeoutMS.yml +171 -0
  181. data/spec/spec_tests/data/client_side_operations_timeout/sessions-override-timeoutMS.yml +168 -0
  182. data/spec/spec_tests/data/client_side_operations_timeout/tailable-awaitData.yml +247 -0
  183. data/spec/spec_tests/data/client_side_operations_timeout/tailable-non-awaitData.yml +181 -0
  184. data/spec/spec_tests/data/crud_unified/aggregate-write-readPreference.yml +4 -0
  185. data/spec/spec_tests/data/crud_unified/db-aggregate-write-readPreference.yml +4 -0
  186. data/spec/spec_tests/data/crud_unified/find-test-all-options.yml +29 -0
  187. data/spec/spec_tests/server_selection_rtt_spec.rb +6 -6
  188. data/spec/support/certificates/atlas-ocsp-ca.crt +81 -83
  189. data/spec/support/certificates/atlas-ocsp.crt +107 -107
  190. data/spec/support/cluster_tools.rb +3 -3
  191. data/spec/support/common_shortcuts.rb +2 -2
  192. data/spec/support/crypt/encrypted_fields/range-encryptedFields-Date.json +1 -1
  193. data/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalNoPrecision.json +1 -1
  194. data/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalPrecision.json +1 -1
  195. data/spec/support/crypt/encrypted_fields/range-encryptedFields-DoubleNoPrecision.json +1 -1
  196. data/spec/support/crypt/encrypted_fields/range-encryptedFields-DoublePrecision.json +1 -1
  197. data/spec/support/crypt/encrypted_fields/range-encryptedFields-Int.json +1 -1
  198. data/spec/support/crypt/encrypted_fields/range-encryptedFields-Long.json +1 -1
  199. data/spec/support/shared/session.rb +2 -2
  200. data/spec/support/spec_setup.rb +2 -2
  201. data/spec/support/utils.rb +3 -1
  202. metadata +78 -91
  203. data/spec/mongo/server/round_trip_time_averager_spec.rb +0 -48
  204. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml +0 -242
  205. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Correctness.yml +0 -423
  206. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml +0 -183
  207. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml +0 -240
  208. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml +0 -236
  209. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml +0 -253
  210. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml +0 -1688
  211. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml +0 -294
  212. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml +0 -906
  213. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml +0 -1685
  214. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml +0 -1681
  215. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml +0 -1698
  216. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml +0 -330
  217. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml +0 -425
  218. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml +0 -227
  219. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml +0 -328
  220. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml +0 -320
  221. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml +0 -337
  222. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml +0 -914
  223. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml +0 -293
  224. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml +0 -519
  225. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml +0 -912
  226. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml +0 -908
  227. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml +0 -925
  228. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml +0 -326
  229. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml +0 -425
  230. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml +0 -225
  231. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml +0 -324
  232. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml +0 -320
  233. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml +0 -339
  234. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml +0 -242
  235. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml +0 -424
  236. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml +0 -183
  237. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml +0 -240
  238. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml +0 -236
  239. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml +0 -255
  240. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml +0 -242
  241. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Correctness.yml +0 -423
  242. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml +0 -183
  243. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml +0 -240
  244. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml +0 -236
  245. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml +0 -255
  246. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml +0 -44
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2024 MongoDB Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Mongo
18
+ # This class stores operation timeout and provides corresponding helper methods.
19
+ #
20
+ # @api private
21
+ class CsotTimeoutHolder
22
+ def initialize(session: nil, operation_timeouts: {})
23
+ @deadline = calculate_deadline(operation_timeouts, session)
24
+ @operation_timeouts = operation_timeouts
25
+ @timeout_sec = (@deadline - Utils.monotonic_time if @deadline)
26
+ end
27
+
28
+ attr_reader :deadline, :timeout_sec, :operation_timeouts
29
+
30
+ # @return [ true | false ] Whether CSOT is enabled for the operation
31
+ def csot?
32
+ !deadline.nil?
33
+ end
34
+
35
+ # @return [ true | false ] Returns false if CSOT is not enabled, or if
36
+ # CSOT is set to 0 (means unlimited), otherwise true.
37
+ def timeout?
38
+ ![ nil, 0 ].include?(@deadline)
39
+ end
40
+
41
+ # @return [ Float | nil ] Returns the remaining seconds of the timeout
42
+ # set for the operation; if no timeout is set, or the timeout is 0
43
+ # (means unlimited) returns nil.
44
+ def remaining_timeout_sec
45
+ return nil unless timeout?
46
+
47
+ deadline - Utils.monotonic_time
48
+ end
49
+
50
+ def remaining_timeout_sec!
51
+ check_timeout!
52
+ remaining_timeout_sec
53
+ end
54
+
55
+ # @return [ Integer | nil ] Returns the remaining milliseconds of the timeout
56
+ # set for the operation; if no timeout is set, or the timeout is 0
57
+ # (means unlimited) returns nil.
58
+ def remaining_timeout_ms
59
+ seconds = remaining_timeout_sec
60
+ return nil if seconds.nil?
61
+
62
+ (seconds * 1_000).to_i
63
+ end
64
+
65
+ def remaining_timeout_ms!
66
+ check_timeout!
67
+ remaining_timeout_ms
68
+ end
69
+
70
+ # @return [ true | false ] Whether the timeout for the operation expired.
71
+ # If no timeout set, this method returns false.
72
+ def timeout_expired?
73
+ if timeout?
74
+ Utils.monotonic_time >= deadline
75
+ else
76
+ false
77
+ end
78
+ end
79
+
80
+ # Check whether the operation timeout expired, and raises an appropriate
81
+ # error if yes.
82
+ #
83
+ # @raise [ Error::TimeoutError ]
84
+ def check_timeout!
85
+ return unless timeout_expired?
86
+
87
+ raise Error::TimeoutError, "Operation took more than #{timeout_sec} seconds"
88
+ end
89
+
90
+ private
91
+
92
+ def calculate_deadline(opts = {}, session = nil)
93
+ check_no_override_inside_transaction!(opts, session)
94
+ return session&.with_transaction_deadline if session&.with_transaction_deadline
95
+
96
+ if (operation_timeout_ms = opts[:operation_timeout_ms])
97
+ calculate_deadline_from_timeout_ms(operation_timeout_ms)
98
+ elsif (inherited_timeout_ms = opts[:inherited_timeout_ms])
99
+ calculate_deadline_from_timeout_ms(inherited_timeout_ms)
100
+ end
101
+ end
102
+
103
+ def check_no_override_inside_transaction!(opts, session)
104
+ return unless opts[:operation_timeout_ms] && session&.with_transaction_deadline
105
+
106
+ raise ArgumentError, 'Cannot override timeout_ms inside with_transaction block'
107
+ end
108
+
109
+ def calculate_deadline_from_timeout_ms(operation_timeout_ms)
110
+ if operation_timeout_ms.positive?
111
+ Utils.monotonic_time + (operation_timeout_ms / 1_000.0)
112
+ elsif operation_timeout_ms.zero?
113
+ 0
114
+ elsif operation_timeout_ms.negative?
115
+ raise ArgumentError, "timeout_ms must be a non-negative integer but #{operation_timeout_ms} given"
116
+ end
117
+ end
118
+ end
119
+ end
@@ -31,7 +31,8 @@ module Mongo
31
31
  db_name:,
32
32
  connection_global_id:,
33
33
  server_address:,
34
- session:
34
+ session:,
35
+ connection: nil
35
36
  )
36
37
  @cursor_id = cursor_id
37
38
  @coll_name = coll_name
@@ -39,6 +40,7 @@ module Mongo
39
40
  @connection_global_id = connection_global_id
40
41
  @server_address = server_address
41
42
  @session = session
43
+ @connection = connection
42
44
  end
43
45
 
44
46
  attr_reader :cursor_id,
@@ -46,7 +48,8 @@ module Mongo
46
48
  :db_name,
47
49
  :connection_global_id,
48
50
  :server_address,
49
- :session
51
+ :session,
52
+ :connection
50
53
 
51
54
  def ==(other)
52
55
  cursor_id == other.cursor_id &&
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongo
4
+ class Cursor
5
+ # This module is used by cursor-implementing classes to indicate that
6
+ # the only cursors they generate are non-tailable, and iterable.
7
+ #
8
+ # @api private
9
+ module NonTailable
10
+ # These views are always non-tailable.
11
+ #
12
+ # @return [ nil ] indicating a non-tailable cursor.
13
+ def cursor_type
14
+ nil
15
+ end
16
+
17
+ # These views apply timeouts to each iteration of a cursor, as
18
+ # opposed to the entire lifetime of the cursor.
19
+ #
20
+ # @return [ :iterable ] indicating a cursor with a timeout mode of
21
+ # "iterable".
22
+ def timeout_mode
23
+ :iterable
24
+ end
25
+ end
26
+ end
27
+ end
data/lib/mongo/cursor.rb CHANGED
@@ -49,6 +49,9 @@ module Mongo
49
49
  # @api private
50
50
  attr_reader :resume_token
51
51
 
52
+ # @return [ Operation::Context ] context the context for this cursor
53
+ attr_reader :context
54
+
52
55
  # Creates a +Cursor+ object.
53
56
  #
54
57
  # @example Instantiate the cursor.
@@ -59,6 +62,8 @@ module Mongo
59
62
  # @param [ Server ] server The server this cursor is locked to.
60
63
  # @param [ Hash ] options The cursor options.
61
64
  #
65
+ # @option options [ Operation::Context ] :context The operation context
66
+ # for this cursor.
62
67
  # @option options [ true, false ] :disable_retry Whether to disable
63
68
  # retrying on error when sending getMore operations (deprecated, getMore
64
69
  # operations are no longer retried)
@@ -80,15 +85,25 @@ module Mongo
80
85
  if @cursor_id.nil?
81
86
  raise ArgumentError, 'Cursor id must be present in the result'
82
87
  end
83
- @connection_global_id = result.connection_global_id
84
88
  @options = options
85
89
  @session = @options[:session]
90
+ @connection_global_id = result.connection_global_id
91
+ @context = @options[:context]&.with(connection_global_id: connection_global_id_for_context) || fresh_context
86
92
  @explicitly_closed = false
87
93
  @lock = Mutex.new
88
- unless closed?
94
+ if server.load_balancer?
95
+ # We need the connection in the cursor only in load balanced topology;
96
+ # we do not need an additional reference to it otherwise.
97
+ @connection = @initial_result.connection
98
+ end
99
+ if closed?
100
+ check_in_connection
101
+ else
89
102
  register
90
- ObjectSpace.define_finalizer(self, self.class.finalize(kill_spec(@connection_global_id),
91
- cluster))
103
+ ObjectSpace.define_finalizer(
104
+ self,
105
+ self.class.finalize(kill_spec(@connection_global_id), cluster)
106
+ )
92
107
  end
93
108
  end
94
109
 
@@ -98,6 +113,9 @@ module Mongo
98
113
  # @api private
99
114
  attr_reader :initial_result
100
115
 
116
+ # @api private
117
+ attr_reader :connection
118
+
101
119
  # Finalize the cursor for garbage collection. Schedules this cursor to be included
102
120
  # in a killCursors operation executed by the Cluster's CursorReaper.
103
121
  #
@@ -284,9 +302,11 @@ module Mongo
284
302
  # the server.
285
303
  #
286
304
  # @return [ nil ] Always nil.
287
- def close
305
+ def close(opts = {})
288
306
  return if closed?
289
307
 
308
+ ctx = context ? context.refresh(timeout_ms: opts[:timeout_ms]) : fresh_context(opts)
309
+
290
310
  unregister
291
311
  read_with_one_retry do
292
312
  spec = {
@@ -295,11 +315,11 @@ module Mongo
295
315
  cursor_ids: [id],
296
316
  }
297
317
  op = Operation::KillCursors.new(spec)
298
- execute_operation(op)
318
+ execute_operation(op, context: ctx)
299
319
  end
300
320
 
301
321
  nil
302
- rescue Error::OperationFailure, Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable
322
+ rescue Error::OperationFailure::Family, Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable
303
323
  # Errors are swallowed since there is noting can be done by handling them.
304
324
  ensure
305
325
  end_session
@@ -307,6 +327,7 @@ module Mongo
307
327
  @lock.synchronize do
308
328
  @explicitly_closed = true
309
329
  end
330
+ check_in_connection
310
331
  end
311
332
 
312
333
  # Get the parsed collection name.
@@ -387,6 +408,7 @@ module Mongo
387
408
  connection_global_id: connection_global_id,
388
409
  server_address: server.address,
389
410
  session: @session,
411
+ connection: @connection
390
412
  )
391
413
  end
392
414
 
@@ -434,15 +456,7 @@ module Mongo
434
456
  # 3.2+ servers use batch_size, 3.0- servers use to_return.
435
457
  # TODO should to_return be calculated in the operation layer?
436
458
  batch_size: batch_size_for_get_more,
437
- to_return: to_return,
438
- max_time_ms: if view.respond_to?(:max_await_time_ms) &&
439
- view.max_await_time_ms &&
440
- view.options[:await_data]
441
- then
442
- view.max_await_time_ms
443
- else
444
- nil
445
- end,
459
+ to_return: to_return
446
460
  }
447
461
  if view.respond_to?(:options) && view.options.is_a?(Hash)
448
462
  spec[:comment] = view.options[:comment] unless view.options[:comment].nil?
@@ -464,7 +478,10 @@ module Mongo
464
478
  # the @cursor_id may be zero (all results fit in the first batch).
465
479
  # Thus we need to check both @cursor_id and the cursor_id of the result
466
480
  # prior to calling unregister here.
467
- unregister if !closed? && result.cursor_id == 0
481
+ if !closed? && result.cursor_id == 0
482
+ unregister
483
+ check_in_connection
484
+ end
468
485
  @cursor_id = set_cursor_id(result)
469
486
 
470
487
  if result.respond_to?(:post_batch_resume_token)
@@ -495,13 +512,22 @@ module Mongo
495
512
  cluster.unregister_cursor(@cursor_id)
496
513
  end
497
514
 
498
- def execute_operation(op)
499
- context = Operation::Context.new(
500
- client: client,
501
- session: @session,
502
- connection_global_id: @connection_global_id,
503
- )
504
- op.execute(@server, context: context)
515
+ def execute_operation(op, context: nil)
516
+ op_context = context || possibly_refreshed_context
517
+ if @connection.nil?
518
+ op.execute(@server, context: op_context)
519
+ else
520
+ op.execute_with_connection(@connection, context: op_context)
521
+ end
522
+ end
523
+
524
+ # Considers the timeout mode and will either return the cursor's
525
+ # context directly, or will return a new (refreshed) context.
526
+ #
527
+ # @return [ Operation::Context ] the (possibly-refreshed) context.
528
+ def possibly_refreshed_context
529
+ return context if view.timeout_mode == :cursor_lifetime
530
+ context.refresh(view: view)
505
531
  end
506
532
 
507
533
  # Sets @cursor_id from the operation result.
@@ -521,6 +547,42 @@ module Mongo
521
547
  end
522
548
  end
523
549
 
550
+ # Returns a newly instantiated operation context based on the
551
+ # default values from the view.
552
+ def fresh_context(opts = {})
553
+ Operation::Context.new(client: view.client,
554
+ session: @session,
555
+ connection_global_id: connection_global_id_for_context,
556
+ operation_timeouts: view.operation_timeouts(opts),
557
+ view: view)
558
+ end
559
+
560
+ # Because a context must not have a connection_global_id if the session
561
+ # is already pinned to one, this method checks to see whether or not there's
562
+ # pinned connection_global_id on the session and returns nil if so.
563
+ def connection_global_id_for_context
564
+ if @session&.pinned_connection_global_id
565
+ nil
566
+ else
567
+ @connection_global_id
568
+ end
569
+ end
570
+
571
+ # Returns the connection that was used to create the cursor back to the
572
+ # corresponding connection pool.
573
+ #
574
+ # In a load balanced topology cursors must use the same connection for the
575
+ # initial and all subsequent operations. Therefore, the connection is not
576
+ # checked into the pool after the initial operation is completed, but
577
+ # only when the cursor is drained.
578
+ def check_in_connection
579
+ # Connection nil means the connection has been already checked in.
580
+ return if @connection.nil?
581
+ return unless @connection.server.load_balancer?
582
+
583
+ @connection.connection_pool.check_in(@connection)
584
+ @connection = nil
585
+ end
524
586
  end
525
587
  end
526
588
 
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongo
4
+ # A shared concern implementing settings and configuration for entities that
5
+ # "host" (or spawn) cursors.
6
+ #
7
+ # The class or module that includes this concern must implement:
8
+ # * timeout_ms -- this must return either the operation level timeout_ms
9
+ # (if set) or an inherited timeout_ms from a hierarchically higher
10
+ # level (if any).
11
+ module CursorHost
12
+ # Returns the cursor associated with this view, if any.
13
+ #
14
+ # @return [ nil | Cursor ] The cursor, if any.
15
+ #
16
+ # @api private
17
+ attr_reader :cursor
18
+
19
+ # @return [ :cursor_lifetime | :iteration ] The timeout mode to be
20
+ # used by this object.
21
+ attr_reader :timeout_mode
22
+
23
+ # Ensure the timeout mode is appropriate for other options that
24
+ # have been given.
25
+ #
26
+ # @param [ Hash ] options The options to inspect.
27
+ # @param [ Array<Symbol> ] forbid The list of options to forbid for this
28
+ # class.
29
+ #
30
+ # @raise [ ArgumentError ] if inconsistent or incompatible options are
31
+ # detected.
32
+ #
33
+ # @api private
34
+ # rubocop:disable Metrics
35
+ def validate_timeout_mode!(options, forbid: [])
36
+ forbid.each do |key|
37
+ raise ArgumentError, "#{key} is not allowed here" if options.key?(key)
38
+ end
39
+
40
+ cursor_type = options[:cursor_type]
41
+ timeout_mode = options[:timeout_mode]
42
+
43
+ if timeout_ms
44
+ # "Tailable cursors only support the ITERATION value for the
45
+ # timeoutMode option. This is the default value and drivers MUST
46
+ # error if the option is set to CURSOR_LIFETIME."
47
+ if cursor_type
48
+ timeout_mode ||= :iteration
49
+ if timeout_mode == :cursor_lifetime
50
+ raise ArgumentError, 'tailable cursors only support `timeout_mode: :iteration`'
51
+ end
52
+
53
+ # "Drivers MUST error if [the maxAwaitTimeMS] option is set,
54
+ # timeoutMS is set to a non-zero value, and maxAwaitTimeMS is
55
+ # greater than or equal to timeoutMS."
56
+ max_await_time_ms = options[:max_await_time_ms] || 0
57
+ if cursor_type == :tailable_await && max_await_time_ms >= timeout_ms
58
+ raise ArgumentError, ':max_await_time_ms must not be >= :timeout_ms'
59
+ end
60
+ else
61
+ # "For non-tailable cursors, the default value of timeoutMode
62
+ # is CURSOR_LIFETIME."
63
+ timeout_mode ||= :cursor_lifetime
64
+ end
65
+ elsif timeout_mode
66
+ # "Drivers MUST error if timeoutMode is set and timeoutMS is not."
67
+ raise ArgumentError, ':timeout_ms must be set if :timeout_mode is set'
68
+ end
69
+
70
+ if timeout_mode == :iteration && respond_to?(:write?) && write?
71
+ raise ArgumentError, 'timeout_mode=:iteration is not supported for aggregation pipelines with $out or $merge'
72
+ end
73
+
74
+ # set it as an instance variable, rather than updating the options,
75
+ # because if the cursor type changes (e.g. via #configure()), the new
76
+ # View instance must be able to select a different default timeout_mode
77
+ # if no timeout_mode was set initially.
78
+ @timeout_mode = timeout_mode
79
+ end
80
+ # rubocop:enable Metrics
81
+ end
82
+ end
@@ -15,6 +15,8 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
+ require 'mongo/cursor/nontailable'
19
+
18
20
  module Mongo
19
21
  class Database
20
22
 
@@ -25,6 +27,8 @@ module Mongo
25
27
  extend Forwardable
26
28
  include Enumerable
27
29
  include Retryable
30
+ include Mongo::CursorHost
31
+ include Cursor::NonTailable
28
32
 
29
33
  def_delegators :@database, :cluster, :read_preference, :client
30
34
  # @api private
@@ -56,6 +60,10 @@ module Mongo
56
60
  # to run the command when access control is enforced.
57
61
  # @option options [ Object ] :comment A user-provided
58
62
  # comment to attach to this command.
63
+ # @option options [ Integer ] :timeout_ms The operation timeout in milliseconds.
64
+ # Must be a non-negative integer. An explicit value of 0 means infinite.
65
+ # The default value is unset which means the value is inherited from
66
+ # the database or the client.
59
67
  #
60
68
  # See https://mongodb.com/docs/manual/reference/command/listCollections/
61
69
  # for more information and usage.
@@ -66,9 +74,14 @@ module Mongo
66
74
  # @since 2.0.0
67
75
  def collection_names(options = {})
68
76
  @batch_size = options[:batch_size]
69
- session = client.send(:get_session, options)
70
- cursor = read_with_retry_cursor(session, ServerSelector.primary, self) do |server|
71
- send_initial_query(server, session, options.merge(name_only: true))
77
+ session = client.get_session(options)
78
+ context = Operation::Context.new(
79
+ client: client,
80
+ session: session,
81
+ operation_timeouts: operation_timeouts(options)
82
+ )
83
+ cursor = read_with_retry_cursor(session, ServerSelector.primary, self, context: context) do |server|
84
+ send_initial_query(server, session, context, options.merge(name_only: true))
72
85
  end
73
86
  cursor.map do |info|
74
87
  if cursor.initial_result.connection_description.features.list_collections_enabled?
@@ -112,20 +125,33 @@ module Mongo
112
125
  #
113
126
  # @since 2.0.5
114
127
  def list_collections(options = {})
115
- session = client.send(:get_session, options)
128
+ session = client.get_session(options)
116
129
  collections_info(session, ServerSelector.primary, options)
117
130
  end
118
131
 
119
132
  # Create the new database view.
120
133
  #
121
134
  # @example Create the new database view.
122
- # View::Index.new(database)
135
+ # Database::View.new(database)
123
136
  #
124
137
  # @param [ Database ] database The database.
138
+ # @param [ Hash ] options The options to configure the view with.
139
+ #
140
+ # @option options [ :cursor_lifetime | :iteration ] :timeout_mode How to interpret
141
+ # :timeout_ms (whether it applies to the lifetime of the cursor, or per
142
+ # iteration).
143
+ # @option options [ Integer ] :timeout_ms The operation timeout in milliseconds.
144
+ # Must be a non-negative integer. An explicit value of 0 means infinite.
145
+ # The default value is unset which means the value is inherited from
146
+ # the database or the client.
125
147
  #
126
148
  # @since 2.0.0
127
- def initialize(database)
149
+ def initialize(database, options = {})
128
150
  @database = database
151
+ @operation_timeout_ms = options.delete(:timeout_ms)
152
+
153
+ validate_timeout_mode!(options)
154
+
129
155
  @batch_size = nil
130
156
  @limit = nil
131
157
  @collection = @database[Database::COMMAND]
@@ -134,6 +160,12 @@ module Mongo
134
160
  # @api private
135
161
  attr_reader :database
136
162
 
163
+ # @return [ Integer | nil | The timeout_ms value that was passed as an
164
+ # option to the view.
165
+ #
166
+ # @api private
167
+ attr_reader :operation_timeout_ms
168
+
137
169
  # Execute an aggregation on the database view.
138
170
  #
139
171
  # @example Aggregate documents.
@@ -152,15 +184,41 @@ module Mongo
152
184
  Collection::View::Aggregation.new(self, pipeline, options)
153
185
  end
154
186
 
187
+ # The timeout_ms value to use for this operation; either specified as an
188
+ # option to the view, or inherited from the database.
189
+ #
190
+ # @return [ Integer | nil ] the timeout_ms for this operation
191
+ def timeout_ms
192
+ operation_timeout_ms || database.timeout_ms
193
+ end
194
+
195
+ # @return [ Hash ] timeout_ms value set on the operation level (if any).
196
+ #
197
+ # @api private
198
+ def operation_timeouts(opts = {})
199
+ {}.tap do |result|
200
+ if opts[:timeout_ms] || operation_timeout_ms
201
+ result[:operation_timeout_ms] = opts.delete(:timeout_ms) || operation_timeout_ms
202
+ else
203
+ result[:inherited_timeout_ms] = database.timeout_ms
204
+ end
205
+ end
206
+ end
207
+
155
208
  private
156
209
 
157
210
  def collections_info(session, server_selector, options = {}, &block)
158
211
  description = nil
159
- cursor = read_with_retry_cursor(session, server_selector, self) do |server|
212
+ context = Operation::Context.new(
213
+ client: client,
214
+ session: session,
215
+ operation_timeouts: operation_timeouts(options)
216
+ )
217
+ cursor = read_with_retry_cursor(session, server_selector, self, context: context) do |server|
160
218
  # TODO take description from the connection used to send the query
161
219
  # once https://jira.mongodb.org/browse/RUBY-1601 is fixed.
162
220
  description = server.description
163
- send_initial_query(server, session, options)
221
+ send_initial_query(server, session, context, options)
164
222
  end
165
223
  # On 3.0+ servers, we get just the collection names.
166
224
  # On 2.6 server, we get collection names prefixed with the database
@@ -224,17 +282,26 @@ module Mongo
224
282
  # types (where possible).
225
283
  #
226
284
  # @return [ Operation::Result ] Result of the query.
227
- def send_initial_query(server, session, options = {})
285
+ def send_initial_query(server, session, context, options = {})
228
286
  opts = options.dup
229
287
  execution_opts = {}
230
288
  if opts.key?(:deserialize_as_bson)
231
289
  execution_opts[:deserialize_as_bson] = opts.delete(:deserialize_as_bson)
232
290
  end
233
- initial_query_op(session, opts).execute(
234
- server,
235
- context: Operation::Context.new(client: client, session: session),
236
- options: execution_opts
237
- )
291
+ if server.load_balancer?
292
+ connection = server.pool.check_out(context: context)
293
+ initial_query_op(session, opts).execute_with_connection(
294
+ connection,
295
+ context: context,
296
+ options: execution_opts
297
+ )
298
+ else
299
+ initial_query_op(session, opts).execute(
300
+ server,
301
+ context: context,
302
+ options: execution_opts
303
+ )
304
+ end
238
305
  end
239
306
  end
240
307
  end