mongo 2.23.0 → 2.24.1

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 (463) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mongo_console +0 -1
  3. data/lib/mongo/active_support.rb +1 -2
  4. data/lib/mongo/address/ipv4.rb +3 -6
  5. data/lib/mongo/address/ipv6.rb +6 -10
  6. data/lib/mongo/address/unix.rb +1 -4
  7. data/lib/mongo/address/validator.rb +16 -28
  8. data/lib/mongo/address.rb +30 -40
  9. data/lib/mongo/auth/aws/conversation.rb +6 -10
  10. data/lib/mongo/auth/aws/credentials.rb +0 -1
  11. data/lib/mongo/auth/aws/credentials_cache.rb +0 -1
  12. data/lib/mongo/auth/aws/credentials_retriever.rb +45 -59
  13. data/lib/mongo/auth/aws/request.rb +20 -35
  14. data/lib/mongo/auth/aws.rb +1 -2
  15. data/lib/mongo/auth/base.rb +20 -29
  16. data/lib/mongo/auth/conversation_base.rb +14 -18
  17. data/lib/mongo/auth/cr/conversation.rb +0 -3
  18. data/lib/mongo/auth/cr.rb +1 -4
  19. data/lib/mongo/auth/credential_cache.rb +0 -2
  20. data/lib/mongo/auth/gssapi/conversation.rb +3 -8
  21. data/lib/mongo/auth/gssapi.rb +1 -4
  22. data/lib/mongo/auth/ldap/conversation.rb +0 -3
  23. data/lib/mongo/auth/ldap.rb +1 -4
  24. data/lib/mongo/auth/roles.rb +16 -19
  25. data/lib/mongo/auth/sasl_conversation_base.rb +7 -11
  26. data/lib/mongo/auth/scram/conversation.rb +2 -5
  27. data/lib/mongo/auth/scram.rb +5 -10
  28. data/lib/mongo/auth/scram256/conversation.rb +2 -5
  29. data/lib/mongo/auth/scram256.rb +1 -3
  30. data/lib/mongo/auth/scram_conversation_base.rb +18 -24
  31. data/lib/mongo/auth/stringprep/profiles/sasl.rb +17 -18
  32. data/lib/mongo/auth/stringprep/tables.rb +2209 -2210
  33. data/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb +36 -38
  34. data/lib/mongo/auth/stringprep/unicode_normalize/tables.rb +1142 -1150
  35. data/lib/mongo/auth/stringprep.rb +9 -12
  36. data/lib/mongo/auth/user/view.rb +3 -5
  37. data/lib/mongo/auth/user.rb +14 -24
  38. data/lib/mongo/auth/x509/conversation.rb +0 -3
  39. data/lib/mongo/auth/x509.rb +7 -9
  40. data/lib/mongo/auth.rb +18 -30
  41. data/lib/mongo/background_thread.rb +9 -17
  42. data/lib/mongo/bson.rb +0 -2
  43. data/lib/mongo/bulk_write/combineable.rb +0 -3
  44. data/lib/mongo/bulk_write/ordered_combiner.rb +1 -3
  45. data/lib/mongo/bulk_write/result.rb +20 -17
  46. data/lib/mongo/bulk_write/result_combiner.rb +17 -13
  47. data/lib/mongo/bulk_write/transformable.rb +16 -19
  48. data/lib/mongo/bulk_write/unordered_combiner.rb +1 -3
  49. data/lib/mongo/bulk_write/validatable.rb +11 -18
  50. data/lib/mongo/bulk_write.rb +76 -91
  51. data/lib/mongo/caching_cursor.rb +2 -7
  52. data/lib/mongo/client.rb +230 -275
  53. data/lib/mongo/client_encryption.rb +4 -5
  54. data/lib/mongo/cluster/periodic_executor.rb +2 -5
  55. data/lib/mongo/cluster/reapers/cursor_reaper.rb +21 -29
  56. data/lib/mongo/cluster/reapers/socket_reaper.rb +1 -6
  57. data/lib/mongo/cluster/sdam_flow.rb +136 -159
  58. data/lib/mongo/cluster/topology/base.rb +15 -18
  59. data/lib/mongo/cluster/topology/load_balanced.rb +24 -14
  60. data/lib/mongo/cluster/topology/no_replica_set_options.rb +3 -6
  61. data/lib/mongo/cluster/topology/replica_set_no_primary.rb +20 -23
  62. data/lib/mongo/cluster/topology/replica_set_with_primary.rb +0 -2
  63. data/lib/mongo/cluster/topology/sharded.rb +19 -9
  64. data/lib/mongo/cluster/topology/single.rb +24 -14
  65. data/lib/mongo/cluster/topology/unknown.rb +20 -10
  66. data/lib/mongo/cluster/topology.rb +29 -25
  67. data/lib/mongo/cluster.rb +148 -183
  68. data/lib/mongo/cluster_time.rb +14 -31
  69. data/lib/mongo/collection/helpers.rb +5 -8
  70. data/lib/mongo/collection/view/aggregation.rb +5 -10
  71. data/lib/mongo/collection/view/builder/aggregation.rb +6 -9
  72. data/lib/mongo/collection/view/builder/map_reduce.rb +18 -17
  73. data/lib/mongo/collection/view/builder.rb +0 -1
  74. data/lib/mongo/collection/view/change_stream/retryable.rb +3 -8
  75. data/lib/mongo/collection/view/change_stream.rb +59 -58
  76. data/lib/mongo/collection/view/explainable.rb +11 -20
  77. data/lib/mongo/collection/view/immutable.rb +1 -3
  78. data/lib/mongo/collection/view/iterable.rb +35 -28
  79. data/lib/mongo/collection/view/map_reduce.rb +20 -25
  80. data/lib/mongo/collection/view/readable.rb +50 -57
  81. data/lib/mongo/collection/view/writable.rb +56 -72
  82. data/lib/mongo/collection/view.rb +9 -8
  83. data/lib/mongo/collection.rb +63 -76
  84. data/lib/mongo/condition_variable.rb +4 -4
  85. data/lib/mongo/config/options.rb +0 -3
  86. data/lib/mongo/config/validators/option.rb +3 -5
  87. data/lib/mongo/config.rb +7 -4
  88. data/lib/mongo/crypt/auto_decryption_context.rb +0 -3
  89. data/lib/mongo/crypt/auto_encrypter.rb +34 -43
  90. data/lib/mongo/crypt/auto_encryption_context.rb +0 -3
  91. data/lib/mongo/crypt/binary.rb +5 -9
  92. data/lib/mongo/crypt/binding.rb +149 -155
  93. data/lib/mongo/crypt/context.rb +10 -17
  94. data/lib/mongo/crypt/data_key_context.rb +2 -7
  95. data/lib/mongo/crypt/encryption_io.rb +29 -39
  96. data/lib/mongo/crypt/explicit_decryption_context.rb +0 -3
  97. data/lib/mongo/crypt/explicit_encrypter.rb +1 -1
  98. data/lib/mongo/crypt/explicit_encryption_context.rb +19 -30
  99. data/lib/mongo/crypt/explicit_encryption_expression_context.rb +0 -2
  100. data/lib/mongo/crypt/handle.rb +42 -48
  101. data/lib/mongo/crypt/hooks.rb +12 -15
  102. data/lib/mongo/crypt/kms/aws/credentials.rb +12 -16
  103. data/lib/mongo/crypt/kms/aws/master_document.rb +6 -9
  104. data/lib/mongo/crypt/kms/aws.rb +0 -2
  105. data/lib/mongo/crypt/kms/azure/credentials_retriever.rb +2 -7
  106. data/lib/mongo/crypt/kms/azure/master_document.rb +15 -19
  107. data/lib/mongo/crypt/kms/azure.rb +0 -1
  108. data/lib/mongo/crypt/kms/credentials.rb +13 -27
  109. data/lib/mongo/crypt/kms/gcp/credentials.rb +12 -14
  110. data/lib/mongo/crypt/kms/gcp/credentials_retriever.rb +7 -9
  111. data/lib/mongo/crypt/kms/gcp/master_document.rb +12 -16
  112. data/lib/mongo/crypt/kms/gcp.rb +0 -2
  113. data/lib/mongo/crypt/kms/kmip/credentials.rb +7 -8
  114. data/lib/mongo/crypt/kms/kmip/master_document.rb +3 -5
  115. data/lib/mongo/crypt/kms/kmip.rb +0 -1
  116. data/lib/mongo/crypt/kms/local/credentials.rb +7 -8
  117. data/lib/mongo/crypt/kms/local/master_document.rb +2 -6
  118. data/lib/mongo/crypt/kms/local.rb +0 -1
  119. data/lib/mongo/crypt/kms/master_key_document.rb +11 -15
  120. data/lib/mongo/crypt/kms.rb +14 -16
  121. data/lib/mongo/crypt/kms_context.rb +0 -2
  122. data/lib/mongo/crypt/rewrap_many_data_key_context.rb +2 -7
  123. data/lib/mongo/crypt/rewrap_many_data_key_result.rb +2 -4
  124. data/lib/mongo/crypt/status.rb +12 -14
  125. data/lib/mongo/crypt.rb +0 -1
  126. data/lib/mongo/csot_timeout_holder.rb +3 -2
  127. data/lib/mongo/cursor/kill_spec.rb +7 -10
  128. data/lib/mongo/cursor.rb +74 -64
  129. data/lib/mongo/cursor_host.rb +8 -10
  130. data/lib/mongo/database/view.rb +16 -37
  131. data/lib/mongo/database.rb +52 -56
  132. data/lib/mongo/dbref.rb +0 -1
  133. data/lib/mongo/distinguishing_semaphore.rb +0 -1
  134. data/lib/mongo/error/auth_error.rb +0 -2
  135. data/lib/mongo/error/bad_load_balancer_target.rb +0 -2
  136. data/lib/mongo/error/bulk_write_error.rb +35 -10
  137. data/lib/mongo/error/change_stream_resumable.rb +0 -2
  138. data/lib/mongo/error/client_closed.rb +0 -2
  139. data/lib/mongo/error/closed_stream.rb +1 -4
  140. data/lib/mongo/error/connection_check_out_timeout.rb +3 -6
  141. data/lib/mongo/error/connection_perished.rb +0 -2
  142. data/lib/mongo/error/connection_unavailable.rb +0 -2
  143. data/lib/mongo/error/credential_check_error.rb +0 -2
  144. data/lib/mongo/error/crypt_error.rb +0 -2
  145. data/lib/mongo/error/extra_file_chunk.rb +1 -4
  146. data/lib/mongo/error/failed_string_prep_validation.rb +5 -6
  147. data/lib/mongo/error/file_not_found.rb +0 -3
  148. data/lib/mongo/error/handshake_error.rb +0 -2
  149. data/lib/mongo/error/insufficient_iteration_count.rb +1 -4
  150. data/lib/mongo/error/internal_driver_error.rb +0 -2
  151. data/lib/mongo/error/invalid_address.rb +0 -2
  152. data/lib/mongo/error/invalid_application_name.rb +0 -3
  153. data/lib/mongo/error/invalid_bulk_operation.rb +1 -4
  154. data/lib/mongo/error/invalid_bulk_operation_type.rb +1 -4
  155. data/lib/mongo/error/invalid_collection_name.rb +1 -4
  156. data/lib/mongo/error/invalid_config_option.rb +0 -3
  157. data/lib/mongo/error/invalid_cursor_operation.rb +0 -2
  158. data/lib/mongo/error/invalid_database_name.rb +1 -4
  159. data/lib/mongo/error/invalid_document.rb +1 -4
  160. data/lib/mongo/error/invalid_file.rb +0 -3
  161. data/lib/mongo/error/invalid_file_revision.rb +0 -3
  162. data/lib/mongo/error/invalid_min_pool_size.rb +0 -3
  163. data/lib/mongo/error/invalid_nonce.rb +0 -3
  164. data/lib/mongo/error/invalid_read_concern.rb +2 -4
  165. data/lib/mongo/error/invalid_read_option.rb +0 -3
  166. data/lib/mongo/error/invalid_replacement_document.rb +2 -5
  167. data/lib/mongo/error/invalid_server_auth_host.rb +0 -2
  168. data/lib/mongo/error/invalid_server_auth_response.rb +0 -2
  169. data/lib/mongo/error/invalid_server_preference.rb +10 -16
  170. data/lib/mongo/error/invalid_session.rb +1 -4
  171. data/lib/mongo/error/invalid_signature.rb +0 -3
  172. data/lib/mongo/error/invalid_transaction_operation.rb +5 -8
  173. data/lib/mongo/error/invalid_txt_record.rb +0 -2
  174. data/lib/mongo/error/invalid_update_document.rb +2 -5
  175. data/lib/mongo/error/invalid_uri.rb +1 -4
  176. data/lib/mongo/error/invalid_write_concern.rb +2 -5
  177. data/lib/mongo/error/kms_error.rb +0 -2
  178. data/lib/mongo/error/labelable.rb +0 -3
  179. data/lib/mongo/error/lint_error.rb +0 -2
  180. data/lib/mongo/error/max_bson_size.rb +8 -11
  181. data/lib/mongo/error/max_message_size.rb +2 -5
  182. data/lib/mongo/error/mismatched_domain.rb +0 -2
  183. data/lib/mongo/error/missing_connection.rb +0 -2
  184. data/lib/mongo/error/missing_file_chunk.rb +0 -3
  185. data/lib/mongo/error/missing_password.rb +0 -2
  186. data/lib/mongo/error/missing_resume_token.rb +1 -4
  187. data/lib/mongo/error/missing_scram_server_signature.rb +2 -4
  188. data/lib/mongo/error/missing_service_id.rb +0 -2
  189. data/lib/mongo/error/mongocryptd_spawn_error.rb +0 -2
  190. data/lib/mongo/error/multi_index_drop.rb +0 -3
  191. data/lib/mongo/error/need_primary_server.rb +0 -2
  192. data/lib/mongo/error/no_server_available.rb +3 -8
  193. data/lib/mongo/error/no_service_connection_available.rb +1 -3
  194. data/lib/mongo/error/no_srv_records.rb +0 -2
  195. data/lib/mongo/error/notable.rb +8 -16
  196. data/lib/mongo/error/operation_failure.rb +62 -36
  197. data/lib/mongo/error/parser.rb +33 -75
  198. data/lib/mongo/error/pool_cleared_error.rb +1 -3
  199. data/lib/mongo/error/pool_closed_error.rb +0 -3
  200. data/lib/mongo/error/pool_error.rb +0 -3
  201. data/lib/mongo/error/pool_paused_error.rb +0 -2
  202. data/lib/mongo/error/raise_original_error.rb +1 -3
  203. data/lib/mongo/error/read_write_retryable.rb +14 -17
  204. data/lib/mongo/error/sdam_error_detection.rb +3 -5
  205. data/lib/mongo/error/server_api_conflict.rb +0 -2
  206. data/lib/mongo/error/server_certificate_revoked.rb +0 -2
  207. data/lib/mongo/error/server_not_usable.rb +0 -2
  208. data/lib/mongo/error/session_ended.rb +1 -3
  209. data/lib/mongo/error/session_not_materialized.rb +1 -3
  210. data/lib/mongo/error/sessions_not_supported.rb +1 -4
  211. data/lib/mongo/error/snapshot_session_invalid_server_version.rb +1 -4
  212. data/lib/mongo/error/snapshot_session_transaction_prohibited.rb +1 -4
  213. data/lib/mongo/error/socket_error.rb +0 -2
  214. data/lib/mongo/error/socket_timeout_error.rb +0 -2
  215. data/lib/mongo/error/transactions_not_supported.rb +3 -6
  216. data/lib/mongo/error/unchangeable_collection_option.rb +1 -4
  217. data/lib/mongo/error/unexpected_chunk_length.rb +0 -3
  218. data/lib/mongo/error/unexpected_response.rb +1 -4
  219. data/lib/mongo/error/unknown_payload_type.rb +0 -3
  220. data/lib/mongo/error/unmet_dependency.rb +0 -2
  221. data/lib/mongo/error/unsupported_array_filters.rb +3 -24
  222. data/lib/mongo/error/unsupported_collation.rb +3 -24
  223. data/lib/mongo/error/unsupported_features.rb +0 -2
  224. data/lib/mongo/error/unsupported_message_type.rb +0 -2
  225. data/lib/mongo/error/unsupported_option.rb +19 -21
  226. data/lib/mongo/error/write_retryable.rb +0 -2
  227. data/lib/mongo/error.rb +10 -24
  228. data/lib/mongo/event/base.rb +0 -2
  229. data/lib/mongo/event/listeners.rb +0 -3
  230. data/lib/mongo/event/publisher.rb +0 -3
  231. data/lib/mongo/event/subscriber.rb +0 -4
  232. data/lib/mongo/event.rb +4 -6
  233. data/lib/mongo/grid/file/chunk.rb +7 -10
  234. data/lib/mongo/grid/file/info.rb +20 -24
  235. data/lib/mongo/grid/file.rb +7 -8
  236. data/lib/mongo/grid/fs_bucket.rb +40 -48
  237. data/lib/mongo/grid/stream/read.rb +25 -35
  238. data/lib/mongo/grid/stream/write.rb +17 -22
  239. data/lib/mongo/grid/stream.rb +2 -4
  240. data/lib/mongo/grid.rb +0 -1
  241. data/lib/mongo/id.rb +0 -1
  242. data/lib/mongo/index/view.rb +49 -48
  243. data/lib/mongo/index.rb +7 -10
  244. data/lib/mongo/lint.rb +31 -37
  245. data/lib/mongo/loggable.rb +5 -8
  246. data/lib/mongo/logger.rb +1 -7
  247. data/lib/mongo/monitoring/cmap_log_subscriber.rb +0 -2
  248. data/lib/mongo/monitoring/command_log_subscriber.rb +25 -33
  249. data/lib/mongo/monitoring/event/cmap/base.rb +0 -2
  250. data/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb +1 -4
  251. data/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb +0 -3
  252. data/lib/mongo/monitoring/event/cmap/connection_checked_in.rb +1 -4
  253. data/lib/mongo/monitoring/event/cmap/connection_checked_out.rb +2 -5
  254. data/lib/mongo/monitoring/event/cmap/connection_closed.rb +1 -4
  255. data/lib/mongo/monitoring/event/cmap/connection_created.rb +1 -4
  256. data/lib/mongo/monitoring/event/cmap/connection_ready.rb +1 -4
  257. data/lib/mongo/monitoring/event/cmap/pool_cleared.rb +0 -3
  258. data/lib/mongo/monitoring/event/cmap/pool_closed.rb +1 -4
  259. data/lib/mongo/monitoring/event/cmap/pool_created.rb +1 -4
  260. data/lib/mongo/monitoring/event/cmap/pool_ready.rb +1 -4
  261. data/lib/mongo/monitoring/event/cmap.rb +0 -1
  262. data/lib/mongo/monitoring/event/command_failed.rb +5 -9
  263. data/lib/mongo/monitoring/event/command_started.rb +8 -12
  264. data/lib/mongo/monitoring/event/command_succeeded.rb +7 -15
  265. data/lib/mongo/monitoring/event/secure.rb +15 -20
  266. data/lib/mongo/monitoring/event/server_closed.rb +1 -4
  267. data/lib/mongo/monitoring/event/server_description_changed.rb +4 -8
  268. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +5 -10
  269. data/lib/mongo/monitoring/event/server_heartbeat_started.rb +1 -4
  270. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +3 -8
  271. data/lib/mongo/monitoring/event/server_opening.rb +1 -4
  272. data/lib/mongo/monitoring/event/topology_changed.rb +2 -5
  273. data/lib/mongo/monitoring/event/topology_closed.rb +1 -4
  274. data/lib/mongo/monitoring/event/topology_opening.rb +1 -4
  275. data/lib/mongo/monitoring/event.rb +0 -1
  276. data/lib/mongo/monitoring/publishable.rb +20 -30
  277. data/lib/mongo/monitoring/sdam_log_subscriber.rb +0 -2
  278. data/lib/mongo/monitoring/server_closed_log_subscriber.rb +0 -3
  279. data/lib/mongo/monitoring/server_description_changed_log_subscriber.rb +2 -3
  280. data/lib/mongo/monitoring/server_opening_log_subscriber.rb +0 -3
  281. data/lib/mongo/monitoring/topology_changed_log_subscriber.rb +8 -8
  282. data/lib/mongo/monitoring/topology_closed_log_subscriber.rb +0 -3
  283. data/lib/mongo/monitoring/topology_opening_log_subscriber.rb +0 -3
  284. data/lib/mongo/monitoring/unified_sdam_log_subscriber.rb +1 -3
  285. data/lib/mongo/monitoring.rb +38 -39
  286. data/lib/mongo/operation/aggregate/op_msg.rb +0 -2
  287. data/lib/mongo/operation/aggregate/result.rb +3 -6
  288. data/lib/mongo/operation/aggregate.rb +0 -2
  289. data/lib/mongo/operation/collections_info/result.rb +0 -3
  290. data/lib/mongo/operation/collections_info.rb +0 -2
  291. data/lib/mongo/operation/command/op_msg.rb +1 -4
  292. data/lib/mongo/operation/command.rb +0 -2
  293. data/lib/mongo/operation/context.rb +13 -16
  294. data/lib/mongo/operation/count/op_msg.rb +2 -4
  295. data/lib/mongo/operation/count.rb +0 -2
  296. data/lib/mongo/operation/create/op_msg.rb +2 -5
  297. data/lib/mongo/operation/create.rb +0 -2
  298. data/lib/mongo/operation/create_index/op_msg.rb +3 -7
  299. data/lib/mongo/operation/create_index.rb +0 -2
  300. data/lib/mongo/operation/create_user/op_msg.rb +2 -4
  301. data/lib/mongo/operation/create_user.rb +0 -2
  302. data/lib/mongo/operation/delete/bulk_result.rb +2 -3
  303. data/lib/mongo/operation/delete/op_msg.rb +3 -10
  304. data/lib/mongo/operation/delete/result.rb +0 -3
  305. data/lib/mongo/operation/delete.rb +1 -5
  306. data/lib/mongo/operation/distinct/op_msg.rb +2 -5
  307. data/lib/mongo/operation/distinct.rb +0 -2
  308. data/lib/mongo/operation/drop/op_msg.rb +0 -2
  309. data/lib/mongo/operation/drop.rb +0 -2
  310. data/lib/mongo/operation/drop_database/op_msg.rb +0 -2
  311. data/lib/mongo/operation/drop_database.rb +0 -2
  312. data/lib/mongo/operation/drop_index/op_msg.rb +4 -6
  313. data/lib/mongo/operation/drop_index.rb +0 -2
  314. data/lib/mongo/operation/explain/op_msg.rb +0 -2
  315. data/lib/mongo/operation/explain/result.rb +0 -3
  316. data/lib/mongo/operation/explain.rb +0 -2
  317. data/lib/mongo/operation/find/builder/command.rb +4 -12
  318. data/lib/mongo/operation/find/builder/flags.rb +9 -15
  319. data/lib/mongo/operation/find/builder/modifiers.rb +1 -4
  320. data/lib/mongo/operation/find/builder.rb +0 -1
  321. data/lib/mongo/operation/find/op_msg.rb +4 -12
  322. data/lib/mongo/operation/find/result.rb +0 -3
  323. data/lib/mongo/operation/find.rb +0 -2
  324. data/lib/mongo/operation/get_more/command_builder.rb +1 -6
  325. data/lib/mongo/operation/get_more/op_msg.rb +10 -4
  326. data/lib/mongo/operation/get_more/result.rb +0 -3
  327. data/lib/mongo/operation/get_more.rb +0 -2
  328. data/lib/mongo/operation/indexes/op_msg.rb +0 -2
  329. data/lib/mongo/operation/indexes/result.rb +1 -5
  330. data/lib/mongo/operation/indexes.rb +0 -2
  331. data/lib/mongo/operation/insert/bulk_result.rb +2 -6
  332. data/lib/mongo/operation/insert/op_msg.rb +2 -4
  333. data/lib/mongo/operation/insert/result.rb +0 -3
  334. data/lib/mongo/operation/insert.rb +2 -5
  335. data/lib/mongo/operation/kill_cursors/command_builder.rb +0 -3
  336. data/lib/mongo/operation/kill_cursors/op_msg.rb +1 -3
  337. data/lib/mongo/operation/kill_cursors.rb +0 -2
  338. data/lib/mongo/operation/list_collections/op_msg.rb +4 -6
  339. data/lib/mongo/operation/list_collections/result.rb +1 -4
  340. data/lib/mongo/operation/list_collections.rb +0 -2
  341. data/lib/mongo/operation/map_reduce/op_msg.rb +0 -2
  342. data/lib/mongo/operation/map_reduce/result.rb +3 -6
  343. data/lib/mongo/operation/map_reduce.rb +0 -2
  344. data/lib/mongo/operation/op_msg_base.rb +0 -1
  345. data/lib/mongo/operation/parallel_scan/op_msg.rb +4 -5
  346. data/lib/mongo/operation/parallel_scan/result.rb +2 -5
  347. data/lib/mongo/operation/parallel_scan.rb +0 -2
  348. data/lib/mongo/operation/remove_user/op_msg.rb +2 -4
  349. data/lib/mongo/operation/remove_user.rb +0 -2
  350. data/lib/mongo/operation/result.rb +38 -47
  351. data/lib/mongo/operation/shared/bypass_document_validation.rb +3 -7
  352. data/lib/mongo/operation/shared/causal_consistency_supported.rb +0 -3
  353. data/lib/mongo/operation/shared/executable.rb +20 -29
  354. data/lib/mongo/operation/shared/executable_no_validate.rb +0 -3
  355. data/lib/mongo/operation/shared/executable_transaction_label.rb +0 -2
  356. data/lib/mongo/operation/shared/idable.rb +3 -6
  357. data/lib/mongo/operation/shared/limited.rb +0 -3
  358. data/lib/mongo/operation/shared/object_id_generator.rb +0 -3
  359. data/lib/mongo/operation/shared/op_msg_executable.rb +0 -2
  360. data/lib/mongo/operation/shared/polymorphic_lookup.rb +0 -2
  361. data/lib/mongo/operation/shared/polymorphic_result.rb +2 -4
  362. data/lib/mongo/operation/shared/read_preference_supported.rb +10 -15
  363. data/lib/mongo/operation/shared/response_handling.rb +13 -26
  364. data/lib/mongo/operation/shared/result/aggregatable.rb +12 -13
  365. data/lib/mongo/operation/shared/sessions_supported.rb +87 -99
  366. data/lib/mongo/operation/shared/specifiable.rb +32 -58
  367. data/lib/mongo/operation/shared/write.rb +12 -17
  368. data/lib/mongo/operation/shared/write_concern_supported.rb +4 -7
  369. data/lib/mongo/operation/update/bulk_result.rb +13 -17
  370. data/lib/mongo/operation/update/op_msg.rb +2 -5
  371. data/lib/mongo/operation/update/result.rb +5 -5
  372. data/lib/mongo/operation/update.rb +1 -5
  373. data/lib/mongo/operation/update_user/op_msg.rb +2 -4
  374. data/lib/mongo/operation/update_user.rb +0 -2
  375. data/lib/mongo/operation/users_info/op_msg.rb +2 -4
  376. data/lib/mongo/operation/users_info/result.rb +1 -4
  377. data/lib/mongo/operation/users_info.rb +0 -2
  378. data/lib/mongo/operation/write_command/op_msg.rb +2 -10
  379. data/lib/mongo/operation/write_command.rb +0 -2
  380. data/lib/mongo/operation.rb +9 -14
  381. data/lib/mongo/options/mapper.rb +8 -15
  382. data/lib/mongo/options/redacted.rb +7 -9
  383. data/lib/mongo/options.rb +0 -1
  384. data/lib/mongo/protocol/bit_vector.rb +3 -5
  385. data/lib/mongo/protocol/caching_hash.rb +2 -7
  386. data/lib/mongo/protocol/compressed.rb +5 -10
  387. data/lib/mongo/protocol/get_more.rb +2 -8
  388. data/lib/mongo/protocol/kill_cursors.rb +2 -8
  389. data/lib/mongo/protocol/message.rb +103 -105
  390. data/lib/mongo/protocol/msg.rb +48 -63
  391. data/lib/mongo/protocol/query.rb +32 -41
  392. data/lib/mongo/protocol/registry.rb +2 -5
  393. data/lib/mongo/protocol/reply.rb +10 -16
  394. data/lib/mongo/protocol/serializers.rb +41 -59
  395. data/lib/mongo/protocol.rb +0 -1
  396. data/lib/mongo/query_cache.rb +7 -15
  397. data/lib/mongo/retryable/backpressure.rb +31 -0
  398. data/lib/mongo/retryable/base_worker.rb +39 -13
  399. data/lib/mongo/retryable/read_worker.rb +77 -21
  400. data/lib/mongo/retryable/retry_policy.rb +59 -0
  401. data/lib/mongo/retryable/write_worker.rb +155 -56
  402. data/lib/mongo/retryable.rb +70 -9
  403. data/lib/mongo/search_index/view.rb +1 -1
  404. data/lib/mongo/semaphore.rb +0 -1
  405. data/lib/mongo/server/app_metadata/environment.rb +3 -3
  406. data/lib/mongo/server/app_metadata.rb +4 -5
  407. data/lib/mongo/server/connection.rb +61 -61
  408. data/lib/mongo/server/connection_base.rb +43 -53
  409. data/lib/mongo/server/connection_common.rb +41 -64
  410. data/lib/mongo/server/connection_pool/generation_manager.rb +6 -11
  411. data/lib/mongo/server/connection_pool/populator.rb +1 -4
  412. data/lib/mongo/server/connection_pool.rb +250 -175
  413. data/lib/mongo/server/description/features.rb +23 -60
  414. data/lib/mongo/server/description/load_balancer.rb +0 -2
  415. data/lib/mongo/server/description.rb +138 -137
  416. data/lib/mongo/server/monitor/app_metadata.rb +3 -4
  417. data/lib/mongo/server/monitor/connection.rb +28 -35
  418. data/lib/mongo/server/monitor.rb +65 -60
  419. data/lib/mongo/server/pending_connection.rb +70 -71
  420. data/lib/mongo/server/push_monitor/connection.rb +0 -3
  421. data/lib/mongo/server/push_monitor.rb +21 -29
  422. data/lib/mongo/server/round_trip_time_calculator.rb +11 -17
  423. data/lib/mongo/server.rb +60 -93
  424. data/lib/mongo/server_selector/base.rb +146 -157
  425. data/lib/mongo/server_selector/nearest.rb +5 -5
  426. data/lib/mongo/server_selector/primary.rb +4 -5
  427. data/lib/mongo/server_selector/primary_preferred.rb +5 -6
  428. data/lib/mongo/server_selector/secondary.rb +5 -6
  429. data/lib/mongo/server_selector/secondary_preferred.rb +4 -5
  430. data/lib/mongo/server_selector.rb +3 -4
  431. data/lib/mongo/session/server_session.rb +6 -7
  432. data/lib/mongo/session/session_pool.rb +20 -34
  433. data/lib/mongo/session.rb +321 -189
  434. data/lib/mongo/socket/ocsp_cache.rb +8 -13
  435. data/lib/mongo/socket/ocsp_verifier.rb +69 -70
  436. data/lib/mongo/socket/ssl.rb +44 -43
  437. data/lib/mongo/socket/tcp.rb +5 -8
  438. data/lib/mongo/socket/unix.rb +0 -4
  439. data/lib/mongo/socket.rb +80 -102
  440. data/lib/mongo/srv/monitor.rb +6 -11
  441. data/lib/mongo/srv/resolver.rb +15 -24
  442. data/lib/mongo/srv/result.rb +18 -24
  443. data/lib/mongo/srv.rb +0 -1
  444. data/lib/mongo/timeout.rb +4 -11
  445. data/lib/mongo/topology_version.rb +8 -13
  446. data/lib/mongo/tracing/open_telemetry/command_tracer.rb +28 -1
  447. data/lib/mongo/tracing/open_telemetry/operation_tracer.rb +1 -1
  448. data/lib/mongo/tracing/open_telemetry/tracer.rb +1 -1
  449. data/lib/mongo/uri/options_mapper.rb +135 -126
  450. data/lib/mongo/uri/srv_protocol.rb +25 -38
  451. data/lib/mongo/uri.rb +95 -139
  452. data/lib/mongo/utils.rb +5 -12
  453. data/lib/mongo/version.rb +1 -1
  454. data/lib/mongo/write_concern/acknowledged.rb +0 -2
  455. data/lib/mongo/write_concern/base.rb +6 -6
  456. data/lib/mongo/write_concern/unacknowledged.rb +0 -2
  457. data/lib/mongo/write_concern.rb +14 -15
  458. data/lib/mongo.rb +2 -3
  459. data/mongo.gemspec +17 -17
  460. metadata +5 -5
  461. data/lib/mongo/error/server_api_not_supported.rb +0 -27
  462. data/lib/mongo/operation/shared/result/use_legacy_error_parser.rb +0 -32
  463. data/lib/mongo/operation/shared/validatable.rb +0 -87
data/lib/mongo/session.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- # rubocop:todo all
3
2
 
4
3
  # Copyright (C) 2017-2020 MongoDB Inc.
5
4
  #
@@ -19,7 +18,6 @@ require 'mongo/session/session_pool'
19
18
  require 'mongo/session/server_session'
20
19
 
21
20
  module Mongo
22
-
23
21
  # A logical session representing a set of sequential operations executed
24
22
  # by an application that are related in some way.
25
23
  #
@@ -36,10 +34,9 @@ module Mongo
36
34
  # Initialize a Session.
37
35
  #
38
36
  # A session can be explicit or implicit. Lifetime of explicit sessions is
39
- # managed by the application - applications explicitry create such sessions
37
+ # managed by the application - applications explicitly create such sessions
40
38
  # and explicitly end them. Implicit sessions are created automatically by
41
- # the driver when sending operations to servers that support sessions
42
- # (3.6+), and their lifetime is managed by the driver.
39
+ # the driver, and their lifetime is managed by the driver.
43
40
  #
44
41
  # When an implicit session is created, it cannot have a server session
45
42
  # associated with it. The server session will be checked out of the
@@ -77,6 +74,8 @@ module Mongo
77
74
  # and *:nearest*.
78
75
  # @option options [ true | false ] :snapshot Set up the session for
79
76
  # snapshot reads.
77
+ # @option options [ BSON::Timestamp ] :snapshot_time The desired snapshot
78
+ # time for snapshot reads. Only valid when :snapshot is true.
80
79
  #
81
80
  # @since 2.5.0
82
81
  # @api private
@@ -85,24 +84,37 @@ module Mongo
85
84
  raise ArgumentError, ':causal_consistency and :snapshot options cannot be both set on a session'
86
85
  end
87
86
 
87
+ if options[:snapshot_time] && !options[:snapshot]
88
+ raise ArgumentError, ':snapshot_time can only be set when :snapshot is true'
89
+ end
90
+
91
+ if options[:snapshot_time] && !options[:snapshot_time].is_a?(BSON::Timestamp)
92
+ raise ArgumentError, ':snapshot_time must be a BSON::Timestamp'
93
+ end
94
+
88
95
  if options[:implicit]
89
96
  unless server_session.nil?
90
97
  raise ArgumentError, 'Implicit session cannot reference server session during construction'
91
98
  end
92
- else
93
- if server_session.nil?
94
- raise ArgumentError, 'Explicit session must reference server session during construction'
95
- end
99
+ elsif server_session.nil?
100
+ raise ArgumentError, 'Explicit session must reference server session during construction'
96
101
  end
97
102
 
98
103
  @server_session = server_session
99
104
  options = options.dup
100
105
 
101
- @client = client.use(:admin)
106
+ # Implicit sessions only need the cluster and client options (never run
107
+ # transactions), so avoid creating a Mongo::Client clone to prevent
108
+ # memory leaks: use the original client directly instead.
109
+ @client = options[:implicit] ? client : client.use(:admin)
110
+ @cluster = @client.cluster
102
111
  @options = options.dup.freeze
103
112
  @cluster_time = nil
104
113
  @state = NO_TRANSACTION_STATE
105
114
  @with_transaction_deadline = nil
115
+ @with_transaction_timeout_ms = nil
116
+ @inside_with_transaction = false
117
+ @snapshot_timestamp = options[:snapshot_time]
106
118
  end
107
119
 
108
120
  # @return [ Hash ] The options for this session.
@@ -115,9 +127,7 @@ module Mongo
115
127
  # @since 2.5.1
116
128
  attr_reader :client
117
129
 
118
- def cluster
119
- @client.cluster
120
- end
130
+ attr_reader :cluster
121
131
 
122
132
  # @return [ true | false ] Whether the session is configured for snapshot
123
133
  # reads.
@@ -154,7 +164,7 @@ module Mongo
154
164
  #
155
165
  # @since 2.6.0
156
166
  def txn_options
157
- @txn_options or raise ArgumentError, "There is no active transaction"
167
+ @txn_options or raise ArgumentError, 'There is no active transaction'
158
168
  end
159
169
 
160
170
  # Is this session an implicit one (not user-created).
@@ -208,8 +218,8 @@ module Mongo
208
218
  #
209
219
  # @return [ true, false ] If writes will be retried.
210
220
  #
211
- # @note Retryable writes are only available on server versions at least 3.6
212
- # and with sharded clusters, replica sets, or load-balanced topologies.
221
+ # @note Retryable writes are only available with sharded clusters, replica
222
+ # sets, or load-balanced topologies.
213
223
  #
214
224
  # @since 2.5.0
215
225
  def retry_writes?
@@ -229,7 +239,7 @@ module Mongo
229
239
  # @since 2.6.0
230
240
  def txn_read_preference
231
241
  rp = txn_options[:read] ||
232
- @client.read_preference
242
+ @client.read_preference
233
243
  Mongo::Lint.validate_underscore_read_preference(rp)
234
244
  rp
235
245
  end
@@ -255,9 +265,7 @@ module Mongo
255
265
  #
256
266
  # @since 2.5.0
257
267
  def session_id
258
- if ended?
259
- raise Error::SessionEnded
260
- end
268
+ raise Error::SessionEnded if ended?
261
269
 
262
270
  # An explicit session will always have a session_id, because during
263
271
  # construction a server session must be provided. An implicit session
@@ -266,9 +274,7 @@ module Mongo
266
274
  # to experience this failure because an implicit session shouldn't be
267
275
  # accessible to applications due to its lifetime being constrained to
268
276
  # operation execution, which is done entirely by the driver.
269
- unless materialized?
270
- raise Error::SessionNotMaterialized
271
- end
277
+ raise Error::SessionNotMaterialized unless materialized?
272
278
 
273
279
  @server_session.session_id
274
280
  end
@@ -296,20 +302,20 @@ module Mongo
296
302
  #
297
303
  # @since 2.5.0
298
304
  MISMATCHED_CLUSTER_ERROR_MSG = 'The configuration of the client used to create this session does not match that ' +
299
- 'of the client owning this operation. Please only use this session for operations through its parent ' +
300
- 'client.'.freeze
305
+ 'of the client owning this operation. Please only use this session for operations through its parent ' +
306
+ 'client.'
301
307
 
302
308
  # Error message describing that the session cannot be used because it has already been ended.
303
309
  #
304
310
  # @since 2.5.0
305
- SESSION_ENDED_ERROR_MSG = 'This session has ended and cannot be used. Please create a new one.'.freeze
311
+ SESSION_ENDED_ERROR_MSG = 'This session has ended and cannot be used. Please create a new one.'
306
312
 
307
313
  # Error message describing that sessions are not supported by the server version.
308
314
  #
309
315
  # @since 2.5.0
310
316
  # @deprecated
311
- SESSIONS_NOT_SUPPORTED = 'Sessions are not supported by the connected servers.'.freeze
312
- # Note: SESSIONS_NOT_SUPPORTED is used by Mongoid - do not remove from driver.
317
+ SESSIONS_NOT_SUPPORTED = 'Sessions are not supported by the connected servers.'
318
+ # NOTE: SESSIONS_NOT_SUPPORTED is used by Mongoid 7.6 and earlier
313
319
 
314
320
  # The state of a session in which the last operation was not related to
315
321
  # any transaction or no operations have yet occurred.
@@ -384,13 +390,15 @@ module Mongo
384
390
  rescue Mongo::Error, Error::AuthError
385
391
  end
386
392
  end
387
- if @server_session
388
- @client.cluster.session_pool.checkin(@server_session)
389
- end
393
+ # Release any pinned connection (e.g. after a committed transaction
394
+ # in load-balanced mode).
395
+ unpin if pinned_connection_global_id
396
+ cluster.session_pool.checkin(@server_session) if @server_session
390
397
  end
391
398
  ensure
392
399
  @server_session = nil
393
400
  @ended = true
401
+ @client = nil
394
402
  end
395
403
 
396
404
  # Executes the provided block in a transaction, retrying as necessary.
@@ -448,38 +456,74 @@ module Mongo
448
456
  #
449
457
  # @since 2.7.0
450
458
  def with_transaction(options = nil)
459
+ @inside_with_transaction = true
460
+ @with_transaction_timeout_ms = options&.dig(:timeout_ms) || @options[:default_timeout_ms] || @client.timeout_ms
451
461
  @with_transaction_deadline = calculate_with_transaction_deadline(options)
452
462
  deadline = if @with_transaction_deadline
453
463
  # CSOT enabled, so we have a customer defined deadline.
454
464
  @with_transaction_deadline
455
465
  else
456
- # CSOT not enabled, so we use the default deadline, 120 seconds.
466
+ # CSOT not enabled, so we use the default deadline, 120 seconds.
457
467
  Utils.monotonic_time + 120
458
468
  end
459
469
  transaction_in_progress = false
470
+ transaction_attempt = 0
471
+ last_error = nil
472
+ overload_error_count = 0
473
+ overload_encountered = false
474
+
460
475
  loop do
461
- commit_options = {}
462
- if options
463
- commit_options[:write_concern] = options[:write_concern]
476
+ if transaction_attempt > 0
477
+ if overload_encountered
478
+ delay = @client.retry_policy.backoff_delay(overload_error_count)
479
+ if backoff_would_exceed_deadline?(deadline, delay)
480
+ make_timeout_error_from(last_error, 'CSOT timeout expired waiting to retry withTransaction')
481
+ end
482
+ raise(last_error) unless @client.retry_policy.should_retry_overload?(overload_error_count, delay)
483
+
484
+ sleep(delay)
485
+ else
486
+ backoff = backoff_seconds_for_retry(transaction_attempt)
487
+ if backoff_would_exceed_deadline?(deadline, backoff)
488
+ make_timeout_error_from(last_error, 'CSOT timeout expired waiting to retry withTransaction')
489
+ end
490
+
491
+ sleep(backoff)
492
+ end
464
493
  end
494
+
495
+ commit_options = {}
496
+ commit_options[:write_concern] = options[:write_concern] if options
465
497
  start_transaction(options)
466
498
  transaction_in_progress = true
499
+ transaction_attempt += 1
500
+
467
501
  begin
468
502
  rv = yield self
469
503
  rescue Exception => e
470
504
  if within_states?(STARTING_TRANSACTION_STATE, TRANSACTION_IN_PROGRESS_STATE)
471
505
  log_warn("Aborting transaction due to #{e.class}: #{e}")
472
- @with_transaction_deadline = nil
506
+ # CSOT: if the deadline is already expired, clear it so that
507
+ # abort_transaction uses a fresh timeout (not the expired deadline).
508
+ # If the deadline is not yet expired, keep it so abort uses remaining time.
509
+ @with_transaction_deadline = nil if @with_transaction_deadline && deadline_expired?(deadline)
473
510
  abort_transaction
474
511
  transaction_in_progress = false
475
512
  end
476
513
 
477
514
  if deadline_expired?(deadline)
478
515
  transaction_in_progress = false
479
- raise
516
+ make_timeout_error_from(e, 'CSOT timeout expired during withTransaction callback')
480
517
  end
481
518
 
482
519
  if e.is_a?(Mongo::Error) && e.label?('TransientTransactionError')
520
+ last_error = e
521
+ if e.label?('SystemOverloadedError')
522
+ overload_encountered = true
523
+ overload_error_count += 1
524
+ elsif overload_encountered
525
+ overload_error_count += 1
526
+ end
483
527
  next
484
528
  end
485
529
 
@@ -490,32 +534,71 @@ module Mongo
490
534
  return rv
491
535
  end
492
536
 
537
+ # CSOT: if the timeout has expired before we can commit, abort the
538
+ # transaction instead and raise a client-side timeout error.
539
+ if @with_transaction_deadline && deadline_expired?(deadline)
540
+ transaction_in_progress = false
541
+ @with_transaction_deadline = nil
542
+ abort_transaction
543
+ raise Mongo::Error::TimeoutError, 'CSOT timeout expired before transaction could be committed'
544
+ end
545
+
493
546
  begin
494
547
  commit_transaction(commit_options)
495
548
  transaction_in_progress = false
496
549
  return rv
497
550
  rescue Mongo::Error => e
498
551
  if e.label?('UnknownTransactionCommitResult')
499
- if deadline_expired?(deadline) ||
500
- e.is_a?(Error::OperationFailure::Family) && e.max_time_ms_expired?
501
- then
552
+ if deadline_expired?(deadline) ||
553
+ (e.is_a?(Error::OperationFailure::Family) && e.max_time_ms_expired?)
502
554
  transaction_in_progress = false
503
- raise
555
+
556
+ raise unless @with_transaction_timeout_ms && deadline_expired?(deadline)
557
+
558
+ make_timeout_error_from(e, 'CSOT timeout expired during withTransaction commit')
504
559
  end
505
- wc_options = case v = commit_options[:write_concern]
506
- when WriteConcern::Base
507
- v.options
508
- when nil
509
- {}
510
- else
511
- v
560
+
561
+ if e.label?('SystemOverloadedError')
562
+ overload_encountered = true
563
+ overload_error_count += 1
564
+ elsif overload_encountered
565
+ overload_error_count += 1
566
+ end
567
+
568
+ if overload_encountered
569
+ delay = @client.retry_policy.backoff_delay(overload_error_count)
570
+ if backoff_would_exceed_deadline?(deadline, delay)
571
+ transaction_in_progress = false
572
+ make_timeout_error_from(e, 'CSOT timeout expired during withTransaction commit')
512
573
  end
574
+ unless @client.retry_policy.should_retry_overload?(overload_error_count, delay)
575
+ transaction_in_progress = false
576
+ raise
577
+ end
578
+ sleep(delay)
579
+ end
580
+
581
+ wc_options = case v = commit_options[:write_concern]
582
+ when WriteConcern::Base
583
+ v.options
584
+ when nil
585
+ {}
586
+ else
587
+ v
588
+ end
513
589
  commit_options[:write_concern] = wc_options.merge(w: :majority)
514
590
  retry
515
591
  elsif e.label?('TransientTransactionError')
516
592
  if Utils.monotonic_time >= deadline
517
593
  transaction_in_progress = false
518
- raise
594
+ make_timeout_error_from(e, 'CSOT timeout expired during withTransaction commit')
595
+ end
596
+ last_error = e
597
+ if e.label?('SystemOverloadedError')
598
+ overload_encountered = true
599
+ overload_error_count += 1
600
+ elsif overload_encountered
601
+ overload_error_count += 1
519
602
  end
520
603
  @state = NO_TRANSACTION_STATE
521
604
  next
@@ -542,6 +625,8 @@ module Mongo
542
625
  end
543
626
  end
544
627
  @with_transaction_deadline = nil
628
+ @with_transaction_timeout_ms = nil
629
+ @inside_with_transaction = false
545
630
  end
546
631
 
547
632
  # Places subsequent operations in this session into a new transaction.
@@ -556,7 +641,7 @@ module Mongo
556
641
  #
557
642
  # @option options [ Integer ] :max_commit_time_ms The maximum amount of
558
643
  # time to allow a single commitTransaction command to run, in milliseconds.
559
- # This options is deprecated, use :timeout_ms instead.
644
+ # This option is deprecated, use :timeout_ms instead.
560
645
  # @option options [ Hash ] :read_concern The read concern options hash,
561
646
  # with the following optional keys:
562
647
  # - *:level* -- the read preference level as a symbol; valid values
@@ -582,28 +667,25 @@ module Mongo
582
667
  if options
583
668
  Lint.validate_read_concern_option(options[:read_concern])
584
669
 
585
- =begin
586
- # It would be handy to detect invalid read preferences here, but
587
- # some of the spec tests require later detection of invalid read prefs.
588
- # Maybe we can do this when lint mode is on.
589
- mode = options[:read] && options[:read][:mode].to_s
590
- if mode && mode != 'primary'
591
- raise Mongo::Error::InvalidTransactionOperation.new(
592
- "read preference in a transaction must be primary (requested: #{mode})"
593
- )
594
- end
595
- =end
670
+ # # It would be handy to detect invalid read preferences here, but
671
+ # # some of the spec tests require later detection of invalid read prefs.
672
+ # # Maybe we can do this when lint mode is on.
673
+ # mode = options[:read] && options[:read][:mode].to_s
674
+ # if mode && mode != 'primary'
675
+ # raise Mongo::Error::InvalidTransactionOperation.new(
676
+ # "read preference in a transaction must be primary (requested: #{mode})"
677
+ # )
678
+ # end
596
679
  end
597
680
 
598
- if snapshot?
599
- raise Mongo::Error::SnapshotSessionTransactionProhibited
600
- end
681
+ raise Mongo::Error::SnapshotSessionTransactionProhibited if snapshot?
601
682
 
602
683
  check_if_ended!
603
684
 
604
685
  if within_states?(STARTING_TRANSACTION_STATE, TRANSACTION_IN_PROGRESS_STATE)
605
686
  raise Mongo::Error::InvalidTransactionOperation.new(
606
- Mongo::Error::InvalidTransactionOperation::TRANSACTION_ALREADY_IN_PROGRESS)
687
+ Mongo::Error::InvalidTransactionOperation::TRANSACTION_ALREADY_IN_PROGRESS
688
+ )
607
689
  end
608
690
 
609
691
  unpin
@@ -613,7 +695,8 @@ module Mongo
613
695
 
614
696
  if txn_write_concern && !WriteConcern.get(txn_write_concern).acknowledged?
615
697
  raise Mongo::Error::InvalidTransactionOperation.new(
616
- Mongo::Error::InvalidTransactionOperation::UNACKNOWLEDGED_WRITE_CONCERN)
698
+ Mongo::Error::InvalidTransactionOperation::UNACKNOWLEDGED_WRITE_CONCERN
699
+ )
617
700
  end
618
701
 
619
702
  @state = STARTING_TRANSACTION_STATE
@@ -642,7 +725,7 @@ module Mongo
642
725
  # @raise [ Error::InvalidTransactionOperation ] If there is no active transaction.
643
726
  #
644
727
  # @since 2.6.0
645
- def commit_transaction(options=nil)
728
+ def commit_transaction(options = nil)
646
729
  QueryCache.clear
647
730
  check_if_ended!
648
731
  check_if_no_transaction!
@@ -650,7 +733,9 @@ module Mongo
650
733
  if within_states?(TRANSACTION_ABORTED_STATE)
651
734
  raise Mongo::Error::InvalidTransactionOperation.new(
652
735
  Mongo::Error::InvalidTransactionOperation.cannot_call_after_msg(
653
- :abortTransaction, :commitTransaction))
736
+ :abortTransaction, :commitTransaction
737
+ )
738
+ )
654
739
  end
655
740
 
656
741
  options ||= {}
@@ -670,9 +755,7 @@ module Mongo
670
755
  @committing_transaction = true
671
756
 
672
757
  write_concern = options[:write_concern] || txn_options[:write_concern]
673
- if write_concern && !write_concern.is_a?(WriteConcern::Base)
674
- write_concern = WriteConcern.get(write_concern)
675
- end
758
+ write_concern = WriteConcern.get(write_concern) if write_concern && !write_concern.is_a?(WriteConcern::Base)
676
759
 
677
760
  context = Operation::Context.new(
678
761
  client: @client,
@@ -680,15 +763,14 @@ module Mongo
680
763
  operation_timeouts: operation_timeouts(options)
681
764
  )
682
765
  write_with_retry(write_concern, ending_transaction: true,
683
- context: context,
684
- ) do |connection, txn_num, context|
685
- if context.retry?
766
+ context: context) do |connection, txn_num, context|
767
+ if context.retry? && !context.overload_only_retry?
686
768
  if write_concern
687
769
  wco = write_concern.options.merge(w: :majority)
688
- wco[:wtimeout] ||= 10000
770
+ wco[:wtimeout] ||= 10_000
689
771
  write_concern = WriteConcern.get(wco)
690
772
  else
691
- write_concern = WriteConcern.get(w: :majority, wtimeout: 10000)
773
+ write_concern = WriteConcern.get(w: :majority, wtimeout: 10_000)
692
774
  end
693
775
  end
694
776
  spec = {
@@ -738,12 +820,15 @@ module Mongo
738
820
  if within_states?(TRANSACTION_COMMITTED_STATE)
739
821
  raise Mongo::Error::InvalidTransactionOperation.new(
740
822
  Mongo::Error::InvalidTransactionOperation.cannot_call_after_msg(
741
- :commitTransaction, :abortTransaction))
823
+ :commitTransaction, :abortTransaction
824
+ )
825
+ )
742
826
  end
743
827
 
744
828
  if within_states?(TRANSACTION_ABORTED_STATE)
745
829
  raise Mongo::Error::InvalidTransactionOperation.new(
746
- Mongo::Error::InvalidTransactionOperation.cannot_call_twice_msg(:abortTransaction))
830
+ Mongo::Error::InvalidTransactionOperation.cannot_call_twice_msg(:abortTransaction)
831
+ )
747
832
  end
748
833
 
749
834
  options ||= {}
@@ -757,21 +842,18 @@ module Mongo
757
842
  operation_timeouts: operation_timeouts(options)
758
843
  )
759
844
  write_with_retry(txn_options[:write_concern],
760
- ending_transaction: true, context: context,
761
- ) do |connection, txn_num, context|
762
- begin
763
- operation = Operation::Command.new(
764
- selector: { abortTransaction: 1 },
765
- db_name: 'admin',
766
- session: self,
767
- txn_num: txn_num
768
- )
769
- tracer.trace_operation(operation, context, op_name: 'abortTransaction') do
770
- operation.execute_with_connection(connection, context: context)
771
- end
772
- ensure
773
- unpin
845
+ ending_transaction: true, context: context) do |connection, txn_num, context|
846
+ operation = Operation::Command.new(
847
+ selector: { abortTransaction: 1 },
848
+ db_name: 'admin',
849
+ session: self,
850
+ txn_num: txn_num
851
+ )
852
+ tracer.trace_operation(operation, context, op_name: 'abortTransaction') do
853
+ operation.execute_with_connection(connection, context: context)
774
854
  end
855
+ ensure
856
+ unpin
775
857
  end
776
858
  end
777
859
 
@@ -835,14 +917,12 @@ module Mongo
835
917
  #
836
918
  # @api private
837
919
  def pin_to_server(server)
838
- if server.nil?
839
- raise ArgumentError, 'Cannot pin to a nil server'
840
- end
841
- if Lint.enabled?
842
- unless server.mongos?
843
- raise Error::LintError, "Attempted to pin the session to server #{server.summary} which is not a mongos"
844
- end
920
+ raise ArgumentError, 'Cannot pin to a nil server' if server.nil?
921
+
922
+ if Lint.enabled? && !server.mongos?
923
+ raise Error::LintError, "Attempted to pin the session to server #{server.summary} which is not a mongos"
845
924
  end
925
+
846
926
  @pinned_server = server
847
927
  end
848
928
 
@@ -850,13 +930,14 @@ module Mongo
850
930
  #
851
931
  # @param [ Integer ] connection_global_id The global id of connection to pin
852
932
  # this session to.
933
+ # @param [ Connection | nil ] connection The connection object to pin to.
853
934
  #
854
935
  # @api private
855
- def pin_to_connection(connection_global_id)
856
- if connection_global_id.nil?
857
- raise ArgumentError, 'Cannot pin to a nil connection id'
858
- end
936
+ def pin_to_connection(connection_global_id, connection: nil)
937
+ raise ArgumentError, 'Cannot pin to a nil connection id' if connection_global_id.nil?
938
+
859
939
  @pinned_connection_global_id = connection_global_id
940
+ @pinned_connection = connection
860
941
  end
861
942
 
862
943
  # Unpins this session from the pinned server or connection,
@@ -866,9 +947,25 @@ module Mongo
866
947
  #
867
948
  # @api private
868
949
  def unpin(connection = nil)
950
+ # Idempotent: if there is no pinned state to clear, do nothing. Nested
951
+ # unpin_maybe handlers (e.g. in BulkWrite#execute_operation wrapping an
952
+ # OpMsg execution that already calls unpin_maybe in its own do_execute)
953
+ # can call this method twice for the same error; checking the connection
954
+ # back into the pool a second time would raise from the pool.
955
+ return if @pinned_server.nil? && @pinned_connection.nil? && @pinned_connection_global_id.nil?
956
+
869
957
  @pinned_server = nil
870
958
  @pinned_connection_global_id = nil
871
- connection.unpin unless connection.nil?
959
+ conn = connection || @pinned_connection
960
+ if conn
961
+ conn.unpin(:transaction)
962
+ # Only check the connection back into the pool if nothing else
963
+ # still holds a pin on it (e.g. an open cursor).
964
+ unless conn.pinned?
965
+ conn.connection_pool.check_in(conn)
966
+ end
967
+ end
968
+ @pinned_connection = nil
872
969
  end
873
970
 
874
971
  # Unpins this session from the pinned server or connection, if the session was pinned
@@ -884,14 +981,12 @@ module Mongo
884
981
  # @api private
885
982
  def unpin_maybe(error, connection = nil)
886
983
  if !within_states?(Session::NO_TRANSACTION_STATE) &&
887
- error.label?('TransientTransactionError')
888
- then
984
+ error.label?('TransientTransactionError')
889
985
  unpin(connection)
890
986
  end
891
987
 
892
988
  if committing_transaction? &&
893
- error.label?('UnknownTransactionCommitResult')
894
- then
989
+ error.label?('UnknownTransactionCommitResult')
895
990
  unpin(connection)
896
991
  end
897
992
  end
@@ -922,9 +1017,7 @@ module Mongo
922
1017
  # @api private
923
1018
  def add_start_transaction!(command)
924
1019
  command.tap do |c|
925
- if starting_transaction?
926
- c[:startTransaction] = true
927
- end
1020
+ c[:startTransaction] = true if starting_transaction?
928
1021
  end
929
1022
  end
930
1023
 
@@ -952,7 +1045,7 @@ module Mongo
952
1045
  #
953
1046
  # @since 2.6.0
954
1047
  # @api private
955
- def add_txn_opts!(command, read, context)
1048
+ def add_txn_opts!(command, _read, context)
956
1049
  command.tap do |c|
957
1050
  # The read concern should be added to any command that starts a transaction.
958
1051
  if starting_transaction?
@@ -975,27 +1068,23 @@ module Mongo
975
1068
  if rc.nil? || rc.empty?
976
1069
  c.delete(:readConcern)
977
1070
  else
978
- c[:readConcern ] = Options::Mapper.transform_values_to_strings(rc)
1071
+ c[:readConcern] = Options::Mapper.transform_values_to_strings(rc)
979
1072
  end
980
1073
  end
981
1074
 
982
1075
  # We need to send the read concern level as a string rather than a symbol.
983
- if c[:readConcern]
984
- c[:readConcern] = Options::Mapper.transform_values_to_strings(c[:readConcern])
985
- end
1076
+ c[:readConcern] = Options::Mapper.transform_values_to_strings(c[:readConcern]) if c[:readConcern]
986
1077
 
987
- if c[:commitTransaction]
988
- if max_time_ms = txn_options[:max_commit_time_ms]
989
- c[:maxTimeMS] = max_time_ms
990
- end
1078
+ if c[:commitTransaction] && (max_time_ms = txn_options[:max_commit_time_ms])
1079
+ c[:maxTimeMS] = max_time_ms
991
1080
  end
992
1081
 
993
1082
  # The write concern should be added to any abortTransaction or commitTransaction command.
994
- if (c[:abortTransaction] || c[:commitTransaction])
1083
+ if c[:abortTransaction] || c[:commitTransaction]
995
1084
  if @already_committed
996
1085
  wc = BSON::Document.new(c[:writeConcern] || txn_write_concern || {})
997
1086
  wc.merge!(w: :majority)
998
- wc[:wtimeout] ||= 10000
1087
+ wc[:wtimeout] ||= 10_000
999
1088
  c[:writeConcern] = wc
1000
1089
  elsif txn_write_concern
1001
1090
  c[:writeConcern] ||= txn_write_concern
@@ -1008,12 +1097,10 @@ module Mongo
1008
1097
  end
1009
1098
 
1010
1099
  # Ignore wtimeout if csot
1011
- if context&.csot?
1012
- c[:writeConcern]&.delete(:wtimeout)
1013
- end
1100
+ c[:writeConcern]&.delete(:wtimeout) if context&.csot?
1014
1101
 
1015
1102
  # We must not send an empty (server default) write concern.
1016
- c.delete(:writeConcern) if c[:writeConcern]&.empty?
1103
+ c.delete(:writeConcern) if c[:writeConcern] && c[:writeConcern].empty?
1017
1104
  end
1018
1105
  end
1019
1106
 
@@ -1035,7 +1122,7 @@ module Mongo
1035
1122
  end
1036
1123
  end
1037
1124
 
1038
- # Ensure that the read preference of a command primary.
1125
+ # Ensure that the read preference of a command is primary.
1039
1126
  #
1040
1127
  # @example
1041
1128
  # session.validate_read_preference!(command)
@@ -1051,11 +1138,21 @@ module Mongo
1051
1138
 
1052
1139
  mode = command['$readPreference']['mode'] || command['$readPreference'][:mode]
1053
1140
 
1054
- if mode && mode != 'primary'
1055
- raise Mongo::Error::InvalidTransactionOperation.new(
1056
- "read preference in a transaction must be primary (requested: #{mode})"
1057
- )
1058
- end
1141
+ return unless mode && mode != 'primary'
1142
+
1143
+ raise Mongo::Error::InvalidTransactionOperation.new(
1144
+ "read preference in a transaction must be primary (requested: #{mode})"
1145
+ )
1146
+ end
1147
+
1148
+ # Reverts the session state to STARTING_TRANSACTION_STATE.
1149
+ # Called before retrying the first command in a transaction so that
1150
+ # startTransaction: true is preserved on the retry.
1151
+ # @api private
1152
+ def revert_to_starting_transaction!
1153
+ return unless within_states?(TRANSACTION_IN_PROGRESS_STATE)
1154
+
1155
+ @state = STARTING_TRANSACTION_STATE
1059
1156
  end
1060
1157
 
1061
1158
  # Update the state of the session due to a (non-commit and non-abort) operation being run.
@@ -1085,8 +1182,8 @@ module Mongo
1085
1182
  # @since 2.5.0
1086
1183
  # @api private
1087
1184
  def validate!(client)
1088
- check_matching_cluster!(client)
1089
1185
  check_if_ended!
1186
+ check_matching_cluster!(client)
1090
1187
  self
1091
1188
  end
1092
1189
 
@@ -1110,10 +1207,8 @@ module Mongo
1110
1207
  end
1111
1208
  @server_session.set_last_use!
1112
1209
 
1113
- if doc = result.reply && result.reply.documents.first
1114
- if doc[:recoveryToken]
1115
- self.recovery_token = doc[:recoveryToken]
1116
- end
1210
+ if (doc = result.reply && result.reply.documents.first) && doc[:recoveryToken]
1211
+ self.recovery_token = doc[:recoveryToken]
1117
1212
  end
1118
1213
 
1119
1214
  result
@@ -1130,11 +1225,11 @@ module Mongo
1130
1225
  #
1131
1226
  # @since 2.5.0
1132
1227
  def advance_operation_time(new_operation_time)
1133
- if @operation_time
1134
- @operation_time = [ @operation_time, new_operation_time ].max
1135
- else
1136
- @operation_time = new_operation_time
1137
- end
1228
+ @operation_time = if @operation_time
1229
+ [ @operation_time, new_operation_time ].max
1230
+ else
1231
+ new_operation_time
1232
+ end
1138
1233
  end
1139
1234
 
1140
1235
  # If not already set, populate a session objects's server_session by
@@ -1144,9 +1239,7 @@ module Mongo
1144
1239
  #
1145
1240
  # @api private
1146
1241
  def materialize_if_needed
1147
- if ended?
1148
- raise Error::SessionEnded
1149
- end
1242
+ raise Error::SessionEnded if ended?
1150
1243
 
1151
1244
  return unless implicit? && !@server_session
1152
1245
 
@@ -1157,9 +1250,7 @@ module Mongo
1157
1250
 
1158
1251
  # @api private
1159
1252
  def materialized?
1160
- if ended?
1161
- raise Error::SessionEnded
1162
- end
1253
+ raise Error::SessionEnded if ended?
1163
1254
 
1164
1255
  !@server_session.nil?
1165
1256
  end
@@ -1174,9 +1265,7 @@ module Mongo
1174
1265
  # @since 2.5.0
1175
1266
  # @api private
1176
1267
  def next_txn_num
1177
- if ended?
1178
- raise Error::SessionEnded
1179
- end
1268
+ raise Error::SessionEnded if ended?
1180
1269
 
1181
1270
  @server_session.next_txn_num
1182
1271
  end
@@ -1190,20 +1279,39 @@ module Mongo
1190
1279
  #
1191
1280
  # @since 2.6.0
1192
1281
  def txn_num
1193
- if ended?
1194
- raise Error::SessionEnded
1195
- end
1282
+ raise Error::SessionEnded if ended?
1196
1283
 
1197
1284
  @server_session.txn_num
1198
1285
  end
1199
1286
 
1287
+ # @return [ BSON::Timestamp | nil ] The snapshot time for this session.
1288
+ # nil if the session is not a snapshot session, or if it is a snapshot
1289
+ # session for which no :snapshot_time option was provided and no read
1290
+ # has yet captured atClusterTime from the server.
1291
+ attr_reader :snapshot_timestamp
1292
+
1293
+ # Sets the snapshot time for the session. Once set, subsequent
1294
+ # assignments are ignored: snapshotTime is established at most once per
1295
+ # session, either from the :snapshot_time option at construction or from
1296
+ # the atClusterTime returned by the first find/aggregate/distinct
1297
+ # response. This keeps the property effectively read-only for callers,
1298
+ # per the snapshot-sessions spec rationale.
1299
+ #
1200
1300
  # @api private
1201
- attr_accessor :snapshot_timestamp
1301
+ def snapshot_timestamp=(value)
1302
+ @snapshot_timestamp ||= value
1303
+ end
1202
1304
 
1203
1305
  # @return [ Integer | nil ] The deadline for the current transaction, if any.
1204
1306
  # @api private
1205
1307
  attr_reader :with_transaction_deadline
1206
1308
 
1309
+ # @return [ Boolean ] Whether we are currently inside a with_transaction block.
1310
+ # @api private
1311
+ def inside_with_transaction?
1312
+ @inside_with_transaction
1313
+ end
1314
+
1207
1315
  private
1208
1316
 
1209
1317
  # Get the read concern the session will use when starting a transaction.
@@ -1229,7 +1337,8 @@ module Mongo
1229
1337
  return unless within_states?(NO_TRANSACTION_STATE)
1230
1338
 
1231
1339
  raise Mongo::Error::InvalidTransactionOperation.new(
1232
- Mongo::Error::InvalidTransactionOperation::NO_TRANSACTION_STARTED)
1340
+ Mongo::Error::InvalidTransactionOperation::NO_TRANSACTION_STARTED
1341
+ )
1233
1342
  end
1234
1343
 
1235
1344
  def txn_write_concern
@@ -1240,25 +1349,23 @@ module Mongo
1240
1349
  # Returns causal consistency document if the last operation time is
1241
1350
  # known and causal consistency is enabled, otherwise returns nil.
1242
1351
  def causal_consistency_doc
1243
- if operation_time && causal_consistency?
1244
- {:afterClusterTime => operation_time}
1245
- else
1246
- nil
1247
- end
1352
+ return unless operation_time && causal_consistency?
1353
+
1354
+ { afterClusterTime: operation_time }
1248
1355
  end
1249
1356
 
1250
1357
  def causal_consistency?
1251
- @causal_consistency ||= (if @options.key?(:causal_consistency)
1252
- !!@options[:causal_consistency]
1253
- else
1254
- true
1255
- end)
1358
+ @causal_consistency ||= if @options.key?(:causal_consistency)
1359
+ !!@options[:causal_consistency]
1360
+ else
1361
+ true
1362
+ end
1256
1363
  end
1257
1364
 
1258
1365
  def set_operation_time(result)
1259
- if result && result.operation_time
1260
- @operation_time = result.operation_time
1261
- end
1366
+ return unless result && result.operation_time
1367
+
1368
+ @operation_time = result.operation_time
1262
1369
  end
1263
1370
 
1264
1371
  def check_if_ended!
@@ -1266,40 +1373,34 @@ module Mongo
1266
1373
  end
1267
1374
 
1268
1375
  def check_matching_cluster!(client)
1269
- if @client.cluster != client.cluster
1270
- raise Mongo::Error::InvalidSession.new(MISMATCHED_CLUSTER_ERROR_MSG)
1271
- end
1376
+ return unless cluster != client.cluster
1377
+
1378
+ raise Mongo::Error::InvalidSession.new(MISMATCHED_CLUSTER_ERROR_MSG)
1272
1379
  end
1273
1380
 
1274
1381
  def check_transactions_supported!
1275
- raise Mongo::Error::TransactionsNotSupported, "standalone topology" if cluster.single?
1276
-
1277
- cluster.next_primary.with_connection do |conn|
1278
- if cluster.replica_set? && !conn.features.transactions_enabled?
1279
- raise Mongo::Error::TransactionsNotSupported, "server version is < 4.0"
1280
- end
1281
- if cluster.sharded? && !conn.features.sharded_transactions_enabled?
1282
- raise Mongo::Error::TransactionsNotSupported, "sharded transactions require server version >= 4.2"
1283
- end
1284
- end
1382
+ raise Mongo::Error::TransactionsNotSupported, 'standalone topology' if cluster.single?
1285
1383
  end
1286
1384
 
1287
1385
  def operation_timeouts(opts)
1288
1386
  {
1289
- inherited_timeout_ms: @client.timeout_ms
1387
+ inherited_timeout_ms: @with_transaction_timeout_ms || @client.timeout_ms
1290
1388
  }.tap do |result|
1291
- if @with_transaction_deadline.nil?
1292
- if timeout_ms = opts[:timeout_ms]
1293
- result[:operation_timeout_ms] = timeout_ms
1294
- elsif default_timeout_ms = options[:default_timeout_ms]
1295
- result[:operation_timeout_ms] = default_timeout_ms
1389
+ if @inside_with_transaction
1390
+ if opts[:timeout_ms]
1391
+ raise Mongo::Error::InvalidTransactionOperation,
1392
+ 'timeoutMS cannot be overridden inside a withTransaction callback'
1296
1393
  end
1394
+ elsif timeout_ms = opts[:timeout_ms]
1395
+ result[:operation_timeout_ms] = timeout_ms
1396
+ elsif default_timeout_ms = options[:default_timeout_ms]
1397
+ result[:operation_timeout_ms] = default_timeout_ms
1297
1398
  end
1298
1399
  end
1299
1400
  end
1300
1401
 
1301
1402
  def calculate_with_transaction_deadline(opts)
1302
- calc = -> (timeout) {
1403
+ calc = lambda { |timeout|
1303
1404
  if timeout == 0
1304
1405
  0
1305
1406
  else
@@ -1322,5 +1423,36 @@ module Mongo
1322
1423
  Utils.monotonic_time >= deadline
1323
1424
  end
1324
1425
  end
1426
+
1427
+ # Exponential backoff settings for with_transaction retries.
1428
+ BACKOFF_INITIAL = 0.005
1429
+ BACKOFF_MAX = 0.5
1430
+ private_constant :BACKOFF_INITIAL, :BACKOFF_MAX
1431
+
1432
+ def backoff_seconds_for_retry(transaction_attempt)
1433
+ exponential = BACKOFF_INITIAL * (1.5**(transaction_attempt - 1))
1434
+ Random.rand * [ exponential, BACKOFF_MAX ].min
1435
+ end
1436
+
1437
+ def backoff_would_exceed_deadline?(deadline, backoff_seconds)
1438
+ return false if deadline.zero?
1439
+
1440
+ Utils.monotonic_time + backoff_seconds >= deadline
1441
+ end
1442
+
1443
+ # Implements makeTimeoutError(lastError) from the transactions-convenient-api spec.
1444
+ # In CSOT mode raises TimeoutError with last_error's message and labels copied.
1445
+ # In non-CSOT mode re-raises last_error directly.
1446
+ def make_timeout_error_from(last_error, timeout_message)
1447
+ if @with_transaction_timeout_ms
1448
+ timeout_error = Mongo::Error::TimeoutError.new("#{timeout_message}: #{last_error}")
1449
+ if last_error.respond_to?(:labels)
1450
+ last_error.labels.each { |label| timeout_error.add_label(label) }
1451
+ end
1452
+ raise timeout_error
1453
+ end
1454
+
1455
+ raise last_error
1456
+ end
1325
1457
  end
1326
1458
  end