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
@@ -17,20 +17,17 @@
17
17
 
18
18
  #pragma once
19
19
 
20
- #include <couchbase/build_config.hxx>
21
-
22
20
  #include "core/app_telemetry_meter.hxx"
23
21
  #include "core/config_listener.hxx"
24
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
25
- #include "core/columnar/bootstrap_notification_subscriber.hxx"
26
- #endif
27
22
  #include "core/logger/logger.hxx"
23
+ #include "core/logger/redaction.hxx"
28
24
  #include "core/metrics/meter_wrapper.hxx"
29
25
  #include "core/operations/http_noop.hxx"
30
26
  #include "core/service_type.hxx"
31
27
  #include "core/tls_context_provider.hxx"
32
28
  #include "core/tracing/noop_tracer.hxx"
33
29
  #include "core/tracing/tracer_wrapper.hxx"
30
+ #include "core/utils/movable_function.hxx"
34
31
  #include "http_command.hxx"
35
32
  #include "http_context.hxx"
36
33
  #include "http_session.hxx"
@@ -38,26 +35,22 @@
38
35
 
39
36
  #include <gsl/narrow>
40
37
 
38
+ #include <atomic>
41
39
  #include <chrono>
40
+ #include <exception>
42
41
  #include <optional>
43
42
  #include <queue>
44
43
  #include <random>
44
+ #include <utility>
45
+ #include <vector>
45
46
 
46
47
  namespace couchbase::core::io
47
48
  {
48
49
 
49
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
50
- class http_session_manager
51
- : public std::enable_shared_from_this<http_session_manager>
52
- , public config_listener
53
- , public columnar::bootstrap_notification_subscriber
54
- {
55
- #else
56
50
  class http_session_manager
57
51
  : public std::enable_shared_from_this<http_session_manager>
58
52
  , public config_listener
59
53
  {
60
- #endif
61
54
  public:
62
55
  http_session_manager(std::string client_id,
63
56
  asio::io_context& ctx,
@@ -75,6 +68,11 @@ public:
75
68
  tracer_ = std::move(tracer);
76
69
  }
77
70
 
71
+ [[nodiscard]] auto tracer() const -> std::shared_ptr<tracing::tracer_wrapper>
72
+ {
73
+ return tracer_;
74
+ }
75
+
78
76
  void set_meter(std::shared_ptr<metrics::meter_wrapper> meter)
79
77
  {
80
78
  meter_ = std::move(meter);
@@ -91,33 +89,11 @@ public:
91
89
  return config_.capabilities;
92
90
  }
93
91
 
94
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
95
- void notify_bootstrap_error(const impl::bootstrap_error& error) override
96
- {
97
- CB_LOG_DEBUG("Received bootstrap error notification. code={}, ec_message={}, message={}, "
98
- "allow_fast_fail={}.",
99
- error.ec.value(),
100
- error.ec.message(),
101
- error.error_message,
102
- allow_fast_fail_ ? "true" : "false");
103
- if (allow_fast_fail_) {
104
- std::scoped_lock bootstrap_error_lock(last_bootstrap_error_mutex_);
105
- last_bootstrap_error_ = error;
106
- drain_deferred_queue(last_bootstrap_error_.value());
107
- }
108
- }
109
-
110
- void notify_bootstrap_success(const std::string& session_id) override
111
- {
112
- CB_LOG_DEBUG("Received successful bootstrap notification. Session={}.", session_id);
113
- std::scoped_lock bootstrap_error_lock(last_bootstrap_error_mutex_);
114
- allow_fast_fail_ = false;
115
- last_bootstrap_error_.reset();
116
- }
117
- #endif
118
-
119
92
  void update_config(topology::configuration config) override
120
93
  {
94
+ // Idle sessions to nodes that have left the cluster, collected under the lock
95
+ // and stopped afterwards (stop() re-enters sessions_mutex_ via on_stop).
96
+ std::vector<std::shared_ptr<http_session>> evicted;
121
97
  {
122
98
  std::scoped_lock config_lock(config_mutex_, sessions_mutex_, next_index_mutex_);
123
99
  config_ = std::move(config);
@@ -125,26 +101,28 @@ public:
125
101
  next_index_ = 0;
126
102
  }
127
103
  for (auto& [type, sessions] : idle_sessions_) {
128
- sessions.remove_if([&opts = options_, &cfg = config_](const auto& session) {
129
- return session && !cfg.has_node(opts.network,
130
- session->type(),
131
- opts.enable_tls,
132
- session->hostname(),
133
- session->port());
104
+ sessions.remove_if([&opts = options_, &cfg = config_, &evicted](const auto& session) {
105
+ if (session && !cfg.has_node(opts.network,
106
+ session->type(),
107
+ opts.enable_tls,
108
+ session->hostname(),
109
+ session->port())) {
110
+ evicted.push_back(session);
111
+ return true;
112
+ }
113
+ return false;
134
114
  });
135
115
  }
136
116
  }
137
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
138
- drain_deferred_queue({});
139
- #endif
140
- }
141
-
142
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
143
- void set_dispatch_timeout(const std::chrono::milliseconds timeout)
144
- {
145
- dispatch_timeout_ = timeout;
117
+ // Tear the evicted connections down now instead of leaving them open until
118
+ // the idle timer fires; the node is gone, so the socket is dead weight (and,
119
+ // against a proxy that has already dropped the far side, a stale handle).
120
+ for (const auto& session : evicted) {
121
+ asio::post(session->get_executor(), [session]() {
122
+ session->stop();
123
+ });
124
+ }
146
125
  }
147
- #endif
148
126
 
149
127
  void set_configuration(const topology::configuration& config, const cluster_options& options)
150
128
  {
@@ -160,18 +138,7 @@ public:
160
138
  options_ = options;
161
139
  next_index_ = next_index;
162
140
  config_ = config;
163
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
164
- if (!configured_) {
165
- configured_ = true;
166
- }
167
- if (allow_fast_fail_) {
168
- allow_fast_fail_ = false;
169
- }
170
- #endif
171
141
  }
172
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
173
- drain_deferred_queue({});
174
- #endif
175
142
  }
176
143
 
177
144
  void export_diag_info(diag::diagnostics_result& res)
@@ -218,31 +185,26 @@ public:
218
185
  const auto& hostname = node.hostname_for(options_.network);
219
186
  const auto& canonical_hostname = node.hostname;
220
187
  std::uint16_t canonical_port = node.port_or(type, options_.enable_tls, 0);
221
- auto session = create_session(type,
222
- node_details{
223
- hostname,
224
- port,
225
- node.node_uuid,
226
- canonical_hostname,
227
- canonical_port,
228
- });
229
- if (session->is_connected()) {
230
- std::scoped_lock lock(sessions_mutex_);
231
- busy_sessions_[type].push_back(session);
188
+ // Created and listed pending in one section, as check_out does, so a close() either
189
+ // stops it or precedes it. Unlisted while its connect is pending, it would be invisible
190
+ // to a close() whose completion lets the io_context stop.
191
+ std::shared_ptr<http_session> session;
192
+ {
193
+ const std::scoped_lock lock(sessions_mutex_);
194
+ session = create_session(type,
195
+ node_details{
196
+ hostname,
197
+ port,
198
+ node.node_uuid,
199
+ canonical_hostname,
200
+ canonical_port,
201
+ },
202
+ generation_);
203
+ pending_sessions_[type].push_back(session);
232
204
  }
233
205
  operations::http_noop_request request{};
234
206
  request.type = type;
235
207
  request.timeout = timeout;
236
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
237
- auto cmd = std::make_shared<operations::http_command<operations::http_noop_request>>(
238
- ctx_,
239
- request,
240
- tracer_,
241
- meter_,
242
- app_telemetry_meter_,
243
- options_.default_timeout_for(request.type),
244
- dispatch_timeout_);
245
- #else
246
208
  auto cmd = std::make_shared<operations::http_command<operations::http_noop_request>>(
247
209
  ctx_,
248
210
  request,
@@ -250,12 +212,13 @@ public:
250
212
  meter_,
251
213
  app_telemetry_meter_,
252
214
  options_.default_timeout_for(request.type));
253
- #endif
215
+ cmd->set_command_session(session);
254
216
  cmd->start(
255
217
  [start = std::chrono::steady_clock::now(),
256
218
  self = shared_from_this(),
257
219
  type,
258
- cmd,
220
+ session,
221
+ weak_cmd = std::weak_ptr(cmd),
259
222
  handler = collector->build_reporter()](operations::http_noop_response&& resp) {
260
223
  diag::ping_state state = diag::ping_state::ok;
261
224
  std::optional<std::string> error{};
@@ -271,27 +234,27 @@ public:
271
234
  ec.message(),
272
235
  resp.ctx.http_status));
273
236
  }
274
- auto remote_address = cmd->session_->remote_address();
237
+ auto remote_address = session->remote_address();
275
238
  // If not connected, the remote address will be empty. Better to
276
239
  // give the user some context on the "attempted" remote address.
277
240
  if (remote_address.empty()) {
278
- remote_address =
279
- fmt::format("{}:{}", cmd->session_->hostname(), cmd->session_->port());
241
+ remote_address = fmt::format("{}:{}", session->hostname(), session->port());
280
242
  }
281
243
  handler->report(
282
244
  diag::endpoint_ping_info{ type,
283
- cmd->session_->id(),
245
+ session->id(),
284
246
  std::chrono::duration_cast<std::chrono::microseconds>(
285
247
  std::chrono::steady_clock::now() - start),
286
248
  remote_address,
287
- cmd->session_->local_address(),
249
+ session->local_address(),
288
250
  state,
289
251
  {},
290
252
  error });
291
- self->check_in(type, cmd->session_);
253
+ if (const auto cmd = weak_cmd.lock(); cmd) {
254
+ self->check_in(type, cmd->session_for_check_in());
255
+ }
292
256
  });
293
257
 
294
- cmd->set_command_session(session);
295
258
  if (!session->is_connected()) {
296
259
  connect_then_send(session, cmd, {}, true);
297
260
  } else {
@@ -318,9 +281,8 @@ public:
318
281
  node_details preferred_node{};
319
282
 
320
283
  if (preferred_node_address.empty() && !undesired_node_address.empty()) {
321
- // This is currently only used by Columnar to avoid the node
322
- // that was last used and to satisfy the requirement for retrying on a different randomly
323
- // selected node (as opposed to any node we have an idle session to).
284
+ // No sticky node, but one to avoid. Retrying elsewhere requires a different node chosen
285
+ // at random, not merely any node this manager happens to hold an idle session to.
324
286
  if (auto n = pick_random_node(type, undesired_node_address); n.port != 0) {
325
287
  preferred_node_address = fmt::format("{}:{}", n.hostname, n.port);
326
288
  preferred_node = std::move(n);
@@ -344,41 +306,26 @@ public:
344
306
  return !s;
345
307
  });
346
308
  std::shared_ptr<http_session> session{};
347
- while (!idle_sessions_[type].empty()) {
348
- if (preferred_node_address.empty()) {
349
- session = idle_sessions_[type].front();
350
- idle_sessions_[type].pop_front();
351
- if (session->reset_idle()) {
352
- break;
353
- }
354
- } else {
355
- auto ptr = std::find_if(idle_sessions_[type].begin(),
356
- idle_sessions_[type].end(),
357
- [&preferred_node_address,
358
- &h = preferred_node.hostname,
359
- &p = preferred_node.port](const auto& s) {
360
- // Check for a match using both the unresolved hostname & IP
361
- // address
362
- return (s->remote_address() == preferred_node_address ||
363
- (s->hostname() == h && s->port() == std::to_string(p)));
364
- });
365
- if (ptr != idle_sessions_[type].end()) {
366
- session = *ptr;
367
- idle_sessions_[type].erase(ptr);
368
- if (session->reset_idle()) {
369
- break;
370
- }
371
- } else {
372
- session = create_session(type, preferred_node);
373
- break;
374
- }
375
- }
376
- CB_LOG_TRACE(
377
- "{} Idle timer has expired for \"{}:{}\". Attempting to select another session.",
378
- session->log_prefix(),
379
- session->hostname(),
380
- session->port());
381
- session.reset();
309
+ // Only a reusable session leaves the idle list. One that is not has a stop queued, by its idle
310
+ // timer or by retire_sessions(), and stays listed until on_stop unlists it, so a close() before
311
+ // then still stops it and waits for it.
312
+ auto& idle = idle_sessions_[type];
313
+ const auto taken = std::find_if(
314
+ idle.begin(),
315
+ idle.end(),
316
+ [&preferred_node_address, &h = preferred_node.hostname, &p = preferred_node.port](
317
+ const auto& s) {
318
+ // Check for a match using both the unresolved hostname & IP address
319
+ const bool matches = preferred_node_address.empty() ||
320
+ s->remote_address() == preferred_node_address ||
321
+ (s->hostname() == h && s->port() == std::to_string(p));
322
+ return matches && is_reusable(*s);
323
+ });
324
+ if (taken != idle.end()) {
325
+ session = *taken;
326
+ idle.erase(taken);
327
+ } else if (!preferred_node_address.empty()) {
328
+ session = create_session(type, preferred_node, generation_);
382
329
  }
383
330
  if (!session) {
384
331
  auto node = preferred_node_address.empty() ? next_node(type)
@@ -386,7 +333,7 @@ public:
386
333
  if (node.port == 0) {
387
334
  return { errc::common::service_not_available, nullptr };
388
335
  }
389
- session = create_session(type, node);
336
+ session = create_session(type, node, generation_);
390
337
  }
391
338
  if (session->is_connected()) {
392
339
  busy_sessions_[type].push_back(session);
@@ -415,17 +362,48 @@ public:
415
362
  return;
416
363
  }
417
364
  bool should_stop = false;
418
- std::chrono::milliseconds idle_timeout{};
419
365
  {
420
- std::scoped_lock lock(config_mutex_);
421
- if (!session->keep_alive() || !config_.has_node(options_.network,
422
- session->type(),
423
- options_.enable_tls,
424
- session->hostname(),
425
- session->port())) {
366
+ // config_mutex_ guards config_, which set_configuration() writes without sessions_mutex_.
367
+ // sessions_mutex_ in the same section keeps update_config()'s eviction, which holds both,
368
+ // from landing between the has_node test and the publication: that would pool a connection
369
+ // to a node that has left the cluster. check_in must therefore never be called with
370
+ // sessions_mutex_ held.
371
+ const std::scoped_lock lock(config_mutex_, sessions_mutex_);
372
+ // stopped_ is set before stop()'s teardown runs on_stop, whose cleanup takes
373
+ // sessions_mutex_. Read under that lock, either the stop is seen here, or its cleanup runs
374
+ // after the publication below and removes the session again.
375
+ //
376
+ // stopping_ is a stop the session's owner has decided and not yet executed, claimed before
377
+ // this check_in can run: by a streaming body under its own mutex while reading_complete_ is
378
+ // false (http_session::read_some runs the body's completion, which sets it, before the
379
+ // stream-end handler).
380
+ if (session->is_stopped() || session->is_stopping()) {
381
+ return;
382
+ }
383
+ if (session->pool_generation() != generation_ || session->tls_epoch() != tls_epoch_ ||
384
+ !session->keep_alive() ||
385
+ !config_.has_node(options_.network,
386
+ session->type(),
387
+ options_.enable_tls,
388
+ session->hostname(),
389
+ session->port())) {
426
390
  should_stop = true;
427
391
  } else {
428
- idle_timeout = options_.idle_http_connection_timeout;
392
+ // Under sessions_mutex_, as check_out's reset_idle() is. set_idle() binds the timer
393
+ // completion to the strand and never calls stop() inline, so on_stop cannot re-enter
394
+ // sessions_mutex_ here.
395
+ session->set_idle(options_.idle_http_connection_timeout);
396
+ idle_sessions_[type].push_back(session);
397
+ if (auto busy_it = busy_sessions_.find(type); busy_it != busy_sessions_.end()) {
398
+ busy_it->second.remove_if([id = session->id()](const auto& s) -> bool {
399
+ return !s || s->id() == id;
400
+ });
401
+ }
402
+ if (auto pend_it = pending_sessions_.find(type); pend_it != pending_sessions_.end()) {
403
+ pend_it->second.remove_if([id = session->id()](const auto& s) -> bool {
404
+ return !s || s->id() == id;
405
+ });
406
+ }
429
407
  }
430
408
  }
431
409
  if (should_stop) {
@@ -433,72 +411,107 @@ public:
433
411
  session->stop();
434
412
  });
435
413
  }
436
- if (!session->is_stopped()) {
437
- // set_idle() arms the idle timer via async_wait — it never calls stop() inline,
438
- // so on_stop cannot re-enter sessions_mutex_ here. It must precede publication to
439
- // idle_sessions_ so a concurrent check_out's reset_idle() finds a pending timer.
440
- session->set_idle(idle_timeout);
441
- CB_LOG_DEBUG("{} put HTTP session back to idle connections", session->log_prefix());
442
- std::scoped_lock lock(sessions_mutex_);
443
- idle_sessions_[type].push_back(session);
444
- if (auto busy_it = busy_sessions_.find(type); busy_it != busy_sessions_.end()) {
445
- busy_it->second.remove_if([id = session->id()](const auto& s) -> bool {
446
- return !s || s->id() == id;
447
- });
448
- }
449
- if (auto pend_it = pending_sessions_.find(type); pend_it != pending_sessions_.end()) {
450
- pend_it->second.remove_if([id = session->id()](const auto& s) -> bool {
451
- return !s || s->id() == id;
452
- });
414
+ CB_LOG_DEBUG("{} put HTTP session back to idle connections", session->log_prefix());
415
+ }
416
+
417
+ // Stops the reuse of every session created before the call, for a credential that the connection
418
+ // carries, such as a client certificate. Idle sessions stop now. A busy or connecting session
419
+ // completes its request first; check_in then stops it, because its TLS epoch is stale.
420
+ void retire_sessions()
421
+ {
422
+ std::vector<std::shared_ptr<http_session>> retired;
423
+ {
424
+ const std::scoped_lock lock(sessions_mutex_);
425
+ ++tls_epoch_;
426
+ // Claimed but left listed until their stop runs, so a close() before then stops them and
427
+ // waits for them. check_out skips a claimed session, and on_stop unlists it.
428
+ for (const auto& [type, sessions] : idle_sessions_) {
429
+ for (const auto& s : sessions) {
430
+ if (s) {
431
+ s->mark_stopping();
432
+ retired.push_back(s);
433
+ }
434
+ }
453
435
  }
454
436
  }
437
+ // Stopped on its own strand, as close() does.
438
+ for (auto& s : retired) {
439
+ asio::post(s->get_executor(), [s]() {
440
+ s->stop();
441
+ });
442
+ }
455
443
  }
456
444
 
457
- void close()
445
+ // on_stopped runs once every session listed at the call has been stopped and holds no
446
+ // read_some() call, on the strand of the last one, or before close() returns when none is listed.
447
+ // Until then the read_some() calls queued on a session hold the body that holds the session, and
448
+ // its on_stop cleanup holds this manager. Whoever stops the io_context must wait for it: a
449
+ // handler still queued then never runs, and the session, its body and this manager are never
450
+ // released. A do_read() the stop aborts is not waited for: its completion holds only the session,
451
+ // which destroying the io_context releases.
452
+ void close(utils::movable_function<void()> on_stopped = {})
458
453
  {
459
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
460
- drain_deferred_queue(errc::common::request_canceled);
461
- #endif
462
454
  std::map<service_type, std::list<std::shared_ptr<http_session>>> busy_sessions, idle_sessions,
463
455
  pending_sessions;
464
456
  {
457
+ // A new generation starts with the lists moved out. check_in and publish_busy refuse a
458
+ // session from an earlier one, so none is listed again; a connect callback stops its session
459
+ // instead. check_out creates sessions in the new generation.
465
460
  const std::scoped_lock lock(sessions_mutex_);
466
- busy_sessions = std::move(busy_sessions_);
467
- idle_sessions = std::move(idle_sessions_);
468
- pending_sessions = std::move(pending_sessions_);
469
- }
470
- for (auto& [type, sessions] : idle_sessions) {
471
- for (auto& s : sessions) {
472
- if (s) {
473
- s->reset_idle();
474
- s.reset();
461
+ ++generation_;
462
+ // Exchanged rather than moved: the manager goes on serving, and a moved-from map is only
463
+ // valid, not empty.
464
+ busy_sessions = std::exchange(busy_sessions_, {});
465
+ idle_sessions = std::exchange(idle_sessions_, {});
466
+ pending_sessions = std::exchange(pending_sessions_, {});
467
+ }
468
+ // Every session is stopped on its own strand, as check_in and update_config() do. stop()
469
+ // closes the stream and cancels the response, which the strand's read and write handlers use.
470
+ // Run on this thread, it races those handlers, and a queued do_write() finds no socket. An
471
+ // idle session is stopped rather than only reset_idle()d, so the read armed by set_idle() is
472
+ // torn down and its shared_ptr does not keep the io_context from draining.
473
+ std::vector<std::shared_ptr<http_session>> stopping;
474
+ for (auto* lists : { &idle_sessions, &busy_sessions, &pending_sessions }) {
475
+ for (auto& [type, sessions] : *lists) {
476
+ for (auto& s : sessions) {
477
+ if (s) {
478
+ stopping.emplace_back(std::move(s));
479
+ }
475
480
  }
476
481
  }
477
482
  }
478
- for (auto& [type, sessions] : busy_sessions) {
479
- for (auto& s : sessions) {
480
- if (s) {
481
- s->stop();
482
- }
483
+ if (stopping.empty()) {
484
+ if (on_stopped) {
485
+ on_stopped();
483
486
  }
487
+ return;
484
488
  }
485
- for (auto& [type, sessions] : pending_sessions) {
486
- for (auto& s : sessions) {
487
- if (s) {
489
+ auto remaining = std::make_shared<std::atomic_size_t>(stopping.size());
490
+ auto done = std::make_shared<utils::movable_function<void()>>(std::move(on_stopped));
491
+ for (auto& s : stopping) {
492
+ asio::post(s->get_executor(), [s, remaining, done]() {
493
+ // A stop() that throws has still torn the session down, so its reads still drain.
494
+ std::exception_ptr error{};
495
+ try {
488
496
  s->stop();
497
+ } catch (...) {
498
+ error = std::current_exception();
489
499
  }
490
- }
500
+ s->on_reads_drained([remaining, done]() {
501
+ if (remaining->fetch_sub(1) == 1 && *done) {
502
+ (*done)();
503
+ }
504
+ });
505
+ if (error) {
506
+ std::rethrow_exception(error);
507
+ }
508
+ });
491
509
  }
492
510
  }
493
511
 
494
512
  template<typename Request, typename Handler>
495
513
  void execute(Request request, Handler&& handler)
496
514
  {
497
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
498
- if (!configured_) {
499
- return defer_command(request, std::move(handler));
500
- }
501
- #endif
502
515
  std::string preferred_node;
503
516
  if constexpr (http_traits::supports_sticky_node_v<Request>) {
504
517
  if (request.send_to_node) {
@@ -513,16 +526,6 @@ public:
513
526
  return handler(request.make_response(std::move(ctx), encoded_response_type{}));
514
527
  }
515
528
 
516
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
517
- auto cmd = std::make_shared<operations::http_command<Request>>(
518
- ctx_,
519
- request,
520
- tracer_,
521
- meter_,
522
- app_telemetry_meter_,
523
- options_.default_timeout_for(request.type),
524
- dispatch_timeout_);
525
- #else
526
529
  auto cmd = std::make_shared<operations::http_command<Request>>(
527
530
  ctx_,
528
531
  request,
@@ -530,15 +533,15 @@ public:
530
533
  meter_,
531
534
  app_telemetry_meter_,
532
535
  options_.default_timeout_for(request.type));
533
- #endif
534
536
 
535
537
  using response_type = typename Request::response_type;
538
+ // Before start(), so a deadline that fires at once still reports the session's endpoint.
539
+ cmd->set_command_session(session);
536
540
  cmd->start([self = shared_from_this(), cmd, handler = std::forward<Handler>(handler)](
537
541
  response_type&& resp) mutable {
538
542
  handler(std::move(resp));
539
- self->check_in(cmd->request.type, cmd->session_);
543
+ self->check_in(cmd->request.type, cmd->session_for_check_in());
540
544
  });
541
- cmd->set_command_session(session);
542
545
  if (!session->is_connected()) {
543
546
  connect_then_send(session, cmd, preferred_node);
544
547
  } else {
@@ -546,49 +549,34 @@ public:
546
549
  }
547
550
  }
548
551
 
549
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
550
552
  void connect_then_send_pending_op(
551
553
  std::shared_ptr<http_session> session,
552
554
  const std::string& preferred_node,
553
- std::chrono::time_point<std::chrono::steady_clock> dispatch_deadline,
554
555
  std::chrono::time_point<std::chrono::steady_clock> deadline,
555
556
  utils::movable_function<void(std::error_code, std::shared_ptr<http_session>)> callback)
556
- #else
557
- void connect_then_send_pending_op(
558
- std::shared_ptr<http_session> session,
559
- const std::string& preferred_node,
560
- std::chrono::time_point<std::chrono::steady_clock> deadline,
561
- utils::movable_function<void(std::error_code, std::shared_ptr<http_session>)> callback)
562
- #endif
563
557
  {
564
558
  session->connect([self = shared_from_this(),
565
559
  session,
566
560
  preferred_node,
567
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
568
- dispatch_deadline,
569
- #endif
570
561
  deadline,
571
562
  cb = std::move(callback)]() mutable {
563
+ // stop() runs this callback, from close() among others. A stopped session is terminal: a
564
+ // replacement would open a socket for an operation the stop has ended.
565
+ if (session->is_stopped()) {
566
+ return cb(errc::common::request_canceled, {});
567
+ }
568
+ // A session from before a close() gets no replacement and no reconnect: close() may not reach
569
+ // it, so it is stopped here.
570
+ if (self->is_retired(*session)) {
571
+ session->stop();
572
+ return cb(errc::common::request_canceled, {});
573
+ }
572
574
  if (!session->is_connected()) {
573
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
574
- auto now = std::chrono::steady_clock::now();
575
- if (session->is_stopped()) {
576
- // Session was forced to stop (e.g. due to cluster being closed or cancellation)
577
- cb(errc::common::request_canceled, {});
578
- return;
579
- }
580
- if (dispatch_deadline < now || deadline < now) {
581
- session->stop();
582
- cb(errc::common::unambiguous_timeout, {});
583
- return;
584
- }
585
- #else
586
575
  if (deadline < std::chrono::steady_clock::now()) {
587
576
  session->stop();
588
577
  return cb(errc::common::unambiguous_timeout, {});
589
578
  return;
590
579
  }
591
- #endif
592
580
 
593
581
  // stop this session and create a new one w/ new hostname + port
594
582
  session->stop();
@@ -599,76 +587,34 @@ public:
599
587
  cb(errc::common::service_not_available, {});
600
588
  return;
601
589
  }
602
- auto new_session = self->create_session(session->type(), node);
590
+ auto new_session = self->create_replacement(session->type(), node, *session);
591
+ if (!new_session) {
592
+ return cb(errc::common::request_canceled, {});
593
+ }
603
594
  if (new_session->is_connected()) {
604
- {
605
- const std::scoped_lock inner_lock(self->sessions_mutex_);
606
- self->busy_sessions_[new_session->type()].push_back(new_session);
595
+ if (!self->publish_busy(new_session, /* leaving_pending */ true)) {
596
+ new_session->stop();
597
+ return cb(errc::common::request_canceled, {});
607
598
  }
608
599
  cb({}, new_session);
609
600
  } else {
610
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
611
- {
612
- const std::scoped_lock inner_lock(self->sessions_mutex_);
613
- self->pending_sessions_[new_session->type()].push_back(new_session);
614
- }
615
- self->connect_then_send_pending_op(
616
- new_session, preferred_node, dispatch_deadline, deadline, cb);
617
- #else
618
- self->connect_then_send_pending_op(new_session, preferred_node, deadline, cb);
619
- #endif
601
+ self->connect_then_send_pending_op(new_session, preferred_node, deadline, std::move(cb));
620
602
  }
621
603
  } else {
622
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
623
- auto now = std::chrono::steady_clock::now();
624
- if (dispatch_deadline < now || deadline < now) {
625
- session->stop();
626
- cb(errc::common::unambiguous_timeout, {});
627
- return;
628
- }
629
- #else
630
604
  if (deadline < std::chrono::steady_clock::now()) {
631
605
  session->stop();
632
606
  cb(errc::common::unambiguous_timeout, {});
633
607
  return;
634
608
  }
635
- #endif
636
- {
637
- const std::scoped_lock inner_lock(self->sessions_mutex_);
638
- self->busy_sessions_[session->type()].push_back(session);
639
- self->pending_sessions_[session->type()].remove_if(
640
- [id = session->id()](const auto& s) -> bool {
641
- return !s || s->id() == id;
642
- });
609
+ if (!self->publish_busy(session, /* leaving_pending */ true)) {
610
+ session->stop();
611
+ return cb(errc::common::request_canceled, {});
643
612
  }
644
613
  cb({}, session);
645
614
  }
646
615
  });
647
616
  }
648
617
 
649
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
650
- void add_to_deferred_queue(utils::movable_function<void(error_union)> command)
651
- {
652
- const std::scoped_lock lock_for_deferred_commands(deferred_commands_mutex_);
653
- deferred_commands_.emplace(std::move(command));
654
- }
655
-
656
- [[nodiscard]] auto dispatch_timeout() const -> std::chrono::milliseconds
657
- {
658
- return dispatch_timeout_;
659
- }
660
-
661
- [[nodiscard]] auto is_configured() const -> bool
662
- {
663
- return configured_;
664
- }
665
-
666
- [[nodiscard]] auto last_bootstrap_error() const -> std::optional<impl::bootstrap_error>
667
- {
668
- return last_bootstrap_error_;
669
- }
670
- #endif
671
-
672
618
  private:
673
619
  template<typename Request>
674
620
  void connect_then_send(std::shared_ptr<http_session> session,
@@ -681,19 +627,27 @@ private:
681
627
  cmd,
682
628
  preferred_node = std::move(preferred_node),
683
629
  reuse_session]() mutable {
630
+ // stop() runs this callback, from close() among others. A stopped session is terminal: a
631
+ // replacement would open a socket and send a request the stop has ended, and with
632
+ // reuse_session the stopped session's connect() would run this callback again inline, an
633
+ // unbounded recursion.
634
+ if (session->is_stopped()) {
635
+ return cmd->invoke_handler(errc::common::request_canceled, {});
636
+ }
637
+ // A session from before a close() gets no replacement and no reconnect: close() may not reach
638
+ // it, so it is stopped here.
639
+ if (self->is_retired(*session)) {
640
+ session->stop();
641
+ return cmd->invoke_handler(errc::common::request_canceled, {});
642
+ }
684
643
  if (!session->is_connected()) {
685
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
686
- auto now = std::chrono::steady_clock::now();
687
- if (cmd->dispatch_deadline_expiry() < now || cmd->deadline_expiry() < now) {
688
- // The http command will stop its session when the deadline expires.
689
- return;
690
- }
691
- #else
692
644
  if (cmd->deadline_expiry() < std::chrono::steady_clock::now()) {
693
- // The http command will stop its session when the deadline expires.
645
+ // The command's deadline completes it, but stops only the session it held when it
646
+ // fired, which may be the one this replaced. Stopped here, a replacement does not stay
647
+ // listed pending until close().
648
+ session->stop();
694
649
  return;
695
650
  }
696
- #endif
697
651
  if (reuse_session) {
698
652
  return self->connect_then_send(session, cmd, preferred_node, reuse_session);
699
653
  }
@@ -706,25 +660,97 @@ private:
706
660
  cmd->invoke_handler(errc::common::service_not_available, {});
707
661
  return;
708
662
  }
709
- auto new_session = self->create_session(session->type(), node);
663
+ auto new_session = self->create_replacement(session->type(), node, *session);
664
+ if (!new_session) {
665
+ return cmd->invoke_handler(errc::common::request_canceled, {});
666
+ }
710
667
  cmd->set_command_session(new_session);
711
668
  if (new_session->is_connected()) {
712
- std::scoped_lock inner_lock(self->sessions_mutex_);
713
- self->busy_sessions_[new_session->type()].push_back(new_session);
669
+ if (!self->publish_busy(new_session, /* leaving_pending */ true)) {
670
+ new_session->stop();
671
+ return cmd->invoke_handler(errc::common::request_canceled, {});
672
+ }
714
673
  cmd->send_to();
715
674
  } else {
716
675
  self->connect_then_send(new_session, cmd, preferred_node);
717
676
  }
718
677
  } else {
719
- std::scoped_lock inner_lock(self->sessions_mutex_);
720
- self->busy_sessions_[session->type()].push_back(session);
678
+ if (!self->publish_busy(session, /* leaving_pending */ true)) {
679
+ session->stop();
680
+ return cmd->invoke_handler(errc::common::request_canceled, {});
681
+ }
682
+ // Outside sessions_mutex_: send_to() can complete the command inline (an encode_to
683
+ // failure, or a session already stopped), and the completion calls check_in, which takes
684
+ // it.
721
685
  cmd->send_to();
722
686
  }
723
687
  });
724
688
  }
725
689
 
726
- auto create_session(service_type type, const node_details& node) -> std::shared_ptr<http_session>
690
+ // Lists a connected session as busy unless its stop() has begun or it is from before a close(),
691
+ // and reports whether it did. stop() sets stopped_ before on_stop's cleanup takes
692
+ // sessions_mutex_, so a stop is either seen here or removes the session after it is listed.
693
+ // Tested outside the lock, a stop landing in between would list a session whose cleanup has
694
+ // already run. A refused session is the caller's to stop: close() does not reach one it never had
695
+ // in a list.
696
+ auto publish_busy(const std::shared_ptr<http_session>& session, bool leaving_pending = false)
697
+ -> bool
698
+ {
699
+ const std::scoped_lock lock(sessions_mutex_);
700
+ if (session->pool_generation() != generation_ || session->is_stopped()) {
701
+ return false;
702
+ }
703
+ busy_sessions_[session->type()].push_back(session);
704
+ if (leaving_pending) {
705
+ pending_sessions_[session->type()].remove_if([id = session->id()](const auto& s) -> bool {
706
+ return !s || s->id() == id;
707
+ });
708
+ }
709
+ return true;
710
+ }
711
+
712
+ auto current_generation() -> std::uint64_t
713
+ {
714
+ const std::scoped_lock lock(sessions_mutex_);
715
+ return generation_;
716
+ }
717
+
718
+ // Serves a generation that a close() has ended.
719
+ auto is_retired(const http_session& session) -> bool
720
+ {
721
+ return session.pool_generation() != current_generation();
722
+ }
723
+
724
+ // A popped idle session goes back into service only if nothing has claimed it and reset_idle()
725
+ // accepts it, which it refuses once the idle timer has fired or a stop() has begun.
726
+ static auto is_reusable(http_session& session) -> bool
727
+ {
728
+ return !session.is_stopping() && session.reset_idle();
729
+ }
730
+
731
+ // A failover replacement for `failed`. It serves the same operation, so it belongs to the same
732
+ // generation, and it is listed pending from creation: close() either stops it or has already
733
+ // retired that generation, and then the replacement is discarded and nullptr returned. Unlisted
734
+ // until its connect completes, it would be invisible to a close() whose completion lets the
735
+ // io_context stop.
736
+ auto create_replacement(service_type type, const node_details& node, const http_session& failed)
737
+ -> std::shared_ptr<http_session>
738
+ {
739
+ auto session = create_session(type, node, failed.pool_generation());
740
+ const std::scoped_lock lock(sessions_mutex_);
741
+ if (session->pool_generation() != generation_) {
742
+ return nullptr;
743
+ }
744
+ pending_sessions_[type].push_back(session);
745
+ return session;
746
+ }
747
+
748
+ auto create_session(service_type type, const node_details& node, std::uint64_t generation)
749
+ -> std::shared_ptr<http_session>
727
750
  {
751
+ // Read before the session takes the TLS context. retire_sessions() runs after the context is
752
+ // replaced, so this epoch is never newer than the context.
753
+ const std::uint64_t tls_epoch = tls_epoch_;
728
754
  std::shared_ptr<http_session> session;
729
755
  if (options_.enable_tls) {
730
756
  session = std::make_shared<http_session>(type,
@@ -743,7 +769,8 @@ private:
743
769
  node.port,
744
770
  node.canonical_hostname,
745
771
  node.canonical_port,
746
- });
772
+ },
773
+ generation);
747
774
  } else {
748
775
  session = std::make_shared<http_session>(type,
749
776
  client_id_,
@@ -760,7 +787,8 @@ private:
760
787
  node.port,
761
788
  node.canonical_hostname,
762
789
  node.canonical_port,
763
- });
790
+ },
791
+ generation);
764
792
  }
765
793
 
766
794
  session->on_stop([type, id = session->id(), self = this->shared_from_this()]() {
@@ -789,87 +817,10 @@ private:
789
817
  }
790
818
  }
791
819
  });
820
+ session->set_tls_epoch(tls_epoch);
792
821
  return session;
793
822
  }
794
823
 
795
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
796
- template<typename Request, typename Handler>
797
- void defer_command(Request request, Handler&& handler)
798
- {
799
- {
800
- std::scoped_lock bootstrap_error_lock(last_bootstrap_error_mutex_);
801
- if (last_bootstrap_error_.has_value()) {
802
- typename Request::error_context_type ctx{};
803
- ctx.ec = last_bootstrap_error_.value().ec;
804
- using response_type = typename Request::encoded_response_type;
805
- return handler(request.make_response(std::move(ctx), response_type{}));
806
- }
807
- }
808
- auto cmd = std::make_shared<operations::http_command<Request>>(
809
- ctx_,
810
- request,
811
- tracer_,
812
- meter_,
813
- app_telemetry_meter_,
814
- options_.default_timeout_for(request.type),
815
- dispatch_timeout_);
816
- using response_type = typename Request::response_type;
817
- cmd->start([self = shared_from_this(), cmd, handler = std::forward<Handler>(handler)](
818
- response_type&& resp) mutable {
819
- handler(std::move(resp));
820
- self->check_in(cmd->request.type, cmd->session_);
821
- });
822
- CB_LOG_DEBUG(R"(Adding HTTP request to deferred queue: {}, client_context_id="{}")",
823
- cmd->request.type,
824
- cmd->client_context_id_);
825
- add_to_deferred_queue([self = shared_from_this(), cmd, request](error_union err) mutable {
826
- if (!std::holds_alternative<std::monostate>(err)) {
827
- using response_type = typename Request::encoded_response_type;
828
- return cmd->invoke_handler(err, response_type{});
829
- }
830
-
831
- // don't do anything if the command wasn't dispatched or has already timed out
832
- auto now = std::chrono::steady_clock::now();
833
- if (cmd->dispatch_deadline_expiry() < now || cmd->deadline_expiry() < now) {
834
- return;
835
- }
836
- std::string preferred_node;
837
- if constexpr (http_traits::supports_sticky_node_v<Request>) {
838
- if (request.send_to_node) {
839
- preferred_node = *request.send_to_node;
840
- }
841
- }
842
- auto [error, session] = self->check_out(request.type, preferred_node);
843
- if (error) {
844
- using response_type = typename Request::encoded_response_type;
845
- return cmd->invoke_handler(error, response_type{});
846
- }
847
- cmd->set_command_session(session);
848
- if (!session->is_connected()) {
849
- self->connect_then_send(session, cmd, preferred_node);
850
- } else {
851
- cmd->send_to();
852
- }
853
- });
854
- }
855
-
856
- void drain_deferred_queue(error_union err)
857
- {
858
- std::queue<utils::movable_function<void(error_union)>> commands{};
859
- {
860
- const std::scoped_lock lock(deferred_commands_mutex_);
861
- std::swap(deferred_commands_, commands);
862
- }
863
- if (!commands.empty()) {
864
- CB_LOG_TRACE("Draining deferred operation queue, size={}", commands.size());
865
- }
866
- while (!commands.empty()) {
867
- commands.front()(err);
868
- commands.pop();
869
- }
870
- }
871
- #endif
872
-
873
824
  auto next_node(service_type type) -> node_details
874
825
  {
875
826
  std::scoped_lock lock(config_mutex_);
@@ -968,15 +919,11 @@ private:
968
919
  std::size_t next_index_{ 0 };
969
920
  std::mutex next_index_mutex_{};
970
921
  std::mutex sessions_mutex_{};
922
+ // Advanced by close(); guarded by sessions_mutex_. A session carries the generation it serves,
923
+ // and check_in and publish_busy refuse one from an earlier generation.
924
+ std::uint64_t generation_{ 0 };
925
+ // Advanced by retire_sessions(). Unlike generation_, it ends no in-flight request.
926
+ std::atomic<std::uint64_t> tls_epoch_{ 0 };
971
927
  query_cache query_cache_{};
972
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
973
- std::atomic_bool configured_{ false };
974
- std::chrono::milliseconds dispatch_timeout_{};
975
- std::atomic_bool allow_fast_fail_{ true };
976
- std::queue<utils::movable_function<void(error_union)>> deferred_commands_{};
977
- std::mutex deferred_commands_mutex_{};
978
- std::optional<impl::bootstrap_error> last_bootstrap_error_;
979
- std::mutex last_bootstrap_error_mutex_{};
980
- #endif
981
928
  };
982
929
  } // namespace couchbase::core::io