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
@@ -142,26 +142,37 @@ module Mongo
142
142
  #
143
143
  # @since 2.0.0
144
144
  def connect!
145
- Timeout.timeout(options[:connect_timeout], Error::SocketTimeoutError, "The socket took over #{options[:connect_timeout]} seconds to connect") do
146
- map_exceptions do
147
- @tcp_socket.connect(::Socket.pack_sockaddr_in(port, host))
148
- end
149
- @socket = OpenSSL::SSL::SSLSocket.new(@tcp_socket, context)
150
- begin
151
- @socket.hostname = @host_name
152
- @socket.sync_close = true
153
- map_exceptions do
154
- @socket.connect
145
+ sockaddr = ::Socket.pack_sockaddr_in(port, host)
146
+ connect_timeout = options[:connect_timeout]
147
+ map_exceptions do
148
+ if connect_timeout && connect_timeout != 0
149
+ deadline = Utils.monotonic_time + connect_timeout
150
+ if BSON::Environment.jruby?
151
+ # We encounter some strange problems with connect_nonblock for
152
+ # ssl sockets on JRuby. Therefore, we use the old +Timeout.timeout+
153
+ # solution, even though it is known to be not very reliable.
154
+ raise Error::SocketTimeoutError, 'connect_timeout expired' if connect_timeout < 0
155
+
156
+ Timeout.timeout(connect_timeout, Error::SocketTimeoutError, "The socket took over #{options[:connect_timeout]} seconds to connect") do
157
+ connect_without_timeout(sockaddr)
158
+ end
159
+ else
160
+ connect_with_timeout(sockaddr, connect_timeout)
155
161
  end
162
+ remaining_timeout = deadline - Utils.monotonic_time
163
+ verify_certificate!(@socket)
164
+ verify_ocsp_endpoint!(@socket, remaining_timeout)
165
+ else
166
+ connect_without_timeout(sockaddr)
156
167
  verify_certificate!(@socket)
157
168
  verify_ocsp_endpoint!(@socket)
158
- rescue
159
- @socket.close
160
- @socket = nil
161
- raise
162
169
  end
163
- self
164
170
  end
171
+ self
172
+ rescue
173
+ @socket&.close
174
+ @socket = nil
175
+ raise
165
176
  end
166
177
  private :connect!
167
178
 
@@ -182,6 +193,87 @@ module Mongo
182
193
 
183
194
  private
184
195
 
196
+ # Connects the socket without a timeout provided.
197
+ #
198
+ # @param [ String ] sockaddr Address to connect to.
199
+ def connect_without_timeout(sockaddr)
200
+ @tcp_socket.connect(sockaddr)
201
+ @socket = OpenSSL::SSL::SSLSocket.new(@tcp_socket, context)
202
+ @socket.hostname = @host_name
203
+ @socket.sync_close = true
204
+ @socket.connect
205
+ end
206
+
207
+ # Connects the socket with the connect timeout. The timeout applies to
208
+ # connecting both ssl socket and the underlying tcp socket.
209
+ #
210
+ # @param [ String ] sockaddr Address to connect to.
211
+ def connect_with_timeout(sockaddr, connect_timeout)
212
+ if connect_timeout <= 0
213
+ raise Error::SocketTimeoutError, "The socket took over #{connect_timeout} seconds to connect"
214
+ end
215
+
216
+ deadline = Utils.monotonic_time + connect_timeout
217
+ connect_tcp_socket_with_timeout(sockaddr, deadline, connect_timeout)
218
+ connnect_ssl_socket_with_timeout(deadline, connect_timeout)
219
+ end
220
+
221
+ def connect_tcp_socket_with_timeout(sockaddr, deadline, connect_timeout)
222
+ if deadline <= Utils.monotonic_time
223
+ raise Error::SocketTimeoutError, "The socket took over #{connect_timeout} seconds to connect"
224
+ end
225
+ begin
226
+ @tcp_socket.connect_nonblock(sockaddr)
227
+ rescue IO::WaitWritable
228
+ with_select_timeout(deadline, connect_timeout) do |select_timeout|
229
+ IO.select(nil, [@tcp_socket], nil, select_timeout)
230
+ end
231
+ retry
232
+ rescue Errno::EISCONN
233
+ # Socket is connected, nothing to do.
234
+ end
235
+ end
236
+
237
+ def connnect_ssl_socket_with_timeout(deadline, connect_timeout)
238
+ if deadline <= Utils.monotonic_time
239
+ raise Error::SocketTimeoutError, "The socket took over #{connect_timeout} seconds to connect"
240
+ end
241
+ @socket = OpenSSL::SSL::SSLSocket.new(@tcp_socket, context)
242
+ @socket.hostname = @host_name
243
+ @socket.sync_close = true
244
+
245
+ # We still have time, connecting ssl socket.
246
+ begin
247
+ @socket.connect_nonblock
248
+ rescue IO::WaitReadable, OpenSSL::SSL::SSLErrorWaitReadable
249
+ with_select_timeout(deadline, connect_timeout) do |select_timeout|
250
+ IO.select([@socket], nil, nil, select_timeout)
251
+ end
252
+ retry
253
+ rescue IO::WaitWritable, OpenSSL::SSL::SSLErrorWaitWritable
254
+ with_select_timeout(deadline, connect_timeout) do |select_timeout|
255
+ IO.select(nil, [@socket], nil, select_timeout)
256
+ end
257
+ retry
258
+ rescue Errno::EISCONN
259
+ # Socket is connected, nothing to do
260
+ end
261
+ end
262
+
263
+ # Raises +Error::SocketTimeoutError+ exception if deadline reached or the
264
+ # block returns nil. The block should call +IO.select+ with the
265
+ # +connect_timeout+ value. It returns nil if the +connect_timeout+ expires.
266
+ def with_select_timeout(deadline, connect_timeout, &block)
267
+ select_timeout = deadline - Utils.monotonic_time
268
+ if select_timeout <= 0
269
+ raise Error::SocketTimeoutError, "The socket took over #{connect_timeout} seconds to connect"
270
+ end
271
+ rv = block.call(select_timeout)
272
+ if rv.nil?
273
+ raise Error::SocketTimeoutError, "The socket took over #{connect_timeout} seconds to connect"
274
+ end
275
+ end
276
+
185
277
  def verify_certificate?
186
278
  # If ssl_verify_certificate is not present, disable only if
187
279
  # ssl_verify is explicitly set to false.
@@ -362,7 +454,7 @@ module Mongo
362
454
  end
363
455
  end
364
456
 
365
- def verify_ocsp_endpoint!(socket)
457
+ def verify_ocsp_endpoint!(socket, timeout = nil)
366
458
  unless verify_ocsp_endpoint?
367
459
  return
368
460
  end
@@ -371,7 +463,7 @@ module Mongo
371
463
  ca_cert = socket.peer_cert_chain.last
372
464
 
373
465
  verifier = OcspVerifier.new(@host_name, cert, ca_cert, context.cert_store,
374
- **Utils.shallow_symbolize_keys(options))
466
+ **Utils.shallow_symbolize_keys(options).merge(timeout: timeout))
375
467
  verifier.verify_with_cache
376
468
  end
377
469
 
@@ -79,16 +79,50 @@ module Mongo
79
79
  # @return [ TCP ] The connected socket instance.
80
80
  #
81
81
  # @since 2.0.0
82
+ # @api private
82
83
  def connect!
83
- Timeout.timeout(options[:connect_timeout], Error::SocketTimeoutError, "The socket took over #{options[:connect_timeout]} seconds to connect") do
84
- socket.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
85
- map_exceptions do
86
- socket.connect(::Socket.pack_sockaddr_in(port, host))
84
+ socket.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
85
+ sockaddr = ::Socket.pack_sockaddr_in(port, host)
86
+ connect_timeout = options[:connect_timeout]
87
+ map_exceptions do
88
+ if connect_timeout && connect_timeout != 0
89
+ connect_with_timeout(sockaddr, connect_timeout)
90
+ else
91
+ connect_without_timeout(sockaddr)
92
+ end
93
+ end
94
+ self
95
+ end
96
+
97
+ # @api private
98
+ def connect_without_timeout(sockaddr)
99
+ socket.connect(sockaddr)
100
+ end
101
+
102
+ # @api private
103
+ def connect_with_timeout(sockaddr, connect_timeout)
104
+ if connect_timeout <= 0
105
+ raise Error::SocketTimeoutError, "The socket took over #{connect_timeout} seconds to connect"
106
+ end
107
+
108
+ deadline = Utils.monotonic_time + connect_timeout
109
+ begin
110
+ socket.connect_nonblock(sockaddr)
111
+ rescue IO::WaitWritable
112
+ select_timeout = deadline - Utils.monotonic_time
113
+ if select_timeout <= 0
114
+ raise Error::SocketTimeoutError, "The socket took over #{connect_timeout} seconds to connect"
115
+ end
116
+ if IO.select(nil, [socket], nil, select_timeout)
117
+ retry
118
+ else
119
+ socket.close
120
+ raise Error::SocketTimeoutError, "The socket took over #{connect_timeout} seconds to connect"
87
121
  end
88
- self
122
+ rescue Errno::EISCONN
123
+ # Socket is connected, nothing more to do
89
124
  end
90
125
  end
91
- private :connect!
92
126
 
93
127
  private
94
128
 
data/lib/mongo/socket.rb CHANGED
@@ -192,27 +192,24 @@ module Mongo
192
192
  # socket.read(4096)
193
193
  #
194
194
  # @param [ Integer ] length The number of bytes to read.
195
- # @param [ Numeric ] timeout The timeout to use for each chunk read.
195
+ # @param [ Numeric ] socket_timeout The timeout to use for each chunk read,
196
+ # mutually exclusive to +timeout+.
197
+ # @param [ Numeric ] timeout The total timeout to the whole read operation,
198
+ # mutually exclusive to +socket_timeout+.
196
199
  #
197
200
  # @raise [ Mongo::SocketError ] If not all data is returned.
198
201
  #
199
202
  # @return [ Object ] The data from the socket.
200
203
  #
201
204
  # @since 2.0.0
202
- def read(length, timeout: nil)
203
- map_exceptions do
204
- data = read_from_socket(length, timeout: timeout)
205
- unless (data.length > 0 || length == 0)
206
- raise IOError, "Expected to read > 0 bytes but read 0 bytes"
207
- end
208
- while data.length < length
209
- chunk = read_from_socket(length - data.length, timeout: timeout)
210
- unless (chunk.length > 0 || length == 0)
211
- raise IOError, "Expected to read > 0 bytes but read 0 bytes"
212
- end
213
- data << chunk
214
- end
215
- data
205
+ def read(length, socket_timeout: nil, timeout: nil)
206
+ if !socket_timeout.nil? && !timeout.nil?
207
+ raise ArgumentError, 'Both timeout and socket_timeout cannot be set'
208
+ end
209
+ if !socket_timeout.nil? || timeout.nil?
210
+ read_without_timeout(length, socket_timeout)
211
+ else
212
+ read_with_timeout(length, timeout)
216
213
  end
217
214
  end
218
215
 
@@ -233,15 +230,16 @@ module Mongo
233
230
  # Writes data to the socket instance.
234
231
  #
235
232
  # @param [ Array<Object> ] args The data to be written.
233
+ # @param [ Numeric ] timeout The total timeout to the whole write operation.
236
234
  #
237
235
  # @return [ Integer ] The length of bytes written to the socket.
238
236
  #
239
237
  # @raise [ Error::SocketError | Error::SocketTimeoutError ] When there is a network error during the write.
240
238
  #
241
239
  # @since 2.0.0
242
- def write(*args)
240
+ def write(*args, timeout: nil)
243
241
  map_exceptions do
244
- do_write(*args)
242
+ do_write(*args, timeout: timeout)
245
243
  end
246
244
  end
247
245
 
@@ -254,7 +252,7 @@ module Mongo
254
252
  true
255
253
  end
256
254
 
257
- # For backwards compatibilty only, do not use.
255
+ # For backwards compatibility only, do not use.
258
256
  #
259
257
  # @return [ true ] Always true.
260
258
  #
@@ -265,18 +263,76 @@ module Mongo
265
263
 
266
264
  private
267
265
 
268
- def read_from_socket(length, timeout: nil)
266
+ # Reads the +length+ bytes from the socket, the read operation duration is
267
+ # limited to +timeout+ second.
268
+ #
269
+ # @param [ Integer ] length The number of bytes to read.
270
+ # @param [ Numeric ] timeout The total timeout to the whole read operation.
271
+ #
272
+ # @return [ Object ] The data from the socket.
273
+ def read_with_timeout(length, timeout)
274
+ deadline = Utils.monotonic_time + timeout
275
+ map_exceptions do
276
+ String.new.tap do |data|
277
+ while data.length < length
278
+ socket_timeout = deadline - Utils.monotonic_time
279
+ if socket_timeout <= 0
280
+ raise Mongo::Error::TimeoutError
281
+ end
282
+ chunk = read_from_socket(length - data.length, socket_timeout: socket_timeout, csot: true)
283
+ unless chunk.length > 0
284
+ raise IOError, "Expected to read > 0 bytes but read 0 bytes"
285
+ end
286
+ data << chunk
287
+ end
288
+ end
289
+ end
290
+ end
291
+
292
+ # Reads the +length+ bytes from the socket. The read operation may involve
293
+ # multiple socket reads, each read is limited to +timeout+ second,
294
+ # if the parameter is provided.
295
+ #
296
+ # @param [ Integer ] length The number of bytes to read.
297
+ # @param [ Numeric ] socket_timeout The timeout to use for each chunk read.
298
+ #
299
+ # @return [ Object ] The data from the socket.
300
+ def read_without_timeout(length, socket_timeout = nil)
301
+ map_exceptions do
302
+ String.new.tap do |data|
303
+ while data.length < length
304
+ chunk = read_from_socket(length - data.length, socket_timeout: socket_timeout)
305
+ unless chunk.length > 0
306
+ raise IOError, "Expected to read > 0 bytes but read 0 bytes"
307
+ end
308
+ data << chunk
309
+ end
310
+ end
311
+ end
312
+ end
313
+
314
+
315
+ # Reads the +length+ bytes from the socket. The read operation may involve
316
+ # multiple socket reads, each read is limited to +timeout+ second,
317
+ # if the parameter is provided.
318
+ #
319
+ # @param [ Integer ] length The number of bytes to read.
320
+ # @param [ Numeric ] :socket_timeout The timeout to use for each chunk read.
321
+ # @param [ true | false ] :csot Whether the CSOT timeout is set for the operation.
322
+ #
323
+ # @return [ Object ] The data from the socket.
324
+ def read_from_socket(length, socket_timeout: nil, csot: false)
269
325
  # Just in case
270
326
  if length == 0
271
327
  return ''.force_encoding('BINARY')
272
328
  end
273
329
 
274
- _timeout = timeout || self.timeout
330
+ _timeout = socket_timeout || self.timeout
275
331
  if _timeout
276
332
  if _timeout > 0
277
333
  deadline = Utils.monotonic_time + _timeout
278
334
  elsif _timeout < 0
279
- raise Errno::ETIMEDOUT, "Negative timeout #{_timeout} given to socket"
335
+ raise_timeout_error!("Negative timeout #{_timeout} given to socket", csot)
280
336
  end
281
337
  end
282
338
 
@@ -331,7 +387,7 @@ module Mongo
331
387
  if deadline
332
388
  select_timeout = deadline - Utils.monotonic_time
333
389
  if select_timeout <= 0
334
- raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data"
390
+ raise_timeout_error!("Took more than #{_timeout} seconds to receive data", csot)
335
391
  end
336
392
  end
337
393
  pipe = options[:pipe]
@@ -373,11 +429,11 @@ module Mongo
373
429
  if deadline
374
430
  select_timeout = deadline - Utils.monotonic_time
375
431
  if select_timeout <= 0
376
- raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data"
432
+ raise_timeout_error!("Took more than #{_timeout} seconds to receive data", csot)
377
433
  end
378
434
  end
379
435
  elsif rv.nil?
380
- raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data (select call timed out)"
436
+ raise_timeout_error!("Took more than #{_timeout} seconds to receive data (select call timed out)", csot)
381
437
  end
382
438
  retry
383
439
  end
@@ -402,9 +458,23 @@ module Mongo
402
458
  # sholud map exceptions.
403
459
  #
404
460
  # @param [ Array<Object> ] args The data to be written.
461
+ # @param [ Numeric ] :timeout The total timeout to the whole write operation.
462
+ #
463
+ # @return [ Integer ] The length of bytes written to the socket.
464
+ def do_write(*args, timeout: nil)
465
+ if timeout.nil?
466
+ write_without_timeout(*args)
467
+ else
468
+ write_with_timeout(*args, timeout: timeout)
469
+ end
470
+ end
471
+
472
+ # Writes data to to the socket.
473
+ #
474
+ # @param [ Array<Object> ] args The data to be written.
405
475
  #
406
476
  # @return [ Integer ] The length of bytes written to the socket.
407
- def do_write(*args)
477
+ def write_without_timeout(*args)
408
478
  # This method used to forward arguments to @socket.write in a
409
479
  # single call like so:
410
480
  #
@@ -428,6 +498,57 @@ module Mongo
428
498
  end
429
499
  end
430
500
 
501
+ # Writes data to to the socket, the write duration is limited to +timeout+.
502
+ #
503
+ # @param [ Array<Object> ] args The data to be written.
504
+ # @param [ Numeric ] :timeout The total timeout to the whole write operation.
505
+ #
506
+ # @return [ Integer ] The length of bytes written to the socket.
507
+ def write_with_timeout(*args, timeout:)
508
+ raise ArgumentError, 'timeout cannot be nil' if timeout.nil?
509
+ raise_timeout_error!("Negative timeout #{timeout} given to socket", true) if timeout < 0
510
+
511
+ written = 0
512
+ args.each do |buf|
513
+ buf = buf.to_s
514
+ i = 0
515
+ while i < buf.length
516
+ chunk = buf[i...(i + WRITE_CHUNK_SIZE)]
517
+ written += write_chunk(chunk, timeout)
518
+ i += WRITE_CHUNK_SIZE
519
+ end
520
+ end
521
+ written
522
+ end
523
+
524
+ def write_chunk(chunk, timeout)
525
+ deadline = Utils.monotonic_time + timeout
526
+ written = 0
527
+ begin
528
+ written += @socket.write_nonblock(chunk[written..-1])
529
+ rescue IO::WaitWritable, Errno::EINTR
530
+ select_timeout = deadline - Utils.monotonic_time
531
+ rv = Kernel.select(nil, [@socket], nil, select_timeout)
532
+ if BSON::Environment.jruby?
533
+ # Ignore the return value of Kernel.select.
534
+ # On JRuby, select appears to return nil prior to timeout expiration
535
+ # (apparently due to a EAGAIN) which then causes us to fail the read
536
+ # even though we could have retried it.
537
+ # Check the deadline ourselves.
538
+ if deadline
539
+ select_timeout = deadline - Utils.monotonic_time
540
+ if select_timeout <= 0
541
+ raise_timeout_error!("Took more than #{timeout} seconds to receive data", true)
542
+ end
543
+ end
544
+ elsif rv.nil?
545
+ raise_timeout_error!("Took more than #{timeout} seconds to receive data (select call timed out)", true)
546
+ end
547
+ retry
548
+ end
549
+ written
550
+ end
551
+
431
552
  def unix_socket?(sock)
432
553
  defined?(UNIXSocket) && sock.is_a?(UNIXSocket)
433
554
  end
@@ -482,5 +603,13 @@ module Mongo
482
603
  def human_address
483
604
  raise NotImplementedError
484
605
  end
606
+
607
+ def raise_timeout_error!(message = nil, csot = false)
608
+ if csot
609
+ raise Mongo::Error::TimeoutError
610
+ else
611
+ raise Errno::ETIMEDOUT, message
612
+ end
613
+ end
485
614
  end
486
615
  end
@@ -272,6 +272,7 @@ module Mongo
272
272
  uri_option 'localThresholdMS', :local_threshold, type: :ms
273
273
  uri_option 'heartbeatFrequencyMS', :heartbeat_frequency, type: :ms
274
274
  uri_option 'maxIdleTimeMS', :max_idle_time, type: :ms
275
+ uri_option 'timeoutMS', :timeout_ms, type: :integer
275
276
 
276
277
  # Write Options
277
278
  uri_option 'w', :w, group: :write_concern, type: :w
data/lib/mongo/version.rb CHANGED
@@ -16,5 +16,5 @@
16
16
 
17
17
  module Mongo
18
18
  # The current version of the driver.
19
- VERSION = '2.20.1'
19
+ VERSION = '2.21.0'
20
20
  end
data/lib/mongo.rb CHANGED
@@ -39,6 +39,7 @@ require 'mongo/bson'
39
39
  require 'mongo/semaphore'
40
40
  require 'mongo/distinguishing_semaphore'
41
41
  require 'mongo/condition_variable'
42
+ require 'mongo/csot_timeout_holder'
42
43
  require 'mongo/options'
43
44
  require 'mongo/loggable'
44
45
  require 'mongo/cluster_time'
@@ -10,6 +10,10 @@ describe 'Atlas connectivity' do
10
10
  require_atlas
11
11
 
12
12
  describe 'connection to Atlas' do
13
+ after do
14
+ client.close
15
+ end
16
+
13
17
  it 'runs ismaster successfully' do
14
18
  expect { client.database.command(:hello => 1) }
15
19
  .not_to raise_error
@@ -10,6 +10,10 @@ describe 'Operations' do
10
10
  require_atlas
11
11
 
12
12
  describe 'ping' do
13
+ after do
14
+ client.close
15
+ end
16
+
13
17
  it 'works' do
14
18
  expect do
15
19
  client.database.command(ping: 1)
@@ -57,7 +57,8 @@ describe 'Auto Encryption' do
57
57
  'jsonSchema' => kind_of(Hash),
58
58
  'isRemoteSchema' => false,
59
59
  ),
60
- { execution_options: { deserialize_as_bson: true } },
60
+ { execution_options: { deserialize_as_bson: true }, timeout_ms: nil },
61
+
61
62
  )
62
63
  .and_raise(Mongo::Error::NoServerAvailable.new(server_selector, cluster))
63
64
  end