mongo 2.20.1 → 2.21.0

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 (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