From 7533328075e3cccb2aab7a2a1e990d7eb3a4f984 Mon Sep 17 00:00:00 2001 From: Vignesh Babu Date: Mon, 11 Mar 2024 11:14:30 -0700 Subject: [PATCH] [server] Allow configuring max incoming connections at the server (#36088) Closes #36088 COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/36088 from Vignesh2208:server-fix e62a8f7ae729cfe8c7c6806d5c7e5c8c3866c2ee PiperOrigin-RevId: 614729878 --- CMakeLists.txt | 8 ++ Makefile | 1 + Package.swift | 2 + build_autogenerated.yaml | 16 ++++ config.m4 | 1 + config.w32 | 1 + gRPC-C++.podspec | 2 + gRPC-Core.podspec | 3 + grpc.gemspec | 2 + grpc.gyp | 3 + include/grpc/impl/channel_arg_names.h | 4 + package.xml | 2 + src/core/BUILD | 19 +++++ .../transport/chttp2/server/chttp2_server.cc | 32 +++++++- .../lib/resource_quota/connection_quota.cc | 71 ++++++++++++++++++ .../lib/resource_quota/connection_quota.h | 61 +++++++++++++++ src/python/grpcio/grpc_core_dependencies.py | 1 + .../resource_quota_end2end_stress_test.cc | 74 ++++++++++++++++++- tools/doxygen/Doxyfile.c++.internal | 2 + tools/doxygen/Doxyfile.core.internal | 2 + 20 files changed, 305 insertions(+), 2 deletions(-) create mode 100644 src/core/lib/resource_quota/connection_quota.cc create mode 100644 src/core/lib/resource_quota/connection_quota.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8faf34698dd..d5fc01ad30c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2383,6 +2383,7 @@ add_library(grpc src/core/lib/promise/trace.cc src/core/lib/resource_quota/api.cc src/core/lib/resource_quota/arena.cc + src/core/lib/resource_quota/connection_quota.cc src/core/lib/resource_quota/memory_quota.cc src/core/lib/resource_quota/periodic_update.cc src/core/lib/resource_quota/resource_quota.cc @@ -3145,6 +3146,7 @@ add_library(grpc_unsecure src/core/lib/promise/trace.cc src/core/lib/resource_quota/api.cc src/core/lib/resource_quota/arena.cc + src/core/lib/resource_quota/connection_quota.cc src/core/lib/resource_quota/memory_quota.cc src/core/lib/resource_quota/periodic_update.cc src/core/lib/resource_quota/resource_quota.cc @@ -5252,6 +5254,7 @@ add_library(grpc_authorization_provider src/core/lib/promise/trace.cc src/core/lib/resource_quota/api.cc src/core/lib/resource_quota/arena.cc + src/core/lib/resource_quota/connection_quota.cc src/core/lib/resource_quota/memory_quota.cc src/core/lib/resource_quota/periodic_update.cc src/core/lib/resource_quota/resource_quota.cc @@ -10177,6 +10180,7 @@ add_executable(chunked_vector_test src/core/lib/promise/activity.cc src/core/lib/promise/trace.cc src/core/lib/resource_quota/arena.cc + src/core/lib/resource_quota/connection_quota.cc src/core/lib/resource_quota/memory_quota.cc src/core/lib/resource_quota/periodic_update.cc src/core/lib/resource_quota/resource_quota.cc @@ -14175,6 +14179,7 @@ add_executable(flow_control_test src/core/lib/iomgr/iomgr_internal.cc src/core/lib/promise/activity.cc src/core/lib/promise/trace.cc + src/core/lib/resource_quota/connection_quota.cc src/core/lib/resource_quota/memory_quota.cc src/core/lib/resource_quota/periodic_update.cc src/core/lib/resource_quota/resource_quota.cc @@ -14263,6 +14268,7 @@ add_executable(for_each_test src/core/lib/promise/activity.cc src/core/lib/promise/trace.cc src/core/lib/resource_quota/arena.cc + src/core/lib/resource_quota/connection_quota.cc src/core/lib/resource_quota/memory_quota.cc src/core/lib/resource_quota/periodic_update.cc src/core/lib/resource_quota/resource_quota.cc @@ -17709,6 +17715,7 @@ add_executable(interceptor_list_test src/core/lib/promise/activity.cc src/core/lib/promise/trace.cc src/core/lib/resource_quota/arena.cc + src/core/lib/resource_quota/connection_quota.cc src/core/lib/resource_quota/memory_quota.cc src/core/lib/resource_quota/periodic_update.cc src/core/lib/resource_quota/resource_quota.cc @@ -18900,6 +18907,7 @@ add_executable(map_pipe_test src/core/lib/promise/activity.cc src/core/lib/promise/trace.cc src/core/lib/resource_quota/arena.cc + src/core/lib/resource_quota/connection_quota.cc src/core/lib/resource_quota/memory_quota.cc src/core/lib/resource_quota/periodic_update.cc src/core/lib/resource_quota/resource_quota.cc diff --git a/Makefile b/Makefile index 4bdbbec686f..23f195b095a 100644 --- a/Makefile +++ b/Makefile @@ -1309,6 +1309,7 @@ LIBGRPC_SRC = \ src/core/lib/promise/trace.cc \ src/core/lib/resource_quota/api.cc \ src/core/lib/resource_quota/arena.cc \ + src/core/lib/resource_quota/connection_quota.cc \ src/core/lib/resource_quota/memory_quota.cc \ src/core/lib/resource_quota/periodic_update.cc \ src/core/lib/resource_quota/resource_quota.cc \ diff --git a/Package.swift b/Package.swift index 64d6d866bf7..9737494d3bc 100644 --- a/Package.swift +++ b/Package.swift @@ -1608,6 +1608,8 @@ let package = Package( "src/core/lib/resource_quota/api.h", "src/core/lib/resource_quota/arena.cc", "src/core/lib/resource_quota/arena.h", + "src/core/lib/resource_quota/connection_quota.cc", + "src/core/lib/resource_quota/connection_quota.h", "src/core/lib/resource_quota/memory_quota.cc", "src/core/lib/resource_quota/memory_quota.h", "src/core/lib/resource_quota/periodic_update.cc", diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index 967ad017f7d..e9478a2a4b8 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -1041,6 +1041,7 @@ libs: - src/core/lib/promise/try_seq.h - src/core/lib/resource_quota/api.h - src/core/lib/resource_quota/arena.h + - src/core/lib/resource_quota/connection_quota.h - src/core/lib/resource_quota/memory_quota.h - src/core/lib/resource_quota/periodic_update.h - src/core/lib/resource_quota/resource_quota.h @@ -1837,6 +1838,7 @@ libs: - src/core/lib/promise/trace.cc - src/core/lib/resource_quota/api.cc - src/core/lib/resource_quota/arena.cc + - src/core/lib/resource_quota/connection_quota.cc - src/core/lib/resource_quota/memory_quota.cc - src/core/lib/resource_quota/periodic_update.cc - src/core/lib/resource_quota/resource_quota.cc @@ -2542,6 +2544,7 @@ libs: - src/core/lib/promise/try_seq.h - src/core/lib/resource_quota/api.h - src/core/lib/resource_quota/arena.h + - src/core/lib/resource_quota/connection_quota.h - src/core/lib/resource_quota/memory_quota.h - src/core/lib/resource_quota/periodic_update.h - src/core/lib/resource_quota/resource_quota.h @@ -2961,6 +2964,7 @@ libs: - src/core/lib/promise/trace.cc - src/core/lib/resource_quota/api.cc - src/core/lib/resource_quota/arena.cc + - src/core/lib/resource_quota/connection_quota.cc - src/core/lib/resource_quota/memory_quota.cc - src/core/lib/resource_quota/periodic_update.cc - src/core/lib/resource_quota/resource_quota.cc @@ -4619,6 +4623,7 @@ libs: - src/core/lib/promise/try_seq.h - src/core/lib/resource_quota/api.h - src/core/lib/resource_quota/arena.h + - src/core/lib/resource_quota/connection_quota.h - src/core/lib/resource_quota/memory_quota.h - src/core/lib/resource_quota/periodic_update.h - src/core/lib/resource_quota/resource_quota.h @@ -4916,6 +4921,7 @@ libs: - src/core/lib/promise/trace.cc - src/core/lib/resource_quota/api.cc - src/core/lib/resource_quota/arena.cc + - src/core/lib/resource_quota/connection_quota.cc - src/core/lib/resource_quota/memory_quota.cc - src/core/lib/resource_quota/periodic_update.cc - src/core/lib/resource_quota/resource_quota.cc @@ -7689,6 +7695,7 @@ targets: - src/core/lib/promise/seq.h - src/core/lib/promise/trace.h - src/core/lib/resource_quota/arena.h + - src/core/lib/resource_quota/connection_quota.h - src/core/lib/resource_quota/memory_quota.h - src/core/lib/resource_quota/periodic_update.h - src/core/lib/resource_quota/resource_quota.h @@ -7738,6 +7745,7 @@ targets: - src/core/lib/promise/activity.cc - src/core/lib/promise/trace.cc - src/core/lib/resource_quota/arena.cc + - src/core/lib/resource_quota/connection_quota.cc - src/core/lib/resource_quota/memory_quota.cc - src/core/lib/resource_quota/periodic_update.cc - src/core/lib/resource_quota/resource_quota.cc @@ -9785,6 +9793,7 @@ targets: - src/core/lib/promise/race.h - src/core/lib/promise/seq.h - src/core/lib/promise/trace.h + - src/core/lib/resource_quota/connection_quota.h - src/core/lib/resource_quota/memory_quota.h - src/core/lib/resource_quota/periodic_update.h - src/core/lib/resource_quota/resource_quota.h @@ -9839,6 +9848,7 @@ targets: - src/core/lib/iomgr/iomgr_internal.cc - src/core/lib/promise/activity.cc - src/core/lib/promise/trace.cc + - src/core/lib/resource_quota/connection_quota.cc - src/core/lib/resource_quota/memory_quota.cc - src/core/lib/resource_quota/periodic_update.cc - src/core/lib/resource_quota/resource_quota.cc @@ -9928,6 +9938,7 @@ targets: - src/core/lib/promise/trace.h - src/core/lib/promise/try_seq.h - src/core/lib/resource_quota/arena.h + - src/core/lib/resource_quota/connection_quota.h - src/core/lib/resource_quota/memory_quota.h - src/core/lib/resource_quota/periodic_update.h - src/core/lib/resource_quota/resource_quota.h @@ -9978,6 +9989,7 @@ targets: - src/core/lib/promise/activity.cc - src/core/lib/promise/trace.cc - src/core/lib/resource_quota/arena.cc + - src/core/lib/resource_quota/connection_quota.cc - src/core/lib/resource_quota/memory_quota.cc - src/core/lib/resource_quota/periodic_update.cc - src/core/lib/resource_quota/resource_quota.cc @@ -11578,6 +11590,7 @@ targets: - src/core/lib/promise/seq.h - src/core/lib/promise/trace.h - src/core/lib/resource_quota/arena.h + - src/core/lib/resource_quota/connection_quota.h - src/core/lib/resource_quota/memory_quota.h - src/core/lib/resource_quota/periodic_update.h - src/core/lib/resource_quota/resource_quota.h @@ -11628,6 +11641,7 @@ targets: - src/core/lib/promise/activity.cc - src/core/lib/promise/trace.cc - src/core/lib/resource_quota/arena.cc + - src/core/lib/resource_quota/connection_quota.cc - src/core/lib/resource_quota/memory_quota.cc - src/core/lib/resource_quota/periodic_update.cc - src/core/lib/resource_quota/resource_quota.cc @@ -12256,6 +12270,7 @@ targets: - src/core/lib/promise/trace.h - src/core/lib/promise/try_seq.h - src/core/lib/resource_quota/arena.h + - src/core/lib/resource_quota/connection_quota.h - src/core/lib/resource_quota/memory_quota.h - src/core/lib/resource_quota/periodic_update.h - src/core/lib/resource_quota/resource_quota.h @@ -12306,6 +12321,7 @@ targets: - src/core/lib/promise/activity.cc - src/core/lib/promise/trace.cc - src/core/lib/resource_quota/arena.cc + - src/core/lib/resource_quota/connection_quota.cc - src/core/lib/resource_quota/memory_quota.cc - src/core/lib/resource_quota/periodic_update.cc - src/core/lib/resource_quota/resource_quota.cc diff --git a/config.m4 b/config.m4 index 4600f743302..71180188c82 100644 --- a/config.m4 +++ b/config.m4 @@ -684,6 +684,7 @@ if test "$PHP_GRPC" != "no"; then src/core/lib/promise/trace.cc \ src/core/lib/resource_quota/api.cc \ src/core/lib/resource_quota/arena.cc \ + src/core/lib/resource_quota/connection_quota.cc \ src/core/lib/resource_quota/memory_quota.cc \ src/core/lib/resource_quota/periodic_update.cc \ src/core/lib/resource_quota/resource_quota.cc \ diff --git a/config.w32 b/config.w32 index dcf40dbb7d3..b9e6c920b2e 100644 --- a/config.w32 +++ b/config.w32 @@ -649,6 +649,7 @@ if (PHP_GRPC != "no") { "src\\core\\lib\\promise\\trace.cc " + "src\\core\\lib\\resource_quota\\api.cc " + "src\\core\\lib\\resource_quota\\arena.cc " + + "src\\core\\lib\\resource_quota\\connection_quota.cc " + "src\\core\\lib\\resource_quota\\memory_quota.cc " + "src\\core\\lib\\resource_quota\\periodic_update.cc " + "src\\core\\lib\\resource_quota\\resource_quota.cc " + diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec index 84a7bfef691..bad4a958ef8 100644 --- a/gRPC-C++.podspec +++ b/gRPC-C++.podspec @@ -1147,6 +1147,7 @@ Pod::Spec.new do |s| 'src/core/lib/promise/try_seq.h', 'src/core/lib/resource_quota/api.h', 'src/core/lib/resource_quota/arena.h', + 'src/core/lib/resource_quota/connection_quota.h', 'src/core/lib/resource_quota/memory_quota.h', 'src/core/lib/resource_quota/periodic_update.h', 'src/core/lib/resource_quota/resource_quota.h', @@ -2410,6 +2411,7 @@ Pod::Spec.new do |s| 'src/core/lib/promise/try_seq.h', 'src/core/lib/resource_quota/api.h', 'src/core/lib/resource_quota/arena.h', + 'src/core/lib/resource_quota/connection_quota.h', 'src/core/lib/resource_quota/memory_quota.h', 'src/core/lib/resource_quota/periodic_update.h', 'src/core/lib/resource_quota/resource_quota.h', diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec index 35adc20a350..ca1624e7ac2 100644 --- a/gRPC-Core.podspec +++ b/gRPC-Core.podspec @@ -1722,6 +1722,8 @@ Pod::Spec.new do |s| 'src/core/lib/resource_quota/api.h', 'src/core/lib/resource_quota/arena.cc', 'src/core/lib/resource_quota/arena.h', + 'src/core/lib/resource_quota/connection_quota.cc', + 'src/core/lib/resource_quota/connection_quota.h', 'src/core/lib/resource_quota/memory_quota.cc', 'src/core/lib/resource_quota/memory_quota.h', 'src/core/lib/resource_quota/periodic_update.cc', @@ -3191,6 +3193,7 @@ Pod::Spec.new do |s| 'src/core/lib/promise/try_seq.h', 'src/core/lib/resource_quota/api.h', 'src/core/lib/resource_quota/arena.h', + 'src/core/lib/resource_quota/connection_quota.h', 'src/core/lib/resource_quota/memory_quota.h', 'src/core/lib/resource_quota/periodic_update.h', 'src/core/lib/resource_quota/resource_quota.h', diff --git a/grpc.gemspec b/grpc.gemspec index eaed281aa80..12d4db8b99b 100644 --- a/grpc.gemspec +++ b/grpc.gemspec @@ -1614,6 +1614,8 @@ Gem::Specification.new do |s| s.files += %w( src/core/lib/resource_quota/api.h ) s.files += %w( src/core/lib/resource_quota/arena.cc ) s.files += %w( src/core/lib/resource_quota/arena.h ) + s.files += %w( src/core/lib/resource_quota/connection_quota.cc ) + s.files += %w( src/core/lib/resource_quota/connection_quota.h ) s.files += %w( src/core/lib/resource_quota/memory_quota.cc ) s.files += %w( src/core/lib/resource_quota/memory_quota.h ) s.files += %w( src/core/lib/resource_quota/periodic_update.cc ) diff --git a/grpc.gyp b/grpc.gyp index 1f7a89d8d52..df31e5f4f3b 100644 --- a/grpc.gyp +++ b/grpc.gyp @@ -883,6 +883,7 @@ 'src/core/lib/promise/trace.cc', 'src/core/lib/resource_quota/api.cc', 'src/core/lib/resource_quota/arena.cc', + 'src/core/lib/resource_quota/connection_quota.cc', 'src/core/lib/resource_quota/memory_quota.cc', 'src/core/lib/resource_quota/periodic_update.cc', 'src/core/lib/resource_quota/resource_quota.cc', @@ -1422,6 +1423,7 @@ 'src/core/lib/promise/trace.cc', 'src/core/lib/resource_quota/api.cc', 'src/core/lib/resource_quota/arena.cc', + 'src/core/lib/resource_quota/connection_quota.cc', 'src/core/lib/resource_quota/memory_quota.cc', 'src/core/lib/resource_quota/periodic_update.cc', 'src/core/lib/resource_quota/resource_quota.cc', @@ -2228,6 +2230,7 @@ 'src/core/lib/promise/trace.cc', 'src/core/lib/resource_quota/api.cc', 'src/core/lib/resource_quota/arena.cc', + 'src/core/lib/resource_quota/connection_quota.cc', 'src/core/lib/resource_quota/memory_quota.cc', 'src/core/lib/resource_quota/periodic_update.cc', 'src/core/lib/resource_quota/resource_quota.cc', diff --git a/include/grpc/impl/channel_arg_names.h b/include/grpc/impl/channel_arg_names.h index b42bf7026ef..7ba19bebb17 100644 --- a/include/grpc/impl/channel_arg_names.h +++ b/include/grpc/impl/channel_arg_names.h @@ -395,6 +395,10 @@ * factory. */ #define GRPC_ARG_EVENT_ENGINE_USE_MEMORY_ALLOCATOR_FACTORY \ "grpc.event_engine_use_memory_allocator_factory" +/** Configure the max number of allowed incoming connections to the server. + * If unspecified, it is unlimited */ +#define GRPC_ARG_MAX_ALLOWED_INCOMING_CONNECTIONS \ + "grpc.max_allowed_incoming_connections" /** \} */ #endif /* GRPC_IMPL_CHANNEL_ARG_NAMES_H */ diff --git a/package.xml b/package.xml index fa35a2c4033..1ba949ae253 100644 --- a/package.xml +++ b/package.xml @@ -1596,6 +1596,8 @@ + + diff --git a/src/core/BUILD b/src/core/BUILD index f0c58279faf..46708db643f 100644 --- a/src/core/BUILD +++ b/src/core/BUILD @@ -1399,6 +1399,23 @@ grpc_cc_library( ], ) +grpc_cc_library( + name = "connection_quota", + srcs = [ + "lib/resource_quota/connection_quota.cc", + ], + hdrs = [ + "lib/resource_quota/connection_quota.h", + ], + external_deps = ["absl/base:core_headers"], + deps = [ + "memory_quota", + "ref_counted", + "//:gpr", + "//:ref_counted_ptr", + ], +) + grpc_cc_library( name = "resource_quota_trace", srcs = [ @@ -1426,6 +1443,7 @@ grpc_cc_library( "@grpc:alt_grpc_base_legacy", ], deps = [ + "connection_quota", "memory_quota", "ref_counted", "thread_quota", @@ -6747,6 +6765,7 @@ grpc_cc_library( "channel_args", "channel_args_endpoint_config", "closure", + "connection_quota", "error", "error_utils", "grpc_insecure_credentials", diff --git a/src/core/ext/transport/chttp2/server/chttp2_server.cc b/src/core/ext/transport/chttp2/server/chttp2_server.cc index dea28445a4f..7ac39d01cff 100644 --- a/src/core/ext/transport/chttp2/server/chttp2_server.cc +++ b/src/core/ext/transport/chttp2/server/chttp2_server.cc @@ -72,6 +72,7 @@ #include "src/core/lib/iomgr/tcp_server.h" #include "src/core/lib/iomgr/unix_sockets_posix.h" #include "src/core/lib/iomgr/vsock.h" +#include "src/core/lib/resource_quota/connection_quota.h" #include "src/core/lib/resource_quota/memory_quota.h" #include "src/core/lib/resource_quota/resource_quota.h" #include "src/core/lib/security/credentials/credentials.h" @@ -283,6 +284,7 @@ class Chttp2ServerListener : public Server::ListenerInterface { grpc_closure* on_destroy_done_ ABSL_GUARDED_BY(mu_) = nullptr; RefCountedPtr channelz_listen_socket_; MemoryQuotaRefPtr memory_quota_; + ConnectionQuotaRefPtr connection_quota_; }; // @@ -504,6 +506,18 @@ void Chttp2ServerListener::ActiveConnection::HandshakingState::OnHandshakeDone( } else { // Remove the connection from the connections_ map since OnClose() // will not be invoked when a config fetcher is set. + auto connection_quota = + self->connection_->listener_->connection_quota_->Ref() + .release(); + auto on_close_transport = [](void* arg, + grpc_error_handle /*handle*/) { + ConnectionQuota* connection_quota = + static_cast(arg); + connection_quota->ReleaseConnections(1); + connection_quota->Unref(); + }; + on_close = GRPC_CLOSURE_CREATE(on_close_transport, connection_quota, + grpc_schedule_on_exec_ctx_); cleanup_connection = true; } grpc_chttp2_transport_start_reading(transport, args->read_buffer, @@ -658,6 +672,7 @@ void Chttp2ServerListener::ActiveConnection::OnClose( self->drain_grace_timer_handle_.reset(); } } + self->listener_->connection_quota_->ReleaseConnections(1); self->Unref(); } @@ -762,7 +777,14 @@ Chttp2ServerListener::Chttp2ServerListener( : server_(server), args_modifier_(args_modifier), args_(args), - memory_quota_(args.GetObject()->memory_quota()) { + memory_quota_(args.GetObject()->memory_quota()), + connection_quota_(MakeRefCounted()) { + auto max_allowed_incoming_connections = + args.GetInt(GRPC_ARG_MAX_ALLOWED_INCOMING_CONNECTIONS); + if (max_allowed_incoming_connections.has_value()) { + connection_quota_->SetMaxIncomingConnections( + max_allowed_incoming_connections.value()); + } GRPC_CLOSURE_INIT(&tcp_server_shutdown_complete_, TcpServerShutdownComplete, this, grpc_schedule_on_exec_ctx); } @@ -821,6 +843,14 @@ void Chttp2ServerListener::OnAccept(void* arg, grpc_endpoint* tcp, grpc_endpoint_destroy(tcp); gpr_free(acceptor); }; + if (!self->connection_quota_->AllowIncomingConnection( + self->memory_quota_, grpc_endpoint_get_peer(tcp))) { + grpc_error_handle error = GRPC_ERROR_CREATE( + "Rejected incoming connection because configured connection quota " + "limits have been exceeded."); + endpoint_cleanup(error); + return; + } if (self->server_->config_fetcher() != nullptr) { if (connection_manager == nullptr) { grpc_error_handle error = GRPC_ERROR_CREATE( diff --git a/src/core/lib/resource_quota/connection_quota.cc b/src/core/lib/resource_quota/connection_quota.cc new file mode 100644 index 00000000000..75d3c7b8edd --- /dev/null +++ b/src/core/lib/resource_quota/connection_quota.cc @@ -0,0 +1,71 @@ +// Copyright 2024 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "src/core/lib/resource_quota/connection_quota.h" + +#include +#include + +#include + +namespace grpc_core { + +ConnectionQuota::ConnectionQuota() = default; + +void ConnectionQuota::SetMaxIncomingConnections(int max_incoming_connections) { + // The maximum can only be configured once. + GPR_ASSERT(max_incoming_connections < INT_MAX); + GPR_ASSERT(max_incoming_connections_.exchange(max_incoming_connections, + std::memory_order_release) == + INT_MAX); +} + +// Returns true if the incoming connection is allowed to be accepted on the +// server. +bool ConnectionQuota::AllowIncomingConnection(MemoryQuotaRefPtr mem_quota, + absl::string_view /*peer*/) { + if (mem_quota->IsMemoryPressureHigh()) { + return false; + } + + if (max_incoming_connections_.load(std::memory_order_relaxed) == INT_MAX) { + return true; + } + + int curr_active_connections = + active_incoming_connections_.load(std::memory_order_acquire); + do { + if (curr_active_connections >= + max_incoming_connections_.load(std::memory_order_relaxed)) { + return false; + } + } while (!active_incoming_connections_.compare_exchange_weak( + curr_active_connections, curr_active_connections + 1, + std::memory_order_acq_rel, std::memory_order_relaxed)); + return true; +} + +// Mark connections as closed. +void ConnectionQuota::ReleaseConnections(int num_connections) { + if (max_incoming_connections_.load(std::memory_order_relaxed) == INT_MAX) { + return; + } + GPR_ASSERT(active_incoming_connections_.fetch_sub( + num_connections, std::memory_order_acq_rel) >= + num_connections); +} + +} // namespace grpc_core diff --git a/src/core/lib/resource_quota/connection_quota.h b/src/core/lib/resource_quota/connection_quota.h new file mode 100644 index 00000000000..32815f57600 --- /dev/null +++ b/src/core/lib/resource_quota/connection_quota.h @@ -0,0 +1,61 @@ +// Copyright 2024 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GRPC_SRC_CORE_LIB_RESOURCE_QUOTA_CONNECTION_QUOTA_H +#define GRPC_SRC_CORE_LIB_RESOURCE_QUOTA_CONNECTION_QUOTA_H + +#include + +#include +#include + +#include "absl/base/thread_annotations.h" + +#include "src/core/lib/gprpp/ref_counted.h" +#include "src/core/lib/gprpp/ref_counted_ptr.h" +#include "src/core/lib/gprpp/sync.h" +#include "src/core/lib/resource_quota/memory_quota.h" + +namespace grpc_core { + +// Tracks the amount of threads in a resource quota. +class ConnectionQuota : public RefCounted { + public: + ConnectionQuota(); + ~ConnectionQuota() override = default; + + ConnectionQuota(const ConnectionQuota&) = delete; + ConnectionQuota& operator=(const ConnectionQuota&) = delete; + + // Set the maximum number of allowed incoming connections on the server. + void SetMaxIncomingConnections(int max_incoming_connections); + + // Returns true if the incoming connection is allowed to be accepted on the + // server. + bool AllowIncomingConnection(MemoryQuotaRefPtr mem_quota, + absl::string_view peer); + + // Mark connections as closed. + void ReleaseConnections(int num_connections); + + private: + std::atomic active_incoming_connections_{0}; + std::atomic max_incoming_connections_{std::numeric_limits::max()}; +}; + +using ConnectionQuotaRefPtr = RefCountedPtr; + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_LIB_RESOURCE_QUOTA_CONNECTION_QUOTA_H diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py index 857b62a8259..f3f4a909fd5 100644 --- a/src/python/grpcio/grpc_core_dependencies.py +++ b/src/python/grpcio/grpc_core_dependencies.py @@ -658,6 +658,7 @@ CORE_SOURCE_FILES = [ 'src/core/lib/promise/trace.cc', 'src/core/lib/resource_quota/api.cc', 'src/core/lib/resource_quota/arena.cc', + 'src/core/lib/resource_quota/connection_quota.cc', 'src/core/lib/resource_quota/memory_quota.cc', 'src/core/lib/resource_quota/periodic_update.cc', 'src/core/lib/resource_quota/resource_quota.cc', diff --git a/test/cpp/end2end/resource_quota_end2end_stress_test.cc b/test/cpp/end2end/resource_quota_end2end_stress_test.cc index 83af5a2fe27..4a8f28deaeb 100644 --- a/test/cpp/end2end/resource_quota_end2end_stress_test.cc +++ b/test/cpp/end2end/resource_quota_end2end_stress_test.cc @@ -53,7 +53,6 @@ class EchoClientUnaryReactor : public grpc::ClientUnaryReactor { EchoClientUnaryReactor(ClientContext* ctx, EchoTestService::Stub* stub, const std::string payload, Status* status) : ctx_(ctx), payload_(payload), status_(status) { - ctx_->set_wait_for_ready(true); request_.set_message(payload); stub->async()->Echo(ctx_, &request_, &response_, this); StartCall(); @@ -120,6 +119,7 @@ class End2EndResourceQuotaUnaryTest : public ::testing::Test { Status status; auto stub = EchoTestService::NewStub( CreateChannel(server_address_, grpc::InsecureChannelCredentials())); + ctx.set_wait_for_ready(true); EchoClientUnaryReactor reactor(&ctx, stub.get(), payload_, &status); reactor.Await(); } @@ -145,6 +145,78 @@ class End2EndResourceQuotaUnaryTest : public ::testing::Test { TEST_F(End2EndResourceQuotaUnaryTest, MultipleUnaryRPCTest) { MakeGrpcCalls(); } +class End2EndConnectionQuotaTest : public ::testing::TestWithParam { + protected: + End2EndConnectionQuotaTest() { + port_ = grpc_pick_unused_port_or_die(); + server_address_ = absl::StrCat("[::]:", port_); + payload_ = std::string(kPayloadSizeBytes, 'a'); + ServerBuilder builder; + builder.AddListeningPort(server_address_, InsecureServerCredentials()); + builder.AddChannelArgument(GRPC_ARG_MAX_ALLOWED_INCOMING_CONNECTIONS, + GetParam()); + builder.AddChannelArgument( + GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS, 10000); + builder.RegisterService(&grpc_service_); + server_ = builder.BuildAndStart(); + } + + ~End2EndConnectionQuotaTest() override { server_->Shutdown(); } + + std::unique_ptr CreateGrpcChannelStub() { + grpc::ChannelArguments args; + args.SetInt(GRPC_ARG_USE_LOCAL_SUBCHANNEL_POOL, 1); + args.SetInt(GRPC_ARG_ENABLE_RETRIES, 0); + args.SetInt(GRPC_ARG_KEEPALIVE_TIME_MS, 20000); + args.SetInt(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 10000); + args.SetInt(GRPC_ARG_HTTP2_MIN_SENT_PING_INTERVAL_WITHOUT_DATA_MS, 15000); + args.SetInt(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1); + + return EchoTestService::NewStub( + CreateCustomChannel(absl::StrCat("ipv6:[::1]:", port_), + grpc::InsecureChannelCredentials(), args)); + } + + void TestExceedingConnectionQuota() { + const int kNumConnections = 2 * GetParam(); + std::vector> stubs; + stubs.reserve(kNumConnections); + for (int i = 0; i < kNumConnections; i++) { + stubs.push_back(CreateGrpcChannelStub()); + } + for (int i = 0; i < kNumConnections; ++i) { + ClientContext ctx; + Status status; + ctx.set_wait_for_ready(false); + EchoClientUnaryReactor reactor(&ctx, stubs[i].get(), payload_, &status); + reactor.Await(); + // The first half RPCs should succeed. + if (i < kNumConnections / 2) { + EXPECT_TRUE(status.ok()); + + } else { + // The second half should fail because they would attempt to create a + // new connection and fail since it would exceed the connection quota + // limit set at the server. + EXPECT_FALSE(status.ok()); + } + } + } + + int port_; + std::unique_ptr server_; + string server_address_; + GrpcCallbackServiceImpl grpc_service_; + std::string payload_; +}; + +TEST_P(End2EndConnectionQuotaTest, ConnectionQuotaTest) { + TestExceedingConnectionQuota(); +} + +INSTANTIATE_TEST_SUITE_P(ConnectionQuotaParamTest, End2EndConnectionQuotaTest, + ::testing::ValuesIn({10, 100})); + } // namespace testing } // namespace grpc diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal index 1fbf624c5d3..39f3d505ed3 100644 --- a/tools/doxygen/Doxyfile.c++.internal +++ b/tools/doxygen/Doxyfile.c++.internal @@ -2613,6 +2613,8 @@ src/core/lib/resource_quota/api.cc \ src/core/lib/resource_quota/api.h \ src/core/lib/resource_quota/arena.cc \ src/core/lib/resource_quota/arena.h \ +src/core/lib/resource_quota/connection_quota.cc \ +src/core/lib/resource_quota/connection_quota.h \ src/core/lib/resource_quota/memory_quota.cc \ src/core/lib/resource_quota/memory_quota.h \ src/core/lib/resource_quota/periodic_update.cc \ diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal index 9f4eb9b2777..7ef0084dfc4 100644 --- a/tools/doxygen/Doxyfile.core.internal +++ b/tools/doxygen/Doxyfile.core.internal @@ -2388,6 +2388,8 @@ src/core/lib/resource_quota/api.cc \ src/core/lib/resource_quota/api.h \ src/core/lib/resource_quota/arena.cc \ src/core/lib/resource_quota/arena.h \ +src/core/lib/resource_quota/connection_quota.cc \ +src/core/lib/resource_quota/connection_quota.h \ src/core/lib/resource_quota/memory_quota.cc \ src/core/lib/resource_quota/memory_quota.h \ src/core/lib/resource_quota/periodic_update.cc \