couchbase 3.8.1 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/ext/CMakeLists.txt +16 -1
- data/ext/cache/cpm/{CPM_0.42.0.cmake → CPM_0.43.1.cmake} +18 -2
- data/ext/cache/extconf_include.rb +6 -6
- data/ext/cache/mozilla-ca-bundle.crt +214 -155
- data/ext/cache/mozilla-ca-bundle.sha256 +1 -1
- data/ext/couchbase/CMakeLists.txt +130 -21
- data/ext/couchbase/cmake/APKBUILD.in +25 -4
- data/ext/couchbase/cmake/Bundler.cmake +143 -34
- data/ext/couchbase/cmake/CPM.cmake +126 -12
- data/ext/couchbase/cmake/Cache.cmake +7 -1
- data/ext/couchbase/cmake/CompilerOptions.cmake +1 -1
- data/ext/couchbase/cmake/CompilerWarnings.cmake +17 -6
- data/ext/couchbase/cmake/GrpcProtobuf.cmake +481 -0
- data/ext/couchbase/cmake/OpenSSL.cmake +10 -3
- data/ext/couchbase/cmake/Packaging.cmake +519 -65
- data/ext/couchbase/cmake/Protostellar.cmake +250 -0
- data/ext/couchbase/cmake/Sanitizers.cmake +61 -50
- data/ext/couchbase/cmake/StaticAnalyzers.cmake +16 -5
- data/ext/couchbase/cmake/TestFramework.cmake +283 -0
- data/ext/couchbase/cmake/TestFrameworkAddTests.cmake +51 -0
- data/ext/couchbase/cmake/Testing.cmake +27 -9
- data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +141 -8
- data/ext/couchbase/cmake/VersionInfo.cmake +50 -0
- data/ext/couchbase/cmake/build_config.hxx.in +2 -1
- data/ext/couchbase/cmake/couchbase-cxx-client.spec.in +101 -23
- data/ext/couchbase/cmake/couchbase_cxx_client_static-config.cmake.in +7 -0
- data/ext/couchbase/cmake/couchbase_cxx_client_static.pc.in +2 -0
- data/ext/couchbase/cmake/gRPC_Generate.cmake +58 -0
- data/ext/couchbase/cmake/protobuf_Generate.cmake +69 -0
- data/ext/couchbase/cmake/require-git-checkout.cmake +25 -0
- data/ext/couchbase/cmake/tarball_glob.txt +49 -0
- data/ext/couchbase/core/agent_group.cxx +0 -8
- data/ext/couchbase/core/agent_group.hxx +0 -4
- data/ext/couchbase/core/analytics_stream.cxx +314 -0
- data/ext/couchbase/core/analytics_stream.hxx +129 -0
- data/ext/couchbase/core/analytics_stream_component.cxx +245 -0
- data/ext/couchbase/core/analytics_stream_component.hxx +67 -0
- data/ext/couchbase/core/app_telemetry_meter.cxx +25 -3
- data/ext/couchbase/core/app_telemetry_meter.hxx +77 -0
- data/ext/couchbase/core/app_telemetry_reporter.cxx +7 -6
- data/ext/couchbase/core/bucket.cxx +381 -87
- data/ext/couchbase/core/bucket.hxx +105 -25
- data/ext/couchbase/core/bucket_unit_test_api.hxx +57 -0
- data/ext/couchbase/core/capella.hxx +91 -0
- data/ext/couchbase/core/cluster.cxx +1554 -398
- data/ext/couchbase/core/cluster.hxx +39 -8
- data/ext/couchbase/core/cluster_agent.cxx +0 -8
- data/ext/couchbase/core/cluster_agent.hxx +0 -4
- data/ext/couchbase/core/cluster_options.hxx +26 -3
- data/ext/couchbase/core/collections_component.cxx +39 -15
- data/ext/couchbase/core/config_profiles.cxx +1 -1
- data/ext/couchbase/core/crud_component.cxx +2 -2
- data/ext/couchbase/core/crud_options.hxx +34 -0
- data/ext/couchbase/core/crypto/cbcrypto.cc +18 -13
- data/ext/couchbase/core/document_id_redaction.hxx +81 -0
- data/ext/couchbase/core/error_context/base_error_context.hxx +7 -6
- data/ext/couchbase/core/error_context/key_value.cxx +25 -2
- data/ext/couchbase/core/error_context/key_value.hxx +60 -33
- data/ext/couchbase/core/error_context/key_value_error_context.hxx +16 -15
- data/ext/couchbase/core/error_context/search.hxx +5 -0
- data/ext/couchbase/core/error_context/search_json.hxx +3 -0
- data/ext/couchbase/core/error_context/subdocument_error_context.hxx +19 -18
- data/ext/couchbase/core/error_context/view.hxx +5 -0
- data/ext/couchbase/core/free_form_http_request.cxx +237 -4
- data/ext/couchbase/core/free_form_http_request.hxx +41 -8
- data/ext/couchbase/core/http_component.cxx +300 -284
- data/ext/couchbase/core/http_component.hxx +0 -4
- data/ext/couchbase/core/impl/analytics.cxx +38 -5
- data/ext/couchbase/core/impl/analytics.hxx +34 -0
- data/ext/couchbase/core/impl/bootstrap_error.hxx +0 -3
- data/ext/couchbase/core/impl/collection.cxx +215 -202
- data/ext/couchbase/core/impl/collection_manager.cxx +1 -1
- data/ext/couchbase/core/impl/dns_srv_tracker.cxx +45 -18
- data/ext/couchbase/core/impl/error.cxx +22 -2
- data/ext/couchbase/core/impl/internal_analytics_stream_result.hxx +111 -0
- data/ext/couchbase/core/impl/internal_query_stream_result.hxx +110 -0
- data/ext/couchbase/core/impl/invoke_with_node_id.hxx +15 -7
- data/ext/couchbase/core/impl/key_value_error_category.cxx +6 -0
- data/ext/couchbase/core/impl/lookup_in_replica.cxx +10 -10
- data/ext/couchbase/core/impl/lookup_in_replica.hxx +4 -4
- data/ext/couchbase/core/impl/node_id.cxx +49 -13
- data/ext/couchbase/core/impl/observability_recorder.cxx +12 -2
- data/ext/couchbase/core/impl/observe_poll.cxx +63 -23
- data/ext/couchbase/core/impl/public_analytics_stream_result.cxx +372 -0
- data/ext/couchbase/core/impl/public_bucket.cxx +48 -2
- data/ext/couchbase/core/impl/public_cluster.cxx +325 -25
- data/ext/couchbase/core/impl/public_query_stream_result.cxx +380 -0
- data/ext/couchbase/core/impl/public_transaction_get_multi_replicas_from_preferred_server_group_result.cxx +42 -0
- data/ext/couchbase/core/impl/public_transaction_get_multi_result.cxx +42 -0
- data/ext/couchbase/core/impl/query.cxx +76 -7
- data/ext/couchbase/core/impl/query.hxx +36 -0
- data/ext/couchbase/core/impl/query_index_manager.cxx +54 -1
- data/ext/couchbase/core/impl/replica_utils.cxx +101 -0
- data/ext/couchbase/core/impl/replica_utils.hxx +64 -0
- data/ext/couchbase/core/impl/resolve_node_id.hxx +125 -0
- data/ext/couchbase/core/impl/scope.cxx +70 -0
- data/ext/couchbase/core/impl/search.cxx +31 -1
- data/ext/couchbase/core/impl/transactions.cxx +130 -0
- data/ext/couchbase/core/impl/wait_until_ready.cxx +439 -0
- data/ext/couchbase/core/impl/wait_until_ready.hxx +110 -0
- data/ext/couchbase/core/impl/with_bucket_config_or_timeout.hxx +180 -0
- data/ext/couchbase/core/impl/with_cancellation.hxx +11 -3
- data/ext/couchbase/core/io/configuration_belongs_to_session.hxx +66 -0
- data/ext/couchbase/core/io/dns_client.cxx +122 -45
- data/ext/couchbase/core/io/dns_codec.hxx +21 -21
- data/ext/couchbase/core/io/dns_config.cxx +21 -6
- data/ext/couchbase/core/io/http_command.hxx +149 -121
- data/ext/couchbase/core/io/http_error.hxx +107 -0
- data/ext/couchbase/core/io/http_parser.cxx +4 -0
- data/ext/couchbase/core/io/http_session.cxx +606 -154
- data/ext/couchbase/core/io/http_session.hxx +147 -30
- data/ext/couchbase/core/io/http_session_manager.hxx +329 -382
- data/ext/couchbase/core/io/http_streaming_parser.cxx +4 -0
- data/ext/couchbase/core/io/http_streaming_response.cxx +328 -43
- data/ext/couchbase/core/io/http_streaming_response.hxx +37 -3
- data/ext/couchbase/core/io/mcbp_buffer_pool.hxx +122 -0
- data/ext/couchbase/core/io/mcbp_command.hxx +150 -51
- data/ext/couchbase/core/io/mcbp_command_id.hxx +45 -0
- data/ext/couchbase/core/io/mcbp_message.cxx +9 -1
- data/ext/couchbase/core/io/mcbp_message.hxx +19 -0
- data/ext/couchbase/core/io/mcbp_output_queue.hxx +150 -0
- data/ext/couchbase/core/io/mcbp_parser.cxx +24 -7
- data/ext/couchbase/core/io/mcbp_session.cxx +316 -317
- data/ext/couchbase/core/io/mcbp_session.hxx +27 -18
- data/ext/couchbase/core/io/opaque_ring_table.hxx +145 -0
- data/ext/couchbase/core/io/retry_orchestrator.hxx +6 -5
- data/ext/couchbase/core/io/stream_deadline.hxx +50 -0
- data/ext/couchbase/core/io/streams.cxx +275 -14
- data/ext/couchbase/core/io/streams.hxx +53 -0
- data/ext/couchbase/core/logger/logger.cxx +36 -0
- data/ext/couchbase/core/logger/logger.hxx +46 -6
- data/ext/couchbase/core/logger/redaction.hxx +503 -0
- data/ext/couchbase/core/management/eventing_status.hxx +10 -0
- data/ext/couchbase/core/management/eventing_status_json.hxx +4 -0
- data/ext/couchbase/core/mcbp/big_endian.cxx +1 -1
- data/ext/couchbase/core/mcbp/codec.cxx +27 -18
- data/ext/couchbase/core/mcbp/operation_queue.cxx +2 -2
- data/ext/couchbase/core/mcbp/packet.hxx +2 -2
- data/ext/couchbase/core/mcbp/queue_request.cxx +23 -6
- data/ext/couchbase/core/mcbp/queue_request.hxx +21 -0
- data/ext/couchbase/core/meta/features.hxx +13 -0
- data/ext/couchbase/core/meta/version.cxx +6 -3
- data/ext/couchbase/core/metrics/logging_meter.cxx +5 -10
- data/ext/couchbase/core/metrics/meter_wrapper.cxx +63 -33
- data/ext/couchbase/core/metrics/meter_wrapper.hxx +55 -3
- data/ext/couchbase/core/operations/analytics_response_parsing.cxx +177 -0
- data/ext/couchbase/core/operations/analytics_response_parsing.hxx +55 -0
- data/ext/couchbase/core/operations/document_analytics.cxx +48 -130
- data/ext/couchbase/core/operations/document_analytics.hxx +14 -2
- data/ext/couchbase/core/operations/document_get_all_replicas.hxx +15 -7
- data/ext/couchbase/core/operations/document_get_any_replica.hxx +19 -8
- data/ext/couchbase/core/operations/document_get_projected.cxx +1 -1
- data/ext/couchbase/core/{impl/get_replica.cxx → operations/document_get_replica.cxx} +21 -3
- data/ext/couchbase/core/{impl/get_replica.hxx → operations/document_get_replica.hxx} +31 -4
- data/ext/couchbase/core/operations/document_lookup_in.cxx +10 -10
- data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +228 -198
- data/ext/couchbase/core/operations/document_lookup_in_any_replica.hxx +215 -217
- data/ext/couchbase/core/operations/document_query.cxx +108 -221
- data/ext/couchbase/core/operations/document_query.hxx +18 -4
- data/ext/couchbase/core/operations/document_search.cxx +52 -8
- data/ext/couchbase/core/operations/document_search.hxx +3 -0
- data/ext/couchbase/core/operations/document_view.cxx +3 -3
- data/ext/couchbase/core/operations/management/analytics_dataset_create.cxx +15 -8
- data/ext/couchbase/core/operations/management/analytics_dataset_create.hxx +4 -0
- data/ext/couchbase/core/operations/management/analytics_dataset_drop.cxx +11 -6
- data/ext/couchbase/core/operations/management/analytics_dataset_get_all.cxx +3 -2
- data/ext/couchbase/core/operations/management/analytics_dataverse_create.cxx +10 -5
- data/ext/couchbase/core/operations/management/analytics_dataverse_drop.cxx +10 -5
- data/ext/couchbase/core/operations/management/analytics_get_pending_mutations.cxx +2 -1
- data/ext/couchbase/core/operations/management/analytics_index_create.cxx +33 -8
- data/ext/couchbase/core/operations/management/analytics_index_drop.cxx +12 -7
- data/ext/couchbase/core/operations/management/analytics_index_get_all.cxx +3 -2
- data/ext/couchbase/core/operations/management/analytics_link_connect.cxx +11 -6
- data/ext/couchbase/core/operations/management/analytics_link_create.cxx +3 -3
- data/ext/couchbase/core/operations/management/analytics_link_disconnect.cxx +10 -4
- data/ext/couchbase/core/operations/management/analytics_link_drop.cxx +6 -4
- data/ext/couchbase/core/operations/management/analytics_link_get_all.cxx +4 -3
- data/ext/couchbase/core/operations/management/analytics_link_replace.cxx +3 -3
- data/ext/couchbase/core/operations/management/analytics_link_utils.hxx +1 -1
- data/ext/couchbase/core/operations/management/bucket_create.cxx +37 -38
- data/ext/couchbase/core/operations/management/bucket_update.cxx +26 -28
- data/ext/couchbase/core/operations/management/change_password.cxx +2 -0
- data/ext/couchbase/core/operations/management/collection_drop.cxx +5 -1
- data/ext/couchbase/core/operations/management/error_utils.cxx +59 -5
- data/ext/couchbase/core/operations/management/error_utils.hxx +29 -1
- data/ext/couchbase/core/operations/management/eventing_deploy_function.cxx +9 -3
- data/ext/couchbase/core/operations/management/eventing_drop_function.cxx +8 -3
- data/ext/couchbase/core/operations/management/eventing_get_all_functions.cxx +4 -2
- data/ext/couchbase/core/operations/management/eventing_get_function.cxx +5 -3
- data/ext/couchbase/core/operations/management/eventing_get_status.cxx +4 -2
- data/ext/couchbase/core/operations/management/eventing_pause_function.cxx +9 -3
- data/ext/couchbase/core/operations/management/eventing_resume_function.cxx +9 -3
- data/ext/couchbase/core/operations/management/eventing_undeploy_function.cxx +9 -3
- data/ext/couchbase/core/operations/management/eventing_upsert_function.cxx +9 -3
- data/ext/couchbase/core/operations/management/group_drop.cxx +3 -1
- data/ext/couchbase/core/operations/management/group_get.cxx +3 -1
- data/ext/couchbase/core/operations/management/group_upsert.cxx +11 -8
- data/ext/couchbase/core/operations/management/query_index_build.cxx +5 -2
- data/ext/couchbase/core/operations/management/query_index_create.cxx +9 -3
- data/ext/couchbase/core/operations/management/query_index_drop.cxx +11 -4
- data/ext/couchbase/core/operations/management/query_index_get_all.cxx +1 -1
- data/ext/couchbase/core/operations/management/query_index_get_all_deferred.cxx +1 -1
- data/ext/couchbase/core/operations/management/scope_drop.cxx +5 -1
- data/ext/couchbase/core/operations/management/scope_get_all.cxx +6 -1
- data/ext/couchbase/core/operations/management/search_get_stats.cxx +10 -0
- data/ext/couchbase/core/operations/management/search_index_analyze_document.cxx +3 -2
- data/ext/couchbase/core/operations/management/search_index_control_ingest.cxx +4 -3
- data/ext/couchbase/core/operations/management/search_index_control_plan_freeze.cxx +4 -3
- data/ext/couchbase/core/operations/management/search_index_control_query.cxx +4 -3
- data/ext/couchbase/core/operations/management/search_index_drop.cxx +2 -2
- data/ext/couchbase/core/operations/management/search_index_get.cxx +2 -2
- data/ext/couchbase/core/operations/management/search_index_get_documents_count.cxx +3 -2
- data/ext/couchbase/core/operations/management/search_index_get_stats.cxx +3 -1
- data/ext/couchbase/core/operations/management/search_index_upsert.cxx +2 -2
- data/ext/couchbase/core/operations/management/user_drop.cxx +3 -1
- data/ext/couchbase/core/operations/management/user_get.cxx +3 -1
- data/ext/couchbase/core/operations/management/user_upsert.cxx +12 -9
- data/ext/couchbase/core/operations/management/view_index_drop.cxx +1 -1
- data/ext/couchbase/core/operations/management/view_index_get.cxx +1 -1
- data/ext/couchbase/core/operations/management/view_index_upsert.cxx +1 -1
- data/ext/couchbase/core/operations/operation_traits.hxx +18 -0
- data/ext/couchbase/core/operations/query_response_parsing.cxx +211 -0
- data/ext/couchbase/core/operations/query_response_parsing.hxx +60 -0
- data/ext/couchbase/core/operations_fwd.hxx +3 -3
- data/ext/couchbase/core/origin.cxx +54 -26
- data/ext/couchbase/core/origin.hxx +12 -0
- data/ext/couchbase/core/orphan_reporter.cxx +2 -1
- data/ext/couchbase/core/platform/random.cc +7 -7
- data/ext/couchbase/core/protocol/client_request.cxx +2 -2
- data/ext/couchbase/core/protocol/client_request.hxx +38 -21
- data/ext/couchbase/core/protocol/client_response.cxx +11 -3
- data/ext/couchbase/core/protocol/client_response.hxx +27 -6
- data/ext/couchbase/core/protocol/cmd_append.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_cluster_map_change_notification.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_decrement.cxx +6 -6
- data/ext/couchbase/core/protocol/cmd_get.cxx +1 -1
- data/ext/couchbase/core/protocol/cmd_get_and_lock.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_get_and_touch.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_get_cluster_config.cxx +6 -4
- data/ext/couchbase/core/protocol/cmd_get_collection_id.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_get_error_map.cxx +1 -1
- data/ext/couchbase/core/protocol/cmd_get_meta.cxx +4 -4
- data/ext/couchbase/core/protocol/cmd_get_replica.cxx +1 -1
- data/ext/couchbase/core/protocol/cmd_hello.cxx +1 -1
- data/ext/couchbase/core/protocol/cmd_increment.cxx +6 -6
- data/ext/couchbase/core/protocol/cmd_insert.cxx +4 -4
- data/ext/couchbase/core/protocol/cmd_lookup_in.cxx +4 -3
- data/ext/couchbase/core/protocol/cmd_lookup_in_replica.cxx +4 -3
- data/ext/couchbase/core/protocol/cmd_mutate_in.cxx +10 -9
- data/ext/couchbase/core/protocol/cmd_mutate_in.hxx +10 -10
- data/ext/couchbase/core/protocol/cmd_observe_seqno.cxx +7 -7
- data/ext/couchbase/core/protocol/cmd_prepend.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_remove.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_replace.cxx +4 -4
- data/ext/couchbase/core/protocol/cmd_touch.cxx +1 -1
- data/ext/couchbase/core/protocol/cmd_upsert.cxx +4 -4
- data/ext/couchbase/core/protocol/datatype.hxx +118 -1
- data/ext/couchbase/core/protocol/frame_info_utils.cxx +1 -1
- data/ext/couchbase/core/protocol/magic.hxx +4 -0
- data/ext/couchbase/core/protocol/magic_fmt.hxx +3 -0
- data/ext/couchbase/core/protocol/server_request.hxx +3 -1
- data/ext/couchbase/core/protocol/status.cxx +1 -1
- data/ext/couchbase/core/protostellar/analytics_converter.hxx +171 -0
- data/ext/couchbase/core/protostellar/bucket_admin_converter.hxx +301 -0
- data/ext/couchbase/core/protostellar/collection_admin_converter.hxx +92 -0
- data/ext/couchbase/core/protostellar/component.cxx +2445 -0
- data/ext/couchbase/core/protostellar/component.hxx +391 -0
- data/ext/couchbase/core/{columnar/bootstrap_notification_subscriber.hxx → protostellar/compression_settings.hxx} +11 -10
- data/ext/couchbase/core/protostellar/credentials.cxx +233 -0
- data/ext/couchbase/core/protostellar/credentials.hxx +88 -0
- data/ext/couchbase/core/protostellar/dispatcher.cxx +57 -0
- data/ext/couchbase/core/protostellar/dispatcher.hxx +482 -0
- data/ext/couchbase/core/protostellar/error_utils.cxx +293 -0
- data/ext/couchbase/core/protostellar/error_utils.hxx +97 -0
- data/ext/couchbase/core/protostellar/json_payload.hxx +49 -0
- data/ext/couchbase/core/protostellar/kv_converter.cxx +70 -0
- data/ext/couchbase/core/protostellar/kv_converter.hxx +600 -0
- data/ext/couchbase/core/protostellar/query_converter.hxx +265 -0
- data/ext/couchbase/core/protostellar/query_index_admin_converter.hxx +291 -0
- data/ext/couchbase/core/protostellar/query_proto.hxx +73 -0
- data/ext/couchbase/core/protostellar/search_converter.hxx +244 -0
- data/ext/couchbase/core/protostellar/search_index_admin_converter.hxx +156 -0
- data/ext/couchbase/core/protostellar/view_converter.hxx +158 -0
- data/ext/couchbase/core/query_stream.cxx +447 -0
- data/ext/couchbase/core/query_stream.hxx +141 -0
- data/ext/couchbase/core/query_stream_component.cxx +243 -0
- data/ext/couchbase/core/query_stream_component.hxx +68 -0
- data/ext/couchbase/core/range_scan_load_balancer.cxx +15 -8
- data/ext/couchbase/core/range_scan_orchestrator.cxx +15 -3
- data/ext/couchbase/core/response_handler.hxx +41 -4
- data/ext/couchbase/core/row_streamer.cxx +332 -46
- data/ext/couchbase/core/row_streamer.hxx +31 -1
- data/ext/couchbase/core/row_streamer_options.hxx +55 -0
- data/ext/couchbase/core/sasl/client.h +3 -0
- data/ext/couchbase/core/search_scoring.hxx +66 -0
- data/ext/couchbase/core/stream_error_details.hxx +69 -0
- data/ext/couchbase/core/timeout_defaults.hxx +0 -1
- data/ext/couchbase/core/topology/capabilities.hxx +6 -0
- data/ext/couchbase/core/topology/capabilities_fmt.hxx +3 -0
- data/ext/couchbase/core/topology/configuration.cxx +21 -6
- data/ext/couchbase/core/topology/configuration.hxx +29 -8
- data/ext/couchbase/core/topology/configuration_json.hxx +3 -0
- data/ext/couchbase/core/topology/error_map_json.hxx +3 -1
- data/ext/couchbase/core/tracing/threshold_logging_tracer.cxx +70 -4
- data/ext/couchbase/core/tracing/threshold_logging_tracer.hxx +10 -0
- data/ext/couchbase/core/transactions/active_transaction_record.cxx +0 -34
- data/ext/couchbase/core/transactions/attempt_context_impl.cxx +362 -241
- data/ext/couchbase/core/transactions/attempt_context_impl.hxx +24 -3
- data/ext/couchbase/core/transactions/error_list.hxx +2 -2
- data/ext/couchbase/core/transactions/get_multi_fetch.hxx +160 -0
- data/ext/couchbase/core/transactions/get_multi_orchestrator.cxx +326 -125
- data/ext/couchbase/core/transactions/get_multi_transaction_id.hxx +62 -0
- data/ext/couchbase/core/transactions/internal/atr_entry.hxx +22 -2
- data/ext/couchbase/core/transactions/internal/transaction_context.hxx +2 -2
- data/ext/couchbase/core/transactions/internal/transactions_cleanup.hxx +23 -1
- data/ext/couchbase/core/transactions/internal/utils.hxx +50 -14
- data/ext/couchbase/core/transactions/staged_mutation.cxx +23 -18
- data/ext/couchbase/core/transactions/staged_mutation.hxx +15 -4
- data/ext/couchbase/core/transactions/transaction_context.cxx +55 -40
- data/ext/couchbase/core/transactions/transaction_get_result.hxx +16 -0
- data/ext/couchbase/core/transactions/transactions.cxx +3 -55
- data/ext/couchbase/core/transactions/transactions_cleanup.cxx +194 -66
- data/ext/couchbase/core/transactions/transactions_config.cxx +0 -11
- data/ext/couchbase/core/transactions/utils.cxx +10 -0
- data/ext/couchbase/core/transactions/waitable_op_list.hxx +15 -4
- data/ext/couchbase/core/transactions.hxx +9 -9
- data/ext/couchbase/core/utils/byteswap.hxx +166 -9
- data/ext/couchbase/core/utils/connection_string.cxx +50 -85
- data/ext/couchbase/core/utils/connection_string.hxx +15 -0
- data/ext/couchbase/core/{columnar/backoff_calculator.hxx → utils/deadline.hxx} +13 -13
- data/ext/couchbase/core/utils/duration_parser.cxx +1 -1
- data/ext/couchbase/core/utils/json_streaming_lexer.cxx +48 -5
- data/ext/couchbase/core/utils/json_streaming_lexer.hxx +4 -1
- data/ext/couchbase/core/utils/movable_function.hxx +301 -52
- data/ext/couchbase/core/utils/name_codec.hxx +154 -11
- data/ext/couchbase/core/{columnar/retry_info.hxx → utils/process_id.hxx} +18 -13
- data/ext/couchbase/core/utils/url_codec.cxx +12 -51
- data/ext/couchbase/core/websocket_codec.cxx +3 -29
- data/ext/couchbase/couchbase/analytics_options.hxx +21 -10
- data/ext/couchbase/couchbase/analytics_row.hxx +105 -0
- data/ext/couchbase/couchbase/analytics_stream_result.hxx +266 -0
- data/ext/couchbase/couchbase/boolean_query.hxx +3 -3
- data/ext/couchbase/couchbase/bucket.hxx +57 -0
- data/ext/couchbase/couchbase/cluster.hxx +216 -5
- data/ext/couchbase/{core/columnar/security_options.hxx → couchbase/cluster_state.hxx} +18 -14
- data/ext/couchbase/couchbase/codec/lenient_json_transcoder.hxx +69 -0
- data/ext/couchbase/couchbase/collection.hxx +234 -14
- data/ext/couchbase/couchbase/common_durability_options.hxx +18 -3
- data/ext/couchbase/couchbase/conjunction_query.hxx +1 -1
- data/ext/couchbase/couchbase/create_dataset_analytics_options.hxx +6 -1
- data/ext/couchbase/couchbase/date_range_query.hxx +2 -2
- data/ext/couchbase/couchbase/decrement_options.hxx +1 -1
- data/ext/couchbase/couchbase/disjunction_query.hxx +1 -1
- data/ext/couchbase/couchbase/error.hxx +32 -0
- data/ext/couchbase/couchbase/error_codes.hxx +32 -0
- data/ext/couchbase/couchbase/geo_bounding_box_query.hxx +1 -1
- data/ext/couchbase/couchbase/geo_distance_query.hxx +1 -1
- data/ext/couchbase/couchbase/geo_polygon_query.hxx +1 -1
- data/ext/couchbase/couchbase/get_replica_options.hxx +68 -0
- data/ext/couchbase/couchbase/get_replica_result.hxx +5 -3
- data/ext/couchbase/couchbase/get_replica_strategy.hxx +161 -0
- data/ext/couchbase/couchbase/get_result.hxx +1 -0
- data/ext/couchbase/couchbase/increment_options.hxx +1 -1
- data/ext/couchbase/couchbase/insert_options.hxx +1 -1
- data/ext/couchbase/couchbase/match_phrase_query.hxx +1 -1
- data/ext/couchbase/couchbase/match_query.hxx +1 -1
- data/ext/couchbase/couchbase/mutate_in_options.hxx +3 -3
- data/ext/couchbase/couchbase/network_options.hxx +1 -1
- data/ext/couchbase/couchbase/node_id.hxx +42 -14
- data/ext/couchbase/couchbase/node_id_for_options.hxx +7 -1
- data/ext/couchbase/couchbase/node_ids_options.hxx +8 -2
- data/ext/couchbase/couchbase/numeric_range_query.hxx +1 -1
- data/ext/couchbase/couchbase/phrase_query.hxx +1 -1
- data/ext/couchbase/couchbase/prefix_query.hxx +1 -1
- data/ext/couchbase/couchbase/query_options.hxx +23 -12
- data/ext/couchbase/couchbase/query_row.hxx +111 -0
- data/ext/couchbase/couchbase/query_stream_result.hxx +302 -0
- data/ext/couchbase/couchbase/query_string_query.hxx +3 -3
- data/ext/couchbase/couchbase/regexp_query.hxx +1 -1
- data/ext/couchbase/couchbase/replace_options.hxx +3 -3
- data/ext/couchbase/couchbase/result.hxx +14 -3
- data/ext/couchbase/couchbase/scan_result_item.hxx +1 -0
- data/ext/couchbase/couchbase/scope.hxx +85 -2
- data/ext/couchbase/couchbase/search_options.hxx +58 -8
- data/ext/couchbase/couchbase/search_scoring.hxx +94 -0
- data/ext/couchbase/couchbase/search_scoring_none.hxx +51 -0
- data/ext/couchbase/couchbase/search_scoring_reciprocal_rank_fusion.hxx +88 -0
- data/ext/couchbase/couchbase/search_scoring_relative_score_fusion.hxx +70 -0
- data/ext/couchbase/couchbase/term_range_query.hxx +1 -1
- data/ext/couchbase/couchbase/tracing/request_span.hxx +46 -0
- data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +23 -21
- data/ext/couchbase/couchbase/transactions/transaction_get_multi_result.hxx +24 -19
- data/ext/couchbase/couchbase/transactions/transaction_get_result.hxx +17 -3
- data/ext/couchbase/couchbase/transactions/transactions_config.hxx +4 -2
- data/ext/couchbase/couchbase/transactions.hxx +27 -18
- data/ext/couchbase/couchbase/upsert_options.hxx +3 -3
- data/ext/couchbase/couchbase/vector_query.hxx +1 -1
- data/ext/couchbase/couchbase/wait_until_ready_options.hxx +111 -0
- data/ext/couchbase/couchbase/wildcard_query.hxx +1 -1
- data/ext/extconf.rb +15 -10
- data/ext/rcb_analytics.cxx +82 -102
- data/ext/rcb_backend.cxx +42 -36
- data/ext/rcb_backend.hxx +2 -0
- data/ext/rcb_buckets.cxx +47 -59
- data/ext/rcb_collections.cxx +42 -52
- data/ext/rcb_crud.cxx +345 -224
- data/ext/rcb_diagnostics.cxx +117 -27
- data/ext/rcb_exceptions.cxx +152 -99
- data/ext/rcb_exceptions.hxx +25 -0
- data/ext/rcb_extras.cxx +18 -19
- data/ext/rcb_hdr_histogram.cxx +19 -4
- data/ext/rcb_logger.cxx +202 -27
- data/ext/rcb_logger.hxx +12 -0
- data/ext/rcb_multi.cxx +40 -40
- data/ext/rcb_observability.cxx +7 -5
- data/ext/rcb_query.cxx +99 -142
- data/ext/rcb_range_scan.cxx +41 -35
- data/ext/rcb_search.cxx +98 -99
- data/ext/rcb_users.cxx +36 -56
- data/ext/rcb_utils.cxx +100 -37
- data/ext/rcb_utils.hxx +287 -33
- data/ext/rcb_version.cxx +1 -1
- data/ext/rcb_views.cxx +55 -51
- data/lib/active_support/cache/couchbase_store.rb +77 -12
- data/lib/couchbase/bucket.rb +20 -0
- data/lib/couchbase/cluster.rb +33 -14
- data/lib/couchbase/collection.rb +36 -1
- data/lib/couchbase/errors.rb +18 -0
- data/lib/couchbase/fork_hooks.rb +16 -7
- data/lib/couchbase/get_replica_strategy.rb +89 -0
- data/lib/couchbase/logger.rb +1 -1
- data/lib/couchbase/management/analytics_index_manager.rb +15 -15
- data/lib/couchbase/management/bucket_manager.rb +6 -6
- data/lib/couchbase/management/collection_manager.rb +6 -6
- data/lib/couchbase/management/query_index_manager.rb +7 -7
- data/lib/couchbase/management/search_index_manager.rb +13 -13
- data/lib/couchbase/management/user_manager.rb +10 -10
- data/lib/couchbase/management/view_index_manager.rb +5 -5
- data/lib/couchbase/options.rb +181 -44
- data/lib/couchbase/protostellar/bucket.rb +4 -0
- data/lib/couchbase/protostellar/cluster.rb +4 -0
- data/lib/couchbase/protostellar/collection.rb +4 -0
- data/lib/couchbase/protostellar/error_handling.rb +2 -1
- data/lib/couchbase/protostellar/request.rb +1 -0
- data/lib/couchbase/protostellar/request_generator/kv.rb +16 -2
- data/lib/couchbase/protostellar/request_generator/search.rb +17 -1
- data/lib/couchbase/protostellar/retry/orchestrator.rb +3 -2
- data/lib/couchbase/protostellar/retry/strategies/fail_fast.rb +33 -0
- data/lib/couchbase/protostellar/retry/strategies.rb +1 -0
- data/lib/couchbase/search_options.rb +95 -6
- data/lib/couchbase/version.rb +1 -1
- metadata +92 -33
- data/ext/0001-fix-build-for-mingw-w64-ucrt-x86_64-toolchain.patch +0 -40
- data/ext/couchbase/core/columnar/agent.hxx +0 -71
- data/ext/couchbase/core/columnar/agent_config.hxx +0 -59
- data/ext/couchbase/core/columnar/background_bootstrap_listener.hxx +0 -42
- data/ext/couchbase/core/columnar/backoff_calculator.cxx +0 -62
- data/ext/couchbase/core/columnar/columnar_agent.cxx +0 -147
- data/ext/couchbase/core/columnar/columnar_agent_config.cxx +0 -48
- data/ext/couchbase/core/columnar/columnar_error.cxx +0 -51
- data/ext/couchbase/core/columnar/database_management_options.hxx +0 -56
- data/ext/couchbase/core/columnar/error.hxx +0 -51
- data/ext/couchbase/core/columnar/error_codes.cxx +0 -107
- data/ext/couchbase/core/columnar/error_codes.hxx +0 -75
- data/ext/couchbase/core/columnar/management_component.cxx +0 -300
- data/ext/couchbase/core/columnar/management_component.hxx +0 -56
- data/ext/couchbase/core/columnar/query_component.cxx +0 -523
- data/ext/couchbase/core/columnar/query_component.hxx +0 -57
- data/ext/couchbase/core/columnar/query_options.hxx +0 -59
- data/ext/couchbase/core/columnar/query_result.cxx +0 -130
- data/ext/couchbase/core/columnar/query_result.hxx +0 -78
- data/ext/couchbase/core/io/config_tracker.cxx +0 -687
- data/ext/couchbase/core/io/config_tracker.hxx +0 -123
|
@@ -17,6 +17,16 @@
|
|
|
17
17
|
|
|
18
18
|
#include <couchbase/build_config.hxx>
|
|
19
19
|
|
|
20
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
21
|
+
#include "core/protostellar/component.hxx"
|
|
22
|
+
#include "core/protostellar/credentials.hxx"
|
|
23
|
+
#include "core/protostellar/error_utils.hxx"
|
|
24
|
+
|
|
25
|
+
#include <couchbase/retry_strategy.hxx>
|
|
26
|
+
|
|
27
|
+
#include <asio/steady_timer.hpp>
|
|
28
|
+
#endif
|
|
29
|
+
|
|
20
30
|
#define COUCHBASE_CXX_CLIENT_IGNORE_CORE_DEPRECATIONS
|
|
21
31
|
#include "cluster.hxx"
|
|
22
32
|
|
|
@@ -24,23 +34,25 @@
|
|
|
24
34
|
#undef COUCHBASE_CXX_CLIENT_IGNORE_CORE_DEPRECATIONS
|
|
25
35
|
|
|
26
36
|
#include "bucket.hxx"
|
|
37
|
+
#include "capella.hxx"
|
|
27
38
|
#include "capella_ca.hxx"
|
|
39
|
+
#include "cluster_label_listener.hxx"
|
|
40
|
+
#include "core/analytics_stream_component.hxx"
|
|
28
41
|
#include "core/app_telemetry_meter.hxx"
|
|
29
42
|
#include "core/app_telemetry_reporter.hxx"
|
|
43
|
+
#include "core/core_sdk_shim.hxx"
|
|
30
44
|
#include "core/diagnostics.hxx"
|
|
31
45
|
#include "core/error.hxx"
|
|
32
|
-
#include "core/
|
|
46
|
+
#include "core/http_component.hxx"
|
|
33
47
|
#include "core/impl/lookup_in_replica.hxx"
|
|
34
48
|
#include "core/impl/observe_seqno.hxx"
|
|
35
49
|
#include "core/io/http_command.hxx"
|
|
36
50
|
#include "core/io/http_message.hxx"
|
|
37
51
|
#include "core/io/http_session_manager.hxx"
|
|
38
52
|
#include "core/io/mcbp_session.hxx"
|
|
39
|
-
#
|
|
40
|
-
#include "core/io/config_tracker.hxx"
|
|
41
|
-
#endif
|
|
42
|
-
#include "cluster_label_listener.hxx"
|
|
53
|
+
#include "core/io/mcbp_traits.hxx"
|
|
43
54
|
#include "core/logger/logger.hxx"
|
|
55
|
+
#include "core/logger/redaction.hxx"
|
|
44
56
|
#include "core/management/analytics_link_azure_blob_external.hxx"
|
|
45
57
|
#include "core/management/analytics_link_couchbase_remote.hxx"
|
|
46
58
|
#include "core/management/analytics_link_s3_external.hxx"
|
|
@@ -59,6 +71,7 @@
|
|
|
59
71
|
#include "core/operations/document_get_and_touch.hxx"
|
|
60
72
|
#include "core/operations/document_get_any_replica.hxx"
|
|
61
73
|
#include "core/operations/document_get_projected.hxx"
|
|
74
|
+
#include "core/operations/document_get_replica.hxx"
|
|
62
75
|
#include "core/operations/document_increment.hxx"
|
|
63
76
|
#include "core/operations/document_insert.hxx"
|
|
64
77
|
#include "core/operations/document_lookup_in.hxx"
|
|
@@ -149,13 +162,14 @@
|
|
|
149
162
|
#include "core/orphan_reporter.hxx"
|
|
150
163
|
#include "core/platform/uuid.h"
|
|
151
164
|
#include "core/protocol/hello_feature.hxx"
|
|
165
|
+
#include "core/query_stream_component.hxx"
|
|
152
166
|
#include "core/service_type.hxx"
|
|
153
167
|
#include "core/tls_verify_mode.hxx"
|
|
154
168
|
#include "core/topology/capabilities.hxx"
|
|
155
169
|
#include "core/tracing/noop_tracer.hxx"
|
|
156
170
|
#include "core/tracing/threshold_logging_tracer.hxx"
|
|
157
171
|
#include "core/tracing/tracer_wrapper.hxx"
|
|
158
|
-
#include "core/utils/
|
|
172
|
+
#include "core/utils/deadline.hxx"
|
|
159
173
|
#include "core/utils/movable_function.hxx"
|
|
160
174
|
#include "crud_component.hxx"
|
|
161
175
|
#include "dispatcher.hxx"
|
|
@@ -174,9 +188,11 @@
|
|
|
174
188
|
#include <asio/post.hpp>
|
|
175
189
|
#include <asio/ssl/verify_mode.hpp>
|
|
176
190
|
|
|
191
|
+
#include <algorithm>
|
|
177
192
|
#include <atomic>
|
|
178
193
|
#include <chrono>
|
|
179
194
|
#include <cstring>
|
|
195
|
+
#include <list>
|
|
180
196
|
#include <map>
|
|
181
197
|
#include <memory>
|
|
182
198
|
#include <mutex>
|
|
@@ -202,6 +218,32 @@ class ping_collector_impl
|
|
|
202
218
|
std::mutex mutex_{};
|
|
203
219
|
|
|
204
220
|
public:
|
|
221
|
+
/**
|
|
222
|
+
* Counted like a reporter, so the ping cannot complete while pings are still being dispatched.
|
|
223
|
+
* This prevents erroneously returning early when the in-flight pings momentarily drop to 0.
|
|
224
|
+
*/
|
|
225
|
+
class dispatch_scope
|
|
226
|
+
{
|
|
227
|
+
public:
|
|
228
|
+
explicit dispatch_scope(std::shared_ptr<ping_collector_impl> collector)
|
|
229
|
+
: collector_{ std::move(collector) }
|
|
230
|
+
{
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
dispatch_scope(const dispatch_scope&) = delete;
|
|
234
|
+
dispatch_scope(dispatch_scope&&) = delete;
|
|
235
|
+
auto operator=(const dispatch_scope&) -> dispatch_scope& = delete;
|
|
236
|
+
auto operator=(dispatch_scope&&) -> dispatch_scope& = delete;
|
|
237
|
+
|
|
238
|
+
~dispatch_scope()
|
|
239
|
+
{
|
|
240
|
+
collector_->release();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private:
|
|
244
|
+
std::shared_ptr<ping_collector_impl> collector_;
|
|
245
|
+
};
|
|
246
|
+
|
|
205
247
|
ping_collector_impl(std::string report_id,
|
|
206
248
|
utils::movable_function<void(diag::ping_result)>&& handler)
|
|
207
249
|
: res_{ std::move(report_id), meta::sdk_id() }
|
|
@@ -226,11 +268,11 @@ public:
|
|
|
226
268
|
|
|
227
269
|
void report(diag::endpoint_ping_info&& info) override
|
|
228
270
|
{
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
invoke_handler();
|
|
271
|
+
{
|
|
272
|
+
const std::scoped_lock lock(mutex_);
|
|
273
|
+
res_.services[info.type].emplace_back(std::move(info));
|
|
233
274
|
}
|
|
275
|
+
release();
|
|
234
276
|
}
|
|
235
277
|
|
|
236
278
|
auto build_reporter() -> std::shared_ptr<diag::ping_reporter> override
|
|
@@ -239,17 +281,115 @@ public:
|
|
|
239
281
|
return shared_from_this();
|
|
240
282
|
}
|
|
241
283
|
|
|
284
|
+
[[nodiscard]] auto begin_dispatch() -> std::shared_ptr<dispatch_scope>
|
|
285
|
+
{
|
|
286
|
+
++expected_;
|
|
287
|
+
return std::make_shared<dispatch_scope>(shared_from_this());
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private:
|
|
291
|
+
void release()
|
|
292
|
+
{
|
|
293
|
+
if (--expected_ == 0) {
|
|
294
|
+
invoke_handler();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
242
298
|
void invoke_handler()
|
|
243
299
|
{
|
|
244
|
-
|
|
245
|
-
|
|
300
|
+
utils::movable_function<void(diag::ping_result)> handler{};
|
|
301
|
+
diag::ping_result res{};
|
|
302
|
+
{
|
|
303
|
+
const std::scoped_lock lock(mutex_);
|
|
304
|
+
if (handler_ == nullptr) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
handler = std::move(handler_);
|
|
246
308
|
handler_ = nullptr;
|
|
309
|
+
res = std::move(res_);
|
|
247
310
|
}
|
|
311
|
+
handler(std::move(res));
|
|
248
312
|
}
|
|
249
313
|
};
|
|
250
314
|
|
|
251
315
|
namespace
|
|
252
316
|
{
|
|
317
|
+
// A wait with an optional deadline, held by cluster_impl::defer_until_open() or
|
|
318
|
+
// cluster_impl::wait_until(). complete() runs on the IO thread; the destructor runs `resume` on
|
|
319
|
+
// whichever thread drops the last reference.
|
|
320
|
+
struct deferred_request {
|
|
321
|
+
utils::movable_function<void(std::error_code)> resume{};
|
|
322
|
+
std::unique_ptr<asio::steady_timer> deadline{};
|
|
323
|
+
std::atomic_bool completed{ false };
|
|
324
|
+
// Where the entry sits in cluster_impl::deferred_until_open_, while it is there. Guarded by
|
|
325
|
+
// deferred_until_open_mutex_.
|
|
326
|
+
std::optional<std::list<std::shared_ptr<deferred_request>>::iterator> queued{};
|
|
327
|
+
|
|
328
|
+
void complete(std::error_code ec)
|
|
329
|
+
{
|
|
330
|
+
if (completed.exchange(true)) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (deadline) {
|
|
334
|
+
// An armed timer keeps the entry and the io_context's work alive until it expires.
|
|
335
|
+
deadline->cancel();
|
|
336
|
+
// The deadline can pass while the IO thread is busy, before the timer callback runs. A
|
|
337
|
+
// request resumed after it would be dispatched with remaining_until()'s floor.
|
|
338
|
+
if (!ec && deadline->expiry() <= std::chrono::steady_clock::now()) {
|
|
339
|
+
ec = errc::common::unambiguous_timeout;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
auto handler = std::move(resume);
|
|
343
|
+
handler(ec);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// complete() for a thread other than the IO thread: the timer is not touched, and is left to
|
|
347
|
+
// expire or to be destroyed with its io_context.
|
|
348
|
+
void complete_off_io_thread(std::error_code ec)
|
|
349
|
+
{
|
|
350
|
+
if (completed.exchange(true)) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
auto handler = std::move(resume);
|
|
354
|
+
if (handler) {
|
|
355
|
+
handler(ec);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
deferred_request() = default;
|
|
360
|
+
deferred_request(const deferred_request&) = delete;
|
|
361
|
+
deferred_request(deferred_request&&) = delete;
|
|
362
|
+
auto operator=(const deferred_request&) -> deferred_request& = delete;
|
|
363
|
+
auto operator=(deferred_request&&) -> deferred_request& = delete;
|
|
364
|
+
|
|
365
|
+
// A stopped io_context drops the timer wait and any completion posted to it. The last reference
|
|
366
|
+
// then goes here, and the request completes with cluster_closed rather than never. The timer is
|
|
367
|
+
// not touched: its io_context may be shutting down.
|
|
368
|
+
~deferred_request()
|
|
369
|
+
{
|
|
370
|
+
try {
|
|
371
|
+
complete_off_io_thread(errc::network::cluster_closed);
|
|
372
|
+
} catch (...) { // NOLINT(bugprone-empty-catch)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
using utils::remaining_until;
|
|
378
|
+
|
|
379
|
+
// A durable mutation gets at least this long, counted from when it is issued.
|
|
380
|
+
constexpr std::chrono::milliseconds durability_timeout_floor{ 1'500 };
|
|
381
|
+
|
|
382
|
+
// Whether T has a client_context_id member: a request that can carry one, or a context that reports
|
|
383
|
+
// it.
|
|
384
|
+
template<typename T, typename = void>
|
|
385
|
+
struct has_client_context_id : std::false_type {
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
template<typename T>
|
|
389
|
+
struct has_client_context_id<T, std::void_t<decltype(std::declval<T&>().client_context_id)>>
|
|
390
|
+
: std::true_type {
|
|
391
|
+
};
|
|
392
|
+
|
|
253
393
|
template<typename Request>
|
|
254
394
|
auto
|
|
255
395
|
is_feature_supported(const Request& /* request */,
|
|
@@ -270,6 +410,10 @@ is_feature_supported(const operations::search_request& request,
|
|
|
270
410
|
if (request.vector_search && !capabilities.supports_vector_search()) {
|
|
271
411
|
return false;
|
|
272
412
|
}
|
|
413
|
+
// "none" is not behind the score fusion capability
|
|
414
|
+
if (is_score_fusion(request.scoring) && !capabilities.supports_score_fusion()) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
273
417
|
|
|
274
418
|
return true;
|
|
275
419
|
}
|
|
@@ -279,6 +423,7 @@ is_feature_supported(const operations::analytics_request& /*request*/,
|
|
|
279
423
|
const configuration_capabilities& capabilities,
|
|
280
424
|
const cluster_options& options) -> bool
|
|
281
425
|
{
|
|
426
|
+
// cppcheck-suppress knownConditionTrueFalse
|
|
282
427
|
if (operations::analytics_request::allow_enterprise_analytics) {
|
|
283
428
|
return true;
|
|
284
429
|
}
|
|
@@ -292,27 +437,75 @@ is_feature_supported(const operations::management::search_index_upsert_request&
|
|
|
292
437
|
{
|
|
293
438
|
return !request.index.is_vector_index() || capabilities.supports_vector_search();
|
|
294
439
|
}
|
|
440
|
+
|
|
441
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
442
|
+
// Extract the error_code from a response's error context. KV responses carry a
|
|
443
|
+
// key_value_error_context (ec() accessor); the query/analytics/search/view/management responses
|
|
444
|
+
// carry a plain error_context::* struct with an `ec` field.
|
|
445
|
+
inline auto
|
|
446
|
+
protostellar_response_ec(const key_value_error_context& ctx) -> std::error_code
|
|
447
|
+
{
|
|
448
|
+
return ctx.ec();
|
|
449
|
+
}
|
|
450
|
+
template<typename Context>
|
|
451
|
+
inline auto
|
|
452
|
+
protostellar_response_ec(const Context& ctx) -> std::error_code
|
|
453
|
+
{
|
|
454
|
+
return ctx.ec;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// The cluster's configured timeouts, in the form the component and the retry loop both read them
|
|
458
|
+
// from. Assigned per field rather than brace-initialised positionally: every member has the same
|
|
459
|
+
// type, so a mis-ordering is not a compile error, and an omitted one silently takes the struct's
|
|
460
|
+
// own default instead of the value the caller configured.
|
|
461
|
+
inline auto
|
|
462
|
+
make_component_timeouts(const cluster_options& options) -> protostellar::component_timeouts
|
|
463
|
+
{
|
|
464
|
+
protostellar::component_timeouts timeouts{};
|
|
465
|
+
timeouts.key_value = options.key_value_timeout;
|
|
466
|
+
timeouts.key_value_durable = options.key_value_durable_timeout;
|
|
467
|
+
timeouts.query = options.query_timeout;
|
|
468
|
+
timeouts.analytics = options.analytics_timeout;
|
|
469
|
+
timeouts.search = options.search_timeout;
|
|
470
|
+
timeouts.view = options.view_timeout;
|
|
471
|
+
timeouts.management = options.management_timeout;
|
|
472
|
+
return timeouts;
|
|
473
|
+
}
|
|
474
|
+
#endif
|
|
295
475
|
} // namespace
|
|
296
476
|
|
|
477
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
478
|
+
// Whether the couchbase2 component can carry this request, answered by asking whether it has an
|
|
479
|
+
// execute() overload for it.
|
|
480
|
+
//
|
|
481
|
+
// The alternative -- a hand-maintained list of request types -- has to agree with the overload set
|
|
482
|
+
// and nothing checks that it does. A type in the list with no overload fails to compile, so that
|
|
483
|
+
// half is safe; an overload with no entry in the list is silently unreachable, and that is how
|
|
484
|
+
// query_index_build_deferred_request came to be wired to a gateway RPC that no caller could
|
|
485
|
+
// reach. Detecting the overload makes the list *be* the overload set.
|
|
486
|
+
template<typename Request, typename = void>
|
|
487
|
+
struct component_routes : std::false_type {
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
template<typename Request>
|
|
491
|
+
struct component_routes<
|
|
492
|
+
Request,
|
|
493
|
+
std::void_t<decltype(std::declval<protostellar::component&>().execute(
|
|
494
|
+
std::declval<Request>(),
|
|
495
|
+
std::declval<utils::movable_function<void(typename Request::response_type)>>()))>>
|
|
496
|
+
: std::true_type {
|
|
497
|
+
};
|
|
498
|
+
#endif
|
|
499
|
+
|
|
297
500
|
class cluster_impl : public std::enable_shared_from_this<cluster_impl>
|
|
298
501
|
{
|
|
299
502
|
public:
|
|
300
|
-
#ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
|
|
301
|
-
explicit cluster_impl(asio::io_context& ctx)
|
|
302
|
-
: ctx_(ctx)
|
|
303
|
-
, work_(asio::make_work_guard(ctx_))
|
|
304
|
-
, session_manager_(std::make_shared<io::http_session_manager>(id_, ctx_, tls_, origin_))
|
|
305
|
-
, retry_backoff_(ctx_)
|
|
306
|
-
{
|
|
307
|
-
}
|
|
308
|
-
#else
|
|
309
503
|
explicit cluster_impl(asio::io_context& ctx)
|
|
310
504
|
: ctx_(ctx)
|
|
311
505
|
, work_(asio::make_work_guard(ctx_))
|
|
312
506
|
, session_manager_(std::make_shared<io::http_session_manager>(id_, ctx_, tls_, origin_))
|
|
313
507
|
{
|
|
314
508
|
}
|
|
315
|
-
#endif
|
|
316
509
|
|
|
317
510
|
auto io_context() -> asio::io_context&
|
|
318
511
|
{
|
|
@@ -334,7 +527,21 @@ public:
|
|
|
334
527
|
tls_options |= asio::ssl::context::no_tlsv1_2; // published: 2008, still in use
|
|
335
528
|
}
|
|
336
529
|
ctx->set_options(tls_options);
|
|
337
|
-
|
|
530
|
+
const auto requested_verify = origin_.options().tls_verify;
|
|
531
|
+
const auto effective_verify = effective_tls_verify_mode(requested_verify, has_capella_host);
|
|
532
|
+
if (requested_verify == tls_verify_mode::none && effective_verify == tls_verify_mode::peer) {
|
|
533
|
+
CB_LOG_WARNING(
|
|
534
|
+
"[{}]: TLS certificate verification cannot be disabled when connecting to Couchbase "
|
|
535
|
+
"Capella; enforcing peer verification (ignoring tls_verify=none).",
|
|
536
|
+
id_);
|
|
537
|
+
} else if (effective_verify == tls_verify_mode::none) {
|
|
538
|
+
CB_LOG_WARNING(
|
|
539
|
+
"[{}]: TLS certificate verification is DISABLED (tls_verify=none). The server's "
|
|
540
|
+
"identity will NOT be checked and the connection is vulnerable to interception. This "
|
|
541
|
+
"is insecure and must not be used in production.",
|
|
542
|
+
id_);
|
|
543
|
+
}
|
|
544
|
+
switch (effective_verify) {
|
|
338
545
|
case tls_verify_mode::none:
|
|
339
546
|
ctx->set_verify_mode(asio::ssl::verify_none);
|
|
340
547
|
break;
|
|
@@ -465,27 +672,58 @@ public:
|
|
|
465
672
|
return handler(errc::common::invalid_argument);
|
|
466
673
|
}
|
|
467
674
|
|
|
468
|
-
|
|
675
|
+
{
|
|
676
|
+
const std::scoped_lock lock(origin_mutex_);
|
|
677
|
+
origin_ = std::move(origin);
|
|
678
|
+
}
|
|
679
|
+
apply_log_redaction();
|
|
469
680
|
CB_LOG_DEBUG(R"(open cluster, id: "{}", core version: "{}", connection string: {}, {})",
|
|
470
681
|
id_,
|
|
471
682
|
couchbase::core::meta::sdk_semver(),
|
|
472
|
-
origin_.connection_string(),
|
|
473
|
-
origin_.to_json());
|
|
683
|
+
logger::user_data(origin_.connection_string()),
|
|
684
|
+
logger::user_data(origin_.to_json()));
|
|
685
|
+
// The scheme is recognised in every build; only the ability to serve it is conditional. The
|
|
686
|
+
// parser stays identical either way, so no API or ABI depends on the build mode -- a build
|
|
687
|
+
// without couchbase2 support answers with a named error instead of attempting an MCBP handshake
|
|
688
|
+
// against the gateway's gRPC port, which surfaced as an unrelated bootstrap timeout.
|
|
689
|
+
if (origin_.uses_protostellar()) {
|
|
690
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
691
|
+
return open_protostellar(std::move(handler));
|
|
692
|
+
#else
|
|
693
|
+
CB_LOG_ERROR(
|
|
694
|
+
R"([{}]: cannot open "{}": this build has no couchbase2 support )"
|
|
695
|
+
"(COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2 is off), so the couchbase2:// scheme cannot be "
|
|
696
|
+
"served.",
|
|
697
|
+
id_,
|
|
698
|
+
logger::user_data(origin_.connection_string()));
|
|
699
|
+
stopped_ = true;
|
|
700
|
+
work_.reset();
|
|
701
|
+
return handler(errc::common::feature_not_available);
|
|
702
|
+
#endif
|
|
703
|
+
}
|
|
704
|
+
{
|
|
705
|
+
const std::scoped_lock lock(deferred_until_open_mutex_);
|
|
706
|
+
opening_ = true;
|
|
707
|
+
}
|
|
474
708
|
setup_observability();
|
|
475
709
|
if (origin_.options().enable_dns_srv) {
|
|
476
|
-
|
|
477
|
-
std::string port;
|
|
478
|
-
std::tie(hostname, port) = origin_.next_address();
|
|
710
|
+
auto [hostname, port] = origin_.next_address();
|
|
479
711
|
dns_srv_tracker_ = std::make_shared<impl::dns_srv_tracker>(
|
|
480
712
|
ctx_, hostname, origin_.options().dns_config, origin_.options().enable_tls);
|
|
713
|
+
{
|
|
714
|
+
// close() completes the open itself rather than waiting out the lookup.
|
|
715
|
+
const std::scoped_lock lock(dns_srv_open_handler_mutex_);
|
|
716
|
+
dns_srv_open_handler_ = std::move(handler);
|
|
717
|
+
}
|
|
481
718
|
return asio::post(asio::bind_executor(
|
|
482
|
-
ctx_,
|
|
483
|
-
[self = shared_from_this(),
|
|
484
|
-
hostname = std::move(hostname),
|
|
485
|
-
handler = std::move(handler)]() mutable {
|
|
719
|
+
ctx_, [self = shared_from_this(), hostname = std::move(hostname)]() mutable {
|
|
486
720
|
return self->dns_srv_tracker_->get_srv_nodes(
|
|
487
|
-
[self, hostname = std::move(hostname)
|
|
488
|
-
|
|
721
|
+
[self, hostname = std::move(hostname)](origin::node_list nodes,
|
|
722
|
+
std::error_code ec) mutable {
|
|
723
|
+
auto handler = self->take_dns_srv_open_handler();
|
|
724
|
+
if (!handler) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
489
727
|
if (ec) {
|
|
490
728
|
#ifdef __clang_analyzer__
|
|
491
729
|
// TODO(CXXCBC-549): clang-tidy-19 reports potential memory leak here
|
|
@@ -496,11 +734,13 @@ public:
|
|
|
496
734
|
});
|
|
497
735
|
}
|
|
498
736
|
if (!nodes.empty()) {
|
|
737
|
+
const std::scoped_lock lock(self->origin_mutex_);
|
|
499
738
|
self->origin_.set_nodes(std::move(nodes));
|
|
500
739
|
CB_LOG_INFO(
|
|
501
740
|
"replace list of bootstrap nodes with addresses from DNS SRV of \"{}\": [{}]",
|
|
502
|
-
hostname,
|
|
503
|
-
|
|
741
|
+
logger::system_data(hostname),
|
|
742
|
+
logger::system_data_list(self->origin_.get_node_addresses(),
|
|
743
|
+
logger::list_entries::quoted));
|
|
504
744
|
}
|
|
505
745
|
return self->do_open(std::move(handler));
|
|
506
746
|
});
|
|
@@ -509,40 +749,63 @@ public:
|
|
|
509
749
|
do_open(std::move(handler));
|
|
510
750
|
}
|
|
511
751
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
752
|
+
// Runs `resume` once: with the error that `start` reports through the completion it is given, or
|
|
753
|
+
// with unambiguous_timeout when `deadline` passes first. A success reported after `deadline` is a
|
|
754
|
+
// timeout too. `resume` runs on the IO thread while the cluster is open. After close() the IO
|
|
755
|
+
// thread may never run again, so a stopped cluster completes at once with cluster_closed, and a
|
|
756
|
+
// wait that reports after close() runs `resume` on the thread that reports.
|
|
757
|
+
template<typename Start>
|
|
758
|
+
void wait_until(std::chrono::steady_clock::time_point deadline,
|
|
759
|
+
utils::movable_function<void(std::error_code)>&& resume,
|
|
760
|
+
Start&& start)
|
|
515
761
|
{
|
|
516
762
|
if (stopped_) {
|
|
517
|
-
return
|
|
518
|
-
}
|
|
519
|
-
if (background_open_started_) {
|
|
520
|
-
CB_LOG_DEBUG("Background open already started for cluster, id: \"{}\"", id_);
|
|
521
|
-
return handler({});
|
|
522
|
-
}
|
|
523
|
-
if (origin.get_nodes().empty()) {
|
|
524
|
-
stopped_ = true;
|
|
525
|
-
work_.reset();
|
|
526
|
-
return handler(errc::common::invalid_argument);
|
|
763
|
+
return resume(errc::network::cluster_closed);
|
|
527
764
|
}
|
|
765
|
+
auto entry = std::make_shared<deferred_request>();
|
|
766
|
+
entry->resume = std::move(resume);
|
|
767
|
+
entry->deadline = std::make_unique<asio::steady_timer>(ctx_, deadline);
|
|
768
|
+
entry->deadline->async_wait([entry](std::error_code ec) {
|
|
769
|
+
if (ec != asio::error::operation_aborted) {
|
|
770
|
+
entry->complete(errc::common::unambiguous_timeout);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
// Held weakly: `start` may keep this completion after the wait ends, as a bucket's queue of
|
|
774
|
+
// configuration waiters does. The pending timer keeps the entry alive until it completes.
|
|
775
|
+
std::forward<Start>(start)(
|
|
776
|
+
[self = shared_from_this(),
|
|
777
|
+
weak_entry = std::weak_ptr<deferred_request>(entry)](std::error_code ec) {
|
|
778
|
+
auto entry = weak_entry.lock();
|
|
779
|
+
if (!entry) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
if (self->ctx_.get_executor().running_in_this_thread()) {
|
|
783
|
+
return entry->complete(ec);
|
|
784
|
+
}
|
|
785
|
+
// A wait that started just before close() can report after the IO thread has stopped, and
|
|
786
|
+
// a stopped io_context keeps what is posted to it without running it.
|
|
787
|
+
if (self->stopped_) {
|
|
788
|
+
entry->complete_off_io_thread(ec);
|
|
789
|
+
// The armed timer would keep the io_context busy until the deadline; dropped if it
|
|
790
|
+
// stopped.
|
|
791
|
+
return asio::post(self->ctx_, [entry] {
|
|
792
|
+
entry->deadline->cancel();
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
asio::post(self->ctx_, [entry, ec] {
|
|
796
|
+
entry->complete(ec);
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
}
|
|
528
800
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
// at this point we will infinitely try to connect
|
|
537
|
-
if (origin_.options().enable_dns_srv) {
|
|
538
|
-
do_background_dns_srv_open();
|
|
539
|
-
} else {
|
|
540
|
-
do_background_open();
|
|
541
|
-
}
|
|
542
|
-
background_open_started_ = true;
|
|
543
|
-
return handler({});
|
|
801
|
+
void open_bucket_until(const std::string& bucket_name,
|
|
802
|
+
std::chrono::steady_clock::time_point deadline,
|
|
803
|
+
utils::movable_function<void(std::error_code)>&& handler)
|
|
804
|
+
{
|
|
805
|
+
wait_until(deadline, std::move(handler), [&](auto&& done) {
|
|
806
|
+
open_bucket(bucket_name, std::forward<decltype(done)>(done));
|
|
807
|
+
});
|
|
544
808
|
}
|
|
545
|
-
#endif
|
|
546
809
|
|
|
547
810
|
void open_bucket(const std::string& bucket_name,
|
|
548
811
|
utils::movable_function<void(std::error_code)>&& handler)
|
|
@@ -550,6 +813,19 @@ public:
|
|
|
550
813
|
if (stopped_) {
|
|
551
814
|
return handler(errc::network::cluster_closed);
|
|
552
815
|
}
|
|
816
|
+
if (is_protostellar()) {
|
|
817
|
+
// couchbase2 has no bucket bootstrap. Opening one would dial the gateway over MCBP, which it
|
|
818
|
+
// does not serve, and fail with unambiguous_timeout only at bootstrap_timeout.
|
|
819
|
+
return handler(errc::common::feature_not_available);
|
|
820
|
+
}
|
|
821
|
+
if (defer_until_open([&] {
|
|
822
|
+
return [self = shared_from_this(), bucket_name, handler = std::move(handler)](
|
|
823
|
+
std::error_code /* no deadline */) mutable {
|
|
824
|
+
self->open_bucket(bucket_name, std::move(handler));
|
|
825
|
+
};
|
|
826
|
+
})) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
553
829
|
std::shared_ptr<bucket> b{};
|
|
554
830
|
{
|
|
555
831
|
const std::scoped_lock lock(buckets_mutex_);
|
|
@@ -623,6 +899,8 @@ public:
|
|
|
623
899
|
|
|
624
900
|
auto update_credentials(const cluster_credentials& auth) -> core::error
|
|
625
901
|
{
|
|
902
|
+
// origin_mutex_ as well: while open() bootstraps, the IO thread writes origin_.
|
|
903
|
+
const std::scoped_lock lock(update_credentials_mutex_, origin_mutex_);
|
|
626
904
|
if (stopped_) {
|
|
627
905
|
return { errc::network::cluster_closed, {} };
|
|
628
906
|
}
|
|
@@ -645,8 +923,7 @@ public:
|
|
|
645
923
|
});
|
|
646
924
|
|
|
647
925
|
if (auth.requires_tls()) {
|
|
648
|
-
// Recreate and atomically swap the TLS context
|
|
649
|
-
// old one.
|
|
926
|
+
// Recreate and atomically swap the TLS context. KV sessions keep the one they connected with.
|
|
650
927
|
const auto new_ctx = std::make_shared<asio::ssl::context>(asio::ssl::context::tls_client);
|
|
651
928
|
auto ec = configure_tls_context(new_ctx);
|
|
652
929
|
if (ec) {
|
|
@@ -655,6 +932,11 @@ public:
|
|
|
655
932
|
return { ec, {} };
|
|
656
933
|
}
|
|
657
934
|
tls_.set_ctx(new_ctx);
|
|
935
|
+
// A pooled HTTP connection presents the certificate it was opened with. A password or JWT is
|
|
936
|
+
// sent with each request.
|
|
937
|
+
if (auth.uses_certificate()) {
|
|
938
|
+
session_manager_->retire_sessions();
|
|
939
|
+
}
|
|
658
940
|
}
|
|
659
941
|
|
|
660
942
|
if (auth.uses_jwt()) {
|
|
@@ -665,14 +947,6 @@ public:
|
|
|
665
947
|
return {};
|
|
666
948
|
}
|
|
667
949
|
|
|
668
|
-
auto origin() const -> std::pair<std::error_code, couchbase::core::origin>
|
|
669
|
-
{
|
|
670
|
-
if (stopped_) {
|
|
671
|
-
return { errc::network::cluster_closed, {} };
|
|
672
|
-
}
|
|
673
|
-
return { {}, origin_ };
|
|
674
|
-
}
|
|
675
|
-
|
|
676
950
|
template<class Request,
|
|
677
951
|
class Handler,
|
|
678
952
|
typename std::enable_if_t<
|
|
@@ -685,7 +959,65 @@ public:
|
|
|
685
959
|
return handler(request.make_response(
|
|
686
960
|
make_key_value_error_context(errc::network::cluster_closed, request.id), response_type{}));
|
|
687
961
|
}
|
|
962
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
963
|
+
if (auto component = protostellar_component(); component) {
|
|
964
|
+
if constexpr (protostellar::component_supports_v<Request>) {
|
|
965
|
+
execute_protostellar(std::move(request), std::forward<Handler>(handler));
|
|
966
|
+
return;
|
|
967
|
+
} else {
|
|
968
|
+
return handler(request.make_response(
|
|
969
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id),
|
|
970
|
+
response_type{}));
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
#endif
|
|
974
|
+
auto timeout = request.timeout.value_or(default_timeout_for(service_type::key_value));
|
|
975
|
+
if constexpr (io::mcbp_traits::supports_durability_v<Request>) {
|
|
976
|
+
if (request.durability_level != durability_level::none &&
|
|
977
|
+
timeout < durability_timeout_floor) {
|
|
978
|
+
CB_LOG_DEBUG(
|
|
979
|
+
R"(Timeout is too low for operation with durability, increasing to sensible value. key="{}", timeout={}ms, floor={}ms)",
|
|
980
|
+
logger::document(request.id),
|
|
981
|
+
timeout.count(),
|
|
982
|
+
durability_timeout_floor.count());
|
|
983
|
+
timeout = durability_timeout_floor;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
execute_until(std::move(request),
|
|
987
|
+
std::chrono::steady_clock::now() + timeout,
|
|
988
|
+
std::forward<Handler>(handler));
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// A key-value request's timeout counts from when it was issued: waiting for the bootstrap or for
|
|
992
|
+
// the bucket to open spends it, and the command gets only what is left. Past `deadline` the
|
|
993
|
+
// request gets unambiguous_timeout and is not dispatched.
|
|
994
|
+
template<class Request, class Handler>
|
|
995
|
+
void execute_until(Request request,
|
|
996
|
+
std::chrono::steady_clock::time_point deadline,
|
|
997
|
+
Handler&& handler)
|
|
998
|
+
{
|
|
999
|
+
using response_type = typename Request::encoded_response_type;
|
|
1000
|
+
if (stopped_) {
|
|
1001
|
+
return handler(request.make_response(
|
|
1002
|
+
make_key_value_error_context(errc::network::cluster_closed, request.id), response_type{}));
|
|
1003
|
+
}
|
|
1004
|
+
auto resume = [&] {
|
|
1005
|
+
return [self = shared_from_this(),
|
|
1006
|
+
deadline,
|
|
1007
|
+
request = std::move(request),
|
|
1008
|
+
handler = std::forward<Handler>(handler)](std::error_code ec) mutable {
|
|
1009
|
+
if (ec) {
|
|
1010
|
+
return handler(
|
|
1011
|
+
request.make_response(make_key_value_error_context(ec, request.id), response_type{}));
|
|
1012
|
+
}
|
|
1013
|
+
self->execute_until(std::move(request), deadline, std::move(handler));
|
|
1014
|
+
};
|
|
1015
|
+
};
|
|
1016
|
+
if (defer_until_open(resume, deadline)) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
688
1019
|
if (auto bucket = find_bucket_by_name(request.id.bucket()); bucket != nullptr) {
|
|
1020
|
+
request.timeout = remaining_until(deadline);
|
|
689
1021
|
return bucket->execute(std::move(request), std::forward<Handler>(handler));
|
|
690
1022
|
}
|
|
691
1023
|
if (request.id.bucket().empty()) {
|
|
@@ -693,16 +1025,7 @@ public:
|
|
|
693
1025
|
make_key_value_error_context(errc::common::bucket_not_found, request.id), response_type{}));
|
|
694
1026
|
}
|
|
695
1027
|
auto bucket_name = request.id.bucket();
|
|
696
|
-
|
|
697
|
-
[self = shared_from_this(),
|
|
698
|
-
request = std::move(request),
|
|
699
|
-
handler = std::forward<Handler>(handler)](std::error_code ec) mutable {
|
|
700
|
-
if (ec) {
|
|
701
|
-
return handler(request.make_response(
|
|
702
|
-
make_key_value_error_context(ec, request.id), response_type{}));
|
|
703
|
-
}
|
|
704
|
-
return self->execute(std::move(request), std::forward<Handler>(handler));
|
|
705
|
-
});
|
|
1028
|
+
open_bucket_until(bucket_name, deadline, resume());
|
|
706
1029
|
}
|
|
707
1030
|
|
|
708
1031
|
template<class Request,
|
|
@@ -716,6 +1039,50 @@ public:
|
|
|
716
1039
|
if (stopped_) {
|
|
717
1040
|
return handler(request.make_response({ errc::network::cluster_closed }, response_type{}));
|
|
718
1041
|
}
|
|
1042
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
1043
|
+
if (auto component = protostellar_component(); component) {
|
|
1044
|
+
if constexpr (component_routes<Request>::value) {
|
|
1045
|
+
// Query/analytics/search/view/management requests carry no retry_context on the request, so
|
|
1046
|
+
// execute_protostellar_http supplies the retry state and re-dispatches a retryable
|
|
1047
|
+
// (service_not_available) response under the same overall-deadline bound as the KV path.
|
|
1048
|
+
execute_protostellar_http(std::move(request), std::forward<Handler>(handler));
|
|
1049
|
+
return;
|
|
1050
|
+
} else {
|
|
1051
|
+
// Remaining management ops over couchbase2 are not wired yet. Reject cleanly rather than
|
|
1052
|
+
// falling through to the MCBP session manager, which is never bootstrapped on this path.
|
|
1053
|
+
return handler(
|
|
1054
|
+
request.make_response({ errc::common::feature_not_available }, response_type{}));
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
#endif
|
|
1058
|
+
// The request's timeout counts from now, not from the end of the bootstrap.
|
|
1059
|
+
if (const auto deadline = std::chrono::steady_clock::now() +
|
|
1060
|
+
request.timeout.value_or(default_timeout_for(request.type));
|
|
1061
|
+
defer_until_open(
|
|
1062
|
+
[&] {
|
|
1063
|
+
return [self = shared_from_this(),
|
|
1064
|
+
deadline,
|
|
1065
|
+
request = std::move(request),
|
|
1066
|
+
handler = std::forward<Handler>(handler)](std::error_code ec) mutable {
|
|
1067
|
+
if (ec) {
|
|
1068
|
+
auto response = request.make_response({ ec }, response_type{});
|
|
1069
|
+
if constexpr (has_client_context_id<Request>::value &&
|
|
1070
|
+
has_client_context_id<decltype(response.ctx)>::value) {
|
|
1071
|
+
// The caller's id, or a generated one, as a dispatched request carries.
|
|
1072
|
+
response.ctx.client_context_id = request.client_context_id.has_value()
|
|
1073
|
+
? *request.client_context_id
|
|
1074
|
+
: uuid::to_string(uuid::random());
|
|
1075
|
+
}
|
|
1076
|
+
return handler(std::move(response));
|
|
1077
|
+
}
|
|
1078
|
+
request.timeout = remaining_until(deadline);
|
|
1079
|
+
self->execute(std::move(request), std::move(handler));
|
|
1080
|
+
};
|
|
1081
|
+
},
|
|
1082
|
+
deadline)) {
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
// cppcheck-suppress knownConditionTrueFalse
|
|
719
1086
|
if (!is_feature_supported(
|
|
720
1087
|
request, session_manager_->configuration_capabilities(), origin_.options())) {
|
|
721
1088
|
return handler(
|
|
@@ -737,32 +1104,59 @@ public:
|
|
|
737
1104
|
bucket_capability cap,
|
|
738
1105
|
Handler&& handler)
|
|
739
1106
|
{
|
|
1107
|
+
// There is no bucket capability to read over couchbase2, and no MCBP connection to read it
|
|
1108
|
+
// over: open_bucket() below would bootstrap a session against a port the gateway does not
|
|
1109
|
+
// serve, so a request that only wants a capability checked would fail on the check rather
|
|
1110
|
+
// than on what it asked for. The gateway still validates the capability the request depends
|
|
1111
|
+
// on -- history retention needs a magma bucket wherever the request arrives from -- so
|
|
1112
|
+
// routing straight through refuses the same requests, one hop later and in the server's own
|
|
1113
|
+
// words.
|
|
1114
|
+
if (is_protostellar()) {
|
|
1115
|
+
return execute(std::move(request), std::forward<Handler>(handler));
|
|
1116
|
+
}
|
|
1117
|
+
// The waits for the bootstrap and for the bucket's configuration spend the request's timeout;
|
|
1118
|
+
// the request gets what is left. The timed hold comes first: open_bucket() would hold the
|
|
1119
|
+
// request without a deadline, and it would stay queued after timing out.
|
|
1120
|
+
const auto deadline = std::chrono::steady_clock::now() +
|
|
1121
|
+
request.timeout.value_or(default_timeout_for(request.type));
|
|
1122
|
+
if (defer_until_open(
|
|
1123
|
+
[&] {
|
|
1124
|
+
return [self = shared_from_this(),
|
|
1125
|
+
deadline,
|
|
1126
|
+
cap,
|
|
1127
|
+
request = std::move(request),
|
|
1128
|
+
handler = std::forward<Handler>(handler)](std::error_code ec) mutable {
|
|
1129
|
+
if (ec) {
|
|
1130
|
+
return handler(request.make_response({ ec }, {}));
|
|
1131
|
+
}
|
|
1132
|
+
request.timeout = remaining_until(deadline);
|
|
1133
|
+
self->execute_with_bucket_capability_check(
|
|
1134
|
+
std::move(request), cap, std::move(handler));
|
|
1135
|
+
};
|
|
1136
|
+
},
|
|
1137
|
+
deadline)) {
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
740
1140
|
auto bucket_name = request.bucket_name;
|
|
741
|
-
return
|
|
1141
|
+
return with_bucket_configuration(
|
|
742
1142
|
bucket_name,
|
|
1143
|
+
deadline,
|
|
743
1144
|
[self = shared_from_this(),
|
|
744
|
-
|
|
1145
|
+
deadline,
|
|
745
1146
|
cap,
|
|
746
1147
|
request = std::move(request),
|
|
747
|
-
handler = std::forward<Handler>(handler)](
|
|
1148
|
+
handler = std::forward<Handler>(handler)](
|
|
1149
|
+
std::error_code ec, const std::shared_ptr<topology::configuration>& config) mutable {
|
|
748
1150
|
if (ec) {
|
|
749
1151
|
handler(request.make_response({ ec }, {}));
|
|
750
1152
|
return;
|
|
751
1153
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
if (!config->capabilities.has_bucket_capability(cap)) {
|
|
761
|
-
handler(request.make_response({ errc::common::feature_not_available }, {}));
|
|
762
|
-
return;
|
|
763
|
-
}
|
|
764
|
-
return self->execute(std::move(request), std::forward<Handler>(handler));
|
|
765
|
-
});
|
|
1154
|
+
if (!config->capabilities.has_bucket_capability(cap)) {
|
|
1155
|
+
handler(request.make_response({ errc::common::feature_not_available }, {}));
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
request.timeout = remaining_until(deadline);
|
|
1159
|
+
return self->execute(std::move(request), std::forward<Handler>(handler));
|
|
766
1160
|
});
|
|
767
1161
|
}
|
|
768
1162
|
|
|
@@ -805,8 +1199,413 @@ public:
|
|
|
805
1199
|
}
|
|
806
1200
|
}
|
|
807
1201
|
|
|
1202
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
1203
|
+
// couchbase2:// open path: the gateway does the routing, so there is no MCBP config bootstrap.
|
|
1204
|
+
// Build a single gRPC channel + KV component to the first endpoint and report success.
|
|
1205
|
+
void open_protostellar(utils::movable_function<void(std::error_code)>&& handler)
|
|
1206
|
+
{
|
|
1207
|
+
// next_address() dereferences the node iterator; a couchbase2:// string that parsed with no
|
|
1208
|
+
// bootstrap node would make it UB, so fail cleanly instead.
|
|
1209
|
+
if (origin_.get_nodes().empty()) {
|
|
1210
|
+
return handler(errc::common::invalid_argument);
|
|
1211
|
+
}
|
|
1212
|
+
auto [hostname, port] = origin_.next_address();
|
|
1213
|
+
auto endpoint = hostname + ":" + port;
|
|
1214
|
+
// Several nodes reach here from an origin assembled programmatically (set_nodes(), or the
|
|
1215
|
+
// hostname/port constructors), and from a connection string naming more than one host:
|
|
1216
|
+
// parse_connection_string() records that as an error on the connection_string, but no caller
|
|
1217
|
+
// reads that field, so the origin is built with every host regardless. Kept as a log rather
|
|
1218
|
+
// than a rejection: one endpoint is as good as another here, and since the node list is
|
|
1219
|
+
// shuffled unless preserve_bootstrap_nodes_order is set, the message is what says which one
|
|
1220
|
+
// this process picked.
|
|
1221
|
+
if (origin_.get_nodes().size() > 1) {
|
|
1222
|
+
CB_LOG_INFO("[{}]: couchbase2 uses a single gateway endpoint (\"{}\"); the remaining {} "
|
|
1223
|
+
"node(s) on this origin are ignored (the gateway fronts the cluster).",
|
|
1224
|
+
id_,
|
|
1225
|
+
logger::system_data(endpoint),
|
|
1226
|
+
origin_.get_nodes().size() - 1);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Apply the same Capella safety clamp as the MCBP path (configure_tls_options): force peer
|
|
1230
|
+
// verification against the managed service even if tls_verify=none was requested, and surface
|
|
1231
|
+
// the same warnings. Without this the couchbase2 path would silently allow MITM on Capella.
|
|
1232
|
+
auto options = origin_.options();
|
|
1233
|
+
const auto requested_verify = options.tls_verify;
|
|
1234
|
+
options.tls_verify = effective_tls_verify_mode(requested_verify, is_capella_host(hostname));
|
|
1235
|
+
if (requested_verify == tls_verify_mode::none && options.tls_verify == tls_verify_mode::peer) {
|
|
1236
|
+
CB_LOG_WARNING(R"([{}]: tls_verify=none was requested but "{}" is a Capella endpoint; )"
|
|
1237
|
+
"enforcing peer verification.",
|
|
1238
|
+
id_,
|
|
1239
|
+
logger::system_data(hostname));
|
|
1240
|
+
}
|
|
1241
|
+
// The "verification is disabled" warning is not repeated here: make_channel_credentials() is
|
|
1242
|
+
// the one point every couchbase2 channel passes through and warns there, so it also covers
|
|
1243
|
+
// callers that build a component directly. Only the Capella clamp is specific to this path.
|
|
1244
|
+
|
|
1245
|
+
// Build the channel before starting observability. The two sibling failure paths in open()
|
|
1246
|
+
// (an origin with no nodes) latch stopped_ and release the work guard while nothing has been
|
|
1247
|
+
// started yet; doing the same after setup_observability() would leave its tracer, meter and
|
|
1248
|
+
// reporters running with close() short-circuiting on stopped_ and never stopping them.
|
|
1249
|
+
std::shared_ptr<grpc::Channel> channel;
|
|
1250
|
+
try {
|
|
1251
|
+
channel = protostellar::make_channel(endpoint, options, origin_.credentials());
|
|
1252
|
+
} catch (const std::exception& e) {
|
|
1253
|
+
CB_LOG_ERROR(R"([{}]: failed to establish couchbase2 channel: {})", id_, e.what());
|
|
1254
|
+
stopped_ = true;
|
|
1255
|
+
work_.reset();
|
|
1256
|
+
return handler(errc::common::invalid_argument);
|
|
1257
|
+
}
|
|
1258
|
+
setup_observability();
|
|
1259
|
+
{
|
|
1260
|
+
// Written under the lock for symmetry with close(): the assignment itself happens before any
|
|
1261
|
+
// caller can reach execute() (the open handler has not fired yet), but leaving one access
|
|
1262
|
+
// unguarded is how the invariant gets lost later.
|
|
1263
|
+
const std::scoped_lock lock(protostellar_mutex_);
|
|
1264
|
+
protostellar_ = std::make_shared<protostellar::component>(
|
|
1265
|
+
ctx_,
|
|
1266
|
+
protostellar::component_config{ std::move(channel),
|
|
1267
|
+
origin_.credentials(),
|
|
1268
|
+
make_component_timeouts(options),
|
|
1269
|
+
make_compression_settings(options) });
|
|
1270
|
+
}
|
|
1271
|
+
CB_LOG_INFO(
|
|
1272
|
+
R"(open couchbase2 cluster, id: "{}", endpoint: "{}")", id_, logger::system_data(endpoint));
|
|
1273
|
+
return handler({});
|
|
1274
|
+
}
|
|
1275
|
+
#endif
|
|
1276
|
+
|
|
1277
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
1278
|
+
// The KV component, or nullptr when this cluster is not a couchbase2 one. Returns a copy under
|
|
1279
|
+
// the lock, in the same shape as find_bucket_by_name(): execute() runs on the caller's thread
|
|
1280
|
+
// while close() resets the member on the io thread, and reading a shared_ptr that another thread
|
|
1281
|
+
// is writing is a data race whatever the refcount does. Holding a copy also keeps the component
|
|
1282
|
+
// alive for the duration of the call that is about to use it.
|
|
1283
|
+
[[nodiscard]] auto protostellar_component() const -> std::shared_ptr<protostellar::component>
|
|
1284
|
+
{
|
|
1285
|
+
const std::scoped_lock lock(protostellar_mutex_);
|
|
1286
|
+
return protostellar_;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// Whether the couchbase2 channel is READY. An idle channel is asked to connect, so polling this
|
|
1290
|
+
// also establishes the connection. cluster_closed once the cluster is closed.
|
|
1291
|
+
[[nodiscard]] auto protostellar_channel_ready() const -> std::pair<std::error_code, bool>
|
|
1292
|
+
{
|
|
1293
|
+
const auto component = protostellar_component();
|
|
1294
|
+
if (stopped_ || component == nullptr) {
|
|
1295
|
+
return { errc::network::cluster_closed, false };
|
|
1296
|
+
}
|
|
1297
|
+
const auto& channel = component->channel();
|
|
1298
|
+
auto state = channel->GetState(/* try_to_connect */ true);
|
|
1299
|
+
if (state != GRPC_CHANNEL_READY && state != GRPC_CHANNEL_SHUTDOWN) {
|
|
1300
|
+
// On gRPC builds that progress a connection only under polling, an attempt stalls while no
|
|
1301
|
+
// call is in flight. A wait whose deadline has already passed polls once without blocking.
|
|
1302
|
+
channel->WaitForStateChange(state, std::chrono::system_clock::now());
|
|
1303
|
+
state = channel->GetState(/* try_to_connect */ false);
|
|
1304
|
+
}
|
|
1305
|
+
switch (state) {
|
|
1306
|
+
case GRPC_CHANNEL_READY:
|
|
1307
|
+
return { {}, true };
|
|
1308
|
+
case GRPC_CHANNEL_SHUTDOWN:
|
|
1309
|
+
return { errc::network::cluster_closed, false };
|
|
1310
|
+
default:
|
|
1311
|
+
return { {}, false };
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
#endif
|
|
1315
|
+
|
|
1316
|
+
// True when the cluster was configured with a couchbase2:// connection string -- a property of
|
|
1317
|
+
// the origin, so it holds from construction and stays true after close(). Compound /
|
|
1318
|
+
// self-dispatching operations (replica reads, legacy durability) check this to reject cleanly
|
|
1319
|
+
// instead of falling through to the MCBP bootstrap path, which does not exist on couchbase2.
|
|
1320
|
+
//
|
|
1321
|
+
// It answers "which transport was configured", never "is the transport usable". Most callers want
|
|
1322
|
+
// exactly the former and reject with feature_not_available, which is a capability statement and
|
|
1323
|
+
// is correct whether the cluster is open or closed. The callers that do need liveness test
|
|
1324
|
+
// stopped_ themselves, or route into a dispatch that re-reads the component under the lock.
|
|
1325
|
+
[[nodiscard]] auto is_protostellar() const -> bool
|
|
1326
|
+
{
|
|
1327
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
1328
|
+
return origin_.uses_protostellar();
|
|
1329
|
+
#else
|
|
1330
|
+
return false;
|
|
1331
|
+
#endif
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
1335
|
+
// Execute a couchbase2 operation through the component with transport-level retry. Retryable
|
|
1336
|
+
// errors (see protostellar::retry_reason_for) consult the request's retry strategy (falling back
|
|
1337
|
+
// to the origin default), and on a positive decision the request is re-issued after a backoff.
|
|
1338
|
+
// Idempotency is enforced by the strategy, so ambiguous mutations are not replayed.
|
|
1339
|
+
//
|
|
1340
|
+
// The deadline is resolved here, before the first attempt, and every attempt is issued against
|
|
1341
|
+
// it. Falling back to the cluster's KV default when the request carries no timeout is what bounds
|
|
1342
|
+
// the loop at all: the public API leaves the per-operation timeout unset unless the caller asks
|
|
1343
|
+
// for one, and the strategy never refuses a retry for service_not_available, so a request issued
|
|
1344
|
+
// with default options against an unavailable gateway would otherwise retry until the process
|
|
1345
|
+
// ended -- never completing, and never reporting an error either.
|
|
1346
|
+
//
|
|
1347
|
+
// Which default applies is resolve_kv_timeout's rule, not a second copy of it: a durable mutation
|
|
1348
|
+
// is bounded by KvDurableTimeout and everything else by the standard KV timeout. It has to be
|
|
1349
|
+
// applied here, because dispatch_protostellar narrows request->timeout on every attempt including
|
|
1350
|
+
// the first -- so the component is always handed an engaged timeout and its own fallback never
|
|
1351
|
+
// selects for a request that arrives through this loop. Resolving with the standard default here
|
|
1352
|
+
// would cap a durable mutation at the budget the durable default exists to replace.
|
|
1353
|
+
template<class Request, class Handler>
|
|
1354
|
+
void execute_protostellar(Request request, Handler&& handler)
|
|
1355
|
+
{
|
|
1356
|
+
// The fallback below is a KV default, so routing anything else here would bound it with a
|
|
1357
|
+
// budget belonging to another service. Non-KV requests carry no retry_context and have their
|
|
1358
|
+
// own path.
|
|
1359
|
+
static_assert(protostellar::component_supports_v<Request>,
|
|
1360
|
+
"execute_protostellar resolves the key-value timeout");
|
|
1361
|
+
const auto deadline =
|
|
1362
|
+
std::chrono::steady_clock::now() +
|
|
1363
|
+
protostellar::resolve_kv_timeout(request.timeout,
|
|
1364
|
+
protostellar::requested_durability(request),
|
|
1365
|
+
make_component_timeouts(origin_.options()));
|
|
1366
|
+
// Held by shared_ptr and re-issued from there. The request owns the document body for a
|
|
1367
|
+
// mutation, so copying it to keep a re-issue possible charged every operation on the data plane
|
|
1368
|
+
// for a retry that in normal operation never happens.
|
|
1369
|
+
dispatch_protostellar(
|
|
1370
|
+
std::make_shared<Request>(std::move(request)), std::forward<Handler>(handler), deadline);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// One attempt of the above, re-entered from the backoff timer for each retry.
|
|
1374
|
+
template<class Request, class Handler>
|
|
1375
|
+
void dispatch_protostellar(std::shared_ptr<Request> request,
|
|
1376
|
+
Handler&& handler,
|
|
1377
|
+
std::chrono::steady_clock::time_point deadline)
|
|
1378
|
+
{
|
|
1379
|
+
// Each attempt gets what is LEFT of the operation's budget rather than a fresh full-length one.
|
|
1380
|
+
// Without this an attempt started just inside the deadline would run for the whole timeout
|
|
1381
|
+
// again, so a retried operation could take roughly twice its nominal budget. Narrowing keeps
|
|
1382
|
+
// the request carrying a duration -- which is all the component accepts -- while making that
|
|
1383
|
+
// duration mean "until the operation's deadline". A non-positive remainder is not
|
|
1384
|
+
// special-cased, and is in fact how the loop ends: the component rejects an exhausted budget
|
|
1385
|
+
// as a timeout instead of dispatching a call with no deadline.
|
|
1386
|
+
request->timeout = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
1387
|
+
deadline - std::chrono::steady_clock::now());
|
|
1388
|
+
// Re-read through the locked accessor on every attempt: a retry re-enters this
|
|
1389
|
+
// function from a timer, long after the caller checked.
|
|
1390
|
+
auto component = protostellar_component();
|
|
1391
|
+
if (!component) {
|
|
1392
|
+
return handler(
|
|
1393
|
+
request->make_response(make_key_value_error_context(errc::network::cluster_closed,
|
|
1394
|
+
request->id,
|
|
1395
|
+
request->retries.retry_attempts(),
|
|
1396
|
+
request->retries.retry_reasons()),
|
|
1397
|
+
typename Request::encoded_response_type{}));
|
|
1398
|
+
}
|
|
1399
|
+
component->execute(
|
|
1400
|
+
*request,
|
|
1401
|
+
[self = shared_from_this(), request, handler = std::forward<Handler>(handler), deadline](
|
|
1402
|
+
auto response) mutable {
|
|
1403
|
+
if (auto reason = protostellar::retry_reason_for(protostellar_response_ec(response.ctx));
|
|
1404
|
+
reason.has_value()) {
|
|
1405
|
+
auto strategy = request->retries.strategy()
|
|
1406
|
+
? request->retries.strategy()
|
|
1407
|
+
: self->origin_.options().default_retry_strategy_;
|
|
1408
|
+
if (strategy) {
|
|
1409
|
+
const auto action = strategy->retry_after(request->retries, reason.value());
|
|
1410
|
+
// The wait is capped at the deadline rather than the retry being refused for landing
|
|
1411
|
+
// past it. Refusing would end the operation early on the last attempt's transport
|
|
1412
|
+
// error -- temporary_failure for an unreachable gateway -- while the caller's budget
|
|
1413
|
+
// was still running, so the same condition that times out over the classic transport
|
|
1414
|
+
// would report a different code here. Capping lets the operation use its whole budget
|
|
1415
|
+
// and end on the deadline: the attempt scheduled at it gets a non-positive remainder
|
|
1416
|
+
// above, which the component answers as a timeout carrying the accumulated attempts
|
|
1417
|
+
// and reasons. That is terminal, so the loop cannot spin on it -- retry_reason_for
|
|
1418
|
+
// does not classify a timeout as retryable. Both other implementations of this loop
|
|
1419
|
+
// cap the same way (io/retry_orchestrator.hxx cap_duration, and Java's
|
|
1420
|
+
// RetryOrchestratorProtostellar.capDuration).
|
|
1421
|
+
const auto when =
|
|
1422
|
+
std::min(std::chrono::steady_clock::now() + action.duration(), deadline);
|
|
1423
|
+
if (action.need_to_retry()) {
|
|
1424
|
+
if (self->stopped_) {
|
|
1425
|
+
// A retry was due and will not happen: close() latches stopped_ before it clears
|
|
1426
|
+
// the component, so this completion arrived after the close. The transport error
|
|
1427
|
+
// the next attempt would have replaced is not the caller's answer -- reporting it
|
|
1428
|
+
// says "try again" about a cluster that is gone -- so report the close, as the
|
|
1429
|
+
// re-entry below does once the component is cleared. Gating on a due retry keeps a
|
|
1430
|
+
// terminal response out of this: request_canceled in particular carries the
|
|
1431
|
+
// CXXCBC-909 rule that the statement must not be replayed.
|
|
1432
|
+
return handler(request->make_response(
|
|
1433
|
+
make_key_value_error_context(errc::network::cluster_closed,
|
|
1434
|
+
request->id,
|
|
1435
|
+
request->retries.retry_attempts(),
|
|
1436
|
+
request->retries.retry_reasons()),
|
|
1437
|
+
typename Request::encoded_response_type{}));
|
|
1438
|
+
}
|
|
1439
|
+
// Recorded before the re-issue, so the next attempt carries the accumulated count
|
|
1440
|
+
// and the error context it builds reports the retries that preceded it.
|
|
1441
|
+
request->retries.record_retry_attempt(reason.value());
|
|
1442
|
+
auto timer = std::make_shared<asio::steady_timer>(self->ctx_);
|
|
1443
|
+
timer->expires_at(when);
|
|
1444
|
+
timer->async_wait([self,
|
|
1445
|
+
request,
|
|
1446
|
+
handler = std::move(handler),
|
|
1447
|
+
deadline,
|
|
1448
|
+
response = std::move(response),
|
|
1449
|
+
timer](const std::error_code& timer_ec) mutable {
|
|
1450
|
+
if (timer_ec) {
|
|
1451
|
+
// Timer cancelled: deliver the last retryable response rather than dropping
|
|
1452
|
+
// the caller's completion, which would hang it.
|
|
1453
|
+
handler(std::move(response));
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
// A cluster closed during the backoff is answered by dispatch_protostellar,
|
|
1457
|
+
// which re-reads the component under the lock and reports cluster_closed. The
|
|
1458
|
+
// last retryable response would instead report a temporary failure, which reads
|
|
1459
|
+
// as "retry later" for a cluster that is gone.
|
|
1460
|
+
self->dispatch_protostellar(std::move(request), std::move(handler), deadline);
|
|
1461
|
+
});
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
// A declined retry reports the transport error even when the cluster is closing: the
|
|
1467
|
+
// strategy, not the close, is what ended the operation, and the caller gets the answer a
|
|
1468
|
+
// close-free run would have given. The close is reported only where it is the more specific
|
|
1469
|
+
// answer -- above, where a retry was due and will not happen, and at the top of this
|
|
1470
|
+
// function, where no attempt was made and there is no transport error to report.
|
|
1471
|
+
handler(std::move(response));
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
template<typename Request>
|
|
1476
|
+
static auto timeout_for(const protostellar::component_timeouts& timeouts)
|
|
1477
|
+
-> std::chrono::milliseconds
|
|
1478
|
+
{
|
|
1479
|
+
if constexpr (std::is_same_v<Request, operations::query_request>) {
|
|
1480
|
+
return timeouts.query;
|
|
1481
|
+
} else if constexpr (std::is_same_v<Request, operations::analytics_request>) {
|
|
1482
|
+
return timeouts.analytics;
|
|
1483
|
+
} else if constexpr (std::is_same_v<Request, operations::search_request>) {
|
|
1484
|
+
return timeouts.search;
|
|
1485
|
+
} else if constexpr (std::is_same_v<Request, operations::document_view_request>) {
|
|
1486
|
+
return timeouts.view;
|
|
1487
|
+
} else {
|
|
1488
|
+
return timeouts.management;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// Retry for non-KV couchbase2 operations (query/analytics/search/view/management). These
|
|
1493
|
+
// HTTP-shaped requests carry no retry_context, so the retry state lives here: a heap
|
|
1494
|
+
// retry_context threaded across re-dispatches, mutated in place so the strategy sees the
|
|
1495
|
+
// accumulated attempts. In practice only service_not_available (a transport UNAVAILABLE -- the
|
|
1496
|
+
// call never reached a server) arises on this path: retry_reason_for is shared with the KV path
|
|
1497
|
+
// and can also report key_value_locked, which no query/analytics/search/view/management response
|
|
1498
|
+
// produces. The strategy permits service_not_available for non-idempotent ops too, so the fixed
|
|
1499
|
+
// retry_context<false> idempotency is not load-bearing. The strategy is the origin
|
|
1500
|
+
// default (these requests have no per-request override). Like the KV path, retries are bounded by
|
|
1501
|
+
// the strategy and by an overall deadline stamped before the first attempt: the request's own
|
|
1502
|
+
// timeout when it carries one, the per-service default otherwise. Every attempt is therefore
|
|
1503
|
+
// bounded, including the first.
|
|
1504
|
+
//
|
|
1505
|
+
// This overload stamps that deadline; the one below carries it. Taking a plain time_point rather
|
|
1506
|
+
// than an optional keeps "no deadline yet" unrepresentable once the loop has started.
|
|
1507
|
+
template<class Request, class Handler>
|
|
1508
|
+
void execute_protostellar_http(Request request, Handler&& handler)
|
|
1509
|
+
{
|
|
1510
|
+
const auto default_timeout = timeout_for<Request>(make_component_timeouts(origin_.options()));
|
|
1511
|
+
const auto deadline =
|
|
1512
|
+
std::chrono::steady_clock::now() + request.timeout.value_or(default_timeout);
|
|
1513
|
+
execute_protostellar_http(
|
|
1514
|
+
std::move(request),
|
|
1515
|
+
std::forward<Handler>(handler),
|
|
1516
|
+
std::make_shared<io::retry_context<false>>(origin_.options().default_retry_strategy_),
|
|
1517
|
+
deadline);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
template<class Request, class Handler>
|
|
1521
|
+
void execute_protostellar_http(Request request,
|
|
1522
|
+
Handler&& handler,
|
|
1523
|
+
std::shared_ptr<io::retry_context<false>> retries,
|
|
1524
|
+
std::chrono::steady_clock::time_point deadline)
|
|
1525
|
+
{
|
|
1526
|
+
// Re-read through the locked accessor on every attempt: a retry re-enters this
|
|
1527
|
+
// function from a timer, long after the caller checked.
|
|
1528
|
+
auto component = protostellar_component();
|
|
1529
|
+
if (!component) {
|
|
1530
|
+
auto response = request.make_response({ errc::network::cluster_closed },
|
|
1531
|
+
typename Request::encoded_response_type{});
|
|
1532
|
+
// A cluster closed mid-backoff reaches here with attempts already behind it, and dropping
|
|
1533
|
+
// them would report the operation as never retried.
|
|
1534
|
+
response.ctx.retry_attempts = retries->retry_attempts();
|
|
1535
|
+
response.ctx.retry_reasons = retries->retry_reasons();
|
|
1536
|
+
return handler(std::move(response));
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
auto request_ptr = std::make_shared<Request>(std::move(request));
|
|
1540
|
+
component->execute(
|
|
1541
|
+
*request_ptr,
|
|
1542
|
+
[self = shared_from_this(),
|
|
1543
|
+
request_ptr,
|
|
1544
|
+
handler = std::forward<Handler>(handler),
|
|
1545
|
+
retries,
|
|
1546
|
+
deadline](auto response) mutable {
|
|
1547
|
+
if (auto reason = protostellar::retry_reason_for(protostellar_response_ec(response.ctx));
|
|
1548
|
+
reason.has_value()) {
|
|
1549
|
+
if (retries->strategy()) {
|
|
1550
|
+
const auto action = retries->strategy()->retry_after(*retries, reason.value());
|
|
1551
|
+
const auto when =
|
|
1552
|
+
std::min(std::chrono::steady_clock::now() + action.duration(), deadline);
|
|
1553
|
+
if (action.need_to_retry()) {
|
|
1554
|
+
if (self->stopped_) {
|
|
1555
|
+
// See dispatch_protostellar(): a retry the close will not let happen reports the
|
|
1556
|
+
// close, not the error the next attempt would have replaced.
|
|
1557
|
+
auto closed = request_ptr->make_response({ errc::network::cluster_closed },
|
|
1558
|
+
typename Request::encoded_response_type{});
|
|
1559
|
+
closed.ctx.retry_attempts = retries->retry_attempts();
|
|
1560
|
+
closed.ctx.retry_reasons = retries->retry_reasons();
|
|
1561
|
+
return handler(std::move(closed));
|
|
1562
|
+
}
|
|
1563
|
+
retries->record_retry_attempt(reason.value());
|
|
1564
|
+
auto timer = std::make_shared<asio::steady_timer>(self->ctx_);
|
|
1565
|
+
timer->expires_at(when);
|
|
1566
|
+
timer->async_wait([self,
|
|
1567
|
+
request_ptr = std::move(request_ptr),
|
|
1568
|
+
handler = std::move(handler),
|
|
1569
|
+
retries,
|
|
1570
|
+
deadline,
|
|
1571
|
+
response = std::move(response),
|
|
1572
|
+
timer](const std::error_code& timer_ec) mutable {
|
|
1573
|
+
if (timer_ec) {
|
|
1574
|
+
// Timer cancelled: deliver the last response rather than dropping the caller's
|
|
1575
|
+
// completion, which would hang it.
|
|
1576
|
+
response.ctx.retry_attempts = retries->retry_attempts();
|
|
1577
|
+
response.ctx.retry_reasons = retries->retry_reasons();
|
|
1578
|
+
handler(std::move(response));
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
// A cluster closed during the backoff is answered by the re-entry below, which
|
|
1582
|
+
// re-reads the component under the lock and reports cluster_closed -- the same
|
|
1583
|
+
// answer a caller gets when the cluster is already closed on arrival.
|
|
1584
|
+
auto next_request = *request_ptr;
|
|
1585
|
+
next_request.timeout = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
1586
|
+
deadline - std::chrono::steady_clock::now());
|
|
1587
|
+
self->execute_protostellar_http(
|
|
1588
|
+
std::move(next_request), std::move(handler), retries, deadline);
|
|
1589
|
+
});
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
// As in dispatch_protostellar(): a declined retry reports the transport error, because the
|
|
1595
|
+
// strategy rather than the close is what ended the operation.
|
|
1596
|
+
response.ctx.retry_attempts = retries->retry_attempts();
|
|
1597
|
+
response.ctx.retry_reasons = retries->retry_reasons();
|
|
1598
|
+
handler(std::move(response));
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
#endif
|
|
1602
|
+
|
|
808
1603
|
void do_open(utils::movable_function<void(std::error_code)> handler)
|
|
809
1604
|
{
|
|
1605
|
+
// After a DNS SRV lookup, close() may already have run; a bootstrap now would outlive it.
|
|
1606
|
+
if (stopped_) {
|
|
1607
|
+
return handler(errc::network::cluster_closed);
|
|
1608
|
+
}
|
|
810
1609
|
// Warn users if idle_http_connection_timeout is too close to server idle timeouts
|
|
811
1610
|
if (origin_.options().idle_http_connection_timeout > std::chrono::milliseconds(4'500)) {
|
|
812
1611
|
CB_LOG_INFO("[{}]: The SDK may produce trivial warnings due to the idle HTTP connection "
|
|
@@ -841,7 +1640,16 @@ public:
|
|
|
841
1640
|
|
|
842
1641
|
if (origin_.options().enable_tls) {
|
|
843
1642
|
const auto ctx = tls_.get_ctx();
|
|
844
|
-
|
|
1643
|
+
std::error_code ec{};
|
|
1644
|
+
{
|
|
1645
|
+
// configure_tls_context() reads the credentials, which a forked child's
|
|
1646
|
+
// set_authenticator() can rewrite while its reopen runs.
|
|
1647
|
+
const std::scoped_lock lock(update_credentials_mutex_);
|
|
1648
|
+
ec = configure_tls_context(ctx);
|
|
1649
|
+
if (!ec) {
|
|
1650
|
+
session_ = io::mcbp_session(id_, {}, ctx_, tls_, origin_, dns_srv_tracker_);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
845
1653
|
if (ec) {
|
|
846
1654
|
#ifdef __clang_analyzer__
|
|
847
1655
|
// TODO(CXXCBC-549): clang-tidy-19 reports potential memory leak here
|
|
@@ -851,13 +1659,14 @@ public:
|
|
|
851
1659
|
return handler(ec);
|
|
852
1660
|
});
|
|
853
1661
|
}
|
|
854
|
-
session_ = io::mcbp_session(id_, {}, ctx_, tls_, origin_, dns_srv_tracker_);
|
|
855
1662
|
} else {
|
|
1663
|
+
const std::scoped_lock lock(update_credentials_mutex_);
|
|
856
1664
|
session_ = io::mcbp_session(id_, {}, ctx_, origin_, dns_srv_tracker_);
|
|
857
1665
|
}
|
|
858
1666
|
session_->bootstrap([self = shared_from_this(), handler = std::move(handler)](
|
|
859
1667
|
std::error_code ec, const topology::configuration& config) mutable {
|
|
860
1668
|
if (!ec) {
|
|
1669
|
+
std::unique_lock origin_lock(self->origin_mutex_);
|
|
861
1670
|
if (self->origin_.options().network == "auto") {
|
|
862
1671
|
self->origin_.options().network =
|
|
863
1672
|
config.select_network(self->session_->bootstrap_hostname());
|
|
@@ -871,208 +1680,44 @@ public:
|
|
|
871
1680
|
self->origin_.options().network);
|
|
872
1681
|
}
|
|
873
1682
|
}
|
|
874
|
-
if (self->origin_.options().network != "default") {
|
|
875
|
-
self->origin_.set_nodes_from_config(config);
|
|
876
|
-
CB_LOG_INFO(
|
|
877
|
-
"replace list of bootstrap nodes with addresses of alternative network \"{}\": [{}]",
|
|
878
|
-
self->origin_.options().network,
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
self->
|
|
886
|
-
self->session_->on_configuration_update(self->
|
|
887
|
-
self->
|
|
888
|
-
self->
|
|
889
|
-
self->
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
#ifdef __clang_analyzer__
|
|
897
|
-
// TODO(CXXCBC-549): clang-tidy-19 reports potential memory leak here
|
|
898
|
-
[[clang::suppress]]
|
|
899
|
-
#endif
|
|
900
|
-
return self->close([ec, handler = std::move(handler)]() mutable {
|
|
901
|
-
handler(ec);
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
handler(ec);
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
#ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
|
|
909
|
-
void do_background_open()
|
|
910
|
-
{
|
|
911
|
-
// TODO(JC): retries on more failures? Right now only retry if load_verify_file() fails...
|
|
912
|
-
|
|
913
|
-
// Disables TLS v1.2 which should be okay cloud/columnar default.
|
|
914
|
-
auto ctx = tls_.get_ctx();
|
|
915
|
-
configure_tls_options(true, ctx);
|
|
916
|
-
if (origin_.options().security_options.trust_only_capella) {
|
|
917
|
-
std::error_code ec{};
|
|
918
|
-
CB_LOG_DEBUG(R"([{}]: use Capella CA for TLS verify)", id_);
|
|
919
|
-
ctx->add_certificate_authority(
|
|
920
|
-
asio::const_buffer(couchbase::core::default_ca::capellaCaCert,
|
|
921
|
-
strlen(couchbase::core::default_ca::capellaCaCert)),
|
|
922
|
-
ec);
|
|
923
|
-
if (ec) {
|
|
924
|
-
CB_LOG_WARNING("[{}]: unable to load Capella CAs: {}", id_, ec.message());
|
|
925
|
-
// we don't consider this fatal and try to continue without it
|
|
926
|
-
}
|
|
927
|
-
} else if (origin_.options().security_options.trust_only_pem_file ||
|
|
928
|
-
origin_.options().security_options.trust_only_pem_string) {
|
|
929
|
-
if (origin_.options().trust_certificate.empty() /* No CA certificate (or other SDK-specific
|
|
930
|
-
trust source) is specified */
|
|
931
|
-
&& origin_.options()
|
|
932
|
-
.trust_certificate_value.empty() /* and certificate value has not been specified */
|
|
933
|
-
&& origin_.options().tls_verify !=
|
|
934
|
-
tls_verify_mode::none /* The user did not disable all TLS verification */) {
|
|
935
|
-
CB_LOG_WARNING("[{}] When TLS is enabled, the cluster options must specify certificate(s) "
|
|
936
|
-
"to trust or ensure that they are "
|
|
937
|
-
"available in system CA store.",
|
|
938
|
-
id_);
|
|
939
|
-
}
|
|
940
|
-
std::error_code ec{};
|
|
941
|
-
// load only the explicit certificate
|
|
942
|
-
// system and default capella certificates are not loaded
|
|
943
|
-
if (!origin_.options().trust_certificate_value.empty()) {
|
|
944
|
-
CB_LOG_DEBUG(R"([{}]: use TLS certificate passed through via options object)", id_);
|
|
945
|
-
ctx->add_certificate_authority(
|
|
946
|
-
asio::const_buffer(origin_.options().trust_certificate_value.data(),
|
|
947
|
-
origin_.options().trust_certificate_value.size()),
|
|
948
|
-
ec);
|
|
949
|
-
if (ec) {
|
|
950
|
-
CB_LOG_WARNING(
|
|
951
|
-
"[{}]: unable to load CA passed via options object: {}", id_, ec.message());
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
if (!origin_.options().trust_certificate.empty()) {
|
|
955
|
-
CB_LOG_DEBUG(
|
|
956
|
-
R"([{}]: use TLS verify file: "{}")", id_, origin_.options().trust_certificate);
|
|
957
|
-
ctx->load_verify_file(origin_.options().trust_certificate, ec);
|
|
958
|
-
if (ec) {
|
|
959
|
-
CB_LOG_ERROR("[{}]: unable to load verify file \"{}\": {}",
|
|
960
|
-
id_,
|
|
961
|
-
origin_.options().trust_certificate,
|
|
962
|
-
ec.message());
|
|
963
|
-
auto backoff = std::chrono::milliseconds(500);
|
|
964
|
-
CB_LOG_DEBUG(
|
|
965
|
-
"[{}] waiting for {}ms before retrying TLS verify file.", id_, backoff.count());
|
|
966
|
-
backoff_then_retry(backoff, [self = shared_from_this()]() {
|
|
967
|
-
self->do_background_open();
|
|
968
|
-
});
|
|
969
|
-
}
|
|
1683
|
+
if (self->origin_.options().network != "default") {
|
|
1684
|
+
self->origin_.set_nodes_from_config(config);
|
|
1685
|
+
CB_LOG_INFO(
|
|
1686
|
+
"replace list of bootstrap nodes with addresses of alternative network \"{}\": [{}]",
|
|
1687
|
+
self->origin_.options().network,
|
|
1688
|
+
logger::system_data_list(
|
|
1689
|
+
self->origin_.get_node_addresses(), logger::list_entries::quoted, ","));
|
|
1690
|
+
}
|
|
1691
|
+
origin_lock.unlock();
|
|
1692
|
+
// FIXME(SA): fix the session manager to receive initial configuration and cluster-wide
|
|
1693
|
+
// session to poll for updates like the bucket does. Or just subscribe before the bootstrap.
|
|
1694
|
+
self->session_manager_->set_configuration(config, self->origin_.options());
|
|
1695
|
+
self->session_->on_configuration_update(self->session_manager_);
|
|
1696
|
+
self->session_->on_configuration_update(self->app_telemetry_reporter_);
|
|
1697
|
+
self->session_->on_configuration_update(self->cluster_label_listener_);
|
|
1698
|
+
self->app_telemetry_reporter_->update_config(config);
|
|
1699
|
+
self->cluster_label_listener_->update_config(config);
|
|
1700
|
+
self->session_->on_stop([self]() {
|
|
1701
|
+
if (self->session_) {
|
|
1702
|
+
self->session_.reset();
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
970
1705
|
}
|
|
971
|
-
} else if (origin_.options().security_options.trust_only_platform) {
|
|
972
|
-
// TODO(CXXCBC-548): security_options updates (use Mozilla certs?)
|
|
973
|
-
CB_LOG_DEBUG(R"([{}]: use default CA for TLS verify)", id_);
|
|
974
|
-
std::error_code ec{};
|
|
975
|
-
// load system certificates
|
|
976
|
-
ctx->set_default_verify_paths(ec);
|
|
977
1706
|
if (ec) {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
for (const auto& cert : origin_.options().security_options.trust_only_certificates) {
|
|
986
|
-
ctx->add_certificate_authority(asio::const_buffer(cert.data(), cert.size()), ec);
|
|
987
|
-
if (ec) {
|
|
988
|
-
CB_LOG_WARNING("[{}]: unable to load CA: {}", id_, ec.message());
|
|
989
|
-
}
|
|
1707
|
+
#ifdef __clang_analyzer__
|
|
1708
|
+
// TODO(CXXCBC-549): clang-tidy-19 reports potential memory leak here
|
|
1709
|
+
[[clang::suppress]]
|
|
1710
|
+
#endif
|
|
1711
|
+
return self->close([ec, handler = std::move(handler)]() mutable {
|
|
1712
|
+
handler(ec);
|
|
1713
|
+
});
|
|
990
1714
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
id_, origin_, ctx_, tls_, dns_srv_tracker_);
|
|
997
|
-
config_tracker_->register_bootstrap_notification_subscriber(session_manager_);
|
|
998
|
-
create_cluster_sessions();
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
void backoff_then_retry(std::chrono::milliseconds backoff,
|
|
1002
|
-
utils::movable_function<void()> callback)
|
|
1003
|
-
{
|
|
1004
|
-
retry_backoff_.expires_after(backoff);
|
|
1005
|
-
retry_backoff_.async_wait(
|
|
1006
|
-
[self = shared_from_this(), cb = std::move(callback)](std::error_code ec) {
|
|
1007
|
-
if (ec == asio::error::operation_aborted || self->stopped_) {
|
|
1008
|
-
return;
|
|
1009
|
-
}
|
|
1010
|
-
if (ec) {
|
|
1011
|
-
CB_LOG_WARNING("[{}] Retry callback received error ec={}.", self->id_, ec.message());
|
|
1012
|
-
}
|
|
1013
|
-
cb();
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
void do_background_dns_srv_open()
|
|
1018
|
-
{
|
|
1019
|
-
std::string hostname;
|
|
1020
|
-
std::string port;
|
|
1021
|
-
std::tie(hostname, port) = origin_.next_address();
|
|
1022
|
-
dns_srv_tracker_ = std::make_shared<impl::dns_srv_tracker>(
|
|
1023
|
-
ctx_, hostname, origin_.options().dns_config, origin_.options().enable_tls);
|
|
1024
|
-
return asio::post(asio::bind_executor(
|
|
1025
|
-
ctx_, [self = shared_from_this(), hostname = std::move(hostname)]() mutable {
|
|
1026
|
-
return self->dns_srv_tracker_->get_srv_nodes(
|
|
1027
|
-
[self, hostname = std::move(hostname)](origin::node_list nodes,
|
|
1028
|
-
std::error_code ec) mutable {
|
|
1029
|
-
if (ec) {
|
|
1030
|
-
auto backoff = std::chrono::milliseconds(500);
|
|
1031
|
-
self->session_manager_->notify_bootstrap_error({ ec, ec.message(), hostname, {} });
|
|
1032
|
-
CB_LOG_DEBUG(
|
|
1033
|
-
"[{}] waiting for {}ms before retrying DNS query.", self->id_, backoff.count());
|
|
1034
|
-
self->backoff_then_retry(backoff, [self]() {
|
|
1035
|
-
self->do_background_dns_srv_open();
|
|
1036
|
-
});
|
|
1037
|
-
return;
|
|
1038
|
-
}
|
|
1039
|
-
if (!nodes.empty()) {
|
|
1040
|
-
self->origin_.set_nodes(std::move(nodes));
|
|
1041
|
-
CB_LOG_INFO(
|
|
1042
|
-
"[{}] Replace list of bootstrap nodes with addresses from DNS SRV of \"{}\": [{}]",
|
|
1043
|
-
self->id_,
|
|
1044
|
-
hostname,
|
|
1045
|
-
utils::join_strings(self->origin_.get_nodes(), ", "));
|
|
1046
|
-
}
|
|
1047
|
-
return self->do_background_open();
|
|
1048
|
-
});
|
|
1049
|
-
}));
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
void create_cluster_sessions()
|
|
1053
|
-
{
|
|
1054
|
-
config_tracker_->create_sessions(
|
|
1055
|
-
[self = shared_from_this()](std::error_code ec,
|
|
1056
|
-
const topology::configuration& cfg,
|
|
1057
|
-
const cluster_options& options) mutable {
|
|
1058
|
-
if (ec) {
|
|
1059
|
-
auto backoff = std::chrono::milliseconds(500);
|
|
1060
|
-
CB_LOG_DEBUG("[{}] Waiting for {}ms before retrying to create cluster sessions.",
|
|
1061
|
-
self->id_,
|
|
1062
|
-
backoff.count());
|
|
1063
|
-
self->backoff_then_retry(backoff, [self]() {
|
|
1064
|
-
self->create_cluster_sessions();
|
|
1065
|
-
});
|
|
1066
|
-
} else {
|
|
1067
|
-
self->session_manager_->set_configuration(cfg, options);
|
|
1068
|
-
self->config_tracker_->on_configuration_update(self->session_manager_);
|
|
1069
|
-
self->config_tracker_->on_configuration_update(self->app_telemetry_reporter_);
|
|
1070
|
-
self->app_telemetry_reporter_->update_config(cfg);
|
|
1071
|
-
self->config_tracker_->register_state_listener();
|
|
1072
|
-
}
|
|
1073
|
-
});
|
|
1715
|
+
// After the open handler, so a held request sees what the handler set up inline, such as the
|
|
1716
|
+
// transactions connect() creates when no metadata collection is configured.
|
|
1717
|
+
handler(ec);
|
|
1718
|
+
self->run_deferred_until_open();
|
|
1719
|
+
});
|
|
1074
1720
|
}
|
|
1075
|
-
#endif
|
|
1076
1721
|
|
|
1077
1722
|
void with_bucket_configuration(
|
|
1078
1723
|
const std::string& bucket_name,
|
|
@@ -1089,6 +1734,13 @@ public:
|
|
|
1089
1734
|
bucket_name,
|
|
1090
1735
|
[self = shared_from_this(), bucket_name, handler = std::move(handler)](auto ec) mutable {
|
|
1091
1736
|
if (ec) {
|
|
1737
|
+
// If the cluster was torn down while this open_bucket was parked in the bootstrap window,
|
|
1738
|
+
// the bucket bootstrap is aborted with request_canceled. Surface that to the caller as
|
|
1739
|
+
// cluster_closed so an in-flight operation parked here (e.g. a scan) observes the
|
|
1740
|
+
// cluster-close contract rather than a bare cancellation.
|
|
1741
|
+
if (self->stopped_) {
|
|
1742
|
+
return handler(errc::network::cluster_closed, nullptr);
|
|
1743
|
+
}
|
|
1092
1744
|
return handler(ec, nullptr);
|
|
1093
1745
|
}
|
|
1094
1746
|
|
|
@@ -1099,6 +1751,32 @@ public:
|
|
|
1099
1751
|
});
|
|
1100
1752
|
}
|
|
1101
1753
|
|
|
1754
|
+
// with_bucket_configuration(), or unambiguous_timeout with no configuration once `deadline`
|
|
1755
|
+
// passes.
|
|
1756
|
+
void with_bucket_configuration(
|
|
1757
|
+
const std::string& bucket_name,
|
|
1758
|
+
std::chrono::steady_clock::time_point deadline,
|
|
1759
|
+
utils::movable_function<void(std::error_code, std::shared_ptr<topology::configuration>)>&&
|
|
1760
|
+
handler)
|
|
1761
|
+
{
|
|
1762
|
+
// Written before the completion is dispatched, and read only after it ran.
|
|
1763
|
+
auto config = std::make_shared<std::shared_ptr<topology::configuration>>();
|
|
1764
|
+
wait_until(
|
|
1765
|
+
deadline,
|
|
1766
|
+
[config, handler = std::move(handler)](std::error_code ec) mutable {
|
|
1767
|
+
handler(ec, ec ? nullptr : std::move(*config));
|
|
1768
|
+
},
|
|
1769
|
+
[&](auto&& done) {
|
|
1770
|
+
with_bucket_configuration(
|
|
1771
|
+
bucket_name,
|
|
1772
|
+
[config, done = std::forward<decltype(done)>(done)](
|
|
1773
|
+
std::error_code ec, std::shared_ptr<topology::configuration> c) mutable {
|
|
1774
|
+
*config = std::move(c);
|
|
1775
|
+
done(ec);
|
|
1776
|
+
});
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1102
1780
|
void ping(std::optional<std::string> report_id,
|
|
1103
1781
|
std::optional<std::string> bucket_name,
|
|
1104
1782
|
std::set<service_type> services,
|
|
@@ -1108,7 +1786,7 @@ public:
|
|
|
1108
1786
|
if (!report_id) {
|
|
1109
1787
|
report_id = std::make_optional(uuid::to_string(uuid::random()));
|
|
1110
1788
|
}
|
|
1111
|
-
if (stopped_) {
|
|
1789
|
+
if (stopped_ || is_protostellar()) {
|
|
1112
1790
|
return handler({ report_id.value(), meta::sdk_id() });
|
|
1113
1791
|
}
|
|
1114
1792
|
if (services.empty()) {
|
|
@@ -1128,20 +1806,44 @@ public:
|
|
|
1128
1806
|
handler = std::move(handler)]() mutable {
|
|
1129
1807
|
auto collector =
|
|
1130
1808
|
std::make_shared<ping_collector_impl>(report_id.value(), std::move(handler));
|
|
1809
|
+
// Held until every endpoint this ping covers has taken a reporter. This prevents returning
|
|
1810
|
+
// early when the number of in-flight pings momentarily drops to 0.
|
|
1811
|
+
auto dispatch = collector->begin_dispatch();
|
|
1131
1812
|
if (bucket_name) {
|
|
1132
1813
|
if (services.find(service_type::key_value) != services.end()) {
|
|
1133
1814
|
if (auto bucket = cluster->find_bucket_by_name(bucket_name.value()); bucket) {
|
|
1134
|
-
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1815
|
+
bucket->ping(collector, timeout);
|
|
1816
|
+
} else {
|
|
1817
|
+
// Capture a weak_ptr, not a strong cluster: open_bucket parks this callback in the
|
|
1818
|
+
// bucket-bootstrap queue, which close() does not drain. A strong self-capture would
|
|
1819
|
+
// form a cluster<->parked-callback cycle that pins the ping collector past cluster
|
|
1820
|
+
// close, so its destructor -- the backstop that reports the ping result -- would
|
|
1821
|
+
// never run and the caller would hang. With a weak capture the callback is released
|
|
1822
|
+
// when the cluster is torn down, the collector is destroyed, and its destructor
|
|
1823
|
+
// delivers the report.
|
|
1824
|
+
//
|
|
1825
|
+
// The dispatch scope rides along so that the KV sessions this callback adds are
|
|
1826
|
+
// still covered by it.
|
|
1827
|
+
cluster->open_bucket(
|
|
1828
|
+
bucket_name.value(),
|
|
1829
|
+
[collector, dispatch, weak = std::weak_ptr(cluster), bucket_name, timeout](
|
|
1830
|
+
std::error_code ec) {
|
|
1831
|
+
if (!ec) {
|
|
1832
|
+
if (auto cluster = weak.lock()) {
|
|
1833
|
+
if (auto bucket = cluster->find_bucket_by_name(bucket_name.value()); bucket) {
|
|
1834
|
+
return bucket->ping(collector, timeout);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1141
1837
|
}
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1144
1840
|
}
|
|
1841
|
+
// Query, search, analytics, management, eventing and views are cluster-level services,
|
|
1842
|
+
// not bucket-scoped. Ping them through the session manager just like the no-bucket path
|
|
1843
|
+
// so a bucket-scoped ping -- and therefore bucket::wait_until_ready() -- reports the
|
|
1844
|
+
// non-KV services too, rather than only key/value. The session manager pings only the
|
|
1845
|
+
// HTTP services it owns and ignores key_value in the requested set.
|
|
1846
|
+
cluster->session_manager_->ping(services, timeout, collector);
|
|
1145
1847
|
} else {
|
|
1146
1848
|
if (services.find(service_type::key_value) != services.end()) {
|
|
1147
1849
|
if (cluster->session_) {
|
|
@@ -1162,7 +1864,7 @@ public:
|
|
|
1162
1864
|
if (!report_id) {
|
|
1163
1865
|
report_id = std::make_optional(uuid::to_string(uuid::random()));
|
|
1164
1866
|
}
|
|
1165
|
-
if (stopped_) {
|
|
1867
|
+
if (stopped_ || is_protostellar()) {
|
|
1166
1868
|
return handler({ report_id.value(), couchbase::core::meta::sdk_id() });
|
|
1167
1869
|
}
|
|
1168
1870
|
asio::post(asio::bind_executor(
|
|
@@ -1179,6 +1881,27 @@ public:
|
|
|
1179
1881
|
}));
|
|
1180
1882
|
}
|
|
1181
1883
|
|
|
1884
|
+
// IMPORTANT — do not std::move observability members out of *self* below.
|
|
1885
|
+
//
|
|
1886
|
+
// The public-API cluster::notify_fork(fork_event::child) in
|
|
1887
|
+
// core/impl/public_cluster.cxx replaces the outer cluster_impl and drops
|
|
1888
|
+
// the old one; the old impl's destructor calls do_close() which lands
|
|
1889
|
+
// here. But user code that acquired a bucket/collection *before* the
|
|
1890
|
+
// fork holds its own copy of core::cluster — and core::cluster is a
|
|
1891
|
+
// shared_ptr<cluster_impl> wrapper — so the inner cluster_impl's
|
|
1892
|
+
// refcount stays > 0 and this object survives close().
|
|
1893
|
+
//
|
|
1894
|
+
// Anything moved out of `self->X_` becomes silently empty to those
|
|
1895
|
+
// still-live references on the next operation. tracer() and meter()
|
|
1896
|
+
// accessors return the member directly without checking stopped_, so an
|
|
1897
|
+
// empty shared_ptr there gets converted to an empty weak_ptr in
|
|
1898
|
+
// observability_recorder and then dereferenced as `this=nullptr` inside
|
|
1899
|
+
// tracer_wrapper::create_span. Same shape for the rest of the
|
|
1900
|
+
// observability/telemetry/orphan set.
|
|
1901
|
+
//
|
|
1902
|
+
// Members whose accessor already gates on stopped_ (e.g. session_manager_
|
|
1903
|
+
// via http_session_manager()) are safe to move out and intentionally
|
|
1904
|
+
// remain that way — callers there get a clean errc::network::cluster_closed.
|
|
1182
1905
|
void close(utils::movable_function<void()>&& handler)
|
|
1183
1906
|
{
|
|
1184
1907
|
if (stopped_) {
|
|
@@ -1187,17 +1910,22 @@ public:
|
|
|
1187
1910
|
stopped_ = true;
|
|
1188
1911
|
asio::post(asio::bind_executor(
|
|
1189
1912
|
ctx_, [self = shared_from_this(), handler = std::move(handler)]() mutable {
|
|
1190
|
-
|
|
1191
|
-
|
|
1913
|
+
// Re-issued after stopped_ is set, so each deferred request completes with cluster_closed.
|
|
1914
|
+
self->run_deferred_until_open();
|
|
1915
|
+
if (auto open_handler = self->take_dns_srv_open_handler(); open_handler) {
|
|
1916
|
+
open_handler(errc::network::cluster_closed);
|
|
1192
1917
|
}
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1918
|
+
std::optional<io::mcbp_session> session{};
|
|
1919
|
+
std::shared_ptr<io::http_session_manager> session_manager{};
|
|
1920
|
+
{
|
|
1921
|
+
// update_credentials() uses both from the caller's thread.
|
|
1922
|
+
const std::scoped_lock lock(self->update_credentials_mutex_);
|
|
1923
|
+
session = std::move(self->session_);
|
|
1924
|
+
session_manager = std::move(self->session_manager_);
|
|
1925
|
+
}
|
|
1926
|
+
if (session) {
|
|
1927
|
+
session->stop(retry_reason::do_not_retry);
|
|
1198
1928
|
}
|
|
1199
|
-
self->retry_backoff_.cancel();
|
|
1200
|
-
#endif
|
|
1201
1929
|
|
|
1202
1930
|
std::map<std::string, std::shared_ptr<bucket>> buckets{};
|
|
1203
1931
|
{
|
|
@@ -1207,29 +1935,86 @@ public:
|
|
|
1207
1935
|
for (const auto& [_, bucket] : buckets) {
|
|
1208
1936
|
bucket->close();
|
|
1209
1937
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
if (const auto meter = std::move(self->meter_); meter) {
|
|
1218
|
-
meter->stop();
|
|
1219
|
-
}
|
|
1220
|
-
if (const auto meter = std::move(self->app_telemetry_meter_); meter) {
|
|
1221
|
-
meter->disable();
|
|
1222
|
-
}
|
|
1223
|
-
if (const auto reporter = std::move(self->app_telemetry_reporter_); reporter) {
|
|
1224
|
-
reporter->stop();
|
|
1225
|
-
}
|
|
1226
|
-
if (const auto orphan_reporter = std::move(self->orphan_reporter_); orphan_reporter) {
|
|
1227
|
-
orphan_reporter->stop();
|
|
1938
|
+
// The rest of the teardown waits until every HTTP session is stopped and holds no
|
|
1939
|
+
// read_some() call. The caller may stop the io_context as soon as `handler` runs, and an
|
|
1940
|
+
// HTTP session whose stop is still queued then is never torn down.
|
|
1941
|
+
if (session_manager) {
|
|
1942
|
+
return session_manager->close([self, handler = std::move(handler)]() mutable {
|
|
1943
|
+
self->finish_close(std::move(handler));
|
|
1944
|
+
});
|
|
1228
1945
|
}
|
|
1229
|
-
handler
|
|
1946
|
+
self->finish_close(std::move(handler));
|
|
1230
1947
|
}));
|
|
1231
1948
|
}
|
|
1232
1949
|
|
|
1950
|
+
// The part of close() that runs once every HTTP session is stopped and holds no read_some() call.
|
|
1951
|
+
void finish_close(utils::movable_function<void()>&& handler)
|
|
1952
|
+
{
|
|
1953
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
1954
|
+
{
|
|
1955
|
+
// Drop the reference under the lock, but destroy the component outside it: ~component
|
|
1956
|
+
// drains in-flight gRPC calls, and holding protostellar_mutex_ across that would block
|
|
1957
|
+
// every concurrent execute() on the io thread for the duration of the drain.
|
|
1958
|
+
std::shared_ptr<protostellar::component> component;
|
|
1959
|
+
{
|
|
1960
|
+
const std::scoped_lock lock(protostellar_mutex_);
|
|
1961
|
+
component = std::move(protostellar_);
|
|
1962
|
+
protostellar_.reset();
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
#endif
|
|
1966
|
+
// Hooks run before the handler: the caller may stop the io_context as soon as it runs, and an
|
|
1967
|
+
// operation waiting on a timer would then never complete.
|
|
1968
|
+
decltype(close_hooks_) hooks{};
|
|
1969
|
+
{
|
|
1970
|
+
const std::scoped_lock lock(close_hooks_mutex_);
|
|
1971
|
+
hooks = std::move(close_hooks_);
|
|
1972
|
+
close_hooks_.clear();
|
|
1973
|
+
}
|
|
1974
|
+
for (auto& [owner, hook] : hooks) {
|
|
1975
|
+
if (!owner.expired()) {
|
|
1976
|
+
hook();
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
work_.reset();
|
|
1980
|
+
// Observability members: stop in place, do NOT std::move. See the
|
|
1981
|
+
// comment above close() for why this matters across fork(child).
|
|
1982
|
+
if (tracer_) {
|
|
1983
|
+
tracer_->stop();
|
|
1984
|
+
}
|
|
1985
|
+
if (meter_) {
|
|
1986
|
+
meter_->stop();
|
|
1987
|
+
}
|
|
1988
|
+
if (app_telemetry_meter_) {
|
|
1989
|
+
app_telemetry_meter_->disable();
|
|
1990
|
+
}
|
|
1991
|
+
if (app_telemetry_reporter_) {
|
|
1992
|
+
app_telemetry_reporter_->stop();
|
|
1993
|
+
}
|
|
1994
|
+
if (orphan_reporter_) {
|
|
1995
|
+
orphan_reporter_->stop();
|
|
1996
|
+
}
|
|
1997
|
+
handler();
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
auto add_close_hook(std::weak_ptr<void> owner, utils::movable_function<void()>&& hook) -> bool
|
|
2001
|
+
{
|
|
2002
|
+
const std::scoped_lock lock(close_hooks_mutex_);
|
|
2003
|
+
// close() sets stopped_ before it posts the teardown, and finish_close() takes the hooks under
|
|
2004
|
+
// this mutex, so a hook that passes this check is called.
|
|
2005
|
+
if (stopped_) {
|
|
2006
|
+
return false;
|
|
2007
|
+
}
|
|
2008
|
+
close_hooks_.erase(std::remove_if(close_hooks_.begin(),
|
|
2009
|
+
close_hooks_.end(),
|
|
2010
|
+
[](const auto& entry) {
|
|
2011
|
+
return entry.first.expired();
|
|
2012
|
+
}),
|
|
2013
|
+
close_hooks_.end());
|
|
2014
|
+
close_hooks_.emplace_back(std::move(owner), std::move(hook));
|
|
2015
|
+
return true;
|
|
2016
|
+
}
|
|
2017
|
+
|
|
1233
2018
|
auto direct_dispatch(const std::string& bucket_name,
|
|
1234
2019
|
std::shared_ptr<couchbase::core::mcbp::queue_request> req) -> std::error_code
|
|
1235
2020
|
{
|
|
@@ -1249,7 +2034,11 @@ public:
|
|
|
1249
2034
|
if (ec) {
|
|
1250
2035
|
return req->cancel(ec);
|
|
1251
2036
|
}
|
|
1252
|
-
|
|
2037
|
+
// open_bucket()'s caller has already returned, so nothing reads this
|
|
2038
|
+
// error code and nothing else completes the request.
|
|
2039
|
+
if (const auto rc = self->direct_dispatch(bucket_name, req); rc) {
|
|
2040
|
+
req->try_callback({}, rc);
|
|
2041
|
+
}
|
|
1253
2042
|
});
|
|
1254
2043
|
return {};
|
|
1255
2044
|
}
|
|
@@ -1274,11 +2063,99 @@ public:
|
|
|
1274
2063
|
if (ec) {
|
|
1275
2064
|
return req->cancel(ec);
|
|
1276
2065
|
}
|
|
1277
|
-
|
|
2066
|
+
// As in direct_dispatch(): this error code has no other
|
|
2067
|
+
// reader. Every error the bucket-level re-queue returns has
|
|
2068
|
+
// already completed the request, so this is a backstop for a
|
|
2069
|
+
// future path that does not -- try_callback() is idempotent.
|
|
2070
|
+
if (const auto rc = self->direct_re_queue(bucket_name, req, is_retry); rc) {
|
|
2071
|
+
req->try_callback({}, rc);
|
|
2072
|
+
}
|
|
1278
2073
|
});
|
|
1279
2074
|
return {};
|
|
1280
2075
|
}
|
|
1281
2076
|
|
|
2077
|
+
// A request issued while open() bootstraps would read origin_ and session_, which the IO thread
|
|
2078
|
+
// writes until the bootstrap ends. While that window is open, the request made by `make_resume`
|
|
2079
|
+
// is held and this returns true. `make_resume` is called only then, so a request that is not
|
|
2080
|
+
// held is not moved from.
|
|
2081
|
+
//
|
|
2082
|
+
// The held request resumes exactly once: with no error when run_deferred_until_open() closes the
|
|
2083
|
+
// window, or with unambiguous_timeout when `deadline` passes first.
|
|
2084
|
+
template<typename MakeResume>
|
|
2085
|
+
auto defer_until_open(MakeResume&& make_resume,
|
|
2086
|
+
std::optional<std::chrono::steady_clock::time_point> deadline = {}) -> bool
|
|
2087
|
+
{
|
|
2088
|
+
const std::scoped_lock lock(deferred_until_open_mutex_);
|
|
2089
|
+
if (!opening_) {
|
|
2090
|
+
return false;
|
|
2091
|
+
}
|
|
2092
|
+
auto entry = std::make_shared<deferred_request>();
|
|
2093
|
+
entry->resume = std::forward<MakeResume>(make_resume)();
|
|
2094
|
+
if (deadline) {
|
|
2095
|
+
entry->deadline = std::make_unique<asio::steady_timer>(ctx_, deadline.value());
|
|
2096
|
+
entry->deadline->async_wait([weak_self = weak_from_this(), entry](std::error_code ec) {
|
|
2097
|
+
if (ec == asio::error::operation_aborted) {
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
// Removed now, or each request that times out during a slow bootstrap stays queued
|
|
2101
|
+
// until the bootstrap ends.
|
|
2102
|
+
if (auto self = weak_self.lock()) {
|
|
2103
|
+
const std::scoped_lock lock(self->deferred_until_open_mutex_);
|
|
2104
|
+
if (entry->queued) {
|
|
2105
|
+
self->deferred_until_open_.erase(*entry->queued);
|
|
2106
|
+
entry->queued.reset();
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
entry->complete(errc::common::unambiguous_timeout);
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
entry->queued = deferred_until_open_.insert(deferred_until_open_.end(), entry);
|
|
2113
|
+
return true;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
// Closes the bootstrap window and resumes what it held. Runs on the IO thread, at the end of a
|
|
2117
|
+
// successful bootstrap or at the start of close(); a failed bootstrap reaches close() first.
|
|
2118
|
+
void run_deferred_until_open()
|
|
2119
|
+
{
|
|
2120
|
+
std::list<std::shared_ptr<deferred_request>> deferred{};
|
|
2121
|
+
{
|
|
2122
|
+
const std::scoped_lock lock(deferred_until_open_mutex_);
|
|
2123
|
+
opening_ = false;
|
|
2124
|
+
std::swap(deferred, deferred_until_open_);
|
|
2125
|
+
for (const auto& entry : deferred) {
|
|
2126
|
+
entry->queued.reset();
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
for (const auto& entry : deferred) {
|
|
2130
|
+
entry->complete({});
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
// A DNS SRV open's handler, taken by whichever of the lookup and close() finishes first.
|
|
2135
|
+
auto take_dns_srv_open_handler() -> utils::movable_function<void(std::error_code)>
|
|
2136
|
+
{
|
|
2137
|
+
const std::scoped_lock lock(dns_srv_open_handler_mutex_);
|
|
2138
|
+
auto handler = std::move(dns_srv_open_handler_);
|
|
2139
|
+
dns_srv_open_handler_ = nullptr;
|
|
2140
|
+
return handler;
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
// The timeouts are not among the fields the bootstrap rewrites, so no lock is needed.
|
|
2144
|
+
[[nodiscard]] auto default_timeout_for(service_type type) const -> std::chrono::milliseconds
|
|
2145
|
+
{
|
|
2146
|
+
return origin_.options().default_timeout_for(type);
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
// origin_ copied under origin_mutex_: the IO thread rewrites it while open() bootstraps.
|
|
2150
|
+
auto origin() const -> std::pair<std::error_code, couchbase::core::origin>
|
|
2151
|
+
{
|
|
2152
|
+
if (stopped_) {
|
|
2153
|
+
return { errc::network::cluster_closed, {} };
|
|
2154
|
+
}
|
|
2155
|
+
const std::scoped_lock lock(origin_mutex_);
|
|
2156
|
+
return { {}, origin_ };
|
|
2157
|
+
}
|
|
2158
|
+
|
|
1282
2159
|
auto http_session_manager()
|
|
1283
2160
|
-> std::pair<std::error_code, std::shared_ptr<io::http_session_manager>>
|
|
1284
2161
|
{
|
|
@@ -1304,6 +2181,29 @@ public:
|
|
|
1304
2181
|
}
|
|
1305
2182
|
|
|
1306
2183
|
private:
|
|
2184
|
+
// Must be called from every open path, before the connection string is logged and before any
|
|
2185
|
+
// session or bucket is constructed, so that connection-context prefixes built later carry their
|
|
2186
|
+
// tags.
|
|
2187
|
+
//
|
|
2188
|
+
// Redaction state is process-wide while it is configured per cluster, so opening a cluster may
|
|
2189
|
+
// only turn it on, never off: one cluster must not silently disable redaction for another, or
|
|
2190
|
+
// for an application that enabled it directly.
|
|
2191
|
+
void apply_log_redaction() const
|
|
2192
|
+
{
|
|
2193
|
+
if (origin_.options().log_redaction) {
|
|
2194
|
+
logger::set_log_redaction(true);
|
|
2195
|
+
}
|
|
2196
|
+
// The warning has to test the state that is in effect rather than what this cluster asked for.
|
|
2197
|
+
// Redaction is process-wide, so another cluster or a direct set_log_redaction() call may have
|
|
2198
|
+
// turned it on, and a cluster opened with only dump_configuration would then write
|
|
2199
|
+
// unannotated dumps into a redacted log without saying so.
|
|
2200
|
+
if (origin_.options().dump_configuration && logger::is_log_redaction_enabled()) {
|
|
2201
|
+
CB_LOG_WARNING("log redaction is enabled, but so is dump_configuration: configuration dumps "
|
|
2202
|
+
"are not annotated for redaction and may contain values that are annotated "
|
|
2203
|
+
"elsewhere in the log");
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
|
|
1307
2207
|
void setup_observability()
|
|
1308
2208
|
{
|
|
1309
2209
|
// ignore the enable_tracing flag if a tracer was passed in
|
|
@@ -1352,15 +2252,10 @@ private:
|
|
|
1352
2252
|
|
|
1353
2253
|
auto has_capella_host() const -> bool
|
|
1354
2254
|
{
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
pos != std::string::npos && pos + suffix.size() == node.size()) {
|
|
1360
|
-
has_capella_host = true;
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
return has_capella_host;
|
|
2255
|
+
auto hostnames = origin_.get_hostnames();
|
|
2256
|
+
return std::any_of(hostnames.begin(), hostnames.end(), [](const std::string& host) {
|
|
2257
|
+
return is_capella_host(host);
|
|
2258
|
+
});
|
|
1364
2259
|
}
|
|
1365
2260
|
|
|
1366
2261
|
std::string id_{ uuid::to_string(uuid::random()) };
|
|
@@ -1372,8 +2267,19 @@ private:
|
|
|
1372
2267
|
std::optional<io::mcbp_session> session_{};
|
|
1373
2268
|
std::shared_ptr<impl::dns_srv_tracker> dns_srv_tracker_{};
|
|
1374
2269
|
std::mutex buckets_mutex_{};
|
|
2270
|
+
// Orders update_credentials() against do_open() constructing session_, and against close() moving
|
|
2271
|
+
// session_ and session_manager_ out.
|
|
2272
|
+
std::mutex update_credentials_mutex_{};
|
|
1375
2273
|
std::map<std::string, std::shared_ptr<bucket>> buckets_{};
|
|
1376
2274
|
couchbase::core::origin origin_{};
|
|
2275
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
2276
|
+
// Set when the cluster was opened with a couchbase2:// connection string; KV ops route here
|
|
2277
|
+
// instead of to the MCBP session manager. Guarded by protostellar_mutex_: execute() reads it on
|
|
2278
|
+
// the caller's thread while close() resets it on the io thread. Always reach it through
|
|
2279
|
+
// protostellar_component(). Mutable so the const observers can lock.
|
|
2280
|
+
mutable std::mutex protostellar_mutex_{};
|
|
2281
|
+
std::shared_ptr<protostellar::component> protostellar_{};
|
|
2282
|
+
#endif
|
|
1377
2283
|
std::shared_ptr<class cluster_label_listener> cluster_label_listener_{
|
|
1378
2284
|
std::make_shared<class cluster_label_listener>()
|
|
1379
2285
|
};
|
|
@@ -1381,22 +2287,77 @@ private:
|
|
|
1381
2287
|
std::shared_ptr<metrics::meter_wrapper> meter_{ nullptr };
|
|
1382
2288
|
std::shared_ptr<orphan_reporter> orphan_reporter_{ nullptr };
|
|
1383
2289
|
std::atomic_bool stopped_{ false };
|
|
2290
|
+
std::mutex close_hooks_mutex_{};
|
|
2291
|
+
std::vector<std::pair<std::weak_ptr<void>, utils::movable_function<void()>>> close_hooks_{};
|
|
2292
|
+
std::mutex deferred_until_open_mutex_{};
|
|
2293
|
+
bool opening_{ false };
|
|
2294
|
+
// Each entry's resume holds this cluster until it runs, as session_'s bootstrap callback does.
|
|
2295
|
+
// close() runs them, so the io_context must outlive the cluster and still be running when
|
|
2296
|
+
// close() is called.
|
|
2297
|
+
std::list<std::shared_ptr<deferred_request>> deferred_until_open_{};
|
|
2298
|
+
std::mutex dns_srv_open_handler_mutex_{};
|
|
2299
|
+
utils::movable_function<void(std::error_code)> dns_srv_open_handler_{};
|
|
2300
|
+
// Taken by origin(), update_credentials() and the writes to origin_ that can overlap them: the
|
|
2301
|
+
// assignment in open(), and the DNS SRV and bootstrap callbacks.
|
|
2302
|
+
mutable std::mutex origin_mutex_{};
|
|
1384
2303
|
std::shared_ptr<core::app_telemetry_meter> app_telemetry_meter_{
|
|
1385
2304
|
std::make_shared<core::app_telemetry_meter>()
|
|
1386
2305
|
};
|
|
1387
|
-
|
|
1388
|
-
#ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
|
|
1389
|
-
std::shared_ptr<couchbase::core::io::cluster_config_tracker> config_tracker_{};
|
|
1390
|
-
asio::steady_timer retry_backoff_;
|
|
1391
|
-
std::atomic_bool background_open_started_{ false };
|
|
1392
|
-
#endif
|
|
1393
2306
|
};
|
|
1394
2307
|
|
|
2308
|
+
namespace
|
|
2309
|
+
{
|
|
2310
|
+
// Holds a replica read issued while open() bootstraps, as cluster_impl::execute() holds a
|
|
2311
|
+
// key-value request. These reach the bucket through open_bucket(), which carries no deadline.
|
|
2312
|
+
// `make_error` builds the response for a request whose timeout passes first.
|
|
2313
|
+
template<typename Request, typename Handler, typename MakeError>
|
|
2314
|
+
auto
|
|
2315
|
+
defer_replica_read(const cluster& self,
|
|
2316
|
+
const std::shared_ptr<cluster_impl>& impl,
|
|
2317
|
+
Request& request,
|
|
2318
|
+
Handler& handler,
|
|
2319
|
+
MakeError make_error) -> bool
|
|
2320
|
+
{
|
|
2321
|
+
const auto deadline =
|
|
2322
|
+
std::chrono::steady_clock::now() +
|
|
2323
|
+
request.timeout.value_or(impl->default_timeout_for(service_type::key_value));
|
|
2324
|
+
return impl->defer_until_open(
|
|
2325
|
+
[&] {
|
|
2326
|
+
return [self,
|
|
2327
|
+
deadline,
|
|
2328
|
+
request = std::move(request),
|
|
2329
|
+
handler = std::move(handler),
|
|
2330
|
+
make_error = std::move(make_error)](std::error_code ec) mutable {
|
|
2331
|
+
if (ec) {
|
|
2332
|
+
return handler(make_error(request, ec));
|
|
2333
|
+
}
|
|
2334
|
+
request.timeout = remaining_until(deadline);
|
|
2335
|
+
self.execute(std::move(request), std::move(handler));
|
|
2336
|
+
};
|
|
2337
|
+
},
|
|
2338
|
+
deadline);
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
// The response for a subdocument replica read that did not run.
|
|
2342
|
+
template<typename Response, typename Request>
|
|
2343
|
+
auto
|
|
2344
|
+
subdocument_replica_error(const Request& request, std::error_code ec) -> Response
|
|
2345
|
+
{
|
|
2346
|
+
return Response{ make_subdocument_error_context(
|
|
2347
|
+
make_key_value_error_context(ec, request.id), ec, {}, {}, false) };
|
|
2348
|
+
}
|
|
2349
|
+
} // namespace
|
|
2350
|
+
|
|
1395
2351
|
cluster::cluster(asio::io_context& ctx)
|
|
1396
2352
|
: impl_{ std::make_shared<cluster_impl>(ctx) }
|
|
1397
2353
|
{
|
|
1398
2354
|
}
|
|
1399
2355
|
|
|
2356
|
+
cluster::cluster(std::shared_ptr<cluster_impl> impl)
|
|
2357
|
+
: impl_{ std::move(impl) }
|
|
2358
|
+
{
|
|
2359
|
+
}
|
|
2360
|
+
|
|
1400
2361
|
auto
|
|
1401
2362
|
cluster::direct_dispatch(const std::string& bucket_name,
|
|
1402
2363
|
std::shared_ptr<couchbase::core::mcbp::queue_request> req) const
|
|
@@ -1445,20 +2406,6 @@ cluster::open(couchbase::core::origin origin,
|
|
|
1445
2406
|
}
|
|
1446
2407
|
}
|
|
1447
2408
|
|
|
1448
|
-
void
|
|
1449
|
-
cluster::open_in_background(
|
|
1450
|
-
[[maybe_unused]] const couchbase::core::origin& origin,
|
|
1451
|
-
[[maybe_unused]] utils::movable_function<void(std::error_code)>&& handler) const
|
|
1452
|
-
{
|
|
1453
|
-
#ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
|
|
1454
|
-
if (impl_) {
|
|
1455
|
-
impl_->open_in_background(origin, std::move(handler));
|
|
1456
|
-
}
|
|
1457
|
-
#else
|
|
1458
|
-
CB_LOG_ERROR("Background open only available for Columnar builds.");
|
|
1459
|
-
#endif
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
2409
|
void
|
|
1463
2410
|
cluster::diagnostics(std::optional<std::string> report_id,
|
|
1464
2411
|
utils::movable_function<void(diag::diagnostics_result)>&& handler) const
|
|
@@ -1561,16 +2508,16 @@ cluster::execute(
|
|
|
1561
2508
|
operations::get_all_replicas_request request,
|
|
1562
2509
|
utils::movable_function<void(operations::get_all_replicas_response)>&& handler) const
|
|
1563
2510
|
{
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
return
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
2511
|
+
if (impl_->is_protostellar()) {
|
|
2512
|
+
return handler(operations::get_all_replicas_response{
|
|
2513
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id) });
|
|
2514
|
+
}
|
|
2515
|
+
if (defer_replica_read(*this, impl_, request, handler, [](const auto& r, std::error_code ec) {
|
|
2516
|
+
return operations::get_all_replicas_response{ make_key_value_error_context(ec, r.id) };
|
|
2517
|
+
})) {
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
return request.execute(impl_, std::move(handler));
|
|
1574
2521
|
}
|
|
1575
2522
|
|
|
1576
2523
|
void
|
|
@@ -1592,16 +2539,16 @@ cluster::execute(
|
|
|
1592
2539
|
operations::get_any_replica_request request,
|
|
1593
2540
|
utils::movable_function<void(operations::get_any_replica_response)>&& handler) const
|
|
1594
2541
|
{
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
return
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
2542
|
+
if (impl_->is_protostellar()) {
|
|
2543
|
+
return handler(operations::get_any_replica_response{
|
|
2544
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id) });
|
|
2545
|
+
}
|
|
2546
|
+
if (defer_replica_read(*this, impl_, request, handler, [](const auto& r, std::error_code ec) {
|
|
2547
|
+
return operations::get_any_replica_response{ make_key_value_error_context(ec, r.id) };
|
|
2548
|
+
})) {
|
|
2549
|
+
return;
|
|
2550
|
+
}
|
|
2551
|
+
return request.execute(impl_, std::move(handler));
|
|
1605
2552
|
}
|
|
1606
2553
|
|
|
1607
2554
|
void
|
|
@@ -1637,6 +2584,19 @@ cluster::execute(
|
|
|
1637
2584
|
operations::lookup_in_any_replica_request request,
|
|
1638
2585
|
utils::movable_function<void(operations::lookup_in_any_replica_response)>&& handler) const
|
|
1639
2586
|
{
|
|
2587
|
+
if (impl_->is_protostellar()) {
|
|
2588
|
+
return handler(operations::lookup_in_any_replica_response{ make_subdocument_error_context(
|
|
2589
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id),
|
|
2590
|
+
errc::common::feature_not_available,
|
|
2591
|
+
{},
|
|
2592
|
+
{},
|
|
2593
|
+
false) });
|
|
2594
|
+
}
|
|
2595
|
+
if (defer_replica_read(*this, impl_, request, handler, [](const auto& r, std::error_code ec) {
|
|
2596
|
+
return subdocument_replica_error<operations::lookup_in_any_replica_response>(r, ec);
|
|
2597
|
+
})) {
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
1640
2600
|
return request.execute(impl_, std::move(handler));
|
|
1641
2601
|
}
|
|
1642
2602
|
|
|
@@ -1645,6 +2605,19 @@ cluster::execute(
|
|
|
1645
2605
|
operations::lookup_in_all_replicas_request request,
|
|
1646
2606
|
utils::movable_function<void(operations::lookup_in_all_replicas_response)>&& handler) const
|
|
1647
2607
|
{
|
|
2608
|
+
if (impl_->is_protostellar()) {
|
|
2609
|
+
return handler(operations::lookup_in_all_replicas_response{ make_subdocument_error_context(
|
|
2610
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id),
|
|
2611
|
+
errc::common::feature_not_available,
|
|
2612
|
+
{},
|
|
2613
|
+
{},
|
|
2614
|
+
false) });
|
|
2615
|
+
}
|
|
2616
|
+
if (defer_replica_read(*this, impl_, request, handler, [](const auto& r, std::error_code ec) {
|
|
2617
|
+
return subdocument_replica_error<operations::lookup_in_all_replicas_response>(r, ec);
|
|
2618
|
+
})) {
|
|
2619
|
+
return;
|
|
2620
|
+
}
|
|
1648
2621
|
return request.execute(impl_, std::move(handler));
|
|
1649
2622
|
}
|
|
1650
2623
|
|
|
@@ -1669,6 +2642,111 @@ cluster::execute(operations::query_request request,
|
|
|
1669
2642
|
return impl_->execute(std::move(request), std::move(handler));
|
|
1670
2643
|
}
|
|
1671
2644
|
|
|
2645
|
+
void
|
|
2646
|
+
cluster::query_stream(operations::query_request request,
|
|
2647
|
+
utils::movable_function<void(couchbase::core::query_stream,
|
|
2648
|
+
error_context::query)>&& handler) const
|
|
2649
|
+
{
|
|
2650
|
+
if (const auto deadline =
|
|
2651
|
+
std::chrono::steady_clock::now() +
|
|
2652
|
+
request.timeout.value_or(impl_->default_timeout_for(service_type::query));
|
|
2653
|
+
impl_->defer_until_open(
|
|
2654
|
+
[&] {
|
|
2655
|
+
return
|
|
2656
|
+
[self = *this, deadline, request = std::move(request), handler = std::move(handler)](
|
|
2657
|
+
std::error_code ec) mutable {
|
|
2658
|
+
if (ec) {
|
|
2659
|
+
error_context::query ctx{};
|
|
2660
|
+
ctx.ec = ec;
|
|
2661
|
+
ctx.statement = request.statement;
|
|
2662
|
+
ctx.client_context_id = request.client_context_id.has_value()
|
|
2663
|
+
? *request.client_context_id
|
|
2664
|
+
: uuid::to_string(uuid::random());
|
|
2665
|
+
return handler({}, std::move(ctx));
|
|
2666
|
+
}
|
|
2667
|
+
request.timeout = remaining_until(deadline);
|
|
2668
|
+
self.query_stream(std::move(request), std::move(handler));
|
|
2669
|
+
};
|
|
2670
|
+
},
|
|
2671
|
+
deadline)) {
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
// Mirror operations::query_request::encode_to: use_replica is only honored when the cluster
|
|
2675
|
+
// advertises read-from-replica support, otherwise the request is rejected up front. Rejected here
|
|
2676
|
+
// means nothing was dispatched, so the context can only describe the request itself.
|
|
2677
|
+
if (request.use_replica.has_value()) {
|
|
2678
|
+
auto reject = [&request](std::error_code ec) {
|
|
2679
|
+
error_context::query ctx{};
|
|
2680
|
+
ctx.ec = ec;
|
|
2681
|
+
ctx.statement = request.statement;
|
|
2682
|
+
// Generate an id when the caller supplied none, the way every dispatched path does, so even
|
|
2683
|
+
// a request rejected before dispatch has a context that can be correlated with the logs.
|
|
2684
|
+
// Not value_or(): its argument is evaluated whether or not the optional is engaged, which
|
|
2685
|
+
// would generate and format a UUID only to discard it whenever the caller supplied an id.
|
|
2686
|
+
ctx.client_context_id = request.client_context_id.has_value()
|
|
2687
|
+
? *request.client_context_id
|
|
2688
|
+
: uuid::to_string(uuid::random());
|
|
2689
|
+
ctx.method = "POST";
|
|
2690
|
+
ctx.path = "/query/service";
|
|
2691
|
+
return ctx;
|
|
2692
|
+
};
|
|
2693
|
+
auto [ec, manager] = impl_->http_session_manager();
|
|
2694
|
+
if (ec) {
|
|
2695
|
+
return handler({}, reject(ec));
|
|
2696
|
+
}
|
|
2697
|
+
if (!manager->configuration_capabilities().supports_read_from_replica()) {
|
|
2698
|
+
return handler({}, reject(errc::common::feature_not_available));
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
auto cluster_opts = impl_->origin().second.options();
|
|
2703
|
+
auto default_timeout = cluster_opts.default_timeout_for(service_type::query);
|
|
2704
|
+
http_component http{ impl_->io_context(), core_sdk_shim{ *this } };
|
|
2705
|
+
const query_stream_component component{
|
|
2706
|
+
impl_->io_context(), std::move(http), default_timeout, cluster_opts.streaming
|
|
2707
|
+
};
|
|
2708
|
+
component.execute(std::move(request), std::move(handler));
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
void
|
|
2712
|
+
cluster::analytics_query_stream(
|
|
2713
|
+
operations::analytics_request request,
|
|
2714
|
+
utils::movable_function<void(couchbase::core::analytics_stream, error_context::analytics)>&&
|
|
2715
|
+
handler) const
|
|
2716
|
+
{
|
|
2717
|
+
if (const auto deadline =
|
|
2718
|
+
std::chrono::steady_clock::now() +
|
|
2719
|
+
request.timeout.value_or(impl_->default_timeout_for(service_type::analytics));
|
|
2720
|
+
impl_->defer_until_open(
|
|
2721
|
+
[&] {
|
|
2722
|
+
return
|
|
2723
|
+
[self = *this, deadline, request = std::move(request), handler = std::move(handler)](
|
|
2724
|
+
std::error_code ec) mutable {
|
|
2725
|
+
if (ec) {
|
|
2726
|
+
error_context::analytics ctx{};
|
|
2727
|
+
ctx.ec = ec;
|
|
2728
|
+
ctx.statement = request.statement;
|
|
2729
|
+
ctx.client_context_id = request.client_context_id.has_value()
|
|
2730
|
+
? *request.client_context_id
|
|
2731
|
+
: uuid::to_string(uuid::random());
|
|
2732
|
+
return handler({}, std::move(ctx));
|
|
2733
|
+
}
|
|
2734
|
+
request.timeout = remaining_until(deadline);
|
|
2735
|
+
self.analytics_query_stream(std::move(request), std::move(handler));
|
|
2736
|
+
};
|
|
2737
|
+
},
|
|
2738
|
+
deadline)) {
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
auto cluster_opts = impl_->origin().second.options();
|
|
2742
|
+
auto default_timeout = cluster_opts.default_timeout_for(service_type::analytics);
|
|
2743
|
+
http_component http{ impl_->io_context(), core_sdk_shim{ *this } };
|
|
2744
|
+
const analytics_stream_component component{
|
|
2745
|
+
impl_->io_context(), std::move(http), default_timeout, cluster_opts.streaming
|
|
2746
|
+
};
|
|
2747
|
+
component.execute(std::move(request), std::move(handler));
|
|
2748
|
+
}
|
|
2749
|
+
|
|
1672
2750
|
void
|
|
1673
2751
|
cluster::execute(operations::remove_request request,
|
|
1674
2752
|
utils::movable_function<void(operations::remove_response)>&& handler) const
|
|
@@ -2331,8 +3409,8 @@ cluster::execute(
|
|
|
2331
3409
|
}
|
|
2332
3410
|
|
|
2333
3411
|
void
|
|
2334
|
-
cluster::execute(
|
|
2335
|
-
utils::movable_function<void(
|
|
3412
|
+
cluster::execute(operations::get_replica_request request,
|
|
3413
|
+
utils::movable_function<void(operations::get_replica_response)>&& handler) const
|
|
2336
3414
|
{
|
|
2337
3415
|
return impl_->execute(std::move(request), std::move(handler));
|
|
2338
3416
|
}
|
|
@@ -2415,6 +3493,10 @@ void
|
|
|
2415
3493
|
cluster::execute(operations::upsert_request_with_legacy_durability request,
|
|
2416
3494
|
utils::movable_function<void(operations::upsert_response)>&& handler) const
|
|
2417
3495
|
{
|
|
3496
|
+
if (impl_->is_protostellar()) {
|
|
3497
|
+
return handler(operations::upsert_response{
|
|
3498
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id) });
|
|
3499
|
+
}
|
|
2418
3500
|
return request.execute(*this, std::move(handler));
|
|
2419
3501
|
}
|
|
2420
3502
|
|
|
@@ -2422,6 +3504,10 @@ void
|
|
|
2422
3504
|
cluster::execute(operations::insert_request_with_legacy_durability request,
|
|
2423
3505
|
utils::movable_function<void(operations::insert_response)>&& handler) const
|
|
2424
3506
|
{
|
|
3507
|
+
if (impl_->is_protostellar()) {
|
|
3508
|
+
return handler(operations::insert_response{
|
|
3509
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id) });
|
|
3510
|
+
}
|
|
2425
3511
|
return request.execute(*this, std::move(handler));
|
|
2426
3512
|
}
|
|
2427
3513
|
|
|
@@ -2429,6 +3515,10 @@ void
|
|
|
2429
3515
|
cluster::execute(operations::append_request_with_legacy_durability request,
|
|
2430
3516
|
utils::movable_function<void(operations::append_response)>&& handler) const
|
|
2431
3517
|
{
|
|
3518
|
+
if (impl_->is_protostellar()) {
|
|
3519
|
+
return handler(operations::append_response{
|
|
3520
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id) });
|
|
3521
|
+
}
|
|
2432
3522
|
return request.execute(*this, std::move(handler));
|
|
2433
3523
|
}
|
|
2434
3524
|
|
|
@@ -2436,6 +3526,10 @@ void
|
|
|
2436
3526
|
cluster::execute(operations::prepend_request_with_legacy_durability request,
|
|
2437
3527
|
utils::movable_function<void(operations::prepend_response)>&& handler) const
|
|
2438
3528
|
{
|
|
3529
|
+
if (impl_->is_protostellar()) {
|
|
3530
|
+
return handler(operations::prepend_response{
|
|
3531
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id) });
|
|
3532
|
+
}
|
|
2439
3533
|
return request.execute(*this, std::move(handler));
|
|
2440
3534
|
}
|
|
2441
3535
|
|
|
@@ -2443,6 +3537,10 @@ void
|
|
|
2443
3537
|
cluster::execute(operations::replace_request_with_legacy_durability request,
|
|
2444
3538
|
utils::movable_function<void(operations::replace_response)>&& handler) const
|
|
2445
3539
|
{
|
|
3540
|
+
if (impl_->is_protostellar()) {
|
|
3541
|
+
return handler(operations::replace_response{
|
|
3542
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id) });
|
|
3543
|
+
}
|
|
2446
3544
|
return request.execute(*this, std::move(handler));
|
|
2447
3545
|
}
|
|
2448
3546
|
|
|
@@ -2450,6 +3548,14 @@ void
|
|
|
2450
3548
|
cluster::execute(operations::mutate_in_request_with_legacy_durability request,
|
|
2451
3549
|
utils::movable_function<void(operations::mutate_in_response)>&& handler) const
|
|
2452
3550
|
{
|
|
3551
|
+
if (impl_->is_protostellar()) {
|
|
3552
|
+
return handler(operations::mutate_in_response{ make_subdocument_error_context(
|
|
3553
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id),
|
|
3554
|
+
errc::common::feature_not_available,
|
|
3555
|
+
{},
|
|
3556
|
+
{},
|
|
3557
|
+
false) });
|
|
3558
|
+
}
|
|
2453
3559
|
return request.execute(*this, std::move(handler));
|
|
2454
3560
|
}
|
|
2455
3561
|
|
|
@@ -2457,6 +3563,10 @@ void
|
|
|
2457
3563
|
cluster::execute(operations::remove_request_with_legacy_durability request,
|
|
2458
3564
|
utils::movable_function<void(operations::remove_response)>&& handler) const
|
|
2459
3565
|
{
|
|
3566
|
+
if (impl_->is_protostellar()) {
|
|
3567
|
+
return handler(operations::remove_response{
|
|
3568
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id) });
|
|
3569
|
+
}
|
|
2460
3570
|
return request.execute(*this, std::move(handler));
|
|
2461
3571
|
}
|
|
2462
3572
|
|
|
@@ -2464,6 +3574,10 @@ void
|
|
|
2464
3574
|
cluster::execute(operations::increment_request_with_legacy_durability request,
|
|
2465
3575
|
utils::movable_function<void(operations::increment_response)>&& handler) const
|
|
2466
3576
|
{
|
|
3577
|
+
if (impl_->is_protostellar()) {
|
|
3578
|
+
return handler(operations::increment_response{
|
|
3579
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id) });
|
|
3580
|
+
}
|
|
2467
3581
|
return request.execute(*this, std::move(handler));
|
|
2468
3582
|
}
|
|
2469
3583
|
|
|
@@ -2471,6 +3585,10 @@ void
|
|
|
2471
3585
|
cluster::execute(operations::decrement_request_with_legacy_durability request,
|
|
2472
3586
|
utils::movable_function<void(operations::decrement_response)>&& handler) const
|
|
2473
3587
|
{
|
|
3588
|
+
if (impl_->is_protostellar()) {
|
|
3589
|
+
return handler(operations::decrement_response{
|
|
3590
|
+
make_key_value_error_context(errc::common::feature_not_available, request.id) });
|
|
3591
|
+
}
|
|
2474
3592
|
return request.execute(*this, std::move(handler));
|
|
2475
3593
|
}
|
|
2476
3594
|
|
|
@@ -2497,7 +3615,7 @@ cluster::execute(operations::lookup_in_request_with_cancellation request,
|
|
|
2497
3615
|
|
|
2498
3616
|
void
|
|
2499
3617
|
cluster::execute(operations::get_replica_request_with_cancellation request,
|
|
2500
|
-
utils::movable_function<void(
|
|
3618
|
+
utils::movable_function<void(operations::get_replica_response)>&& handler) const
|
|
2501
3619
|
{
|
|
2502
3620
|
return impl_->execute(std::move(request), std::move(handler));
|
|
2503
3621
|
}
|
|
@@ -2526,13 +3644,13 @@ cluster::to_string() const -> std::string
|
|
|
2526
3644
|
}
|
|
2527
3645
|
|
|
2528
3646
|
auto
|
|
2529
|
-
cluster::tracer() const ->
|
|
3647
|
+
cluster::tracer() const -> std::shared_ptr<tracing::tracer_wrapper>
|
|
2530
3648
|
{
|
|
2531
3649
|
return impl_->tracer();
|
|
2532
3650
|
}
|
|
2533
3651
|
|
|
2534
3652
|
auto
|
|
2535
|
-
cluster::meter() const ->
|
|
3653
|
+
cluster::meter() const -> std::shared_ptr<metrics::meter_wrapper>
|
|
2536
3654
|
{
|
|
2537
3655
|
return impl_->meter();
|
|
2538
3656
|
}
|
|
@@ -2543,4 +3661,42 @@ cluster::cluster_label_listener() const -> const std::shared_ptr<core::cluster_l
|
|
|
2543
3661
|
return impl_->cluster_label_listener();
|
|
2544
3662
|
}
|
|
2545
3663
|
|
|
3664
|
+
auto
|
|
3665
|
+
cluster::find_bucket_by_name(const std::string& name) const -> std::shared_ptr<bucket>
|
|
3666
|
+
{
|
|
3667
|
+
return impl_->find_bucket_by_name(name);
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3670
|
+
auto
|
|
3671
|
+
cluster::is_protostellar() const -> bool
|
|
3672
|
+
{
|
|
3673
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
3674
|
+
if (impl_) {
|
|
3675
|
+
return impl_->is_protostellar();
|
|
3676
|
+
}
|
|
3677
|
+
#endif
|
|
3678
|
+
return false;
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3681
|
+
auto
|
|
3682
|
+
cluster::add_close_hook(std::weak_ptr<void> owner, utils::movable_function<void()>&& hook) const
|
|
3683
|
+
-> bool
|
|
3684
|
+
{
|
|
3685
|
+
if (!impl_) {
|
|
3686
|
+
return false;
|
|
3687
|
+
}
|
|
3688
|
+
return impl_->add_close_hook(std::move(owner), std::move(hook));
|
|
3689
|
+
}
|
|
3690
|
+
|
|
3691
|
+
auto
|
|
3692
|
+
cluster::protostellar_channel_ready() const -> std::pair<std::error_code, bool>
|
|
3693
|
+
{
|
|
3694
|
+
#ifdef COUCHBASE_CXX_CLIENT_BUILD_COUCHBASE2
|
|
3695
|
+
if (impl_ && impl_->is_protostellar()) {
|
|
3696
|
+
return impl_->protostellar_channel_ready();
|
|
3697
|
+
}
|
|
3698
|
+
#endif
|
|
3699
|
+
return { errc::common::feature_not_available, false };
|
|
3700
|
+
}
|
|
3701
|
+
|
|
2546
3702
|
} // namespace couchbase::core
|