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
@@ -18,6 +18,7 @@
18
18
  #include "http_session.hxx"
19
19
 
20
20
  #include "core/logger/logger.hxx"
21
+ #include "core/logger/redaction.hxx"
21
22
  #include "core/meta/version.hxx"
22
23
  #include "core/platform/base64.h"
23
24
  #include "core/platform/uuid.h"
@@ -25,12 +26,21 @@
25
26
 
26
27
  #include <couchbase/error_codes.hxx>
27
28
 
29
+ #include <asio/ssl/error.hpp>
30
+ #include <gsl/util>
28
31
  #include <spdlog/fmt/bin_to_hex.h>
29
32
 
33
+ #include <exception>
30
34
  #include <utility>
31
35
 
32
36
  namespace couchbase::core::io
33
37
  {
38
+ namespace
39
+ {
40
+ // Between connection attempts, and before a resend after a refused client certificate.
41
+ constexpr std::chrono::milliseconds reconnect_backoff{ 500 };
42
+ } // namespace
43
+
34
44
  http_session_info::http_session_info(const std::string& client_id, const std::string& session_id)
35
45
  : log_prefix_(fmt::format("[{}/{}]", client_id, session_id))
36
46
  {
@@ -61,12 +71,11 @@ http_session_info::http_session_info(const std::string& client_id,
61
71
  fmt::format("{}:{}", remote_endpoint_address_, remote_endpoint_.port());
62
72
  }
63
73
 
64
- log_prefix_ = fmt::format("[{}/{}] <{}:{}:{}>",
65
- client_id,
66
- session_id,
67
- local_endpoint_.port(),
68
- remote_endpoint_.address().to_string(),
69
- remote_endpoint_.port());
74
+ const auto endpoints = fmt::format("{}:{}:{}",
75
+ local_endpoint_.port(),
76
+ remote_endpoint_.address().to_string(),
77
+ remote_endpoint_.port());
78
+ log_prefix_ = fmt::format("[{}/{}] <{}>", client_id, session_id, logger::system_data(endpoints));
70
79
  }
71
80
 
72
81
  auto
@@ -106,11 +115,13 @@ http_session::http_session(couchbase::core::service_type type,
106
115
  origin& origin,
107
116
  std::string hostname,
108
117
  std::string service,
109
- couchbase::core::http_context http_ctx)
118
+ couchbase::core::http_context http_ctx,
119
+ std::uint64_t pool_generation)
110
120
  : type_(type)
111
121
  , client_id_(std::move(client_id))
112
122
  , node_uuid_(std::move(node_uuid))
113
123
  , id_(uuid::to_string(uuid::random()))
124
+ , pool_generation_(pool_generation)
114
125
  , ctx_(ctx)
115
126
  , resolver_(ctx_)
116
127
  , stream_(std::make_unique<plain_stream_impl>(ctx_))
@@ -134,17 +145,19 @@ http_session::http_session(couchbase::core::service_type type,
134
145
  origin& origin,
135
146
  std::string hostname,
136
147
  std::string service,
137
- couchbase::core::http_context http_ctx)
148
+ couchbase::core::http_context http_ctx,
149
+ std::uint64_t pool_generation)
138
150
  : type_(type)
139
151
  , client_id_(std::move(client_id))
140
152
  , node_uuid_(std::move(node_uuid))
141
153
  , id_(uuid::to_string(uuid::random()))
154
+ , pool_generation_(pool_generation)
142
155
  , ctx_(ctx)
143
156
  , resolver_(ctx_)
144
157
  , stream_(std::make_unique<tls_stream_impl>(ctx_, tls))
145
- , connect_deadline_timer_(ctx_)
146
- , idle_timer_(ctx_)
147
- , retry_backoff_(ctx_)
158
+ , connect_deadline_timer_(stream_->get_executor())
159
+ , idle_timer_(stream_->get_executor())
160
+ , retry_backoff_(stream_->get_executor())
148
161
  , origin_(origin)
149
162
  , hostname_(std::move(hostname))
150
163
  , service_(std::move(service))
@@ -185,6 +198,21 @@ http_session::local_address() -> std::string
185
198
  return info_.local_address();
186
199
  }
187
200
 
201
+ auto
202
+ http_session::dispatched_endpoints() -> std::pair<std::string, std::string>
203
+ {
204
+ const std::scoped_lock lock(info_mutex_);
205
+ return { dispatched_from_, dispatched_to_ };
206
+ }
207
+
208
+ void
209
+ http_session::record_dispatch()
210
+ {
211
+ const std::scoped_lock lock(info_mutex_);
212
+ dispatched_from_ = info_.local_address();
213
+ dispatched_to_ = info_.remote_address();
214
+ }
215
+
188
216
  auto
189
217
  http_session::remote_endpoint() -> const asio::ip::tcp::endpoint&
190
218
  {
@@ -195,15 +223,16 @@ http_session::remote_endpoint() -> const asio::ip::tcp::endpoint&
195
223
  auto
196
224
  http_session::diag_info() -> diag::endpoint_diag_info
197
225
  {
226
+ const auto last_active = last_active_.load();
198
227
  return { type_,
199
228
  id_,
200
- last_active_.time_since_epoch().count() == 0
229
+ last_active.time_since_epoch().count() == 0
201
230
  ? std::nullopt
202
231
  : std::make_optional(std::chrono::duration_cast<std::chrono::microseconds>(
203
- std::chrono::steady_clock::now() - last_active_)),
232
+ std::chrono::steady_clock::now() - last_active)),
204
233
  remote_address(),
205
234
  local_address(),
206
- state_ };
235
+ state_.load() };
207
236
  }
208
237
 
209
238
  auto
@@ -219,6 +248,12 @@ http_session::id() const -> const std::string&
219
248
  return id_;
220
249
  }
221
250
 
251
+ auto
252
+ http_session::pool_generation() const -> std::uint64_t
253
+ {
254
+ return pool_generation_;
255
+ }
256
+
222
257
  auto
223
258
  http_session::node_uuid() const -> const std::string&
224
259
  {
@@ -276,26 +311,36 @@ void
276
311
  http_session::initiate_connect()
277
312
  {
278
313
  if (stopped_) {
279
- return;
314
+ // stop() has begun, so nothing connects. Its teardown may already have taken an earlier
315
+ // callback, so the one just stored, which holds this session, is run here. Whichever of this
316
+ // call and the teardown moves it out under connect_callback_mutex_ runs it.
317
+ return invoke_connect_callback();
280
318
  }
281
319
  if (state_ != diag::endpoint_state::connecting) {
282
- CB_LOG_DEBUG(
283
- "{} {}:{} attempt to establish HTTP connection", info_.log_prefix(), hostname_, service_);
320
+ CB_LOG_DEBUG("{} {} attempt to establish HTTP connection",
321
+ info_.log_prefix(),
322
+ logger::system_data(fmt::format("{}:{}", hostname_, service_)));
284
323
  state_ = diag::endpoint_state::connecting;
285
- async_resolve(http_ctx_.options.use_ip_protocol,
286
- resolver_,
287
- hostname_,
288
- service_,
289
- [capture0 = shared_from_this()](auto&& PH1, auto&& PH2) {
290
- capture0->on_resolve(std::forward<decltype(PH1)>(PH1),
291
- std::forward<decltype(PH2)>(PH2));
292
- });
324
+ async_resolve(
325
+ http_ctx_.options.use_ip_protocol,
326
+ resolver_,
327
+ hostname_,
328
+ service_,
329
+ asio::bind_executor(get_executor(), [capture0 = shared_from_this()](auto&& PH1, auto&& PH2) {
330
+ capture0->on_resolve(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2));
331
+ }));
293
332
  } else {
294
333
  // reset state in case the session is being reused
295
334
  state_ = diag::endpoint_state::disconnected;
296
- auto backoff = std::chrono::milliseconds(500);
335
+ auto backoff = reconnect_backoff;
297
336
  CB_LOG_DEBUG(
298
337
  "{} waiting for {}ms before trying to connect", info_.log_prefix(), backoff.count());
338
+ const std::scoped_lock lock(retry_backoff_mutex_);
339
+ // stop() can set stopped_ on another thread after the test at the top of this function.
340
+ // cppcheck-suppress knownConditionTrueFalse
341
+ if (stopped_) {
342
+ return;
343
+ }
299
344
  retry_backoff_.expires_after(backoff);
300
345
  retry_backoff_.async_wait([self = shared_from_this()](std::error_code ec) mutable {
301
346
  if (ec == asio::error::operation_aborted || self->stopped_) {
@@ -332,6 +377,132 @@ http_session::cancel_current_response(std::error_code ec)
332
377
  }
333
378
  }
334
379
 
380
+ void
381
+ http_session::remember_request(bool uses_certificate)
382
+ {
383
+ // Only a session with certificate credentials resends, so no other session keeps a copy.
384
+ const std::scoped_lock lock(output_buffer_mutex_);
385
+ if (fresh_connection_ && uses_certificate) {
386
+ unanswered_request_ = output_buffer_;
387
+ } else {
388
+ unanswered_request_.clear();
389
+ }
390
+ }
391
+
392
+ namespace
393
+ {
394
+ // The alerts a server sends when it refuses the client certificate. OpenSSL and BoringSSL report a
395
+ // received alert as the reason SSL_AD_REASON_OFFSET plus the alert's code.
396
+ auto
397
+ is_refused_certificate(std::error_code ec) -> bool
398
+ {
399
+ if (ec.category() != asio::error::get_ssl_category()) {
400
+ return false;
401
+ }
402
+ // OpenSSL 1.1 defines ERR_GET_REASON() as a macro with a C-style cast; OpenSSL 3.0 and later,
403
+ // and BoringSSL, define it as an inline function.
404
+ #if defined(__GNUC__) || defined(__clang__)
405
+ #pragma GCC diagnostic push
406
+ #pragma GCC diagnostic ignored "-Wold-style-cast"
407
+ #endif
408
+ const int alert = ERR_GET_REASON(static_cast<std::uint32_t>(ec.value())) - SSL_AD_REASON_OFFSET;
409
+ #if defined(__GNUC__) || defined(__clang__)
410
+ #pragma GCC diagnostic pop
411
+ #endif
412
+ switch (alert) {
413
+ case SSL_AD_BAD_CERTIFICATE:
414
+ case SSL_AD_UNSUPPORTED_CERTIFICATE:
415
+ case SSL_AD_CERTIFICATE_REVOKED:
416
+ case SSL_AD_CERTIFICATE_EXPIRED:
417
+ case SSL_AD_CERTIFICATE_UNKNOWN:
418
+ case SSL_AD_UNKNOWN_CA:
419
+ case SSL_AD_ACCESS_DENIED:
420
+ #if defined(SSL_AD_CERTIFICATE_REQUIRED)
421
+ case SSL_AD_CERTIFICATE_REQUIRED:
422
+ #endif
423
+ return true;
424
+ default:
425
+ return false;
426
+ }
427
+ }
428
+ } // namespace
429
+
430
+ // Under TLS 1.3 the server checks the client certificate after the client's handshake has
431
+ // completed. A refusal therefore arrives as an alert when the response to the first request on a
432
+ // new connection is read. The server has processed nothing on that connection, so the request
433
+ // is written again on a new one, with the current TLS context. This repeats until the request is
434
+ // answered or its command's deadline stops this session. Only a session with certificate
435
+ // credentials resends: other credentials present no client certificate, and a rotation cannot
436
+ // change that.
437
+ auto
438
+ http_session::resend_after_rejected_handshake(std::error_code ec) -> bool
439
+ {
440
+ if (stopped_ || !fresh_connection_ || !is_refused_certificate(ec) ||
441
+ !credentials().uses_certificate()) {
442
+ return false;
443
+ }
444
+ {
445
+ const std::scoped_lock lock(output_buffer_mutex_);
446
+ if (unanswered_request_.empty()) {
447
+ return false;
448
+ }
449
+ }
450
+ if (!refusal_logged_.exchange(true)) {
451
+ CB_LOG_WARNING("{} client certificate refused by the server ({}), reconnecting",
452
+ info_.log_prefix(),
453
+ ec.message());
454
+ } else {
455
+ CB_LOG_DEBUG("{} client certificate refused by the server ({}), reconnecting",
456
+ info_.log_prefix(),
457
+ ec.message());
458
+ }
459
+ connected_ = false;
460
+ reading_ = false;
461
+ {
462
+ const std::scoped_lock lock(writing_buffer_mutex_);
463
+ writing_buffer_.clear();
464
+ }
465
+ state_ = diag::endpoint_state::disconnected;
466
+ stream_->close([self = shared_from_this()](std::error_code) {
467
+ const std::scoped_lock lock(self->retry_backoff_mutex_);
468
+ if (self->stopped_) {
469
+ return;
470
+ }
471
+ self->retry_backoff_.expires_after(reconnect_backoff);
472
+ self->retry_backoff_.async_wait([self](std::error_code wait_ec) {
473
+ if (wait_ec == asio::error::operation_aborted || self->stopped_) {
474
+ return;
475
+ }
476
+ self->reconnect_to_resend();
477
+ });
478
+ });
479
+ return true;
480
+ }
481
+
482
+ // initiate_connect() also runs the connect callback after a failed attempt and its backoff. The
483
+ // callback then connects again.
484
+ void
485
+ http_session::reconnect_to_resend()
486
+ {
487
+ {
488
+ const std::scoped_lock lock(connect_callback_mutex_);
489
+ connect_callback_ = [self = shared_from_this()]() {
490
+ if (self->stopped_) {
491
+ return;
492
+ }
493
+ if (!self->connected_) {
494
+ return self->reconnect_to_resend();
495
+ }
496
+ {
497
+ const std::scoped_lock buffer_lock(self->output_buffer_mutex_);
498
+ self->output_buffer_ = self->unanswered_request_;
499
+ }
500
+ self->flush();
501
+ };
502
+ }
503
+ initiate_connect();
504
+ }
505
+
335
506
  void
336
507
  http_session::invoke_connect_callback()
337
508
  {
@@ -348,24 +519,56 @@ http_session::invoke_connect_callback()
348
519
  void
349
520
  http_session::stop()
350
521
  {
351
- if (stopped_) {
522
+ // One caller wins. A stop() from a response handler the teardown below runs returns here; a
523
+ // second teardown would lock current_response_mutex_ again inside cancel_current_response().
524
+ // An exchange rather than a load and a store: two threads stopping at once would both pass a
525
+ // load and run the teardown concurrently.
526
+ if (stopped_.exchange(true)) {
352
527
  return;
353
528
  }
354
- stopped_ = true;
529
+ // Every step runs although a callback throws, because a later stop() returns above and on_stop
530
+ // is what removes the session from the manager's pools. The first exception is rethrown once the
531
+ // teardown is complete; later ones are dropped. Not a scope guard: its destructor is noexcept.
532
+ std::exception_ptr failure;
533
+ const auto run_step = [&failure](auto&& step) {
534
+ try {
535
+ step();
536
+ } catch (...) {
537
+ if (!failure) {
538
+ failure = std::current_exception();
539
+ }
540
+ }
541
+ };
355
542
  state_ = diag::endpoint_state::disconnecting;
356
543
  stream_->close([](std::error_code) {
357
544
  });
358
- invoke_connect_callback();
359
- connect_deadline_timer_.cancel();
360
- idle_timer_.cancel();
361
- retry_backoff_.cancel();
545
+ run_step([this]() {
546
+ invoke_connect_callback();
547
+ });
548
+ {
549
+ const std::scoped_lock lock(connect_deadline_timer_mutex_);
550
+ connect_deadline_timer_.cancel();
551
+ }
552
+ {
553
+ const std::scoped_lock lock(idle_timer_mutex_);
554
+ idle_timer_.cancel();
555
+ }
556
+ {
557
+ const std::scoped_lock lock(retry_backoff_mutex_);
558
+ retry_backoff_.cancel();
559
+ }
362
560
 
363
- cancel_current_response(errc::common::request_canceled);
561
+ run_step([this]() {
562
+ cancel_current_response(errc::common::request_canceled);
563
+ });
364
564
 
365
565
  if (auto handler = std::move(on_stop_handler_); handler) {
366
- handler();
566
+ run_step(handler);
367
567
  }
368
568
  state_ = diag::endpoint_state::disconnected;
569
+ if (failure) {
570
+ std::rethrow_exception(failure);
571
+ }
369
572
  }
370
573
 
371
574
  auto
@@ -380,6 +583,18 @@ http_session::is_stopped() const -> bool
380
583
  return stopped_;
381
584
  }
382
585
 
586
+ void
587
+ http_session::mark_stopping()
588
+ {
589
+ stopping_ = true;
590
+ }
591
+
592
+ auto
593
+ http_session::is_stopping() const -> bool
594
+ {
595
+ return stopping_;
596
+ }
597
+
383
598
  void
384
599
  http_session::write(const std::vector<std::uint8_t>& buf)
385
600
  {
@@ -409,32 +624,35 @@ http_session::flush()
409
624
  if (stopped_) {
410
625
  return;
411
626
  }
412
- asio::post(asio::bind_executor(ctx_, [self = shared_from_this()]() {
627
+ asio::post(get_executor(), [self = shared_from_this()]() {
413
628
  self->do_write();
414
- }));
629
+ });
415
630
  }
416
631
 
417
632
  void
418
633
  http_session::write_and_stream(
419
634
  io::http_request& request,
420
- #ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
421
- utils::movable_function<void(couchbase::core::error_union, io::http_streaming_response)>
422
- resp_handler,
423
- #else
424
635
  utils::movable_function<void(std::error_code, io::http_streaming_response)> resp_handler,
425
- #endif
426
636
  utils::movable_function<void()> stream_end_handler)
427
637
  {
428
- if (stopped_) {
429
- resp_handler(errc::common::request_canceled, {});
430
- stream_end_handler();
431
- return;
432
- }
433
638
  {
434
639
  streaming_response_context ctx{ std::move(resp_handler), std::move(stream_end_handler) };
435
- const std::scoped_lock lock(current_response_mutex_);
436
- std::swap(current_streaming_response_, ctx);
437
- streaming_response_ = true;
640
+ bool installed = false;
641
+ {
642
+ // As in write_and_subscribe: the stopped_ test shares the section that installs the
643
+ // context, so a stop is either seen here or its cancel_current_response() ends the stream.
644
+ const std::scoped_lock lock(current_response_mutex_);
645
+ if (!stopped_) {
646
+ std::swap(current_streaming_response_, ctx);
647
+ streaming_response_ = true;
648
+ installed = true;
649
+ }
650
+ }
651
+ if (!installed) {
652
+ ctx.resp_handler(errc::common::request_canceled, {});
653
+ ctx.stream_end_handler();
654
+ return;
655
+ }
438
656
  }
439
657
  if (request.headers["connection"] == "keep-alive") {
440
658
  keep_alive_ = true;
@@ -459,54 +677,120 @@ http_session::write_and_stream(
459
677
  }
460
678
  write("\r\n");
461
679
  write(request.body);
680
+ remember_request(creds.uses_certificate());
462
681
  flush();
463
682
  }
464
683
 
465
684
  void
466
685
  http_session::set_idle(std::chrono::milliseconds timeout)
467
686
  {
687
+ const std::scoped_lock lock(idle_timer_mutex_);
688
+ // check_in can call this off the strand after its is_stopped() test, while a teardown runs. The
689
+ // teardown cancels the idle timer under this mutex after stopped_ is set, so either that cancel
690
+ // finds the wait armed here, or this test sees the stop. Armed after the cancel, the wait would
691
+ // hold the stopped session until the idle timeout.
692
+ if (stopped_) {
693
+ return;
694
+ }
468
695
  idle_timer_.expires_after(timeout);
469
- return idle_timer_.async_wait([self = shared_from_this()](std::error_code ec) {
470
- if (ec == asio::error::operation_aborted) {
471
- return;
472
- }
473
- CB_LOG_DEBUG("{} idle timeout expired, stopping session: \"{}:{}\"",
474
- self->info_.log_prefix(),
475
- self->hostname_,
476
- self->service_);
477
- self->stop();
696
+ // Bound to the session strand, so the idle-timeout stop() is serialised with the completion of
697
+ // the liveness read armed below.
698
+ idle_timer_.async_wait(
699
+ asio::bind_executor(get_executor(), [self = shared_from_this()](std::error_code ec) {
700
+ if (ec == asio::error::operation_aborted) {
701
+ return;
702
+ }
703
+ CB_LOG_DEBUG("{} idle timeout expired, stopping session: \"{}\"",
704
+ self->info_.log_prefix(),
705
+ logger::system_data(fmt::format("{}:{}", self->hostname_, self->service_)));
706
+ self->stop();
707
+ }));
708
+ // Keep a read armed while the connection is idle so that a peer-initiated
709
+ // close (FIN/RST) is noticed promptly and the session removed from the pool,
710
+ // instead of being handed to the next request as a dead socket that then
711
+ // stalls until the service timeout. Other SDKs get this from their HTTP
712
+ // stacks (Netty channelInactive, Go net/http and Rust hyper liveness checks);
713
+ // here the read completion (EOF/error, or unexpected data) tears the session
714
+ // down -- see do_read(). Posted onto the session executor so it never races
715
+ // with an in-flight read.
716
+ idle_ = true;
717
+ asio::post(get_executor(), [self = shared_from_this()]() {
718
+ self->do_read();
478
719
  });
479
720
  }
480
721
 
481
722
  auto
482
723
  http_session::reset_idle() -> bool
483
724
  {
725
+ // The session is leaving the pool to service a request, so the read armed by
726
+ // set_idle() is no longer an idle liveness probe -- it becomes the read that
727
+ // receives the response. Clear the flag first so do_read() treats any
728
+ // subsequent completion as normal request/response traffic, not a stale
729
+ // connection.
730
+ idle_ = false;
484
731
  // Return true if cancel() is successful. Since the idle_timer_ has a single pending
485
732
  // wait per session, we know the timer has already expired if cancel() returns 0.
486
- return idle_timer_.cancel() != 0;
733
+ // stop() sets stopped_ before it cancels the idle timer under this mutex, so a stop whose cancel
734
+ // has not run yet is seen here, and one whose cancel has run leaves nothing to cancel.
735
+ const bool reset = [this]() {
736
+ const std::scoped_lock lock(idle_timer_mutex_);
737
+ return idle_timer_.cancel() != 0 && !stopped_;
738
+ }();
739
+ if (!reset) {
740
+ // The idle timer already fired, and its (strand-bound) handler is about to stop() this
741
+ // session, or a stop() has begun: the session is being torn down rather than checked out.
742
+ // Restore idle_ so the still-armed liveness read stays on the idle path (tearing the
743
+ // connection down) instead of being fed to the response parser as if it were request traffic.
744
+ idle_ = true;
745
+ }
746
+ return reset;
487
747
  }
488
748
 
489
749
  void
490
750
  http_session::read_some(
491
751
  utils::movable_function<void(std::string, bool, std::error_code)>&& callback)
492
752
  {
493
- if (stopped_ || !stream_->is_open()) {
494
- callback({}, {}, errc::common::request_canceled);
753
+ // Initiated on the strand: callers pull from arbitrary io threads, and the body's close() closes
754
+ // stream_ through a stop() it posts to the strand.
755
+ asio::dispatch(get_executor(),
756
+ [self = shared_from_this(), callback = std::move(callback)]() mutable {
757
+ self->do_read_some(std::move(callback));
758
+ });
759
+ }
760
+
761
+ void
762
+ http_session::do_read_some(read_callback&& callback)
763
+ {
764
+ if (read_some_in_flight_) {
765
+ queued_reads_.push_back(std::move(callback));
495
766
  return;
496
767
  }
497
- std::unique_lock<std::mutex> lock{ read_some_mutex_ };
768
+ read_some_in_flight_ = true;
769
+ if (stopped_ || !stream_->is_open()) {
770
+ // Completed by the drain in finish_read_some(), never inline: this runs from that function's
771
+ // first branch, which a noexcept scope guard reaches, so a callback throwing here would
772
+ // terminate. At the front, since it was issued before anything still queued.
773
+ queued_reads_.push_front(std::move(callback));
774
+ return finish_read_some(errc::common::request_canceled, false);
775
+ }
498
776
  return stream_->async_read_some(
499
777
  asio::buffer(input_buffer_),
500
- [self = shared_from_this(), callback = std::move(callback), lck = std::move(lock)](
501
- std::error_code ec, std::size_t bytes_transferred) mutable {
778
+ [self = shared_from_this(),
779
+ callback = std::move(callback)](std::error_code ec, std::size_t bytes_transferred) mutable {
780
+ // Declared first, so it runs last: after the callback and the stream-end handler.
781
+ std::error_code queued_ec{};
782
+ bool response_ended = false;
783
+ const auto release = gsl::finally([&self, &queued_ec, &response_ended]() {
784
+ self->finish_read_some(queued_ec, response_ended);
785
+ });
502
786
  if (ec == asio::error::operation_aborted || self->stopped_) {
503
787
  CB_LOG_PROTOCOL("[HTTP, IN] type={}, host=\"{}\", rc={}, bytes_received={}",
504
788
  self->type_,
505
789
  self->info_.remote_address(),
506
790
  ec ? ec.message() : "ok",
507
791
  bytes_transferred);
508
- lck.unlock();
509
- callback({}, {}, errc::common::request_canceled);
792
+ queued_ec = errc::common::request_canceled;
793
+ callback({}, {}, queued_ec);
510
794
  return;
511
795
  }
512
796
  CB_LOG_PROTOCOL("[HTTP, IN] type={}, host=\"{}\", rc={}, bytes_received={}{:a}",
@@ -522,46 +806,146 @@ http_session::read_some(
522
806
  if (ec) {
523
807
  CB_LOG_ERROR(
524
808
  "{} IO error while reading from the socket: {}", self->info_.log_prefix(), ec.message());
525
- lck.unlock();
526
- callback({}, {}, ec);
527
- return self->stop();
809
+ // Stopped before the callback, as on a parse failure: a throwing callback must not leave
810
+ // the failed connection running and listed as busy.
811
+ self->stop();
812
+ queued_ec = ec;
813
+ return callback({}, {}, ec);
528
814
  }
529
815
  http_streaming_parser::feeding_result res{};
816
+ std::string data;
817
+ streaming_response_context ctx{};
818
+ bool stopped = false;
530
819
  {
820
+ // One section, as in do_read(): stop() sets stopped_ before its cancel takes this mutex,
821
+ // so the section either sees the stop or takes the parts of the response before the cancel.
531
822
  const std::scoped_lock lock(self->current_response_mutex_);
532
- res = self->current_streaming_response_.parser.feed(
533
- reinterpret_cast<const char*>(self->input_buffer_.data()), bytes_transferred);
823
+ stopped = self->stopped_;
824
+ if (!stopped) {
825
+ res = self->current_streaming_response_.parser.feed(
826
+ reinterpret_cast<const char*>(self->input_buffer_.data()), bytes_transferred);
827
+ if (!res.failure) {
828
+ std::swap(data, self->current_streaming_response_.parser.body_chunk);
829
+ if (res.complete) {
830
+ std::swap(self->current_streaming_response_, ctx);
831
+ }
832
+ }
833
+ }
834
+ }
835
+ if (stopped) {
836
+ queued_ec = errc::common::request_canceled;
837
+ return callback({}, {}, queued_ec);
534
838
  }
535
839
  if (res.failure) {
536
840
  self->stop();
537
- lck.unlock();
538
- return callback({}, {}, errc::common::parsing_failure);
841
+ queued_ec = errc::common::parsing_failure;
842
+ return callback({}, {}, queued_ec);
539
843
  }
540
-
541
- std::string data;
542
- {
543
- const std::scoped_lock lock(self->current_response_mutex_);
544
- std::swap(data, self->current_streaming_response_.parser.body_chunk);
545
- }
546
-
844
+ response_ended = res.complete;
547
845
  if (res.complete) {
548
- streaming_response_context ctx{};
549
- {
550
- const std::scoped_lock lock(self->current_response_mutex_);
551
- std::swap(self->current_streaming_response_, ctx);
552
- }
553
- if (ctx.stream_end_handler) {
554
- ctx.stream_end_handler();
555
- }
556
- if (ctx.resp->must_close_connection()) {
846
+ // Cleared with the end of the response: check_in then posts the liveness read's do_read().
847
+ self->body_pulled_ = false;
848
+ if (ctx.must_close_connection) {
557
849
  self->keep_alive_ = false;
558
850
  }
559
851
  }
560
- lck.unlock();
852
+ // The stream-end handler checks this connection back into the keep-alive pool, and must not
853
+ // run until the body has observed the end of its response. Until it does,
854
+ // http_streaming_response_body_impl still holds this session with reading_complete_ false,
855
+ // and treats it as a response abandoned mid-body: a deadline expiry reaching close_impl in
856
+ // that state stops the session. Checking in first publishes the connection while that is
857
+ // still true, so the stop lands on whichever request took it out of the pool next.
858
+ //
859
+ // Check-in therefore waits for the whole body callback chain, which ends in consumer code: a
860
+ // request issued from inside that callback does not find this connection pooled and opens
861
+ // another. The guard runs the handler even if the callback throws, which would otherwise
862
+ // leave the session checked out and never returned to the pool.
863
+ const auto end_stream = gsl::finally([handler = std::move(ctx.stream_end_handler)]() mutable {
864
+ if (handler) {
865
+ handler();
866
+ }
867
+ });
561
868
  callback(std::move(data), !res.complete, {});
562
869
  });
563
870
  }
564
871
 
872
+ void
873
+ http_session::finish_read_some(std::error_code ec, bool response_ended)
874
+ {
875
+ if (!ec && !response_ended && !queued_reads_.empty()) {
876
+ read_some_in_flight_ = false;
877
+ auto next = std::move(queued_reads_.front());
878
+ queued_reads_.pop_front();
879
+ return do_read_some(std::move(next));
880
+ }
881
+ // Nothing more belongs to this response. Each queued read completes without touching the socket.
882
+ if (queued_reads_.empty()) {
883
+ read_some_in_flight_ = false;
884
+ return notify_reads_drained();
885
+ }
886
+ if (!ec) {
887
+ // A clean end: the stream-end handler has already checked the connection in, and another
888
+ // request may have taken it out. Its reads must not queue behind these, which belong to the
889
+ // response that ended, so they are moved out and the flag cleared before they drain.
890
+ auto ended = std::make_shared<std::deque<read_callback>>();
891
+ std::swap(*ended, queued_reads_);
892
+ read_some_in_flight_ = false;
893
+ ++ended_drains_;
894
+ return drain_ended_reads(std::move(ended));
895
+ }
896
+ // After a failure the session is being torn down. The flag stays set until the queue is empty, so
897
+ // a read_some() from one of these callbacks queues behind the rest and completes the same way.
898
+ // One callback per handler posted to the strand, never inline: this function runs from noexcept
899
+ // scope guards, where a throwing callback would terminate. The guard posts the next handler even
900
+ // if the callback throws, so the exception reaches io_context::run() and the rest still complete
901
+ // in order.
902
+ asio::post(get_executor(), [self = shared_from_this(), ec]() {
903
+ auto callback = std::move(self->queued_reads_.front());
904
+ self->queued_reads_.pop_front();
905
+ const auto next = gsl::finally([&self, ec]() {
906
+ self->finish_read_some(ec, true);
907
+ });
908
+ callback({}, false, ec);
909
+ });
910
+ }
911
+
912
+ void
913
+ http_session::on_reads_drained(utils::movable_function<void()>&& handler)
914
+ {
915
+ reads_drained_.emplace_back(std::move(handler));
916
+ notify_reads_drained();
917
+ }
918
+
919
+ void
920
+ http_session::notify_reads_drained()
921
+ {
922
+ if (read_some_in_flight_ || ended_drains_ != 0) {
923
+ return;
924
+ }
925
+ // Posted, never inline: this runs from noexcept scope guards.
926
+ for (auto& handler : std::exchange(reads_drained_, {})) {
927
+ asio::post(get_executor(), std::move(handler));
928
+ }
929
+ }
930
+
931
+ void
932
+ http_session::drain_ended_reads(std::shared_ptr<std::deque<read_callback>> reads)
933
+ {
934
+ if (reads->empty()) {
935
+ --ended_drains_;
936
+ return notify_reads_drained();
937
+ }
938
+ // Posted one per handler for the reason finish_read_some() posts its drain.
939
+ asio::post(get_executor(), [self = shared_from_this(), reads]() {
940
+ auto callback = std::move(reads->front());
941
+ reads->pop_front();
942
+ const auto next = gsl::finally([&self, &reads]() {
943
+ self->drain_ended_reads(reads);
944
+ });
945
+ callback({}, false, {});
946
+ });
947
+ }
948
+
565
949
  void
566
950
  http_session::on_resolve(std::error_code ec, const asio::ip::tcp::resolver::results_type& endpoints)
567
951
  {
@@ -569,16 +953,17 @@ http_session::on_resolve(std::error_code ec, const asio::ip::tcp::resolver::resu
569
953
  return;
570
954
  }
571
955
  if (ec) {
572
- CB_LOG_ERROR(
573
- "{} error on resolve \"{}:{}\": {}", info_.log_prefix(), hostname_, service_, ec.message());
956
+ CB_LOG_ERROR("{} error on resolve \"{}\": {}",
957
+ info_.log_prefix(),
958
+ logger::system_data(fmt::format("{}:{}", hostname_, service_)),
959
+ ec.message());
574
960
  return initiate_connect();
575
961
  }
576
962
  last_active_ = std::chrono::steady_clock::now();
577
963
  endpoints_ = endpoints;
578
- CB_LOG_TRACE("{} resolved \"{}:{}\" to {} endpoint(s)",
964
+ CB_LOG_TRACE("{} resolved \"{}\" to {} endpoint(s)",
579
965
  info_.log_prefix(),
580
- hostname_,
581
- service_,
966
+ logger::system_data(fmt::format("{}:{}", hostname_, service_)),
582
967
  endpoints_.size());
583
968
  do_connect(endpoints_.begin());
584
969
  }
@@ -590,43 +975,49 @@ http_session::do_connect(asio::ip::tcp::resolver::results_type::iterator it)
590
975
  return;
591
976
  }
592
977
  if (it != endpoints_.end()) {
593
- CB_LOG_DEBUG("{} connecting to {}:{} (\"{}:{}\"), timeout={}ms",
978
+ CB_LOG_DEBUG("{} connecting to {} (\"{}\"), timeout={}ms",
594
979
  info_.log_prefix(),
595
- it->endpoint().address().to_string(),
596
- it->endpoint().port(),
597
- hostname_,
598
- service_,
980
+ logger::system_data(fmt::format(
981
+ "{}:{}", it->endpoint().address().to_string(), it->endpoint().port())),
982
+ logger::system_data(fmt::format("{}:{}", hostname_, service_)),
599
983
  http_ctx_.options.connect_timeout.count());
600
- connect_deadline_timer_.expires_after(http_ctx_.options.connect_timeout);
601
- connect_deadline_timer_.async_wait([self = shared_from_this(),
602
- it](const auto timer_ec) mutable {
603
- if (timer_ec == asio::error::operation_aborted || self->stopped_) {
984
+ {
985
+ const std::scoped_lock lock(connect_deadline_timer_mutex_);
986
+ // stop() can set stopped_ on another thread after the test at the top of this function.
987
+ // cppcheck-suppress knownConditionTrueFalse
988
+ if (stopped_) {
604
989
  return;
605
990
  }
606
- CB_LOG_DEBUG("{} unable to connect to {}:{} in time, reconnecting",
607
- self->info_.log_prefix(),
608
- self->hostname_,
609
- self->service_);
610
- return self->stream_->close([self, next_address = ++it](std::error_code ec) {
611
- if (ec) {
612
- CB_LOG_WARNING("{} unable to close socket, but continue connecting attempt to {}:{}: {}",
613
- self->info_.log_prefix(),
614
- next_address->endpoint().address().to_string(),
615
- next_address->endpoint().port(),
616
- ec.value());
617
- }
618
- self->do_connect(next_address);
619
- });
620
- });
621
-
622
- stream_->async_connect(it->endpoint(), [capture0 = shared_from_this(), it](auto&& PH1) {
623
- capture0->on_connect(std::forward<decltype(PH1)>(PH1), it);
991
+ connect_deadline_timer_.expires_after(http_ctx_.options.connect_timeout);
992
+ connect_deadline_timer_.async_wait(
993
+ [self = shared_from_this(), it](const auto timer_ec) mutable {
994
+ if (timer_ec == asio::error::operation_aborted || self->stopped_) {
995
+ return;
996
+ }
997
+ CB_LOG_DEBUG("{} unable to connect to {} in time, reconnecting",
998
+ self->info_.log_prefix(),
999
+ logger::system_data(fmt::format("{}:{}", self->hostname_, self->service_)));
1000
+ return self->stream_->close([self, next_address = ++it](std::error_code ec) {
1001
+ if (ec) {
1002
+ CB_LOG_WARNING(
1003
+ "{} unable to close socket, but continue connecting attempt to {}: {}",
1004
+ self->info_.log_prefix(),
1005
+ logger::system_data(fmt::format("{}:{}",
1006
+ next_address->endpoint().address().to_string(),
1007
+ next_address->endpoint().port())),
1008
+ ec.value());
1009
+ }
1010
+ self->do_connect(next_address);
1011
+ });
1012
+ });
1013
+ }
1014
+ stream_->async_connect(it->endpoint(), hostname_, [self = shared_from_this(), it](auto&& ec) {
1015
+ self->on_connect(std::forward<decltype(ec)>(ec), it);
624
1016
  });
625
1017
  } else {
626
- CB_LOG_ERROR("{} no more endpoints left to connect, \"{}:{}\" is not reachable",
1018
+ CB_LOG_ERROR("{} no more endpoints left to connect, \"{}\" is not reachable",
627
1019
  info_.log_prefix(),
628
- hostname_,
629
- service_);
1020
+ logger::system_data(fmt::format("{}:{}", hostname_, service_)));
630
1021
  return initiate_connect();
631
1022
  }
632
1023
  }
@@ -640,10 +1031,10 @@ http_session::on_connect(const std::error_code& ec,
640
1031
  }
641
1032
  last_active_ = std::chrono::steady_clock::now();
642
1033
  if (!stream_->is_open() || ec) {
643
- CB_LOG_WARNING("{} unable to connect to {}:{}: {}{}",
1034
+ CB_LOG_WARNING("{} unable to connect to {}: {}{}",
644
1035
  info_.log_prefix(),
645
- it->endpoint().address().to_string(),
646
- it->endpoint().port(),
1036
+ logger::system_data(fmt::format(
1037
+ "{}:{}", it->endpoint().address().to_string(), it->endpoint().port())),
647
1038
  ec.message(),
648
1039
  (ec == asio::error::connection_refused)
649
1040
  ? ", check server ports and cluster encryption setting"
@@ -651,11 +1042,13 @@ http_session::on_connect(const std::error_code& ec,
651
1042
  if (stream_->is_open()) {
652
1043
  stream_->close([self = shared_from_this(), next_address = ++it](std::error_code ec) {
653
1044
  if (ec) {
654
- CB_LOG_WARNING("{} unable to close socket, but continue connecting attempt to {}:{}: {}",
655
- self->info_.log_prefix(),
656
- next_address->endpoint().address().to_string(),
657
- next_address->endpoint().port(),
658
- ec.value());
1045
+ CB_LOG_WARNING(
1046
+ "{} unable to close socket, but continue connecting attempt to {}: {}",
1047
+ self->info_.log_prefix(),
1048
+ logger::system_data(fmt::format("{}:{}",
1049
+ next_address->endpoint().address().to_string(),
1050
+ next_address->endpoint().port())),
1051
+ ec.value());
659
1052
  }
660
1053
  self->do_connect(next_address);
661
1054
  });
@@ -665,15 +1058,19 @@ http_session::on_connect(const std::error_code& ec,
665
1058
  } else {
666
1059
  state_ = diag::endpoint_state::connected;
667
1060
  connected_ = true;
668
- CB_LOG_DEBUG("{} connected to {}:{}",
1061
+ CB_LOG_DEBUG("{} connected to {}",
669
1062
  info_.log_prefix(),
670
- it->endpoint().address().to_string(),
671
- it->endpoint().port());
1063
+ logger::system_data(fmt::format(
1064
+ "{}:{}", it->endpoint().address().to_string(), it->endpoint().port())));
672
1065
  {
673
1066
  const std::scoped_lock lock(info_mutex_);
674
1067
  info_ = http_session_info(client_id_, id_, stream_->local_endpoint(), it->endpoint());
675
1068
  }
676
- connect_deadline_timer_.cancel();
1069
+ {
1070
+ const std::scoped_lock lock(connect_deadline_timer_mutex_);
1071
+ connect_deadline_timer_.cancel();
1072
+ }
1073
+ fresh_connection_ = true;
677
1074
  invoke_connect_callback();
678
1075
  flush();
679
1076
  }
@@ -682,7 +1079,10 @@ http_session::on_connect(const std::error_code& ec,
682
1079
  void
683
1080
  http_session::do_read()
684
1081
  {
685
- if (stopped_ || reading_ || !stream_->is_open()) {
1082
+ // body_pulled_: past a streaming response's head the body belongs to read_some(), which reads the
1083
+ // same socket into the same buffer. A do_read() armed then, such as the one a write completion
1084
+ // starts after the head has already arrived, would take a part of that body and drop it.
1085
+ if (stopped_ || reading_ || body_pulled_ || !stream_->is_open()) {
686
1086
  return;
687
1087
  }
688
1088
  reading_ = true;
@@ -708,10 +1108,35 @@ http_session::do_read()
708
1108
 
709
1109
  self->last_active_ = std::chrono::steady_clock::now();
710
1110
  if (ec) {
711
- CB_LOG_ERROR(
712
- "{} IO error while reading from the socket: {}", self->info_.log_prefix(), ec.message());
1111
+ if (self->idle_) {
1112
+ // Expected: the peer closed a pooled idle connection. Tear it down
1113
+ // quietly so it is removed from the pool rather than reused dead.
1114
+ CB_LOG_DEBUG("{} idle HTTP connection closed by peer ({}), stopping session",
1115
+ self->info_.log_prefix(),
1116
+ ec.message());
1117
+ } else {
1118
+ if (self->resend_after_rejected_handshake(ec)) {
1119
+ return;
1120
+ }
1121
+ CB_LOG_ERROR("{} IO error while reading from the socket: {}",
1122
+ self->info_.log_prefix(),
1123
+ ec.message());
1124
+ }
713
1125
  return self->stop();
714
1126
  }
1127
+ if (self->idle_) {
1128
+ // A pooled keep-alive connection must not receive data until it is
1129
+ // checked out for a request (which clears idle_ via reset_idle()).
1130
+ // Unsolicited bytes mean the connection is in an unexpected state, so
1131
+ // drop it rather than feed them into an empty response parser.
1132
+ CB_LOG_DEBUG("{} unexpected data on idle HTTP connection, stopping session",
1133
+ self->info_.log_prefix());
1134
+ return self->stop();
1135
+ }
1136
+ if (self->fresh_connection_.exchange(false)) {
1137
+ const std::scoped_lock lock(self->output_buffer_mutex_);
1138
+ self->unanswered_request_.clear();
1139
+ }
715
1140
 
716
1141
  if (self->streaming_response_) {
717
1142
  // If streaming the response, read at least the entire header and then call the
@@ -732,23 +1157,37 @@ http_session::do_read()
732
1157
  std::swap(self->current_streaming_response_, ctx);
733
1158
  }
734
1159
 
735
- ctx.resp = http_streaming_response{ self->ctx_, ctx.parser, self };
1160
+ http_streaming_response resp{ self->ctx_, ctx.parser, self };
1161
+ ctx.must_close_connection = resp.must_close_connection();
736
1162
  ctx.parser.body_chunk = "";
737
1163
 
738
- if (res.complete && ctx.resp->must_close_connection()) {
1164
+ if (res.complete && ctx.must_close_connection) {
739
1165
  self->keep_alive_ = false;
740
1166
  }
1167
+ self->body_pulled_ = !res.complete;
741
1168
  self->reading_ = false;
742
- if (auto handler = std::move(ctx.resp_handler); handler) {
743
- handler({}, *ctx.resp);
744
- }
745
- if (!res.complete) {
746
- const std::scoped_lock lock(self->current_response_mutex_);
747
- std::swap(self->current_streaming_response_, ctx);
748
- } else {
1169
+ // Runs even if the response handler throws. Skipped, the context holding the parser
1170
+ // would be dropped, so the rest of the body is fed to an empty parser and the stream-end
1171
+ // handler never runs.
1172
+ const auto reinstall_or_end = gsl::finally([&self, &ctx, complete = res.complete]() {
1173
+ if (!complete) {
1174
+ // A stop() that ran while ctx was swapped out, from the response handler or from
1175
+ // another thread, ended nothing, and a context reinstalled into a stopped session is
1176
+ // never ended: its stream-end handler keeps this session alive. stop() sets
1177
+ // stopped_ before its teardown takes this mutex, so either the teardown finds the
1178
+ // reinstalled context or this test sees the stop.
1179
+ const std::scoped_lock lock(self->current_response_mutex_);
1180
+ if (!self->stopped_) {
1181
+ std::swap(self->current_streaming_response_, ctx);
1182
+ return;
1183
+ }
1184
+ }
749
1185
  if (auto handler = std::move(ctx.stream_end_handler); handler) {
750
1186
  handler();
751
1187
  }
1188
+ });
1189
+ if (auto handler = std::move(ctx.resp_handler); handler) {
1190
+ handler({}, std::move(resp));
752
1191
  }
753
1192
  return;
754
1193
  }
@@ -756,20 +1195,27 @@ http_session::do_read()
756
1195
  return self->do_read();
757
1196
  }
758
1197
  http_parser::feeding_result res{};
1198
+ response_context ctx{};
759
1199
  {
1200
+ // One section for the feed and the take. stop() sets stopped_ before its
1201
+ // cancel_current_response() takes this mutex, so either the stop is seen here and its
1202
+ // cancel completes the response, or the cancel runs after the take and finds no handler.
1203
+ // A cancel between a feed and a separate take would leave an empty context, whose handler
1204
+ // throws std::bad_function_call on the io thread.
760
1205
  const std::scoped_lock lock(self->current_response_mutex_);
1206
+ if (self->stopped_) {
1207
+ return;
1208
+ }
761
1209
  res = self->current_response_.parser.feed(
762
1210
  reinterpret_cast<const char*>(self->input_buffer_.data()), bytes_transferred);
1211
+ if (res.complete) {
1212
+ std::swap(self->current_response_, ctx);
1213
+ }
763
1214
  }
764
1215
  if (res.failure) {
765
1216
  return self->stop();
766
1217
  }
767
1218
  if (res.complete) {
768
- response_context ctx{};
769
- {
770
- const std::scoped_lock lock(self->current_response_mutex_);
771
- std::swap(self->current_response_, ctx);
772
- }
773
1219
  if (ctx.parser.response.must_close_connection()) {
774
1220
  self->keep_alive_ = false;
775
1221
  }
@@ -793,6 +1239,9 @@ http_session::do_write()
793
1239
  return;
794
1240
  }
795
1241
  std::swap(writing_buffer_, output_buffer_);
1242
+ // Recorded where the request is handed to the socket. A request a stop() keeps from this point
1243
+ // is not reported as written on this connection.
1244
+ record_dispatch();
796
1245
  std::vector<asio::const_buffer> buffers;
797
1246
  buffers.reserve(writing_buffer_.size());
798
1247
  for (auto& buf : writing_buffer_) {
@@ -815,6 +1264,9 @@ http_session::do_write()
815
1264
  }
816
1265
  self->last_active_ = std::chrono::steady_clock::now();
817
1266
  if (ec) {
1267
+ if (self->resend_after_rejected_handshake(ec)) {
1268
+ return;
1269
+ }
818
1270
  CB_LOG_ERROR(
819
1271
  "{} IO error while writing to the socket: {}", self->info_.log_prefix(), ec.message());
820
1272
  return self->stop();