mongo 2.23.0 → 2.24.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 (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 +11 -16
  46. data/lib/mongo/bulk_write/result_combiner.rb +9 -12
  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 +2 -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 +7 -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 +7 -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 +22 -35
  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 +0 -3
  280. data/lib/mongo/monitoring/server_opening_log_subscriber.rb +0 -3
  281. data/lib/mongo/monitoring/topology_changed_log_subscriber.rb +5 -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 -48
  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 +19 -28
  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 +195 -167
  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 +117 -138
  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 +133 -157
  425. data/lib/mongo/server_selector/nearest.rb +2 -5
  426. data/lib/mongo/server_selector/primary.rb +1 -5
  427. data/lib/mongo/server_selector/primary_preferred.rb +2 -6
  428. data/lib/mongo/server_selector/secondary.rb +2 -6
  429. data/lib/mongo/server_selector/secondary_preferred.rb +1 -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 +287 -188
  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 +1 -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 +1 -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
@@ -89,20 +86,24 @@ module Mongo
89
86
  unless server_session.nil?
90
87
  raise ArgumentError, 'Implicit session cannot reference server session during construction'
91
88
  end
92
- else
93
- if server_session.nil?
94
- raise ArgumentError, 'Explicit session must reference server session during construction'
95
- end
89
+ elsif server_session.nil?
90
+ raise ArgumentError, 'Explicit session must reference server session during construction'
96
91
  end
97
92
 
98
93
  @server_session = server_session
99
94
  options = options.dup
100
95
 
101
- @client = client.use(:admin)
96
+ # Implicit sessions only need the cluster and client options (never run
97
+ # transactions), so avoid creating a Mongo::Client clone to prevent
98
+ # memory leaks: use the original client directly instead.
99
+ @client = options[:implicit] ? client : client.use(:admin)
100
+ @cluster = @client.cluster
102
101
  @options = options.dup.freeze
103
102
  @cluster_time = nil
104
103
  @state = NO_TRANSACTION_STATE
105
104
  @with_transaction_deadline = nil
105
+ @with_transaction_timeout_ms = nil
106
+ @inside_with_transaction = false
106
107
  end
107
108
 
108
109
  # @return [ Hash ] The options for this session.
@@ -115,9 +116,7 @@ module Mongo
115
116
  # @since 2.5.1
116
117
  attr_reader :client
117
118
 
118
- def cluster
119
- @client.cluster
120
- end
119
+ attr_reader :cluster
121
120
 
122
121
  # @return [ true | false ] Whether the session is configured for snapshot
123
122
  # reads.
@@ -154,7 +153,7 @@ module Mongo
154
153
  #
155
154
  # @since 2.6.0
156
155
  def txn_options
157
- @txn_options or raise ArgumentError, "There is no active transaction"
156
+ @txn_options or raise ArgumentError, 'There is no active transaction'
158
157
  end
159
158
 
160
159
  # Is this session an implicit one (not user-created).
@@ -208,8 +207,8 @@ module Mongo
208
207
  #
209
208
  # @return [ true, false ] If writes will be retried.
210
209
  #
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.
210
+ # @note Retryable writes are only available with sharded clusters, replica
211
+ # sets, or load-balanced topologies.
213
212
  #
214
213
  # @since 2.5.0
215
214
  def retry_writes?
@@ -229,7 +228,7 @@ module Mongo
229
228
  # @since 2.6.0
230
229
  def txn_read_preference
231
230
  rp = txn_options[:read] ||
232
- @client.read_preference
231
+ @client.read_preference
233
232
  Mongo::Lint.validate_underscore_read_preference(rp)
234
233
  rp
235
234
  end
@@ -255,9 +254,7 @@ module Mongo
255
254
  #
256
255
  # @since 2.5.0
257
256
  def session_id
258
- if ended?
259
- raise Error::SessionEnded
260
- end
257
+ raise Error::SessionEnded if ended?
261
258
 
262
259
  # An explicit session will always have a session_id, because during
263
260
  # construction a server session must be provided. An implicit session
@@ -266,9 +263,7 @@ module Mongo
266
263
  # to experience this failure because an implicit session shouldn't be
267
264
  # accessible to applications due to its lifetime being constrained to
268
265
  # operation execution, which is done entirely by the driver.
269
- unless materialized?
270
- raise Error::SessionNotMaterialized
271
- end
266
+ raise Error::SessionNotMaterialized unless materialized?
272
267
 
273
268
  @server_session.session_id
274
269
  end
@@ -296,20 +291,20 @@ module Mongo
296
291
  #
297
292
  # @since 2.5.0
298
293
  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
294
+ 'of the client owning this operation. Please only use this session for operations through its parent ' +
295
+ 'client.'
301
296
 
302
297
  # Error message describing that the session cannot be used because it has already been ended.
303
298
  #
304
299
  # @since 2.5.0
305
- SESSION_ENDED_ERROR_MSG = 'This session has ended and cannot be used. Please create a new one.'.freeze
300
+ SESSION_ENDED_ERROR_MSG = 'This session has ended and cannot be used. Please create a new one.'
306
301
 
307
302
  # Error message describing that sessions are not supported by the server version.
308
303
  #
309
304
  # @since 2.5.0
310
305
  # @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.
306
+ SESSIONS_NOT_SUPPORTED = 'Sessions are not supported by the connected servers.'
307
+ # NOTE: SESSIONS_NOT_SUPPORTED is used by Mongoid 7.6 and earlier
313
308
 
314
309
  # The state of a session in which the last operation was not related to
315
310
  # any transaction or no operations have yet occurred.
@@ -384,13 +379,15 @@ module Mongo
384
379
  rescue Mongo::Error, Error::AuthError
385
380
  end
386
381
  end
387
- if @server_session
388
- @client.cluster.session_pool.checkin(@server_session)
389
- end
382
+ # Release any pinned connection (e.g. after a committed transaction
383
+ # in load-balanced mode).
384
+ unpin if pinned_connection_global_id
385
+ cluster.session_pool.checkin(@server_session) if @server_session
390
386
  end
391
387
  ensure
392
388
  @server_session = nil
393
389
  @ended = true
390
+ @client = nil
394
391
  end
395
392
 
396
393
  # Executes the provided block in a transaction, retrying as necessary.
@@ -448,38 +445,74 @@ module Mongo
448
445
  #
449
446
  # @since 2.7.0
450
447
  def with_transaction(options = nil)
448
+ @inside_with_transaction = true
449
+ @with_transaction_timeout_ms = options&.dig(:timeout_ms) || @options[:default_timeout_ms] || @client.timeout_ms
451
450
  @with_transaction_deadline = calculate_with_transaction_deadline(options)
452
451
  deadline = if @with_transaction_deadline
453
452
  # CSOT enabled, so we have a customer defined deadline.
454
453
  @with_transaction_deadline
455
454
  else
456
- # CSOT not enabled, so we use the default deadline, 120 seconds.
455
+ # CSOT not enabled, so we use the default deadline, 120 seconds.
457
456
  Utils.monotonic_time + 120
458
457
  end
459
458
  transaction_in_progress = false
459
+ transaction_attempt = 0
460
+ last_error = nil
461
+ overload_error_count = 0
462
+ overload_encountered = false
463
+
460
464
  loop do
461
- commit_options = {}
462
- if options
463
- commit_options[:write_concern] = options[:write_concern]
465
+ if transaction_attempt > 0
466
+ if overload_encountered
467
+ delay = @client.retry_policy.backoff_delay(overload_error_count)
468
+ if backoff_would_exceed_deadline?(deadline, delay)
469
+ make_timeout_error_from(last_error, 'CSOT timeout expired waiting to retry withTransaction')
470
+ end
471
+ raise(last_error) unless @client.retry_policy.should_retry_overload?(overload_error_count, delay)
472
+
473
+ sleep(delay)
474
+ else
475
+ backoff = backoff_seconds_for_retry(transaction_attempt)
476
+ if backoff_would_exceed_deadline?(deadline, backoff)
477
+ make_timeout_error_from(last_error, 'CSOT timeout expired waiting to retry withTransaction')
478
+ end
479
+
480
+ sleep(backoff)
481
+ end
464
482
  end
483
+
484
+ commit_options = {}
485
+ commit_options[:write_concern] = options[:write_concern] if options
465
486
  start_transaction(options)
466
487
  transaction_in_progress = true
488
+ transaction_attempt += 1
489
+
467
490
  begin
468
491
  rv = yield self
469
492
  rescue Exception => e
470
493
  if within_states?(STARTING_TRANSACTION_STATE, TRANSACTION_IN_PROGRESS_STATE)
471
494
  log_warn("Aborting transaction due to #{e.class}: #{e}")
472
- @with_transaction_deadline = nil
495
+ # CSOT: if the deadline is already expired, clear it so that
496
+ # abort_transaction uses a fresh timeout (not the expired deadline).
497
+ # If the deadline is not yet expired, keep it so abort uses remaining time.
498
+ @with_transaction_deadline = nil if @with_transaction_deadline && deadline_expired?(deadline)
473
499
  abort_transaction
474
500
  transaction_in_progress = false
475
501
  end
476
502
 
477
503
  if deadline_expired?(deadline)
478
504
  transaction_in_progress = false
479
- raise
505
+ make_timeout_error_from(e, 'CSOT timeout expired during withTransaction callback')
480
506
  end
481
507
 
482
508
  if e.is_a?(Mongo::Error) && e.label?('TransientTransactionError')
509
+ last_error = e
510
+ if e.label?('SystemOverloadedError')
511
+ overload_encountered = true
512
+ overload_error_count += 1
513
+ elsif overload_encountered
514
+ overload_error_count += 1
515
+ end
483
516
  next
484
517
  end
485
518
 
@@ -490,32 +523,71 @@ module Mongo
490
523
  return rv
491
524
  end
492
525
 
526
+ # CSOT: if the timeout has expired before we can commit, abort the
527
+ # transaction instead and raise a client-side timeout error.
528
+ if @with_transaction_deadline && deadline_expired?(deadline)
529
+ transaction_in_progress = false
530
+ @with_transaction_deadline = nil
531
+ abort_transaction
532
+ raise Mongo::Error::TimeoutError, 'CSOT timeout expired before transaction could be committed'
533
+ end
534
+
493
535
  begin
494
536
  commit_transaction(commit_options)
495
537
  transaction_in_progress = false
496
538
  return rv
497
539
  rescue Mongo::Error => e
498
540
  if e.label?('UnknownTransactionCommitResult')
499
- if deadline_expired?(deadline) ||
500
- e.is_a?(Error::OperationFailure::Family) && e.max_time_ms_expired?
501
- then
541
+ if deadline_expired?(deadline) ||
542
+ (e.is_a?(Error::OperationFailure::Family) && e.max_time_ms_expired?)
502
543
  transaction_in_progress = false
503
- raise
544
+
545
+ raise unless @with_transaction_timeout_ms && deadline_expired?(deadline)
546
+
547
+ make_timeout_error_from(e, 'CSOT timeout expired during withTransaction commit')
504
548
  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
549
+
550
+ if e.label?('SystemOverloadedError')
551
+ overload_encountered = true
552
+ overload_error_count += 1
553
+ elsif overload_encountered
554
+ overload_error_count += 1
555
+ end
556
+
557
+ if overload_encountered
558
+ delay = @client.retry_policy.backoff_delay(overload_error_count)
559
+ if backoff_would_exceed_deadline?(deadline, delay)
560
+ transaction_in_progress = false
561
+ make_timeout_error_from(e, 'CSOT timeout expired during withTransaction commit')
512
562
  end
563
+ unless @client.retry_policy.should_retry_overload?(overload_error_count, delay)
564
+ transaction_in_progress = false
565
+ raise
566
+ end
567
+ sleep(delay)
568
+ end
569
+
570
+ wc_options = case v = commit_options[:write_concern]
571
+ when WriteConcern::Base
572
+ v.options
573
+ when nil
574
+ {}
575
+ else
576
+ v
577
+ end
513
578
  commit_options[:write_concern] = wc_options.merge(w: :majority)
514
579
  retry
515
580
  elsif e.label?('TransientTransactionError')
516
581
  if Utils.monotonic_time >= deadline
517
582
  transaction_in_progress = false
518
- raise
583
+ make_timeout_error_from(e, 'CSOT timeout expired during withTransaction commit')
584
+ end
585
+ last_error = e
586
+ if e.label?('SystemOverloadedError')
587
+ overload_encountered = true
588
+ overload_error_count += 1
589
+ elsif overload_encountered
590
+ overload_error_count += 1
519
591
  end
520
592
  @state = NO_TRANSACTION_STATE
521
593
  next
@@ -542,6 +614,8 @@ module Mongo
542
614
  end
543
615
  end
544
616
  @with_transaction_deadline = nil
617
+ @with_transaction_timeout_ms = nil
618
+ @inside_with_transaction = false
545
619
  end
546
620
 
547
621
  # Places subsequent operations in this session into a new transaction.
@@ -556,7 +630,7 @@ module Mongo
556
630
  #
557
631
  # @option options [ Integer ] :max_commit_time_ms The maximum amount of
558
632
  # time to allow a single commitTransaction command to run, in milliseconds.
559
- # This options is deprecated, use :timeout_ms instead.
633
+ # This option is deprecated, use :timeout_ms instead.
560
634
  # @option options [ Hash ] :read_concern The read concern options hash,
561
635
  # with the following optional keys:
562
636
  # - *:level* -- the read preference level as a symbol; valid values
@@ -582,28 +656,25 @@ module Mongo
582
656
  if options
583
657
  Lint.validate_read_concern_option(options[:read_concern])
584
658
 
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
659
+ # # It would be handy to detect invalid read preferences here, but
660
+ # # some of the spec tests require later detection of invalid read prefs.
661
+ # # Maybe we can do this when lint mode is on.
662
+ # mode = options[:read] && options[:read][:mode].to_s
663
+ # if mode && mode != 'primary'
664
+ # raise Mongo::Error::InvalidTransactionOperation.new(
665
+ # "read preference in a transaction must be primary (requested: #{mode})"
666
+ # )
667
+ # end
596
668
  end
597
669
 
598
- if snapshot?
599
- raise Mongo::Error::SnapshotSessionTransactionProhibited
600
- end
670
+ raise Mongo::Error::SnapshotSessionTransactionProhibited if snapshot?
601
671
 
602
672
  check_if_ended!
603
673
 
604
674
  if within_states?(STARTING_TRANSACTION_STATE, TRANSACTION_IN_PROGRESS_STATE)
605
675
  raise Mongo::Error::InvalidTransactionOperation.new(
606
- Mongo::Error::InvalidTransactionOperation::TRANSACTION_ALREADY_IN_PROGRESS)
676
+ Mongo::Error::InvalidTransactionOperation::TRANSACTION_ALREADY_IN_PROGRESS
677
+ )
607
678
  end
608
679
 
609
680
  unpin
@@ -613,7 +684,8 @@ module Mongo
613
684
 
614
685
  if txn_write_concern && !WriteConcern.get(txn_write_concern).acknowledged?
615
686
  raise Mongo::Error::InvalidTransactionOperation.new(
616
- Mongo::Error::InvalidTransactionOperation::UNACKNOWLEDGED_WRITE_CONCERN)
687
+ Mongo::Error::InvalidTransactionOperation::UNACKNOWLEDGED_WRITE_CONCERN
688
+ )
617
689
  end
618
690
 
619
691
  @state = STARTING_TRANSACTION_STATE
@@ -642,7 +714,7 @@ module Mongo
642
714
  # @raise [ Error::InvalidTransactionOperation ] If there is no active transaction.
643
715
  #
644
716
  # @since 2.6.0
645
- def commit_transaction(options=nil)
717
+ def commit_transaction(options = nil)
646
718
  QueryCache.clear
647
719
  check_if_ended!
648
720
  check_if_no_transaction!
@@ -650,7 +722,9 @@ module Mongo
650
722
  if within_states?(TRANSACTION_ABORTED_STATE)
651
723
  raise Mongo::Error::InvalidTransactionOperation.new(
652
724
  Mongo::Error::InvalidTransactionOperation.cannot_call_after_msg(
653
- :abortTransaction, :commitTransaction))
725
+ :abortTransaction, :commitTransaction
726
+ )
727
+ )
654
728
  end
655
729
 
656
730
  options ||= {}
@@ -670,9 +744,7 @@ module Mongo
670
744
  @committing_transaction = true
671
745
 
672
746
  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
747
+ write_concern = WriteConcern.get(write_concern) if write_concern && !write_concern.is_a?(WriteConcern::Base)
676
748
 
677
749
  context = Operation::Context.new(
678
750
  client: @client,
@@ -680,15 +752,14 @@ module Mongo
680
752
  operation_timeouts: operation_timeouts(options)
681
753
  )
682
754
  write_with_retry(write_concern, ending_transaction: true,
683
- context: context,
684
- ) do |connection, txn_num, context|
685
- if context.retry?
755
+ context: context) do |connection, txn_num, context|
756
+ if context.retry? && !context.overload_only_retry?
686
757
  if write_concern
687
758
  wco = write_concern.options.merge(w: :majority)
688
- wco[:wtimeout] ||= 10000
759
+ wco[:wtimeout] ||= 10_000
689
760
  write_concern = WriteConcern.get(wco)
690
761
  else
691
- write_concern = WriteConcern.get(w: :majority, wtimeout: 10000)
762
+ write_concern = WriteConcern.get(w: :majority, wtimeout: 10_000)
692
763
  end
693
764
  end
694
765
  spec = {
@@ -738,12 +809,15 @@ module Mongo
738
809
  if within_states?(TRANSACTION_COMMITTED_STATE)
739
810
  raise Mongo::Error::InvalidTransactionOperation.new(
740
811
  Mongo::Error::InvalidTransactionOperation.cannot_call_after_msg(
741
- :commitTransaction, :abortTransaction))
812
+ :commitTransaction, :abortTransaction
813
+ )
814
+ )
742
815
  end
743
816
 
744
817
  if within_states?(TRANSACTION_ABORTED_STATE)
745
818
  raise Mongo::Error::InvalidTransactionOperation.new(
746
- Mongo::Error::InvalidTransactionOperation.cannot_call_twice_msg(:abortTransaction))
819
+ Mongo::Error::InvalidTransactionOperation.cannot_call_twice_msg(:abortTransaction)
820
+ )
747
821
  end
748
822
 
749
823
  options ||= {}
@@ -757,21 +831,18 @@ module Mongo
757
831
  operation_timeouts: operation_timeouts(options)
758
832
  )
759
833
  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
834
+ ending_transaction: true, context: context) do |connection, txn_num, context|
835
+ operation = Operation::Command.new(
836
+ selector: { abortTransaction: 1 },
837
+ db_name: 'admin',
838
+ session: self,
839
+ txn_num: txn_num
840
+ )
841
+ tracer.trace_operation(operation, context, op_name: 'abortTransaction') do
842
+ operation.execute_with_connection(connection, context: context)
774
843
  end
844
+ ensure
845
+ unpin
775
846
  end
776
847
  end
777
848
 
@@ -835,14 +906,12 @@ module Mongo
835
906
  #
836
907
  # @api private
837
908
  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
909
+ raise ArgumentError, 'Cannot pin to a nil server' if server.nil?
910
+
911
+ if Lint.enabled? && !server.mongos?
912
+ raise Error::LintError, "Attempted to pin the session to server #{server.summary} which is not a mongos"
845
913
  end
914
+
846
915
  @pinned_server = server
847
916
  end
848
917
 
@@ -850,13 +919,14 @@ module Mongo
850
919
  #
851
920
  # @param [ Integer ] connection_global_id The global id of connection to pin
852
921
  # this session to.
922
+ # @param [ Connection | nil ] connection The connection object to pin to.
853
923
  #
854
924
  # @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
925
+ def pin_to_connection(connection_global_id, connection: nil)
926
+ raise ArgumentError, 'Cannot pin to a nil connection id' if connection_global_id.nil?
927
+
859
928
  @pinned_connection_global_id = connection_global_id
929
+ @pinned_connection = connection
860
930
  end
861
931
 
862
932
  # Unpins this session from the pinned server or connection,
@@ -868,7 +938,16 @@ module Mongo
868
938
  def unpin(connection = nil)
869
939
  @pinned_server = nil
870
940
  @pinned_connection_global_id = nil
871
- connection.unpin unless connection.nil?
941
+ conn = connection || @pinned_connection
942
+ if conn
943
+ conn.unpin(:transaction)
944
+ # Only check the connection back into the pool if nothing else
945
+ # still holds a pin on it (e.g. an open cursor).
946
+ unless conn.pinned?
947
+ conn.connection_pool.check_in(conn)
948
+ end
949
+ end
950
+ @pinned_connection = nil
872
951
  end
873
952
 
874
953
  # Unpins this session from the pinned server or connection, if the session was pinned
@@ -884,14 +963,12 @@ module Mongo
884
963
  # @api private
885
964
  def unpin_maybe(error, connection = nil)
886
965
  if !within_states?(Session::NO_TRANSACTION_STATE) &&
887
- error.label?('TransientTransactionError')
888
- then
966
+ error.label?('TransientTransactionError')
889
967
  unpin(connection)
890
968
  end
891
969
 
892
970
  if committing_transaction? &&
893
- error.label?('UnknownTransactionCommitResult')
894
- then
971
+ error.label?('UnknownTransactionCommitResult')
895
972
  unpin(connection)
896
973
  end
897
974
  end
@@ -922,9 +999,7 @@ module Mongo
922
999
  # @api private
923
1000
  def add_start_transaction!(command)
924
1001
  command.tap do |c|
925
- if starting_transaction?
926
- c[:startTransaction] = true
927
- end
1002
+ c[:startTransaction] = true if starting_transaction?
928
1003
  end
929
1004
  end
930
1005
 
@@ -952,7 +1027,7 @@ module Mongo
952
1027
  #
953
1028
  # @since 2.6.0
954
1029
  # @api private
955
- def add_txn_opts!(command, read, context)
1030
+ def add_txn_opts!(command, _read, context)
956
1031
  command.tap do |c|
957
1032
  # The read concern should be added to any command that starts a transaction.
958
1033
  if starting_transaction?
@@ -975,27 +1050,23 @@ module Mongo
975
1050
  if rc.nil? || rc.empty?
976
1051
  c.delete(:readConcern)
977
1052
  else
978
- c[:readConcern ] = Options::Mapper.transform_values_to_strings(rc)
1053
+ c[:readConcern] = Options::Mapper.transform_values_to_strings(rc)
979
1054
  end
980
1055
  end
981
1056
 
982
1057
  # 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
1058
+ c[:readConcern] = Options::Mapper.transform_values_to_strings(c[:readConcern]) if c[:readConcern]
986
1059
 
987
- if c[:commitTransaction]
988
- if max_time_ms = txn_options[:max_commit_time_ms]
989
- c[:maxTimeMS] = max_time_ms
990
- end
1060
+ if c[:commitTransaction] && (max_time_ms = txn_options[:max_commit_time_ms])
1061
+ c[:maxTimeMS] = max_time_ms
991
1062
  end
992
1063
 
993
1064
  # The write concern should be added to any abortTransaction or commitTransaction command.
994
- if (c[:abortTransaction] || c[:commitTransaction])
1065
+ if c[:abortTransaction] || c[:commitTransaction]
995
1066
  if @already_committed
996
1067
  wc = BSON::Document.new(c[:writeConcern] || txn_write_concern || {})
997
1068
  wc.merge!(w: :majority)
998
- wc[:wtimeout] ||= 10000
1069
+ wc[:wtimeout] ||= 10_000
999
1070
  c[:writeConcern] = wc
1000
1071
  elsif txn_write_concern
1001
1072
  c[:writeConcern] ||= txn_write_concern
@@ -1008,12 +1079,10 @@ module Mongo
1008
1079
  end
1009
1080
 
1010
1081
  # Ignore wtimeout if csot
1011
- if context&.csot?
1012
- c[:writeConcern]&.delete(:wtimeout)
1013
- end
1082
+ c[:writeConcern]&.delete(:wtimeout) if context&.csot?
1014
1083
 
1015
1084
  # We must not send an empty (server default) write concern.
1016
- c.delete(:writeConcern) if c[:writeConcern]&.empty?
1085
+ c.delete(:writeConcern) if c[:writeConcern] && c[:writeConcern].empty?
1017
1086
  end
1018
1087
  end
1019
1088
 
@@ -1035,7 +1104,7 @@ module Mongo
1035
1104
  end
1036
1105
  end
1037
1106
 
1038
- # Ensure that the read preference of a command primary.
1107
+ # Ensure that the read preference of a command is primary.
1039
1108
  #
1040
1109
  # @example
1041
1110
  # session.validate_read_preference!(command)
@@ -1051,11 +1120,21 @@ module Mongo
1051
1120
 
1052
1121
  mode = command['$readPreference']['mode'] || command['$readPreference'][:mode]
1053
1122
 
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
1123
+ return unless mode && mode != 'primary'
1124
+
1125
+ raise Mongo::Error::InvalidTransactionOperation.new(
1126
+ "read preference in a transaction must be primary (requested: #{mode})"
1127
+ )
1128
+ end
1129
+
1130
+ # Reverts the session state to STARTING_TRANSACTION_STATE.
1131
+ # Called before retrying the first command in a transaction so that
1132
+ # startTransaction: true is preserved on the retry.
1133
+ # @api private
1134
+ def revert_to_starting_transaction!
1135
+ return unless within_states?(TRANSACTION_IN_PROGRESS_STATE)
1136
+
1137
+ @state = STARTING_TRANSACTION_STATE
1059
1138
  end
1060
1139
 
1061
1140
  # Update the state of the session due to a (non-commit and non-abort) operation being run.
@@ -1085,8 +1164,8 @@ module Mongo
1085
1164
  # @since 2.5.0
1086
1165
  # @api private
1087
1166
  def validate!(client)
1088
- check_matching_cluster!(client)
1089
1167
  check_if_ended!
1168
+ check_matching_cluster!(client)
1090
1169
  self
1091
1170
  end
1092
1171
 
@@ -1110,10 +1189,8 @@ module Mongo
1110
1189
  end
1111
1190
  @server_session.set_last_use!
1112
1191
 
1113
- if doc = result.reply && result.reply.documents.first
1114
- if doc[:recoveryToken]
1115
- self.recovery_token = doc[:recoveryToken]
1116
- end
1192
+ if (doc = result.reply && result.reply.documents.first) && doc[:recoveryToken]
1193
+ self.recovery_token = doc[:recoveryToken]
1117
1194
  end
1118
1195
 
1119
1196
  result
@@ -1130,11 +1207,11 @@ module Mongo
1130
1207
  #
1131
1208
  # @since 2.5.0
1132
1209
  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
1210
+ @operation_time = if @operation_time
1211
+ [ @operation_time, new_operation_time ].max
1212
+ else
1213
+ new_operation_time
1214
+ end
1138
1215
  end
1139
1216
 
1140
1217
  # If not already set, populate a session objects's server_session by
@@ -1144,9 +1221,7 @@ module Mongo
1144
1221
  #
1145
1222
  # @api private
1146
1223
  def materialize_if_needed
1147
- if ended?
1148
- raise Error::SessionEnded
1149
- end
1224
+ raise Error::SessionEnded if ended?
1150
1225
 
1151
1226
  return unless implicit? && !@server_session
1152
1227
 
@@ -1157,9 +1232,7 @@ module Mongo
1157
1232
 
1158
1233
  # @api private
1159
1234
  def materialized?
1160
- if ended?
1161
- raise Error::SessionEnded
1162
- end
1235
+ raise Error::SessionEnded if ended?
1163
1236
 
1164
1237
  !@server_session.nil?
1165
1238
  end
@@ -1174,9 +1247,7 @@ module Mongo
1174
1247
  # @since 2.5.0
1175
1248
  # @api private
1176
1249
  def next_txn_num
1177
- if ended?
1178
- raise Error::SessionEnded
1179
- end
1250
+ raise Error::SessionEnded if ended?
1180
1251
 
1181
1252
  @server_session.next_txn_num
1182
1253
  end
@@ -1190,9 +1261,7 @@ module Mongo
1190
1261
  #
1191
1262
  # @since 2.6.0
1192
1263
  def txn_num
1193
- if ended?
1194
- raise Error::SessionEnded
1195
- end
1264
+ raise Error::SessionEnded if ended?
1196
1265
 
1197
1266
  @server_session.txn_num
1198
1267
  end
@@ -1204,6 +1273,12 @@ module Mongo
1204
1273
  # @api private
1205
1274
  attr_reader :with_transaction_deadline
1206
1275
 
1276
+ # @return [ Boolean ] Whether we are currently inside a with_transaction block.
1277
+ # @api private
1278
+ def inside_with_transaction?
1279
+ @inside_with_transaction
1280
+ end
1281
+
1207
1282
  private
1208
1283
 
1209
1284
  # Get the read concern the session will use when starting a transaction.
@@ -1229,7 +1304,8 @@ module Mongo
1229
1304
  return unless within_states?(NO_TRANSACTION_STATE)
1230
1305
 
1231
1306
  raise Mongo::Error::InvalidTransactionOperation.new(
1232
- Mongo::Error::InvalidTransactionOperation::NO_TRANSACTION_STARTED)
1307
+ Mongo::Error::InvalidTransactionOperation::NO_TRANSACTION_STARTED
1308
+ )
1233
1309
  end
1234
1310
 
1235
1311
  def txn_write_concern
@@ -1240,25 +1316,23 @@ module Mongo
1240
1316
  # Returns causal consistency document if the last operation time is
1241
1317
  # known and causal consistency is enabled, otherwise returns nil.
1242
1318
  def causal_consistency_doc
1243
- if operation_time && causal_consistency?
1244
- {:afterClusterTime => operation_time}
1245
- else
1246
- nil
1247
- end
1319
+ return unless operation_time && causal_consistency?
1320
+
1321
+ { afterClusterTime: operation_time }
1248
1322
  end
1249
1323
 
1250
1324
  def causal_consistency?
1251
- @causal_consistency ||= (if @options.key?(:causal_consistency)
1252
- !!@options[:causal_consistency]
1253
- else
1254
- true
1255
- end)
1325
+ @causal_consistency ||= if @options.key?(:causal_consistency)
1326
+ !!@options[:causal_consistency]
1327
+ else
1328
+ true
1329
+ end
1256
1330
  end
1257
1331
 
1258
1332
  def set_operation_time(result)
1259
- if result && result.operation_time
1260
- @operation_time = result.operation_time
1261
- end
1333
+ return unless result && result.operation_time
1334
+
1335
+ @operation_time = result.operation_time
1262
1336
  end
1263
1337
 
1264
1338
  def check_if_ended!
@@ -1266,40 +1340,34 @@ module Mongo
1266
1340
  end
1267
1341
 
1268
1342
  def check_matching_cluster!(client)
1269
- if @client.cluster != client.cluster
1270
- raise Mongo::Error::InvalidSession.new(MISMATCHED_CLUSTER_ERROR_MSG)
1271
- end
1343
+ return unless cluster != client.cluster
1344
+
1345
+ raise Mongo::Error::InvalidSession.new(MISMATCHED_CLUSTER_ERROR_MSG)
1272
1346
  end
1273
1347
 
1274
1348
  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
1349
+ raise Mongo::Error::TransactionsNotSupported, 'standalone topology' if cluster.single?
1285
1350
  end
1286
1351
 
1287
1352
  def operation_timeouts(opts)
1288
1353
  {
1289
- inherited_timeout_ms: @client.timeout_ms
1354
+ inherited_timeout_ms: @with_transaction_timeout_ms || @client.timeout_ms
1290
1355
  }.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
1356
+ if @inside_with_transaction
1357
+ if opts[:timeout_ms]
1358
+ raise Mongo::Error::InvalidTransactionOperation,
1359
+ 'timeoutMS cannot be overridden inside a withTransaction callback'
1296
1360
  end
1361
+ elsif timeout_ms = opts[:timeout_ms]
1362
+ result[:operation_timeout_ms] = timeout_ms
1363
+ elsif default_timeout_ms = options[:default_timeout_ms]
1364
+ result[:operation_timeout_ms] = default_timeout_ms
1297
1365
  end
1298
1366
  end
1299
1367
  end
1300
1368
 
1301
1369
  def calculate_with_transaction_deadline(opts)
1302
- calc = -> (timeout) {
1370
+ calc = lambda { |timeout|
1303
1371
  if timeout == 0
1304
1372
  0
1305
1373
  else
@@ -1322,5 +1390,36 @@ module Mongo
1322
1390
  Utils.monotonic_time >= deadline
1323
1391
  end
1324
1392
  end
1393
+
1394
+ # Exponential backoff settings for with_transaction retries.
1395
+ BACKOFF_INITIAL = 0.005
1396
+ BACKOFF_MAX = 0.5
1397
+ private_constant :BACKOFF_INITIAL, :BACKOFF_MAX
1398
+
1399
+ def backoff_seconds_for_retry(transaction_attempt)
1400
+ exponential = BACKOFF_INITIAL * (1.5**(transaction_attempt - 1))
1401
+ Random.rand * [ exponential, BACKOFF_MAX ].min
1402
+ end
1403
+
1404
+ def backoff_would_exceed_deadline?(deadline, backoff_seconds)
1405
+ return false if deadline.zero?
1406
+
1407
+ Utils.monotonic_time + backoff_seconds >= deadline
1408
+ end
1409
+
1410
+ # Implements makeTimeoutError(lastError) from the transactions-convenient-api spec.
1411
+ # In CSOT mode raises TimeoutError with last_error's message and labels copied.
1412
+ # In non-CSOT mode re-raises last_error directly.
1413
+ def make_timeout_error_from(last_error, timeout_message)
1414
+ if @with_transaction_timeout_ms
1415
+ timeout_error = Mongo::Error::TimeoutError.new("#{timeout_message}: #{last_error}")
1416
+ if last_error.respond_to?(:labels)
1417
+ last_error.labels.each { |label| timeout_error.add_label(label) }
1418
+ end
1419
+ raise timeout_error
1420
+ end
1421
+
1422
+ raise last_error
1423
+ end
1325
1424
  end
1326
1425
  end