couchbase 3.8.2 → 3.9.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 (473) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/ext/CMakeLists.txt +16 -1
  4. data/ext/cache/cpm/{CPM_0.42.0.cmake → CPM_0.43.1.cmake} +18 -2
  5. data/ext/cache/extconf_include.rb +6 -6
  6. data/ext/cache/mozilla-ca-bundle.crt +121 -55
  7. data/ext/cache/mozilla-ca-bundle.sha256 +1 -1
  8. data/ext/couchbase/CMakeLists.txt +136 -23
  9. data/ext/couchbase/cmake/APKBUILD.in +25 -4
  10. data/ext/couchbase/cmake/Bundler.cmake +143 -34
  11. data/ext/couchbase/cmake/CPM.cmake +126 -12
  12. data/ext/couchbase/cmake/Cache.cmake +7 -1
  13. data/ext/couchbase/cmake/CompilerOptions.cmake +1 -1
  14. data/ext/couchbase/cmake/CompilerWarnings.cmake +17 -6
  15. data/ext/couchbase/cmake/GrpcProtobuf.cmake +481 -0
  16. data/ext/couchbase/cmake/OpenSSL.cmake +82 -7
  17. data/ext/couchbase/cmake/Packaging.cmake +519 -65
  18. data/ext/couchbase/cmake/Protostellar.cmake +250 -0
  19. data/ext/couchbase/cmake/Sanitizers.cmake +61 -50
  20. data/ext/couchbase/cmake/StaticAnalyzers.cmake +16 -5
  21. data/ext/couchbase/cmake/TestFramework.cmake +283 -0
  22. data/ext/couchbase/cmake/TestFrameworkAddTests.cmake +51 -0
  23. data/ext/couchbase/cmake/Testing.cmake +27 -9
  24. data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +141 -8
  25. data/ext/couchbase/cmake/VersionInfo.cmake +50 -0
  26. data/ext/couchbase/cmake/build_config.hxx.in +2 -1
  27. data/ext/couchbase/cmake/couchbase-cxx-client.spec.in +101 -23
  28. data/ext/couchbase/cmake/couchbase_cxx_client_static-config.cmake.in +7 -0
  29. data/ext/couchbase/cmake/couchbase_cxx_client_static.pc.in +2 -0
  30. data/ext/couchbase/cmake/gRPC_Generate.cmake +58 -0
  31. data/ext/couchbase/cmake/protobuf_Generate.cmake +69 -0
  32. data/ext/couchbase/cmake/require-git-checkout.cmake +25 -0
  33. data/ext/couchbase/cmake/tarball_glob.txt +49 -0
  34. data/ext/couchbase/core/agent_group.cxx +0 -8
  35. data/ext/couchbase/core/agent_group.hxx +0 -4
  36. data/ext/couchbase/core/analytics_stream.cxx +314 -0
  37. data/ext/couchbase/core/analytics_stream.hxx +129 -0
  38. data/ext/couchbase/core/analytics_stream_component.cxx +245 -0
  39. data/ext/couchbase/core/analytics_stream_component.hxx +67 -0
  40. data/ext/couchbase/core/app_telemetry_meter.cxx +25 -3
  41. data/ext/couchbase/core/app_telemetry_meter.hxx +77 -0
  42. data/ext/couchbase/core/app_telemetry_reporter.cxx +7 -6
  43. data/ext/couchbase/core/bucket.cxx +381 -87
  44. data/ext/couchbase/core/bucket.hxx +105 -25
  45. data/ext/couchbase/core/bucket_unit_test_api.hxx +57 -0
  46. data/ext/couchbase/core/capella.hxx +91 -0
  47. data/ext/couchbase/core/cluster.cxx +1551 -397
  48. data/ext/couchbase/core/cluster.hxx +39 -8
  49. data/ext/couchbase/core/cluster_agent.cxx +0 -8
  50. data/ext/couchbase/core/cluster_agent.hxx +0 -4
  51. data/ext/couchbase/core/cluster_options.hxx +26 -3
  52. data/ext/couchbase/core/collections_component.cxx +39 -15
  53. data/ext/couchbase/core/config_profiles.cxx +1 -1
  54. data/ext/couchbase/core/crud_component.cxx +2 -2
  55. data/ext/couchbase/core/crud_options.hxx +34 -0
  56. data/ext/couchbase/core/crypto/cbcrypto.cc +18 -13
  57. data/ext/couchbase/core/document_id_redaction.hxx +81 -0
  58. data/ext/couchbase/core/error_context/base_error_context.hxx +7 -6
  59. data/ext/couchbase/core/error_context/key_value.cxx +25 -2
  60. data/ext/couchbase/core/error_context/key_value.hxx +60 -33
  61. data/ext/couchbase/core/error_context/key_value_error_context.hxx +16 -15
  62. data/ext/couchbase/core/error_context/search.hxx +5 -0
  63. data/ext/couchbase/core/error_context/search_json.hxx +3 -0
  64. data/ext/couchbase/core/error_context/subdocument_error_context.hxx +19 -18
  65. data/ext/couchbase/core/error_context/view.hxx +5 -0
  66. data/ext/couchbase/core/free_form_http_request.cxx +237 -4
  67. data/ext/couchbase/core/free_form_http_request.hxx +41 -8
  68. data/ext/couchbase/core/http_component.cxx +300 -284
  69. data/ext/couchbase/core/http_component.hxx +0 -4
  70. data/ext/couchbase/core/impl/analytics.cxx +38 -5
  71. data/ext/couchbase/core/impl/analytics.hxx +34 -0
  72. data/ext/couchbase/core/impl/bootstrap_error.hxx +0 -3
  73. data/ext/couchbase/core/impl/collection.cxx +215 -202
  74. data/ext/couchbase/core/impl/collection_manager.cxx +1 -1
  75. data/ext/couchbase/core/impl/dns_srv_tracker.cxx +45 -18
  76. data/ext/couchbase/core/impl/error.cxx +22 -2
  77. data/ext/couchbase/core/impl/internal_analytics_stream_result.hxx +111 -0
  78. data/ext/couchbase/core/impl/internal_query_stream_result.hxx +110 -0
  79. data/ext/couchbase/core/impl/invoke_with_node_id.hxx +15 -7
  80. data/ext/couchbase/core/impl/key_value_error_category.cxx +6 -0
  81. data/ext/couchbase/core/impl/lookup_in_replica.cxx +10 -10
  82. data/ext/couchbase/core/impl/lookup_in_replica.hxx +4 -4
  83. data/ext/couchbase/core/impl/node_id.cxx +49 -13
  84. data/ext/couchbase/core/impl/observability_recorder.cxx +12 -2
  85. data/ext/couchbase/core/impl/observe_poll.cxx +63 -23
  86. data/ext/couchbase/core/impl/public_analytics_stream_result.cxx +372 -0
  87. data/ext/couchbase/core/impl/public_bucket.cxx +48 -2
  88. data/ext/couchbase/core/impl/public_cluster.cxx +229 -42
  89. data/ext/couchbase/core/impl/public_query_stream_result.cxx +380 -0
  90. data/ext/couchbase/core/impl/public_transaction_get_multi_replicas_from_preferred_server_group_result.cxx +42 -0
  91. data/ext/couchbase/core/impl/public_transaction_get_multi_result.cxx +42 -0
  92. data/ext/couchbase/core/impl/query.cxx +76 -7
  93. data/ext/couchbase/core/impl/query.hxx +36 -0
  94. data/ext/couchbase/core/impl/query_index_manager.cxx +54 -1
  95. data/ext/couchbase/core/impl/replica_utils.cxx +101 -0
  96. data/ext/couchbase/core/impl/replica_utils.hxx +64 -0
  97. data/ext/couchbase/core/impl/resolve_node_id.hxx +125 -0
  98. data/ext/couchbase/core/impl/scope.cxx +70 -0
  99. data/ext/couchbase/core/impl/search.cxx +31 -1
  100. data/ext/couchbase/core/impl/transactions.cxx +130 -0
  101. data/ext/couchbase/core/impl/wait_until_ready.cxx +439 -0
  102. data/ext/couchbase/core/impl/wait_until_ready.hxx +110 -0
  103. data/ext/couchbase/core/impl/with_bucket_config_or_timeout.hxx +180 -0
  104. data/ext/couchbase/core/impl/with_cancellation.hxx +11 -3
  105. data/ext/couchbase/core/io/configuration_belongs_to_session.hxx +1 -2
  106. data/ext/couchbase/core/io/dns_client.cxx +122 -45
  107. data/ext/couchbase/core/io/dns_codec.hxx +21 -21
  108. data/ext/couchbase/core/io/dns_config.cxx +21 -6
  109. data/ext/couchbase/core/io/http_command.hxx +149 -121
  110. data/ext/couchbase/core/io/http_error.hxx +107 -0
  111. data/ext/couchbase/core/io/http_parser.cxx +4 -0
  112. data/ext/couchbase/core/io/http_session.cxx +606 -154
  113. data/ext/couchbase/core/io/http_session.hxx +147 -30
  114. data/ext/couchbase/core/io/http_session_manager.hxx +329 -382
  115. data/ext/couchbase/core/io/http_streaming_parser.cxx +4 -0
  116. data/ext/couchbase/core/io/http_streaming_response.cxx +328 -43
  117. data/ext/couchbase/core/io/http_streaming_response.hxx +37 -3
  118. data/ext/couchbase/core/io/mcbp_buffer_pool.hxx +122 -0
  119. data/ext/couchbase/core/io/mcbp_command.hxx +150 -51
  120. data/ext/couchbase/core/io/mcbp_command_id.hxx +45 -0
  121. data/ext/couchbase/core/io/mcbp_message.cxx +9 -1
  122. data/ext/couchbase/core/io/mcbp_message.hxx +19 -0
  123. data/ext/couchbase/core/io/mcbp_output_queue.hxx +150 -0
  124. data/ext/couchbase/core/io/mcbp_parser.cxx +24 -7
  125. data/ext/couchbase/core/io/mcbp_session.cxx +309 -301
  126. data/ext/couchbase/core/io/mcbp_session.hxx +27 -18
  127. data/ext/couchbase/core/io/opaque_ring_table.hxx +145 -0
  128. data/ext/couchbase/core/io/retry_orchestrator.hxx +6 -5
  129. data/ext/couchbase/core/io/stream_deadline.hxx +50 -0
  130. data/ext/couchbase/core/io/streams.cxx +151 -21
  131. data/ext/couchbase/core/io/streams.hxx +48 -1
  132. data/ext/couchbase/core/logger/logger.cxx +36 -0
  133. data/ext/couchbase/core/logger/logger.hxx +46 -6
  134. data/ext/couchbase/core/logger/redaction.hxx +503 -0
  135. data/ext/couchbase/core/management/eventing_status.hxx +10 -0
  136. data/ext/couchbase/core/management/eventing_status_json.hxx +4 -0
  137. data/ext/couchbase/core/mcbp/big_endian.cxx +1 -1
  138. data/ext/couchbase/core/mcbp/codec.cxx +27 -18
  139. data/ext/couchbase/core/mcbp/operation_queue.cxx +2 -2
  140. data/ext/couchbase/core/mcbp/packet.hxx +2 -2
  141. data/ext/couchbase/core/mcbp/queue_request.cxx +23 -6
  142. data/ext/couchbase/core/mcbp/queue_request.hxx +21 -0
  143. data/ext/couchbase/core/meta/features.hxx +13 -0
  144. data/ext/couchbase/core/meta/version.cxx +6 -3
  145. data/ext/couchbase/core/metrics/logging_meter.cxx +5 -10
  146. data/ext/couchbase/core/metrics/meter_wrapper.cxx +63 -33
  147. data/ext/couchbase/core/metrics/meter_wrapper.hxx +55 -3
  148. data/ext/couchbase/core/operations/analytics_response_parsing.cxx +177 -0
  149. data/ext/couchbase/core/operations/analytics_response_parsing.hxx +55 -0
  150. data/ext/couchbase/core/operations/document_analytics.cxx +48 -130
  151. data/ext/couchbase/core/operations/document_analytics.hxx +14 -2
  152. data/ext/couchbase/core/operations/document_get_all_replicas.hxx +15 -7
  153. data/ext/couchbase/core/operations/document_get_any_replica.hxx +19 -8
  154. data/ext/couchbase/core/operations/document_get_projected.cxx +1 -1
  155. data/ext/couchbase/core/{impl/get_replica.cxx → operations/document_get_replica.cxx} +21 -3
  156. data/ext/couchbase/core/{impl/get_replica.hxx → operations/document_get_replica.hxx} +31 -4
  157. data/ext/couchbase/core/operations/document_lookup_in.cxx +10 -10
  158. data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +228 -198
  159. data/ext/couchbase/core/operations/document_lookup_in_any_replica.hxx +215 -217
  160. data/ext/couchbase/core/operations/document_query.cxx +108 -221
  161. data/ext/couchbase/core/operations/document_query.hxx +18 -4
  162. data/ext/couchbase/core/operations/document_search.cxx +52 -8
  163. data/ext/couchbase/core/operations/document_search.hxx +3 -0
  164. data/ext/couchbase/core/operations/document_view.cxx +3 -3
  165. data/ext/couchbase/core/operations/management/analytics_dataset_create.cxx +15 -8
  166. data/ext/couchbase/core/operations/management/analytics_dataset_create.hxx +4 -0
  167. data/ext/couchbase/core/operations/management/analytics_dataset_drop.cxx +11 -6
  168. data/ext/couchbase/core/operations/management/analytics_dataset_get_all.cxx +3 -2
  169. data/ext/couchbase/core/operations/management/analytics_dataverse_create.cxx +10 -5
  170. data/ext/couchbase/core/operations/management/analytics_dataverse_drop.cxx +10 -5
  171. data/ext/couchbase/core/operations/management/analytics_get_pending_mutations.cxx +2 -1
  172. data/ext/couchbase/core/operations/management/analytics_index_create.cxx +33 -8
  173. data/ext/couchbase/core/operations/management/analytics_index_drop.cxx +12 -7
  174. data/ext/couchbase/core/operations/management/analytics_index_get_all.cxx +3 -2
  175. data/ext/couchbase/core/operations/management/analytics_link_connect.cxx +11 -6
  176. data/ext/couchbase/core/operations/management/analytics_link_create.cxx +3 -3
  177. data/ext/couchbase/core/operations/management/analytics_link_disconnect.cxx +10 -4
  178. data/ext/couchbase/core/operations/management/analytics_link_drop.cxx +6 -4
  179. data/ext/couchbase/core/operations/management/analytics_link_get_all.cxx +4 -3
  180. data/ext/couchbase/core/operations/management/analytics_link_replace.cxx +3 -3
  181. data/ext/couchbase/core/operations/management/analytics_link_utils.hxx +1 -1
  182. data/ext/couchbase/core/operations/management/change_password.cxx +2 -0
  183. data/ext/couchbase/core/operations/management/collection_drop.cxx +5 -1
  184. data/ext/couchbase/core/operations/management/error_utils.cxx +60 -6
  185. data/ext/couchbase/core/operations/management/error_utils.hxx +29 -1
  186. data/ext/couchbase/core/operations/management/eventing_deploy_function.cxx +9 -3
  187. data/ext/couchbase/core/operations/management/eventing_drop_function.cxx +8 -3
  188. data/ext/couchbase/core/operations/management/eventing_get_all_functions.cxx +4 -2
  189. data/ext/couchbase/core/operations/management/eventing_get_function.cxx +5 -3
  190. data/ext/couchbase/core/operations/management/eventing_get_status.cxx +4 -2
  191. data/ext/couchbase/core/operations/management/eventing_pause_function.cxx +9 -3
  192. data/ext/couchbase/core/operations/management/eventing_resume_function.cxx +9 -3
  193. data/ext/couchbase/core/operations/management/eventing_undeploy_function.cxx +9 -3
  194. data/ext/couchbase/core/operations/management/eventing_upsert_function.cxx +9 -3
  195. data/ext/couchbase/core/operations/management/group_drop.cxx +3 -1
  196. data/ext/couchbase/core/operations/management/group_get.cxx +3 -1
  197. data/ext/couchbase/core/operations/management/group_upsert.cxx +11 -8
  198. data/ext/couchbase/core/operations/management/query_index_build.cxx +5 -2
  199. data/ext/couchbase/core/operations/management/query_index_create.cxx +9 -3
  200. data/ext/couchbase/core/operations/management/query_index_drop.cxx +11 -4
  201. data/ext/couchbase/core/operations/management/query_index_get_all.cxx +1 -1
  202. data/ext/couchbase/core/operations/management/query_index_get_all_deferred.cxx +1 -1
  203. data/ext/couchbase/core/operations/management/scope_drop.cxx +5 -1
  204. data/ext/couchbase/core/operations/management/scope_get_all.cxx +6 -1
  205. data/ext/couchbase/core/operations/management/search_get_stats.cxx +10 -0
  206. data/ext/couchbase/core/operations/management/search_index_analyze_document.cxx +3 -2
  207. data/ext/couchbase/core/operations/management/search_index_control_ingest.cxx +4 -3
  208. data/ext/couchbase/core/operations/management/search_index_control_plan_freeze.cxx +4 -3
  209. data/ext/couchbase/core/operations/management/search_index_control_query.cxx +4 -3
  210. data/ext/couchbase/core/operations/management/search_index_drop.cxx +2 -2
  211. data/ext/couchbase/core/operations/management/search_index_get.cxx +2 -2
  212. data/ext/couchbase/core/operations/management/search_index_get_documents_count.cxx +3 -2
  213. data/ext/couchbase/core/operations/management/search_index_get_stats.cxx +3 -1
  214. data/ext/couchbase/core/operations/management/search_index_upsert.cxx +2 -2
  215. data/ext/couchbase/core/operations/management/user_drop.cxx +3 -1
  216. data/ext/couchbase/core/operations/management/user_get.cxx +3 -1
  217. data/ext/couchbase/core/operations/management/user_upsert.cxx +12 -9
  218. data/ext/couchbase/core/operations/management/view_index_drop.cxx +1 -1
  219. data/ext/couchbase/core/operations/management/view_index_get.cxx +1 -1
  220. data/ext/couchbase/core/operations/management/view_index_upsert.cxx +1 -1
  221. data/ext/couchbase/core/operations/operation_traits.hxx +18 -0
  222. data/ext/couchbase/core/operations/query_response_parsing.cxx +211 -0
  223. data/ext/couchbase/core/operations/query_response_parsing.hxx +60 -0
  224. data/ext/couchbase/core/operations_fwd.hxx +3 -3
  225. data/ext/couchbase/core/origin.cxx +54 -26
  226. data/ext/couchbase/core/origin.hxx +12 -0
  227. data/ext/couchbase/core/orphan_reporter.cxx +2 -1
  228. data/ext/couchbase/core/platform/random.cc +7 -7
  229. data/ext/couchbase/core/protocol/client_request.cxx +2 -2
  230. data/ext/couchbase/core/protocol/client_request.hxx +38 -21
  231. data/ext/couchbase/core/protocol/client_response.cxx +11 -3
  232. data/ext/couchbase/core/protocol/client_response.hxx +27 -6
  233. data/ext/couchbase/core/protocol/cmd_append.cxx +2 -2
  234. data/ext/couchbase/core/protocol/cmd_cluster_map_change_notification.cxx +2 -2
  235. data/ext/couchbase/core/protocol/cmd_decrement.cxx +6 -6
  236. data/ext/couchbase/core/protocol/cmd_get.cxx +1 -1
  237. data/ext/couchbase/core/protocol/cmd_get_and_lock.cxx +2 -2
  238. data/ext/couchbase/core/protocol/cmd_get_and_touch.cxx +2 -2
  239. data/ext/couchbase/core/protocol/cmd_get_cluster_config.cxx +6 -4
  240. data/ext/couchbase/core/protocol/cmd_get_collection_id.cxx +2 -2
  241. data/ext/couchbase/core/protocol/cmd_get_error_map.cxx +1 -1
  242. data/ext/couchbase/core/protocol/cmd_get_meta.cxx +4 -4
  243. data/ext/couchbase/core/protocol/cmd_get_replica.cxx +1 -1
  244. data/ext/couchbase/core/protocol/cmd_hello.cxx +1 -1
  245. data/ext/couchbase/core/protocol/cmd_increment.cxx +6 -6
  246. data/ext/couchbase/core/protocol/cmd_insert.cxx +4 -4
  247. data/ext/couchbase/core/protocol/cmd_lookup_in.cxx +4 -3
  248. data/ext/couchbase/core/protocol/cmd_lookup_in_replica.cxx +4 -3
  249. data/ext/couchbase/core/protocol/cmd_mutate_in.cxx +10 -9
  250. data/ext/couchbase/core/protocol/cmd_mutate_in.hxx +10 -10
  251. data/ext/couchbase/core/protocol/cmd_observe_seqno.cxx +7 -7
  252. data/ext/couchbase/core/protocol/cmd_prepend.cxx +2 -2
  253. data/ext/couchbase/core/protocol/cmd_remove.cxx +2 -2
  254. data/ext/couchbase/core/protocol/cmd_replace.cxx +4 -4
  255. data/ext/couchbase/core/protocol/cmd_touch.cxx +1 -1
  256. data/ext/couchbase/core/protocol/cmd_upsert.cxx +4 -4
  257. data/ext/couchbase/core/protocol/datatype.hxx +118 -1
  258. data/ext/couchbase/core/protocol/frame_info_utils.cxx +1 -1
  259. data/ext/couchbase/core/protocol/magic.hxx +4 -0
  260. data/ext/couchbase/core/protocol/magic_fmt.hxx +3 -0
  261. data/ext/couchbase/core/protocol/server_request.hxx +3 -1
  262. data/ext/couchbase/core/protocol/status.cxx +1 -1
  263. data/ext/couchbase/core/protostellar/analytics_converter.hxx +171 -0
  264. data/ext/couchbase/core/protostellar/bucket_admin_converter.hxx +301 -0
  265. data/ext/couchbase/core/protostellar/collection_admin_converter.hxx +92 -0
  266. data/ext/couchbase/core/protostellar/component.cxx +2445 -0
  267. data/ext/couchbase/core/protostellar/component.hxx +391 -0
  268. data/ext/couchbase/core/{columnar/bootstrap_notification_subscriber.hxx → protostellar/compression_settings.hxx} +11 -10
  269. data/ext/couchbase/core/protostellar/credentials.cxx +233 -0
  270. data/ext/couchbase/core/protostellar/credentials.hxx +88 -0
  271. data/ext/couchbase/core/protostellar/dispatcher.cxx +57 -0
  272. data/ext/couchbase/core/protostellar/dispatcher.hxx +482 -0
  273. data/ext/couchbase/core/protostellar/error_utils.cxx +293 -0
  274. data/ext/couchbase/core/protostellar/error_utils.hxx +97 -0
  275. data/ext/couchbase/core/protostellar/json_payload.hxx +49 -0
  276. data/ext/couchbase/core/protostellar/kv_converter.cxx +70 -0
  277. data/ext/couchbase/core/protostellar/kv_converter.hxx +600 -0
  278. data/ext/couchbase/core/protostellar/query_converter.hxx +265 -0
  279. data/ext/couchbase/core/protostellar/query_index_admin_converter.hxx +291 -0
  280. data/ext/couchbase/core/protostellar/query_proto.hxx +73 -0
  281. data/ext/couchbase/core/protostellar/search_converter.hxx +244 -0
  282. data/ext/couchbase/core/protostellar/search_index_admin_converter.hxx +156 -0
  283. data/ext/couchbase/core/protostellar/view_converter.hxx +158 -0
  284. data/ext/couchbase/core/query_stream.cxx +447 -0
  285. data/ext/couchbase/core/query_stream.hxx +141 -0
  286. data/ext/couchbase/core/query_stream_component.cxx +243 -0
  287. data/ext/couchbase/core/query_stream_component.hxx +68 -0
  288. data/ext/couchbase/core/range_scan_load_balancer.cxx +15 -8
  289. data/ext/couchbase/core/range_scan_orchestrator.cxx +15 -3
  290. data/ext/couchbase/core/response_handler.hxx +41 -4
  291. data/ext/couchbase/core/row_streamer.cxx +332 -46
  292. data/ext/couchbase/core/row_streamer.hxx +31 -1
  293. data/ext/couchbase/core/row_streamer_options.hxx +55 -0
  294. data/ext/couchbase/core/sasl/client.h +3 -0
  295. data/ext/couchbase/core/search_scoring.hxx +66 -0
  296. data/ext/couchbase/core/stream_error_details.hxx +69 -0
  297. data/ext/couchbase/core/timeout_defaults.hxx +0 -1
  298. data/ext/couchbase/core/topology/capabilities.hxx +6 -0
  299. data/ext/couchbase/core/topology/capabilities_fmt.hxx +3 -0
  300. data/ext/couchbase/core/topology/configuration.cxx +21 -6
  301. data/ext/couchbase/core/topology/configuration.hxx +29 -8
  302. data/ext/couchbase/core/topology/configuration_json.hxx +3 -0
  303. data/ext/couchbase/core/topology/error_map_json.hxx +3 -1
  304. data/ext/couchbase/core/tracing/threshold_logging_tracer.cxx +70 -4
  305. data/ext/couchbase/core/tracing/threshold_logging_tracer.hxx +10 -0
  306. data/ext/couchbase/core/transactions/active_transaction_record.cxx +0 -34
  307. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +108 -63
  308. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +19 -2
  309. data/ext/couchbase/core/transactions/error_list.hxx +2 -2
  310. data/ext/couchbase/core/transactions/get_multi_fetch.hxx +160 -0
  311. data/ext/couchbase/core/transactions/get_multi_orchestrator.cxx +326 -125
  312. data/ext/couchbase/core/transactions/get_multi_transaction_id.hxx +62 -0
  313. data/ext/couchbase/core/transactions/internal/atr_entry.hxx +22 -2
  314. data/ext/couchbase/core/transactions/internal/transaction_context.hxx +2 -2
  315. data/ext/couchbase/core/transactions/internal/transactions_cleanup.hxx +23 -1
  316. data/ext/couchbase/core/transactions/internal/utils.hxx +50 -14
  317. data/ext/couchbase/core/transactions/staged_mutation.cxx +23 -18
  318. data/ext/couchbase/core/transactions/staged_mutation.hxx +15 -4
  319. data/ext/couchbase/core/transactions/transaction_context.cxx +55 -40
  320. data/ext/couchbase/core/transactions/transaction_get_result.hxx +16 -0
  321. data/ext/couchbase/core/transactions/transactions.cxx +3 -55
  322. data/ext/couchbase/core/transactions/transactions_cleanup.cxx +165 -72
  323. data/ext/couchbase/core/transactions/transactions_config.cxx +0 -11
  324. data/ext/couchbase/core/transactions/utils.cxx +10 -0
  325. data/ext/couchbase/core/transactions/waitable_op_list.hxx +2 -2
  326. data/ext/couchbase/core/transactions.hxx +9 -9
  327. data/ext/couchbase/core/utils/byteswap.hxx +166 -9
  328. data/ext/couchbase/core/utils/connection_string.cxx +50 -85
  329. data/ext/couchbase/core/utils/connection_string.hxx +15 -0
  330. data/ext/couchbase/core/{columnar/backoff_calculator.hxx → utils/deadline.hxx} +13 -13
  331. data/ext/couchbase/core/utils/duration_parser.cxx +1 -1
  332. data/ext/couchbase/core/utils/json_streaming_lexer.cxx +48 -5
  333. data/ext/couchbase/core/utils/json_streaming_lexer.hxx +4 -1
  334. data/ext/couchbase/core/utils/movable_function.hxx +301 -52
  335. data/ext/couchbase/core/utils/name_codec.hxx +154 -11
  336. data/ext/couchbase/core/{columnar/retry_info.hxx → utils/process_id.hxx} +18 -13
  337. data/ext/couchbase/core/utils/url_codec.cxx +12 -51
  338. data/ext/couchbase/core/websocket_codec.cxx +13 -37
  339. data/ext/couchbase/couchbase/analytics_options.hxx +21 -10
  340. data/ext/couchbase/couchbase/analytics_row.hxx +105 -0
  341. data/ext/couchbase/couchbase/analytics_stream_result.hxx +266 -0
  342. data/ext/couchbase/couchbase/boolean_query.hxx +3 -3
  343. data/ext/couchbase/couchbase/bucket.hxx +57 -0
  344. data/ext/couchbase/couchbase/cluster.hxx +183 -5
  345. data/ext/couchbase/{core/columnar/security_options.hxx → couchbase/cluster_state.hxx} +18 -14
  346. data/ext/couchbase/couchbase/codec/lenient_json_transcoder.hxx +69 -0
  347. data/ext/couchbase/couchbase/collection.hxx +234 -14
  348. data/ext/couchbase/couchbase/common_durability_options.hxx +18 -3
  349. data/ext/couchbase/couchbase/conjunction_query.hxx +1 -1
  350. data/ext/couchbase/couchbase/create_dataset_analytics_options.hxx +6 -1
  351. data/ext/couchbase/couchbase/date_range_query.hxx +2 -2
  352. data/ext/couchbase/couchbase/decrement_options.hxx +1 -1
  353. data/ext/couchbase/couchbase/disjunction_query.hxx +1 -1
  354. data/ext/couchbase/couchbase/error.hxx +32 -0
  355. data/ext/couchbase/couchbase/error_codes.hxx +33 -1
  356. data/ext/couchbase/couchbase/geo_bounding_box_query.hxx +1 -1
  357. data/ext/couchbase/couchbase/geo_distance_query.hxx +1 -1
  358. data/ext/couchbase/couchbase/geo_polygon_query.hxx +1 -1
  359. data/ext/couchbase/couchbase/get_replica_options.hxx +68 -0
  360. data/ext/couchbase/couchbase/get_replica_result.hxx +5 -3
  361. data/ext/couchbase/couchbase/get_replica_strategy.hxx +161 -0
  362. data/ext/couchbase/couchbase/get_result.hxx +1 -0
  363. data/ext/couchbase/couchbase/increment_options.hxx +1 -1
  364. data/ext/couchbase/couchbase/insert_options.hxx +1 -1
  365. data/ext/couchbase/couchbase/match_phrase_query.hxx +1 -1
  366. data/ext/couchbase/couchbase/match_query.hxx +1 -1
  367. data/ext/couchbase/couchbase/mutate_in_options.hxx +3 -3
  368. data/ext/couchbase/couchbase/network_options.hxx +1 -1
  369. data/ext/couchbase/couchbase/node_id.hxx +42 -14
  370. data/ext/couchbase/couchbase/node_id_for_options.hxx +7 -1
  371. data/ext/couchbase/couchbase/node_ids_options.hxx +8 -2
  372. data/ext/couchbase/couchbase/numeric_range_query.hxx +1 -1
  373. data/ext/couchbase/couchbase/phrase_query.hxx +1 -1
  374. data/ext/couchbase/couchbase/prefix_query.hxx +1 -1
  375. data/ext/couchbase/couchbase/query_options.hxx +23 -12
  376. data/ext/couchbase/couchbase/query_row.hxx +111 -0
  377. data/ext/couchbase/couchbase/query_stream_result.hxx +302 -0
  378. data/ext/couchbase/couchbase/query_string_query.hxx +3 -3
  379. data/ext/couchbase/couchbase/regexp_query.hxx +1 -1
  380. data/ext/couchbase/couchbase/replace_options.hxx +3 -3
  381. data/ext/couchbase/couchbase/result.hxx +14 -3
  382. data/ext/couchbase/couchbase/scan_result_item.hxx +1 -0
  383. data/ext/couchbase/couchbase/scope.hxx +85 -2
  384. data/ext/couchbase/couchbase/search_options.hxx +58 -8
  385. data/ext/couchbase/couchbase/search_scoring.hxx +94 -0
  386. data/ext/couchbase/couchbase/search_scoring_none.hxx +51 -0
  387. data/ext/couchbase/couchbase/search_scoring_reciprocal_rank_fusion.hxx +88 -0
  388. data/ext/couchbase/couchbase/search_scoring_relative_score_fusion.hxx +70 -0
  389. data/ext/couchbase/couchbase/term_range_query.hxx +1 -1
  390. data/ext/couchbase/couchbase/tracing/request_span.hxx +46 -0
  391. data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +23 -21
  392. data/ext/couchbase/couchbase/transactions/transaction_get_multi_result.hxx +24 -19
  393. data/ext/couchbase/couchbase/transactions/transaction_get_result.hxx +17 -3
  394. data/ext/couchbase/couchbase/transactions/transactions_config.hxx +4 -2
  395. data/ext/couchbase/couchbase/transactions.hxx +27 -18
  396. data/ext/couchbase/couchbase/upsert_options.hxx +3 -3
  397. data/ext/couchbase/couchbase/vector_query.hxx +1 -1
  398. data/ext/couchbase/couchbase/wait_until_ready_options.hxx +111 -0
  399. data/ext/couchbase/couchbase/wildcard_query.hxx +1 -1
  400. data/ext/extconf.rb +15 -10
  401. data/ext/rcb_analytics.cxx +82 -102
  402. data/ext/rcb_backend.cxx +42 -36
  403. data/ext/rcb_backend.hxx +2 -0
  404. data/ext/rcb_buckets.cxx +47 -59
  405. data/ext/rcb_collections.cxx +42 -52
  406. data/ext/rcb_crud.cxx +345 -224
  407. data/ext/rcb_diagnostics.cxx +117 -27
  408. data/ext/rcb_exceptions.cxx +152 -99
  409. data/ext/rcb_exceptions.hxx +25 -0
  410. data/ext/rcb_extras.cxx +18 -19
  411. data/ext/rcb_hdr_histogram.cxx +19 -4
  412. data/ext/rcb_logger.cxx +202 -27
  413. data/ext/rcb_logger.hxx +12 -0
  414. data/ext/rcb_multi.cxx +40 -40
  415. data/ext/rcb_observability.cxx +7 -5
  416. data/ext/rcb_query.cxx +99 -142
  417. data/ext/rcb_range_scan.cxx +41 -35
  418. data/ext/rcb_search.cxx +98 -99
  419. data/ext/rcb_users.cxx +36 -56
  420. data/ext/rcb_utils.cxx +100 -37
  421. data/ext/rcb_utils.hxx +287 -33
  422. data/ext/rcb_version.cxx +1 -1
  423. data/ext/rcb_views.cxx +55 -51
  424. data/lib/active_support/cache/couchbase_store.rb +77 -12
  425. data/lib/couchbase/bucket.rb +20 -0
  426. data/lib/couchbase/cluster.rb +17 -0
  427. data/lib/couchbase/collection.rb +36 -1
  428. data/lib/couchbase/errors.rb +18 -0
  429. data/lib/couchbase/fork_hooks.rb +16 -7
  430. data/lib/couchbase/get_replica_strategy.rb +89 -0
  431. data/lib/couchbase/logger.rb +1 -1
  432. data/lib/couchbase/management/analytics_index_manager.rb +15 -15
  433. data/lib/couchbase/management/bucket_manager.rb +6 -6
  434. data/lib/couchbase/management/collection_manager.rb +6 -6
  435. data/lib/couchbase/management/query_index_manager.rb +7 -7
  436. data/lib/couchbase/management/search_index_manager.rb +13 -13
  437. data/lib/couchbase/management/user_manager.rb +10 -10
  438. data/lib/couchbase/management/view_index_manager.rb +5 -5
  439. data/lib/couchbase/options.rb +174 -42
  440. data/lib/couchbase/protostellar/bucket.rb +4 -0
  441. data/lib/couchbase/protostellar/cluster.rb +4 -0
  442. data/lib/couchbase/protostellar/collection.rb +4 -0
  443. data/lib/couchbase/protostellar/error_handling.rb +2 -1
  444. data/lib/couchbase/protostellar/request.rb +1 -0
  445. data/lib/couchbase/protostellar/request_generator/kv.rb +16 -2
  446. data/lib/couchbase/protostellar/request_generator/search.rb +17 -1
  447. data/lib/couchbase/protostellar/retry/orchestrator.rb +3 -2
  448. data/lib/couchbase/protostellar/retry/strategies/fail_fast.rb +33 -0
  449. data/lib/couchbase/protostellar/retry/strategies.rb +1 -0
  450. data/lib/couchbase/search_options.rb +95 -6
  451. data/lib/couchbase/version.rb +1 -1
  452. metadata +91 -33
  453. data/ext/0001-fix-build-for-mingw-w64-ucrt-x86_64-toolchain.patch +0 -40
  454. data/ext/couchbase/core/columnar/agent.hxx +0 -71
  455. data/ext/couchbase/core/columnar/agent_config.hxx +0 -59
  456. data/ext/couchbase/core/columnar/background_bootstrap_listener.hxx +0 -42
  457. data/ext/couchbase/core/columnar/backoff_calculator.cxx +0 -62
  458. data/ext/couchbase/core/columnar/columnar_agent.cxx +0 -147
  459. data/ext/couchbase/core/columnar/columnar_agent_config.cxx +0 -48
  460. data/ext/couchbase/core/columnar/columnar_error.cxx +0 -51
  461. data/ext/couchbase/core/columnar/database_management_options.hxx +0 -56
  462. data/ext/couchbase/core/columnar/error.hxx +0 -51
  463. data/ext/couchbase/core/columnar/error_codes.cxx +0 -107
  464. data/ext/couchbase/core/columnar/error_codes.hxx +0 -75
  465. data/ext/couchbase/core/columnar/management_component.cxx +0 -300
  466. data/ext/couchbase/core/columnar/management_component.hxx +0 -56
  467. data/ext/couchbase/core/columnar/query_component.cxx +0 -523
  468. data/ext/couchbase/core/columnar/query_component.hxx +0 -57
  469. data/ext/couchbase/core/columnar/query_options.hxx +0 -59
  470. data/ext/couchbase/core/columnar/query_result.cxx +0 -130
  471. data/ext/couchbase/core/columnar/query_result.hxx +0 -78
  472. data/ext/couchbase/core/io/config_tracker.cxx +0 -687
  473. data/ext/couchbase/core/io/config_tracker.hxx +0 -123
@@ -69,11 +69,31 @@ public:
69
69
  {
70
70
  }
71
71
 
72
+ /**
73
+ * @brief Determines whether this ATR entry's transaction has exceeded its configured timeout.
74
+ *
75
+ * Uses the ATR document's CAS as a server-side clock (CAS encodes a server timestamp in
76
+ * nanoseconds). The elapsed time is computed as:
77
+ * @code elapsed_ms = (cas / 1_000_000) - timestamp_start_ms @endcode
78
+ *
79
+ * Returns @c false conservatively if either @c timestamp_start_ms or @c expires_after_ms are
80
+ * absent — this avoids spurious expiry decisions when the ATR entry has not yet been fully
81
+ * initialised or when the CAS has not advanced past the recorded start time.
82
+ *
83
+ * @param safety_margin Additional milliseconds added to the configured TTL before declaring
84
+ * the entry expired. Useful to declare expiry slightly early in order
85
+ * to avoid races with the owning client.
86
+ * @return @c true if the transaction has exceeded @c (expires_after_ms + safety_margin) of
87
+ * elapsed CAS-time since @c timestamp_start_ms.
88
+ */
72
89
  [[nodiscard]] bool has_expired(std::uint32_t safety_margin = 0) const
73
90
  {
74
91
  std::uint64_t cas_ms = cas_ / 1000000;
75
- if (timestamp_start_ms_ && cas_ms > *timestamp_start_ms_) {
76
- std::uint32_t expires_after_ms = *expires_after_ms_;
92
+ if (timestamp_start_ms_ && expires_after_ms_ && cas_ms > *timestamp_start_ms_) {
93
+ // Compute the effective TTL in std::uint64_t: expires_after_ms + safety_margin would
94
+ // otherwise be evaluated in std::uint32_t and could overflow for large TTLs/margins,
95
+ // flipping the comparison.
96
+ const std::uint64_t expires_after_ms = *expires_after_ms_;
77
97
  return (cas_ms - *timestamp_start_ms_) > (expires_after_ms + safety_margin);
78
98
  }
79
99
  return false;
@@ -40,7 +40,7 @@ using txn_complete_callback =
40
40
  std::function<void(std::optional<transaction_exception>,
41
41
  std::optional<::couchbase::transactions::transaction_result>)>;
42
42
 
43
- struct exp_delay;
43
+ struct async_timed_exp_delay;
44
44
 
45
45
  class transaction_context : public std::enable_shared_from_this<transaction_context>
46
46
  {
@@ -174,6 +174,6 @@ private:
174
174
  std::shared_ptr<attempt_context_impl> current_attempt_context_;
175
175
  mutable std::mutex mutex_;
176
176
 
177
- std::unique_ptr<exp_delay> delay_;
177
+ std::unique_ptr<async_timed_exp_delay> delay_;
178
178
  };
179
179
  } // namespace couchbase::core::transactions
@@ -22,8 +22,12 @@
22
22
 
23
23
  #include <couchbase/transactions/transactions_config.hxx>
24
24
 
25
+ #include <asio/thread_pool.hpp>
26
+
25
27
  #include <atomic>
26
28
  #include <condition_variable>
29
+ #include <cstdint>
30
+ #include <optional>
27
31
  #include <thread>
28
32
 
29
33
  namespace couchbase::core
@@ -118,7 +122,7 @@ public:
118
122
  void add_collection(const couchbase::transactions::transaction_keyspace& keyspace);
119
123
  auto collections() -> std::list<couchbase::transactions::transaction_keyspace>
120
124
  {
121
- std::lock_guard<std::mutex> lock(mutex_);
125
+ std::scoped_lock<std::mutex> lock(mutex_);
122
126
  return collections_;
123
127
  }
124
128
 
@@ -127,6 +131,11 @@ public:
127
131
  // only used for testing
128
132
  void force_cleanup_entry(atr_cleanup_entry& entry, transactions_cleanup_attempt& attempt);
129
133
  // only used for testing
134
+ [[nodiscard]] auto client_uuid() const -> const std::string&
135
+ {
136
+ return client_uuid_;
137
+ }
138
+ // only used for testing
130
139
  auto force_cleanup_atr(const core::document_id& atr_id,
131
140
  std::vector<transactions_cleanup_attempt>& results) -> atr_cleanup_stats;
132
141
  auto get_active_clients(const couchbase::transactions::transaction_keyspace& keyspace,
@@ -147,7 +156,20 @@ private:
147
156
  mutable std::mutex mutex_;
148
157
  std::list<std::thread> lost_attempt_cleanup_workers_;
149
158
 
159
+ // Bounded, shared pool of threads for the blocking per-ATR cleanup checks. These checks block on
160
+ // KV, so they must not run on the single Asio IO thread. The pool is shared across every
161
+ // collection cleanup worker, so this count bounds the TOTAL number of concurrent get_atr checks
162
+ // across the whole cleanup subsystem -- deliberately, to cap load on the cluster regardless of
163
+ // how many keyspaces are registered for cleanup. The same value doubles as each worker's
164
+ // in-flight cap: with one collection a worker saturates the pool; with many collections the
165
+ // shared pool multiplexes them (fair FIFO) and per-collection throughput drops accordingly, which
166
+ // is the intended trade-off (bounded cluster load over guaranteed per-collection pacing).
167
+ static constexpr std::size_t max_in_flight_atr_checks_{ 16 };
168
+ std::optional<asio::thread_pool> atr_cleanup_pool_;
169
+
150
170
  const std::string client_uuid_;
171
+ // The process that registered client_uuid_. A forked child inherits the uuid but not the record.
172
+ const std::int64_t client_record_owner_pid_;
151
173
  std::list<couchbase::transactions::transaction_keyspace> collections_;
152
174
 
153
175
  void attempts_loop();
@@ -47,6 +47,25 @@ namespace transactions
47
47
  std::uint64_t
48
48
  now_ns_from_vbucket(const tao::json::value& vbucket);
49
49
 
50
+ /**
51
+ * ${Mutation.CAS} is written by kvengine with 'macroToString(htonll(info.cas))'. Discussed this
52
+ * with the KV team and, though there is consensus that this is off (htonll is definitely wrong,
53
+ * and a string is an odd choice), there are clients (SyncGateway) that consume the current
54
+ * string, so it can't be changed.
55
+ *
56
+ * The byte order inside that string is therefore fixed by the server, whatever host reads it,
57
+ * so recovering the value needs an unconditional reversal rather than a network-to-host
58
+ * conversion. See core::utils::reverse_bytes.
59
+ *
60
+ * Looks like: "0x000058a71dd25c15"
61
+ * Want: 0x155CD21DA7580000 (1539336197457313792 in base10, an epoch time in
62
+ * nanoseconds)
63
+ *
64
+ * @return epoch time in milliseconds, or 0 for an empty string
65
+ */
66
+ std::uint64_t
67
+ parse_mutation_cas(const std::string& cas);
68
+
50
69
  std::string
51
70
  jsonify(const tao::json::value& obj);
52
71
 
@@ -175,7 +194,7 @@ jitter()
175
194
  static std::mt19937 gen(rd());
176
195
  static std::uniform_real_distribution<> dist(1 - RETRY_OP_JITTER, 1 + RETRY_OP_JITTER);
177
196
 
178
- std::lock_guard<std::mutex> lock(mtx);
197
+ std::scoped_lock<std::mutex> lock(mtx);
179
198
  return dist(gen);
180
199
  }
181
200
 
@@ -260,7 +279,13 @@ retry_op(std::function<R()> func)
260
279
  DEFAULT_RETRY_OP_DELAY, std::numeric_limits<std::size_t>::max(), func);
261
280
  }
262
281
 
263
- struct exp_delay {
282
+ // Exponential backoff bounded by a timeout, waiting on a timer so that it is safe to call from a
283
+ // completion handler. The first call starts the timeout and completes inline. Later calls complete
284
+ // after the backoff, or with retry_operation_timeout past the timeout or on cancellation, or with
285
+ // retry_operation_retries_exhausted after max_retries. Calls must not overlap: state is
286
+ // unsynchronised and expires_after cancels a pending wait.
287
+ struct async_timed_exp_delay {
288
+ std::shared_ptr<asio::steady_timer> timer;
264
289
  std::chrono::nanoseconds initial_delay;
265
290
  std::chrono::nanoseconds max_delay;
266
291
  std::chrono::nanoseconds timeout;
@@ -269,36 +294,44 @@ struct exp_delay {
269
294
  std::size_t max_retries{ 100 };
270
295
 
271
296
  template<typename R1, typename P1, typename R2, typename P2, typename R3, typename P3>
272
- exp_delay(std::chrono::duration<R1, P1> initial,
273
- std::chrono::duration<R2, P2> max,
274
- std::chrono::duration<R3, P3> limit)
275
- : initial_delay(std::chrono::duration_cast<std::chrono::nanoseconds>(initial))
297
+ async_timed_exp_delay(std::shared_ptr<asio::steady_timer> timer,
298
+ std::chrono::duration<R1, P1> initial,
299
+ std::chrono::duration<R2, P2> max,
300
+ std::chrono::duration<R3, P3> limit)
301
+ : timer(std::move(timer))
302
+ , initial_delay(std::chrono::duration_cast<std::chrono::nanoseconds>(initial))
276
303
  , max_delay(std::chrono::duration_cast<std::chrono::nanoseconds>(max))
277
304
  , timeout(std::chrono::duration_cast<std::chrono::nanoseconds>(limit))
278
305
  {
279
306
  }
280
- void operator()() const
307
+ void operator()(utils::movable_function<void(std::exception_ptr)> callback) const
281
308
  {
282
309
  auto now = std::chrono::steady_clock::now();
283
310
  if (retries >= max_retries) {
284
- throw retry_operation_retries_exhausted("retries exhausted");
311
+ return callback(
312
+ std::make_exception_ptr(retry_operation_retries_exhausted("retries exhausted")));
285
313
  }
286
314
  if (!end_time) {
287
315
  end_time = std::chrono::steady_clock::now() + timeout;
288
- return;
316
+ return callback({});
289
317
  }
290
318
  if (now > *end_time) {
291
- throw retry_operation_timeout("timed out");
319
+ return callback(std::make_exception_ptr(retry_operation_timeout("timed out")));
292
320
  }
293
321
  auto delay = initial_delay * (jitter() * pow(2, retries++));
294
322
  if (delay > max_delay) {
295
323
  delay = max_delay;
296
324
  }
297
325
  if (now + delay > *end_time) {
298
- std::this_thread::sleep_for(*end_time - now);
299
- } else {
300
- std::this_thread::sleep_for(delay);
326
+ delay = *end_time - now;
301
327
  }
328
+ timer->expires_after(std::chrono::duration_cast<std::chrono::nanoseconds>(delay));
329
+ timer->async_wait([callback = std::move(callback)](std::error_code ec) mutable {
330
+ if (ec == asio::error::operation_aborted) {
331
+ return callback(std::make_exception_ptr(retry_operation_timeout("retry aborted")));
332
+ }
333
+ callback({});
334
+ });
302
335
  }
303
336
  };
304
337
 
@@ -353,7 +386,10 @@ struct async_exp_delay {
353
386
 
354
387
  void operator()(utils::movable_function<void(std::exception_ptr)> callback) const
355
388
  {
356
- if (retries++ >= max_retries) {
389
+ // retries must be incremented exactly once per call: the guard reads it without mutating, and
390
+ // pow() does the single post-increment. Incrementing in both places double-counts -- halving
391
+ // the effective max_retries and doubling the exponent growth per attempt.
392
+ if (retries >= max_retries) {
357
393
  callback(std::make_exception_ptr(retry_operation_retries_exhausted("retries exhausted")));
358
394
  return;
359
395
  }
@@ -110,7 +110,7 @@ staged_mutation::operation_id() const -> const std::string&
110
110
  }
111
111
 
112
112
  auto
113
- staged_mutation::type_as_string() const -> std::string
113
+ staged_mutation::type_as_string() const -> std::string_view
114
114
  {
115
115
  switch (type_) {
116
116
  case staged_mutation_type::INSERT:
@@ -149,7 +149,7 @@ unstaging_state::wait_until_unstage_possible() -> bool
149
149
  void
150
150
  unstaging_state::notify_unstage_complete()
151
151
  {
152
- const std::lock_guard lock(mutex_);
152
+ const std::scoped_lock lock(mutex_);
153
153
  in_flight_count_--;
154
154
  cv_.notify_one();
155
155
  }
@@ -157,7 +157,7 @@ unstaging_state::notify_unstage_complete()
157
157
  void
158
158
  unstaging_state::notify_unstage_error()
159
159
  {
160
- const std::lock_guard lock(mutex_);
160
+ const std::scoped_lock lock(mutex_);
161
161
  abort_ = true;
162
162
  in_flight_count_--;
163
163
  cv_.notify_all();
@@ -166,14 +166,14 @@ unstaging_state::notify_unstage_error()
166
166
  auto
167
167
  staged_mutation_queue::empty() -> bool
168
168
  {
169
- const std::lock_guard<std::mutex> lock(mutex_);
169
+ const std::scoped_lock<std::mutex> lock(mutex_);
170
170
  return queue_.empty();
171
171
  }
172
172
 
173
173
  void
174
174
  staged_mutation_queue::add(staged_mutation&& mutation)
175
175
  {
176
- const std::lock_guard<std::mutex> lock(mutex_);
176
+ const std::scoped_lock<std::mutex> lock(mutex_);
177
177
  // Can only have one staged mutation per document.
178
178
  queue_.erase(std::remove_if(queue_.begin(),
179
179
  queue_.end(),
@@ -188,16 +188,21 @@ void
188
188
  staged_mutation_queue::extract_to(const std::string& prefix,
189
189
  core::operations::mutate_in_request& req)
190
190
  {
191
- const std::lock_guard<std::mutex> lock(mutex_);
191
+ const std::scoped_lock<std::mutex> lock(mutex_);
192
192
  tao::json::value inserts = tao::json::empty_array;
193
193
  tao::json::value replaces = tao::json::empty_array;
194
194
  tao::json::value removes = tao::json::empty_array;
195
195
 
196
196
  for (const auto& mutation : queue_) {
197
- const tao::json::value doc{ { ATR_FIELD_PER_DOC_ID, mutation.id().key() },
198
- { ATR_FIELD_PER_DOC_BUCKET, mutation.id().bucket() },
199
- { ATR_FIELD_PER_DOC_SCOPE, mutation.id().scope() },
200
- { ATR_FIELD_PER_DOC_COLLECTION, mutation.id().collection() } };
197
+ const tao::json::value doc{
198
+ { ATR_FIELD_PER_DOC_ID, mutation.id().key() },
199
+ { ATR_FIELD_PER_DOC_BUCKET, mutation.id().bucket() },
200
+ { ATR_FIELD_PER_DOC_SCOPE, mutation.id().scope() },
201
+ {
202
+ ATR_FIELD_PER_DOC_COLLECTION,
203
+ mutation.id().collection(),
204
+ },
205
+ };
201
206
  switch (mutation.type()) {
202
207
  case staged_mutation_type::INSERT:
203
208
  inserts.push_back(doc);
@@ -232,7 +237,7 @@ staged_mutation_queue::extract_to(const std::string& prefix,
232
237
  void
233
238
  staged_mutation_queue::remove_any(const core::document_id& id)
234
239
  {
235
- const std::lock_guard<std::mutex> lock(mutex_);
240
+ const std::scoped_lock<std::mutex> lock(mutex_);
236
241
  auto new_end = std::remove_if(queue_.begin(), queue_.end(), [&id](const staged_mutation& item) {
237
242
  return document_ids_equal(item.id(), id);
238
243
  });
@@ -242,7 +247,7 @@ staged_mutation_queue::remove_any(const core::document_id& id)
242
247
  auto
243
248
  staged_mutation_queue::find_any(const core::document_id& id) -> staged_mutation*
244
249
  {
245
- const std::lock_guard<std::mutex> lock(mutex_);
250
+ const std::scoped_lock<std::mutex> lock(mutex_);
246
251
  for (auto& item : queue_) {
247
252
  if (document_ids_equal(item.id(), id)) {
248
253
  return &item;
@@ -254,7 +259,7 @@ staged_mutation_queue::find_any(const core::document_id& id) -> staged_mutation*
254
259
  auto
255
260
  staged_mutation_queue::find_replace(const core::document_id& id) -> staged_mutation*
256
261
  {
257
- const std::lock_guard<std::mutex> lock(mutex_);
262
+ const std::scoped_lock<std::mutex> lock(mutex_);
258
263
  for (auto& item : queue_) {
259
264
  if (item.type() == staged_mutation_type::REPLACE && document_ids_equal(item.id(), id)) {
260
265
  return &item;
@@ -266,7 +271,7 @@ staged_mutation_queue::find_replace(const core::document_id& id) -> staged_mutat
266
271
  auto
267
272
  staged_mutation_queue::find_insert(const core::document_id& id) -> staged_mutation*
268
273
  {
269
- const std::lock_guard<std::mutex> lock(mutex_);
274
+ const std::scoped_lock<std::mutex> lock(mutex_);
270
275
  for (auto& item : queue_) {
271
276
  if (item.type() == staged_mutation_type::INSERT && document_ids_equal(item.id(), id)) {
272
277
  return &item;
@@ -278,7 +283,7 @@ staged_mutation_queue::find_insert(const core::document_id& id) -> staged_mutati
278
283
  auto
279
284
  staged_mutation_queue::find_remove(const core::document_id& id) -> staged_mutation*
280
285
  {
281
- const std::lock_guard<std::mutex> lock(mutex_);
286
+ const std::scoped_lock<std::mutex> lock(mutex_);
282
287
  for (auto& item : queue_) {
283
288
  if (item.type() == staged_mutation_type::REMOVE && document_ids_equal(item.id(), id)) {
284
289
  return &item;
@@ -289,7 +294,7 @@ staged_mutation_queue::find_remove(const core::document_id& id) -> staged_mutati
289
294
  void
290
295
  staged_mutation_queue::iterate(const std::function<void(staged_mutation&)>& op)
291
296
  {
292
- const std::lock_guard<std::mutex> lock(mutex_);
297
+ const std::scoped_lock<std::mutex> lock(mutex_);
293
298
  for (auto& item : queue_) {
294
299
  op(item);
295
300
  }
@@ -299,7 +304,7 @@ void
299
304
  staged_mutation_queue::commit(const std::shared_ptr<attempt_context_impl>& ctx)
300
305
  {
301
306
  CB_ATTEMPT_CTX_LOG_TRACE(ctx, "committing staged mutations...");
302
- const std::lock_guard<std::mutex> lock(mutex_);
307
+ const std::scoped_lock<std::mutex> lock(mutex_);
303
308
 
304
309
  unstaging_state state{ ctx };
305
310
  std::vector<std::future<void>> futures{};
@@ -390,7 +395,7 @@ void
390
395
  staged_mutation_queue::rollback(const std::shared_ptr<attempt_context_impl>& ctx)
391
396
  {
392
397
  CB_ATTEMPT_CTX_LOG_TRACE(ctx, "rolling back staged mutations...");
393
- const std::lock_guard<std::mutex> lock(mutex_);
398
+ const std::scoped_lock<std::mutex> lock(mutex_);
394
399
 
395
400
  unstaging_state state{ ctx };
396
401
  std::vector<std::future<void>> futures{};
@@ -23,8 +23,12 @@
23
23
 
24
24
  #include <couchbase/codec/encoded_value.hxx>
25
25
 
26
+ #include <atomic>
27
+ #include <condition_variable>
26
28
  #include <mutex>
29
+ #include <optional>
27
30
  #include <string>
31
+ #include <string_view>
28
32
  #include <vector>
29
33
 
30
34
  namespace couchbase::core::transactions
@@ -32,7 +36,7 @@ namespace couchbase::core::transactions
32
36
  enum class staged_mutation_type {
33
37
  INSERT,
34
38
  REMOVE,
35
- REPLACE
39
+ REPLACE,
36
40
  };
37
41
 
38
42
  class staged_mutation
@@ -62,7 +66,14 @@ public:
62
66
  [[nodiscard]] auto current_user_flags() const -> std::uint32_t;
63
67
  [[nodiscard]] auto doc_metadata() const -> const std::optional<document_metadata>&;
64
68
  [[nodiscard]] auto operation_id() const -> const std::string&;
65
- [[nodiscard]] auto type_as_string() const -> std::string;
69
+ /**
70
+ * @brief Returns the human-readable name of the mutation type as a string literal view.
71
+ *
72
+ * Returns one of @c "INSERT", @c "REPLACE", or @c "REMOVE" without any heap allocation.
73
+ * The returned view is backed by a string literal and remains valid for the lifetime of the
74
+ * program.
75
+ */
76
+ [[nodiscard]] auto type_as_string() const -> std::string_view;
66
77
 
67
78
  void cas(couchbase::cas cas);
68
79
 
@@ -166,8 +177,8 @@ public:
166
177
  void extract_to(const std::string& prefix, core::operations::mutate_in_request& req);
167
178
  void commit(const std::shared_ptr<attempt_context_impl>& ctx);
168
179
  void rollback(const std::shared_ptr<attempt_context_impl>& ctx);
169
- void iterate(const std::function<void(staged_mutation&)>&);
170
- void remove_any(const core::document_id&);
180
+ void iterate(const std::function<void(staged_mutation& /*mutation*/)>&);
181
+ void remove_any(const core::document_id& /*id*/);
171
182
 
172
183
  auto find_any(const core::document_id& id) -> staged_mutation*;
173
184
  auto find_replace(const core::document_id& id) -> staged_mutation*;
@@ -27,6 +27,7 @@
27
27
 
28
28
  #include <asio/post.hpp>
29
29
  #include <asio/steady_timer.hpp>
30
+ #include <optional>
30
31
  #include <utility>
31
32
 
32
33
  namespace couchbase::core::transactions
@@ -59,9 +60,11 @@ transaction_context::transaction_context(transactions& txns,
59
60
  , config_(config.apply(txns.config()))
60
61
  , deferred_elapsed_(0)
61
62
  , cleanup_(txns.cleanup())
62
- , delay_(new exp_delay(std::chrono::milliseconds(1),
63
- std::chrono::milliseconds(100),
64
- 2 * config_.timeout))
63
+ , delay_(std::make_unique<async_timed_exp_delay>(
64
+ std::make_shared<asio::steady_timer>(txns.cluster_ref().io_context()),
65
+ std::chrono::milliseconds(1),
66
+ std::chrono::milliseconds(100),
67
+ 2 * config_.timeout))
65
68
  {
66
69
  // add metadata_collection to cleanup, if present
67
70
  if (config_.metadata_collection) {
@@ -77,7 +80,7 @@ void
77
80
  transaction_context::add_attempt()
78
81
  {
79
82
  const transaction_attempt attempt{};
80
- const std::lock_guard<std::mutex> lock(mutex_);
83
+ const std::scoped_lock<std::mutex> lock(mutex_);
81
84
  attempts_.push_back(attempt);
82
85
  }
83
86
 
@@ -138,21 +141,25 @@ void
138
141
  transaction_context::new_attempt_context(async_attempt_context::VoidCallback&& cb)
139
142
  {
140
143
  asio::post(transactions_.cluster_ref().io_context(),
141
- [self = shared_from_this(), cb = std::move(cb)]() {
144
+ [self = shared_from_this(), cb = std::move(cb)]() mutable {
142
145
  // the first time we call the delay, it just records an end time. After
143
146
  // that, it actually delays.
144
- try {
145
- (*self->delay_)();
146
- self->current_attempt_context_ = attempt_context_impl::create(self);
147
- CB_ATTEMPT_CTX_LOG_INFO(self->current_attempt_context_,
148
- "starting attempt {}/{}/{}/",
149
- self->num_attempts(),
150
- self->transaction_id(),
151
- self->current_attempt_context_->id());
152
- cb(nullptr);
153
- } catch (...) {
154
- cb(std::current_exception());
155
- }
147
+ (*self->delay_)([self, cb = std::move(cb)](const std::exception_ptr& err) mutable {
148
+ if (err) {
149
+ return cb(err);
150
+ }
151
+ try {
152
+ self->current_attempt_context_ = attempt_context_impl::create(self);
153
+ CB_ATTEMPT_CTX_LOG_INFO(self->current_attempt_context_,
154
+ "starting attempt {}/{}/{}/",
155
+ self->num_attempts(),
156
+ self->transaction_id(),
157
+ self->current_attempt_context_->id());
158
+ cb(nullptr);
159
+ } catch (...) {
160
+ cb(std::current_exception());
161
+ }
162
+ });
156
163
  });
157
164
  }
158
165
 
@@ -299,6 +306,18 @@ transaction_context::existing_error(bool previous_op_failed)
299
306
  void
300
307
  transaction_context::handle_error(const std::exception_ptr& err, txn_complete_callback&& callback)
301
308
  {
309
+ auto auto_rollback = [this]() -> std::optional<final_error> {
310
+ try {
311
+ current_attempt_context_->rollback();
312
+ return {};
313
+ } catch (const std::exception& e) {
314
+ CB_ATTEMPT_CTX_LOG_ERROR(current_attempt_context_, "auto rollback failed: {}", e.what());
315
+ } catch (...) {
316
+ CB_ATTEMPT_CTX_LOG_ERROR(current_attempt_context_, "auto rollback failed");
317
+ }
318
+ return current_attempt_context_->query_check_expired_ ? EXPIRED : FAILED;
319
+ };
320
+
302
321
  try {
303
322
  try {
304
323
  std::rethrow_exception(err);
@@ -316,16 +335,15 @@ transaction_context::handle_error(const std::exception_ptr& err, txn_complete_ca
316
335
  if (er.should_rollback()) {
317
336
  CB_ATTEMPT_CTX_LOG_TRACE(current_attempt_context_,
318
337
  "got rollback-able exception, rolling back");
319
- try {
320
- current_attempt_context_->rollback();
321
- } catch (const std::exception& er_rollback) {
338
+ if (auto rollback_failed = auto_rollback(); rollback_failed) {
322
339
  cleanup().add_attempt(current_attempt_context_);
323
- CB_ATTEMPT_CTX_LOG_TRACE(
324
- current_attempt_context_,
325
- "got error \"{}\" while auto rolling back, throwing original error",
326
- er_rollback.what(),
327
- er.what());
328
- auto final = er.get_final_exception(*this);
340
+ // A failed auto-rollback raises the original error without retry, or EXPIRED in place of
341
+ // FAILED.
342
+ auto raised = er;
343
+ if (rollback_failed == EXPIRED && raised.to_raise() == FAILED) {
344
+ raised.expired();
345
+ }
346
+ auto final = raised.get_final_exception(*this);
329
347
  // if you get here, we didn't throw, yet we had an error. Fall through
330
348
  // in this case. Note the current logic is such that rollback will not
331
349
  // have a commit ambiguous error, so we should always throw.
@@ -359,28 +377,25 @@ transaction_context::handle_error(const std::exception_ptr& err, txn_complete_ca
359
377
  return callback(final, res);
360
378
  } catch (const std::exception& ex) {
361
379
  CB_ATTEMPT_CTX_LOG_ERROR(current_attempt_context_, "got runtime error \"{}\"", ex.what());
362
- try {
363
- current_attempt_context_->rollback();
364
- } catch (...) {
365
- CB_ATTEMPT_CTX_LOG_ERROR(
366
- current_attempt_context_, "got error rolling back \"{}\"", ex.what());
367
- }
380
+ const auto rollback_failed = auto_rollback();
368
381
  cleanup().add_attempt(current_attempt_context_);
369
382
  // the assumption here is this must come from the logic, not
370
383
  // our operations (which only throw transaction_operation_failed),
371
384
  auto op_failed = transaction_operation_failed(FAIL_OTHER, ex.what());
385
+ if (rollback_failed == EXPIRED) {
386
+ op_failed.expired();
387
+ }
372
388
  return callback(op_failed.get_final_exception(*this), std::nullopt);
373
389
  } catch (...) {
374
390
  CB_ATTEMPT_CTX_LOG_ERROR(current_attempt_context_, "got unexpected error, rolling back");
375
- try {
376
- current_attempt_context_->rollback();
377
- } catch (...) {
378
- CB_ATTEMPT_CTX_LOG_ERROR(current_attempt_context_, "got error rolling back unexpected error");
379
- }
391
+ const auto rollback_failed = auto_rollback();
380
392
  cleanup().add_attempt(current_attempt_context_);
381
393
  // the assumption here is this must come from the logic, not
382
394
  // our operations (which only throw transaction_operation_failed),
383
395
  auto op_failed = transaction_operation_failed(FAIL_OTHER, "Unexpected error");
396
+ if (rollback_failed == EXPIRED) {
397
+ op_failed.expired();
398
+ }
384
399
  return callback(op_failed.get_final_exception(*this), std::nullopt);
385
400
  }
386
401
  }
@@ -408,7 +423,7 @@ transaction_context::finalize(txn_complete_callback&& cb)
408
423
  void
409
424
  transaction_context::current_attempt_state(attempt_state s)
410
425
  {
411
- const std::lock_guard<std::mutex> lock(mutex_);
426
+ const std::scoped_lock<std::mutex> lock(mutex_);
412
427
  if (attempts_.empty()) {
413
428
  throw std::runtime_error("transaction_context has no attempts yet");
414
429
  }
@@ -488,7 +503,7 @@ transaction_context::new_attempt_context()
488
503
  auto
489
504
  transaction_context::current_attempt() const -> const transaction_attempt&
490
505
  {
491
- const std::lock_guard<std::mutex> lock(mutex_);
506
+ const std::scoped_lock<std::mutex> lock(mutex_);
492
507
  if (attempts_.empty()) {
493
508
  throw std::runtime_error("transaction context has no attempts yet");
494
509
  }
@@ -498,7 +513,7 @@ transaction_context::current_attempt() const -> const transaction_attempt&
498
513
  auto
499
514
  transaction_context::num_attempts() const -> std::size_t
500
515
  {
501
- const std::lock_guard<std::mutex> lock(mutex_);
516
+ const std::scoped_lock<std::mutex> lock(mutex_);
502
517
  return attempts_.size();
503
518
  }
504
519
 
@@ -23,9 +23,11 @@
23
23
  #include "transaction_links.hxx"
24
24
 
25
25
  #include <couchbase/codec/encoded_value.hxx>
26
+ #include <couchbase/error_codes.hxx>
26
27
  #include <couchbase/fmt/cas.hxx>
27
28
  #include <couchbase/transactions/transaction_get_result.hxx>
28
29
 
30
+ #include <system_error>
29
31
  #include <utility>
30
32
 
31
33
  namespace couchbase::core::transactions
@@ -108,6 +110,20 @@ public:
108
110
  cas != nullptr && cas->is_string() && cas_.value() == 0U) {
109
111
  cas_ = couchbase::cas(stoull(cas->as<std::string>()));
110
112
  }
113
+ // This ctor parses the JSON row returned by a query-mode KV get/insert/replace,
114
+ // where the document CAS is carried as the string field "scas" (a numeric
115
+ // "cas" is also accepted above). Per the transactions query spec, KV
116
+ // get/insert/replace result handling requires scas to be present and raises a
117
+ // DecodingFailure when it is absent: without a usable CAS, cas_ stays at its
118
+ // default 0, which is not a valid document CAS and would corrupt a later COMMIT
119
+ // that stages against it. The query KV op handlers translate this
120
+ // DecodingFailure into a FAIL_OTHER (rollback). "doc" is intentionally not
121
+ // required here, since insert/replace rows and content-less reads legitimately
122
+ // carry a CAS but no content.
123
+ if (cas_.value() == 0U) {
124
+ throw std::system_error(couchbase::errc::common::decoding_failure,
125
+ "transaction get result row is missing a usable CAS (scas or cas)");
126
+ }
111
127
  if (const auto* doc = json.find("doc"); doc != nullptr) {
112
128
  content_ = { core::utils::json::generate_binary(doc->get_object()),
113
129
  codec::codec_flags::json_common_flags };