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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
2
|
+
/*
|
|
3
|
+
* Copyright 2026. Couchbase, Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#pragma once
|
|
19
|
+
|
|
20
|
+
#include <grpcpp/grpcpp.h>
|
|
21
|
+
|
|
22
|
+
#include <memory>
|
|
23
|
+
#include <string>
|
|
24
|
+
|
|
25
|
+
namespace couchbase::core
|
|
26
|
+
{
|
|
27
|
+
struct cluster_options;
|
|
28
|
+
struct cluster_credentials;
|
|
29
|
+
} // namespace couchbase::core
|
|
30
|
+
|
|
31
|
+
namespace couchbase::core::protostellar
|
|
32
|
+
{
|
|
33
|
+
|
|
34
|
+
// Translate the SDK's TLS options and credentials into gRPC's SSL material: root certs come from an
|
|
35
|
+
// inline PEM value, else a CA file, else the SDK's own default set -- the Capella root plus the
|
|
36
|
+
// bundled Mozilla roots unless those are disabled. The client chain/key are set together for
|
|
37
|
+
// certificate (mTLS) authentication.
|
|
38
|
+
//
|
|
39
|
+
// The default set matches what MCBP's configure_tls_context() contributes from material this SDK
|
|
40
|
+
// ships, as RFC 77 (Bootstrapping -> Security) requires. It does not additionally fold in the
|
|
41
|
+
// platform trust store that MCBP reaches through set_default_verify_paths(): gRPC takes roots only
|
|
42
|
+
// as a single PEM that replaces its store outright, and under OpenSSL/BoringSSL the platform set is
|
|
43
|
+
// not a defined collection but whatever the host ships, re-pointable at run time via
|
|
44
|
+
// SSL_CERT_FILE/SSL_CERT_DIR. See credentials.cxx for the full reasoning and how the other SDKs
|
|
45
|
+
// handle it.
|
|
46
|
+
[[nodiscard]] auto
|
|
47
|
+
build_ssl_options(const cluster_options& options, const cluster_credentials& credentials)
|
|
48
|
+
-> grpc::SslCredentialsOptions;
|
|
49
|
+
|
|
50
|
+
// "Authorization" header values: Basic base64(user:password) and Bearer <jwt>.
|
|
51
|
+
[[nodiscard]] auto
|
|
52
|
+
basic_auth_value(const std::string& username, const std::string& password) -> std::string;
|
|
53
|
+
[[nodiscard]] auto
|
|
54
|
+
bearer_auth_value(const std::string& jwt_token) -> std::string;
|
|
55
|
+
|
|
56
|
+
// The per-RPC authorization header the credentials imply: Bearer for a JWT, Basic for
|
|
57
|
+
// username/password, empty when neither applies (e.g. certificate auth, where identity is the
|
|
58
|
+
// client cert). couchbase2 attaches this as call metadata rather than as gRPC CallCredentials,
|
|
59
|
+
// so it works on both secure and insecure channels.
|
|
60
|
+
[[nodiscard]] auto
|
|
61
|
+
authorization_header(const cluster_credentials& credentials) -> std::string;
|
|
62
|
+
|
|
63
|
+
// Whether these options select a channel that encrypts but does NOT verify the server certificate
|
|
64
|
+
// or hostname. make_channel_credentials() branches on exactly this predicate, so pinning it in a
|
|
65
|
+
// test pins the real decision: grpc::ChannelCredentials is opaque, and nothing about the object it
|
|
66
|
+
// returns reveals which branch produced it.
|
|
67
|
+
//
|
|
68
|
+
// Only tls_verify=none on a TLS-enabled channel reaches that branch. A plaintext channel is a
|
|
69
|
+
// different thing, not a weaker one -- it presents no certificate at all, and
|
|
70
|
+
// make_channel_credentials refuses to put credentials on it.
|
|
71
|
+
[[nodiscard]] auto
|
|
72
|
+
tls_peer_verification_disabled(const cluster_options& options) -> bool;
|
|
73
|
+
|
|
74
|
+
// Channel credentials for a couchbase2 endpoint: TLS (from build_ssl_options) when TLS is
|
|
75
|
+
// enabled, otherwise insecure. When tls_peer_verification_disabled() holds, the channel is
|
|
76
|
+
// encrypted but unverified, and taking that branch is logged at warning level.
|
|
77
|
+
[[nodiscard]] auto
|
|
78
|
+
make_channel_credentials(const cluster_options& options, const cluster_credentials& credentials)
|
|
79
|
+
-> std::shared_ptr<grpc::ChannelCredentials>;
|
|
80
|
+
|
|
81
|
+
// Build a channel to a couchbase2 endpoint honouring the TLS options, with the default channel
|
|
82
|
+
// arguments (keepalive).
|
|
83
|
+
[[nodiscard]] auto
|
|
84
|
+
make_channel(const std::string& endpoint,
|
|
85
|
+
const cluster_options& options,
|
|
86
|
+
const cluster_credentials& credentials) -> std::shared_ptr<grpc::Channel>;
|
|
87
|
+
|
|
88
|
+
} // namespace couchbase::core::protostellar
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
2
|
+
/*
|
|
3
|
+
* Copyright 2026. Couchbase, Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#include "core/protostellar/dispatcher.hxx"
|
|
19
|
+
|
|
20
|
+
#include <grpcpp/create_channel.h>
|
|
21
|
+
|
|
22
|
+
#include <utility>
|
|
23
|
+
|
|
24
|
+
namespace couchbase::core::protostellar
|
|
25
|
+
{
|
|
26
|
+
auto
|
|
27
|
+
default_channel_arguments() -> grpc::ChannelArguments
|
|
28
|
+
{
|
|
29
|
+
grpc::ChannelArguments args;
|
|
30
|
+
args.SetInt(GRPC_ARG_KEEPALIVE_TIME_MS, 30'000);
|
|
31
|
+
args.SetInt(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 5'000);
|
|
32
|
+
args.SetInt(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1);
|
|
33
|
+
// gRPC's 4 MB default is too small for the largest values Couchbase serves.
|
|
34
|
+
args.SetMaxReceiveMessageSize(static_cast<int>(max_receive_message_size));
|
|
35
|
+
return args;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
auto
|
|
39
|
+
make_insecure_channel(const std::string& endpoint) -> std::shared_ptr<grpc::Channel>
|
|
40
|
+
{
|
|
41
|
+
return grpc::CreateCustomChannel(
|
|
42
|
+
endpoint, grpc::InsecureChannelCredentials(), default_channel_arguments());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
dispatcher::dispatcher(asio::io_context& io, std::shared_ptr<grpc::Channel> channel)
|
|
46
|
+
: io_{ io }
|
|
47
|
+
, channel_{ std::move(channel) }
|
|
48
|
+
{
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
auto
|
|
52
|
+
dispatcher::channel() const -> const std::shared_ptr<grpc::Channel>&
|
|
53
|
+
{
|
|
54
|
+
return channel_;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
} // namespace couchbase::core::protostellar
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
2
|
+
/*
|
|
3
|
+
* Copyright 2026. Couchbase, Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#pragma once
|
|
19
|
+
|
|
20
|
+
#include "core/utils/movable_function.hxx"
|
|
21
|
+
|
|
22
|
+
#include <asio/io_context.hpp>
|
|
23
|
+
#include <asio/post.hpp>
|
|
24
|
+
|
|
25
|
+
#include <grpcpp/grpcpp.h>
|
|
26
|
+
#include <grpcpp/support/client_callback.h>
|
|
27
|
+
|
|
28
|
+
#include <algorithm>
|
|
29
|
+
#include <chrono>
|
|
30
|
+
#include <condition_variable>
|
|
31
|
+
#include <cstddef>
|
|
32
|
+
#include <functional>
|
|
33
|
+
#include <memory>
|
|
34
|
+
#include <mutex>
|
|
35
|
+
#include <string>
|
|
36
|
+
#include <type_traits>
|
|
37
|
+
#include <unordered_map>
|
|
38
|
+
#include <utility>
|
|
39
|
+
#include <vector>
|
|
40
|
+
|
|
41
|
+
namespace couchbase::core::protostellar
|
|
42
|
+
{
|
|
43
|
+
|
|
44
|
+
// RFC 77 (Bootstrapping -> Maximum Message Size): couchbase2 clients raise the receive limit to
|
|
45
|
+
// 25 MiB, so nothing the transport hands to a converter can legitimately exceed it -- including a
|
|
46
|
+
// value a compressed payload claims to inflate to.
|
|
47
|
+
constexpr std::size_t max_receive_message_size{ std::size_t{ 25 } * 1024 * 1024 };
|
|
48
|
+
|
|
49
|
+
// Registry of in-flight gRPC calls so the transport can cancel and drain them at shutdown. Without
|
|
50
|
+
// it, teardown could destroy the io_context (and channel) while a gRPC callback is still pending on
|
|
51
|
+
// gRPC's threadpool -- the callback would then post onto a freed io_context (use-after-free). Each
|
|
52
|
+
// completion posts its handler and *then* removes its context; cancel_and_drain() TryCancels
|
|
53
|
+
// whatever is still outstanding and blocks until every callback has done both. That order is the
|
|
54
|
+
// contract, not an incidental detail: remove() is what releases the drain, so deregistering first
|
|
55
|
+
// would let the drain return while a gRPC thread was still posting onto the io_context -- exactly
|
|
56
|
+
// the use-after-free above. Held via shared_ptr so a late callback can safely touch it even while
|
|
57
|
+
// the owning dispatcher is being destroyed.
|
|
58
|
+
class call_tracker
|
|
59
|
+
{
|
|
60
|
+
public:
|
|
61
|
+
// The tracker OWNS a shared_ptr to each context so a snapshot can keep it alive while cancelling
|
|
62
|
+
// even if a concurrent completion removes and releases the caller's own reference.
|
|
63
|
+
void add(std::shared_ptr<grpc::ClientContext> context)
|
|
64
|
+
{
|
|
65
|
+
auto* key = context.get();
|
|
66
|
+
const std::scoped_lock lock{ mutex_ };
|
|
67
|
+
outstanding_.emplace(key, entry{ std::move(context), {}, {} });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// As above, for a call that cancellation alone does not drive to completion. `on_cancel` runs
|
|
71
|
+
// straight after TryCancel; `owner` keeps the call's own object alive for as long as it stays
|
|
72
|
+
// registered, so that hook cannot run against freed storage.
|
|
73
|
+
void add(std::shared_ptr<grpc::ClientContext> context,
|
|
74
|
+
std::function<void()> on_cancel,
|
|
75
|
+
std::shared_ptr<void> owner)
|
|
76
|
+
{
|
|
77
|
+
auto* key = context.get();
|
|
78
|
+
const std::scoped_lock lock{ mutex_ };
|
|
79
|
+
outstanding_.emplace(key, entry{ std::move(context), std::move(on_cancel), std::move(owner) });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
void remove(grpc::ClientContext* context)
|
|
83
|
+
{
|
|
84
|
+
const std::scoped_lock lock{ mutex_ };
|
|
85
|
+
outstanding_.erase(context);
|
|
86
|
+
if (outstanding_.empty()) {
|
|
87
|
+
idle_.notify_all();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Cancel every in-flight call and block until all of their gRPC callbacks have run. Runs on
|
|
92
|
+
// whichever thread destroys the dispatcher: the io_context thread, another thread while the io
|
|
93
|
+
// thread keeps running, or a thread that never runs the io_context at all. Nothing here may
|
|
94
|
+
// therefore depend on io_context work making progress. gRPC's own threads deliver the
|
|
95
|
+
// cancellations and the completions post back onto the io_context, where they stay queued until
|
|
96
|
+
// the drain returns, so blocking here cannot deadlock against them.
|
|
97
|
+
void cancel_and_drain()
|
|
98
|
+
{
|
|
99
|
+
// Snapshot the entries under the lock, then release it before calling TryCancel or any cancel
|
|
100
|
+
// hook so no external gRPC call runs while mutex_ is held (a completion delivered on a gRPC
|
|
101
|
+
// thread takes that same lock in remove(), and releasing a read hold can deliver OnDone
|
|
102
|
+
// inline). The snapshot keeps every context and owner alive across the unlocked cancel, so a
|
|
103
|
+
// concurrent completion that removes and releases its own reference cannot leave a dangling
|
|
104
|
+
// pointer here.
|
|
105
|
+
std::vector<entry> pending;
|
|
106
|
+
{
|
|
107
|
+
const std::scoped_lock lock{ mutex_ };
|
|
108
|
+
pending.reserve(outstanding_.size());
|
|
109
|
+
for (const auto& [ptr, item] : outstanding_) {
|
|
110
|
+
pending.push_back(item);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const auto& item : pending) {
|
|
114
|
+
item.context->TryCancel();
|
|
115
|
+
if (item.on_cancel) {
|
|
116
|
+
item.on_cancel();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
std::unique_lock lock{ mutex_ };
|
|
120
|
+
idle_.wait(lock, [this] {
|
|
121
|
+
return outstanding_.empty();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private:
|
|
126
|
+
struct entry {
|
|
127
|
+
std::shared_ptr<grpc::ClientContext> context;
|
|
128
|
+
// Run straight after TryCancel for calls that cancellation alone does not finish. A streaming
|
|
129
|
+
// call registers one that releases its read hold: gRPC defers OnDone while a hold is active,
|
|
130
|
+
// and the hold is otherwise released only from an io_context continuation -- which cannot run
|
|
131
|
+
// when the drain is itself blocking the io thread. Empty for unary calls, which take no holds.
|
|
132
|
+
std::function<void()> on_cancel;
|
|
133
|
+
// Keeps a self-owning call object (the streaming reactor) alive for as long as it is
|
|
134
|
+
// registered, so on_cancel cannot run against freed storage.
|
|
135
|
+
std::shared_ptr<void> owner;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
std::mutex mutex_{};
|
|
139
|
+
std::condition_variable idle_{};
|
|
140
|
+
std::unordered_map<grpc::ClientContext*, entry> outstanding_{};
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Handle to an in-flight unary call. cancel() maps to gRPC's client-side TryCancel; the call still
|
|
144
|
+
// completes afterwards (with a CANCELLED status) and its handler is posted as usual, so callers
|
|
145
|
+
// never leak a pending operation. As with any completion, the handler runs when the io_context runs
|
|
146
|
+
// it -- cancelling does not make it run inline.
|
|
147
|
+
class pending_call
|
|
148
|
+
{
|
|
149
|
+
public:
|
|
150
|
+
pending_call() = default;
|
|
151
|
+
explicit pending_call(std::shared_ptr<grpc::ClientContext> context)
|
|
152
|
+
: context_{ std::move(context) }
|
|
153
|
+
{
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
void cancel() const
|
|
157
|
+
{
|
|
158
|
+
if (context_) {
|
|
159
|
+
context_->TryCancel();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private:
|
|
164
|
+
std::shared_ptr<grpc::ClientContext> context_{};
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Every call gets a deadline, including one whose budget is already spent.
|
|
168
|
+
//
|
|
169
|
+
// gRPC has no "expire immediately" input: leaving the deadline unset means the call runs until the
|
|
170
|
+
// channel breaks, so guarding the set_deadline with `timeout > 0` turns "no time left" into "no
|
|
171
|
+
// limit" -- the wrong direction for the mistake to go, and one that leaves cluster::close() waiting
|
|
172
|
+
// on a call with no reason to finish. Clamping to the smallest positive deadline expires the call
|
|
173
|
+
// instead, which is what an exhausted budget should do.
|
|
174
|
+
//
|
|
175
|
+
// This is the second line of defence, not the only one: every caller in component.cxx rejects a
|
|
176
|
+
// non-positive timeout up front (see fail_expired there), because failing before anything is sent
|
|
177
|
+
// carries information this layer cannot reconstruct -- that the operation provably never reached
|
|
178
|
+
// the server, hence unambiguous_timeout even for a mutation. The clamp exists so a caller that
|
|
179
|
+
// forgets, or a retry loop passing on what is left of a budget, cannot produce an unbounded call.
|
|
180
|
+
[[nodiscard]] inline auto
|
|
181
|
+
call_deadline(std::chrono::milliseconds timeout) -> std::chrono::system_clock::time_point
|
|
182
|
+
{
|
|
183
|
+
return std::chrono::system_clock::now() + std::max(timeout, std::chrono::milliseconds{ 1 });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Default channel arguments: TCP keepalive so an idle channel detects a dead gateway.
|
|
187
|
+
[[nodiscard]] auto
|
|
188
|
+
default_channel_arguments() -> grpc::ChannelArguments;
|
|
189
|
+
|
|
190
|
+
// Build an insecure (plaintext) channel for the non-TLS couchbase2 path. TLS and per-call
|
|
191
|
+
// credentials live on the secure-channel path (see credentials.hxx/.cxx).
|
|
192
|
+
[[nodiscard]] auto
|
|
193
|
+
make_insecure_channel(const std::string& endpoint) -> std::shared_ptr<grpc::Channel>;
|
|
194
|
+
|
|
195
|
+
// Reads a gRPC server stream via the callback API and hands each message, plus the terminal
|
|
196
|
+
// status, to the io_context. Assumes a single-threaded io_context (the SDK contract), so the FIFO
|
|
197
|
+
// post order guarantees every row is delivered before the completion runs.
|
|
198
|
+
//
|
|
199
|
+
// Held by shared_ptr, with every posted continuation carrying a strong reference. That covers both
|
|
200
|
+
// endings: the last reference is dropped either by the io_context running the completion or by the
|
|
201
|
+
// io_context being destroyed with the completion still queued. It also lets the read hold below be
|
|
202
|
+
// released from another thread without racing the reactor's own destruction.
|
|
203
|
+
template<typename Response>
|
|
204
|
+
class streaming_reactor final
|
|
205
|
+
: public grpc::ClientReadReactor<Response>
|
|
206
|
+
, public std::enable_shared_from_this<streaming_reactor<Response>>
|
|
207
|
+
{
|
|
208
|
+
public:
|
|
209
|
+
streaming_reactor(asio::io_context& io,
|
|
210
|
+
std::shared_ptr<call_tracker> tracker,
|
|
211
|
+
std::shared_ptr<grpc::ClientContext> context,
|
|
212
|
+
utils::movable_function<void(Response)> on_row,
|
|
213
|
+
utils::movable_function<void(grpc::Status)> on_done)
|
|
214
|
+
: io_{ io }
|
|
215
|
+
, tracker_{ std::move(tracker) }
|
|
216
|
+
, context_{ std::move(context) }
|
|
217
|
+
, on_row_{ std::move(on_row) }
|
|
218
|
+
, on_done_{ std::move(on_done) }
|
|
219
|
+
{
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
void begin()
|
|
223
|
+
{
|
|
224
|
+
// Because backpressure re-issues StartRead from the io_context (outside the reaction, see
|
|
225
|
+
// OnReadDone), a hold keeps the call alive between a completed read and the next StartRead --
|
|
226
|
+
// otherwise gRPC could finalize the call (fire OnDone, free the reader) while a queued read
|
|
227
|
+
// task still holds a pointer to it, and the stray StartRead would hit a destroyed call. This is
|
|
228
|
+
// gRPC's documented requirement for issuing an operation from outside a reaction, not a local
|
|
229
|
+
// precaution: see the AddHold contract in grpcpp/impl/codegen/client_callback.h.
|
|
230
|
+
//
|
|
231
|
+
// The hold is released in OnReadDone once a read comes back not-ok, or by release_hold() when
|
|
232
|
+
// the call is cancelled while no read is outstanding. That second caller is not the read loop's
|
|
233
|
+
// owner and cannot know whether a read task is still queued, so the hold alone does not close
|
|
234
|
+
// the window -- release_hold() and the queued StartRead are ordered against each other instead.
|
|
235
|
+
//
|
|
236
|
+
// This runs under the same lock, because server_stream() registers the cancel hook before the
|
|
237
|
+
// invoker binds the reactor to a call: a drain in that window would otherwise reach RemoveHold
|
|
238
|
+
// with no reader bound and no hold taken. Under the lock the two orders are both well defined
|
|
239
|
+
// -- either the hold is taken and the drain releases it, or the drain gets there first and no
|
|
240
|
+
// hold is taken at all. The call is still started in that second case, so it runs to OnDone and
|
|
241
|
+
// releases the drain rather than stranding it.
|
|
242
|
+
const std::scoped_lock lock{ mutex_ };
|
|
243
|
+
if (!hold_released_) {
|
|
244
|
+
this->AddHold();
|
|
245
|
+
hold_taken_ = true;
|
|
246
|
+
}
|
|
247
|
+
this->StartRead(¤t_);
|
|
248
|
+
this->StartCall();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Releases begin()'s hold, at most once however many times this is called -- RemoveHold has to
|
|
252
|
+
// balance AddHold exactly, and two callers race for it: OnReadDone when reads stop, and
|
|
253
|
+
// cancel_and_drain() through the hook registered in server_stream().
|
|
254
|
+
//
|
|
255
|
+
// The drain needs that hook because a hold defers OnDone. Between a completed read and the next
|
|
256
|
+
// StartRead no read is outstanding for TryCancel to fail, so nothing would drive the call to
|
|
257
|
+
// completion, and the drain -- which may be running on the io_context thread itself -- would wait
|
|
258
|
+
// for an OnDone that cannot fire until a continuation that cannot run has run.
|
|
259
|
+
//
|
|
260
|
+
// The lock orders this release against the queued StartRead in OnReadDone; it is not there to
|
|
261
|
+
// make the flag thread-safe. Removing the last hold destroys the reader synchronously, inside
|
|
262
|
+
// RemoveHold, so the two must not overlap: whichever side acquires the lock first, the other sees
|
|
263
|
+
// a decision already made rather than a reader mid-destruction. Releasing while a read is
|
|
264
|
+
// outstanding is harmless -- the outstanding read keeps gRPC's own count above zero, so it
|
|
265
|
+
// finalizes only once that read completes.
|
|
266
|
+
void release_hold()
|
|
267
|
+
{
|
|
268
|
+
const std::scoped_lock lock{ mutex_ };
|
|
269
|
+
if (!hold_released_) {
|
|
270
|
+
hold_released_ = true;
|
|
271
|
+
// Nothing to balance if begin() has not run yet: the reactor is reachable from the tracker
|
|
272
|
+
// before the invoker binds it to a call, and RemoveHold on an unbound reactor dereferences a
|
|
273
|
+
// reader that does not exist. Setting the flag is enough -- begin() reads it and skips the
|
|
274
|
+
// hold.
|
|
275
|
+
if (hold_taken_) {
|
|
276
|
+
this->RemoveHold();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
void OnReadDone(bool ok) override
|
|
282
|
+
{
|
|
283
|
+
if (!ok) {
|
|
284
|
+
release_hold(); // no more reads; let the call finalize and OnDone fire
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
auto message = std::make_shared<Response>(std::move(current_));
|
|
288
|
+
// Backpressure: issue the next read from *inside* the io_context continuation, only after the
|
|
289
|
+
// consumer has processed this row. On a single-threaded io_context that caps the amount gRPC
|
|
290
|
+
// reads ahead of a slow consumer at one message, instead of draining the whole stream into the
|
|
291
|
+
// asio post queue. current_ is moved-from here but no read is outstanding until StartRead runs.
|
|
292
|
+
// The continuation holds a strong reference so it cannot outlive its own reactor.
|
|
293
|
+
//
|
|
294
|
+
// The consumer runs before the lock is taken, deliberately: it is caller code, and holding the
|
|
295
|
+
// reactor's lock across it would let a slow or blocking consumer stall a concurrent drain.
|
|
296
|
+
asio::post(io_, [self = this->shared_from_this(), message]() {
|
|
297
|
+
self->on_row_(std::move(*message));
|
|
298
|
+
const std::scoped_lock lock{ self->mutex_ };
|
|
299
|
+
if (self->hold_released_) {
|
|
300
|
+
// The hold is gone, so gRPC may already have destroyed the reader -- it does so inside
|
|
301
|
+
// RemoveHold -- and a StartRead would dispatch through it. Nothing is lost by stopping
|
|
302
|
+
// here: the hold was released either because reads had finished or because the call was
|
|
303
|
+
// cancelled, and either way it is already on its way to OnDone.
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
self->StartRead(&self->current_);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
void OnDone(const grpc::Status& status) override
|
|
311
|
+
{
|
|
312
|
+
// Post before deregistering, for the same reason as unary(): remove() releases
|
|
313
|
+
// cancel_and_drain(), so deregistering first would let ~dispatcher() return while this thread
|
|
314
|
+
// is still inside asio::post(). The handle and the context pointer are cached in locals first
|
|
315
|
+
// because the posted task owns this reactor and may destroy it on the io thread as soon as it
|
|
316
|
+
// is enqueued -- reading tracker_ or context_ after the post could touch freed storage. The
|
|
317
|
+
// tracker owns a shared_ptr to the context, so the cached pointer stays valid as a key until
|
|
318
|
+
// remove() erases it, and remove() never dereferences it.
|
|
319
|
+
auto tracker = tracker_;
|
|
320
|
+
auto* context = context_.get();
|
|
321
|
+
// Copy the status into the closure -- it is a reference parameter, so the posted task must own
|
|
322
|
+
// its own copy rather than capture a reference that dangles once OnDone returns.
|
|
323
|
+
//
|
|
324
|
+
// The strong reference is what destroys the reactor: dropped when this task runs, or when the
|
|
325
|
+
// task is destroyed unrun because the io_context went away first. A shared_ptr rather than the
|
|
326
|
+
// unique_ptr this held before backpressure, because the cancel hook needs a weak reference to
|
|
327
|
+
// the same object.
|
|
328
|
+
asio::post(io_, [self = this->shared_from_this(), status_copy = status]() mutable {
|
|
329
|
+
// Moved rather than copied: the task owns this status and does not touch it again, and
|
|
330
|
+
// grpc::Status carries two std::strings.
|
|
331
|
+
self->on_done_(std::move(status_copy));
|
|
332
|
+
});
|
|
333
|
+
tracker->remove(context);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private:
|
|
337
|
+
// Guards the hold flags and, with them, the ordering between releasing the hold and issuing a
|
|
338
|
+
// read -- both the next read in OnReadDone and the first one in begin(). Uncontended in steady
|
|
339
|
+
// state: only the io_context thread takes it, and the cancel hook once at teardown.
|
|
340
|
+
std::mutex mutex_{};
|
|
341
|
+
bool hold_taken_{ false };
|
|
342
|
+
bool hold_released_{ false };
|
|
343
|
+
asio::io_context& io_;
|
|
344
|
+
std::shared_ptr<call_tracker> tracker_;
|
|
345
|
+
Response current_{};
|
|
346
|
+
std::shared_ptr<grpc::ClientContext> context_;
|
|
347
|
+
utils::movable_function<void(Response)> on_row_;
|
|
348
|
+
utils::movable_function<void(grpc::Status)> on_done_;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// Bridges gRPC's callback API onto an asio::io_context. A unary RPC is launched on gRPC's own
|
|
352
|
+
// threadpool; its completion is posted back onto the io_context, so callers observe results on
|
|
353
|
+
// the SDK's execution context — the same threading contract as the MCBP path.
|
|
354
|
+
class dispatcher
|
|
355
|
+
{
|
|
356
|
+
public:
|
|
357
|
+
dispatcher(asio::io_context& io, std::shared_ptr<grpc::Channel> channel);
|
|
358
|
+
dispatcher(const dispatcher&) = delete;
|
|
359
|
+
dispatcher(dispatcher&&) = delete;
|
|
360
|
+
auto operator=(const dispatcher&) -> dispatcher& = delete;
|
|
361
|
+
auto operator=(dispatcher&&) -> dispatcher& = delete;
|
|
362
|
+
|
|
363
|
+
// Cancels and drains every in-flight call before the io_context/channel go away, so no gRPC
|
|
364
|
+
// callback can fire against freed state after the owning component is destroyed.
|
|
365
|
+
~dispatcher()
|
|
366
|
+
{
|
|
367
|
+
tracker_->cancel_and_drain();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
[[nodiscard]] auto channel() const -> const std::shared_ptr<grpc::Channel>&;
|
|
371
|
+
|
|
372
|
+
// Issue a unary RPC.
|
|
373
|
+
// invoker(grpc::ClientContext&, Response&, callback) launches the async call, e.g.
|
|
374
|
+
// stub->async()->Get(&ctx, &request, &response, std::move(callback));
|
|
375
|
+
// handler(grpc::Status, Response&&) runs once, on the io_context, when the call completes.
|
|
376
|
+
// The request must outlive the call (the caller owns it). Returns a pending_call for
|
|
377
|
+
// cancellation.
|
|
378
|
+
template<typename Response, typename Invoker, typename Handler>
|
|
379
|
+
auto unary(std::chrono::milliseconds timeout, Invoker&& invoker, Handler&& handler)
|
|
380
|
+
-> pending_call
|
|
381
|
+
{
|
|
382
|
+
struct call_state {
|
|
383
|
+
std::shared_ptr<grpc::ClientContext> context{ std::make_shared<grpc::ClientContext>() };
|
|
384
|
+
Response response{};
|
|
385
|
+
};
|
|
386
|
+
auto state = std::make_shared<call_state>();
|
|
387
|
+
state->context->set_deadline(call_deadline(timeout));
|
|
388
|
+
|
|
389
|
+
// Register the call so shutdown can cancel and drain it. The context is kept alive both by
|
|
390
|
+
// `state` (captured by the callback below) and by the tracker's own reference, so the raw
|
|
391
|
+
// pointer the tracker keys on is valid for the TryCancel in cancel_and_drain().
|
|
392
|
+
auto* context = state->context.get();
|
|
393
|
+
tracker_->add(state->context);
|
|
394
|
+
|
|
395
|
+
// Hold the handler in a shared_ptr so the gRPC-thread callback can hand it to the posted
|
|
396
|
+
// task. Capture the io_context by pointer (never a reference to a local) so nothing dangles
|
|
397
|
+
// once unary() returns; the dispatcher (and its io_context) must outlive the call. Capture the
|
|
398
|
+
// tracker (shared) so deregistration is safe even as the dispatcher is being destroyed.
|
|
399
|
+
auto handler_holder = std::make_shared<std::decay_t<Handler>>(std::forward<Handler>(handler));
|
|
400
|
+
auto* io = &io_;
|
|
401
|
+
auto tracker = tracker_;
|
|
402
|
+
|
|
403
|
+
// If the invoker throws before it launches the async call, the completion callback below never
|
|
404
|
+
// runs, so deregister here to keep cancel_and_drain() from waiting on a call that will never
|
|
405
|
+
// complete.
|
|
406
|
+
try {
|
|
407
|
+
std::forward<Invoker>(invoker)(
|
|
408
|
+
*state->context,
|
|
409
|
+
state->response,
|
|
410
|
+
[state, handler_holder, io, tracker, context](grpc::Status status) {
|
|
411
|
+
// Post before deregistering. remove() is what releases cancel_and_drain(), so
|
|
412
|
+
// deregistering first would let ~dispatcher() return -- and its owner tear the
|
|
413
|
+
// io_context down -- while this thread is still inside asio::post(*io, ...), which is
|
|
414
|
+
// the use-after-free the tracker exists to prevent. Ordered this way, the drain cannot
|
|
415
|
+
// return until the post has been issued. The captures keep state and the tracker alive,
|
|
416
|
+
// so nothing here depends on the dispatcher still existing.
|
|
417
|
+
asio::post(*io, [state, handler_holder, status = std::move(status)]() mutable {
|
|
418
|
+
(*handler_holder)(std::move(status), std::move(state->response));
|
|
419
|
+
});
|
|
420
|
+
tracker->remove(context);
|
|
421
|
+
});
|
|
422
|
+
} catch (...) {
|
|
423
|
+
tracker_->remove(context);
|
|
424
|
+
throw;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return pending_call{ state->context };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Issue a server-streaming RPC.
|
|
431
|
+
// invoker(grpc::ClientContext&, grpc::ClientReadReactor<Response>*) binds the reactor to the
|
|
432
|
+
// call, e.g. stub->async()->Query(&ctx, &request, reactor).
|
|
433
|
+
// on_row(Response) runs on the io_context for each streamed message; on_done(grpc::Status)
|
|
434
|
+
// runs once when the stream ends. The request must outlive the call (capture it in on_done).
|
|
435
|
+
template<typename Response, typename Invoker>
|
|
436
|
+
auto server_stream(std::chrono::milliseconds timeout,
|
|
437
|
+
Invoker&& invoker,
|
|
438
|
+
utils::movable_function<void(Response)> on_row,
|
|
439
|
+
utils::movable_function<void(grpc::Status)> on_done) -> pending_call
|
|
440
|
+
{
|
|
441
|
+
auto context = std::make_shared<grpc::ClientContext>();
|
|
442
|
+
context->set_deadline(call_deadline(timeout));
|
|
443
|
+
// gRPC only borrows the reactor for the duration of the call; ownership stays with this
|
|
444
|
+
// shared_ptr and the continuations the reactor posts.
|
|
445
|
+
auto reactor = std::make_shared<streaming_reactor<Response>>(
|
|
446
|
+
io_, tracker_, context, std::move(on_row), std::move(on_done));
|
|
447
|
+
// Register before launch so shutdown can cancel it; the reactor deregisters in OnDone. The hook
|
|
448
|
+
// releases the reactor's read hold, without which a cancelled-but-parked stream never reaches
|
|
449
|
+
// OnDone and the drain blocks forever; the reactor doubles as the owner so the hook always has
|
|
450
|
+
// a live object to call.
|
|
451
|
+
tracker_->add(
|
|
452
|
+
context,
|
|
453
|
+
[weak = std::weak_ptr<streaming_reactor<Response>>{ reactor }]() {
|
|
454
|
+
if (const auto locked = weak.lock()) {
|
|
455
|
+
locked->release_hold();
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
reactor);
|
|
459
|
+
// If the invoker fails to bind the reactor to a call, OnDone never fires, so deregister here
|
|
460
|
+
// rather than block cancel_and_drain() forever at shutdown. Dropping the tracker's reference
|
|
461
|
+
// leaves this scope holding the last one, so the reactor is destroyed as the exception unwinds.
|
|
462
|
+
bool bound{ false };
|
|
463
|
+
try {
|
|
464
|
+
std::forward<Invoker>(invoker)(*context, reactor.get());
|
|
465
|
+
bound = true;
|
|
466
|
+
reactor->begin();
|
|
467
|
+
} catch (...) {
|
|
468
|
+
if (!bound) {
|
|
469
|
+
tracker_->remove(context.get());
|
|
470
|
+
}
|
|
471
|
+
throw;
|
|
472
|
+
}
|
|
473
|
+
return pending_call{ context };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private:
|
|
477
|
+
asio::io_context& io_;
|
|
478
|
+
std::shared_ptr<grpc::Channel> channel_;
|
|
479
|
+
std::shared_ptr<call_tracker> tracker_{ std::make_shared<call_tracker>() };
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
} // namespace couchbase::core::protostellar
|