Skip to content

Commit de48687

Browse files
authored
HTTP: add overload action to close idle HTTP connections (#43612)
Commit Message: HTTP: add overload action to close idle HTTP connections Additional Description: This change introduces the "CloseIdleHttpConnections" overload action to proactively terminate idle downstream connections during resource overload. The implementation currently supports QUIC connections only, with HTTP/2 support planned for a follow-up change. A periodic timer is established on worker threads upon action activation to invoke the closing logic across active listeners, guarded by a reloadable runtime feature. For QUIC, a new configuration option is added to enable this behavior, which leverages the session idle list to terminate sessions. Risk Level: low Testing: added overload integration test Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Ting Pan <panting@google.com>
1 parent a99a8b0 commit de48687

15 files changed

Lines changed: 241 additions & 7 deletions

File tree

changelogs/current.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,11 @@ new_features:
10181018
- area: ratelimit
10191019
change: |
10201020
Support populating rate limit descriptors from cluster locality metadata.
1021+
- area: http
1022+
change: |
1023+
Added a new overload action :ref:`envoy.overload_actions.close_idle_http_connections <config_overload_manager_overload_actions>`
1024+
that closes idle downstream HTTP connections when the overload action is active. Currently only HTTP/3 idle connections
1025+
trimming is supported, and HTTP/1 and HTTP/2 support are to be implemented.
10211026
10221027
- area: contrib
10231028
change: |

docs/root/configuration/operations/overload_manager/overload_manager.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ The following overload actions are supported:
149149
- Envoy will reset expensive streams to terminate them. See
150150
:ref:`below <config_overload_manager_reset_streams>` for details on configuration.
151151

152+
* - envoy.overload_actions.close_idle_http_connections
153+
- Envoy will close idle downstream HTTP/3 QUIC connections when the action is active.
154+
When the action is *saturated*, connections will be closed aggressively (ignoring the idle timer threshold).
155+
When the action is in a *scaled active* state, the idle timer threshold is still respected.
156+
Note that this action is currently only supported for HTTP/3 QUIC connections.
157+
152158
.. _config_overload_manager_shrink_heap:
153159

154160
Shrink Heap

envoy/network/connection_handler.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ class ConnectionHandler {
127127
*/
128128
virtual const std::string& statPrefix() const PURE;
129129

130+
/**
131+
* Close idle HTTP connections.
132+
*/
133+
virtual void closeIdleHttpConnections(bool is_saturated) PURE;
134+
130135
/**
131136
* Used by ConnectionHandler to manage listeners.
132137
*/
@@ -172,6 +177,12 @@ class ConnectionHandler {
172177
*/
173178
virtual void onFilterChainDraining(
174179
const std::list<const Network::FilterChain*>& draining_filter_chains) PURE;
180+
181+
// New method for handling idle connection closing
182+
virtual void onCloseIdleHttpConnections(bool /*is_saturated*/) {
183+
// Default implementation does nothing.
184+
// Specific listener types (TCP, QUIC) will override this.
185+
}
175186
};
176187

177188
using ActiveListenerPtr = std::unique_ptr<ActiveListener>;

envoy/server/overload/overload_manager.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,20 @@ class OverloadActionNameValues {
4242
// Overload action to reset streams using excessive memory.
4343
const std::string ResetStreams = "envoy.overload_actions.reset_high_memory_stream";
4444

45+
// Overload action to terminate idle downstream HTTP connections.
46+
const std::string CloseIdleHttpConnections = "envoy.overload_actions.close_idle_http_connections";
47+
4548
// This should be kept current with the Overload actions available.
4649
// This is the last member of this class to duplicating the strings with
4750
// proper lifetime guarantees.
48-
const std::array<absl::string_view, 7> WellKnownActions = {StopAcceptingRequests,
51+
const std::array<absl::string_view, 8> WellKnownActions = {StopAcceptingRequests,
4952
DisableHttpKeepAlive,
5053
StopAcceptingConnections,
5154
RejectIncomingConnections,
5255
ShrinkHeap,
5356
ReduceTimeouts,
54-
ResetStreams};
57+
ResetStreams,
58+
CloseIdleHttpConnections};
5559
};
5660

5761
using OverloadActionNames = ConstSingleton<OverloadActionNameValues>;

envoy/server/overload/thread_local_overload_state.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ using OverloadProactiveResources = ConstSingleton<OverloadProactiveResourceNameV
4949
*/
5050
class OverloadActionState {
5151
public:
52+
enum class Phase : uint8_t {
53+
Inactive,
54+
Scaling,
55+
Saturated,
56+
};
57+
5258
static constexpr OverloadActionState inactive() { return OverloadActionState(UnitFloat::min()); }
5359

5460
static constexpr OverloadActionState saturated() { return OverloadActionState(UnitFloat::max()); }
@@ -57,6 +63,15 @@ class OverloadActionState {
5763

5864
UnitFloat value() const { return action_value_; }
5965
bool isSaturated() const { return action_value_.value() == UnitFloat::max().value(); }
66+
Phase phase() const {
67+
if (action_value_ == UnitFloat::min()) {
68+
return Phase::Inactive;
69+
} else if (action_value_ == UnitFloat::max()) {
70+
return Phase::Saturated;
71+
} else {
72+
return Phase::Scaling;
73+
}
74+
}
6075

6176
private:
6277
UnitFloat action_value_;

source/common/listener_manager/connection_handler_impl.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,18 @@ void ConnectionHandlerImpl::setListenerRejectFraction(UnitFloat reject_fraction)
309309
}
310310
}
311311

312+
void ConnectionHandlerImpl::closeIdleHttpConnections(bool is_saturated) {
313+
for (const auto& [tag, listener] : listener_map_by_tag_) {
314+
listener->invokeListenerMethod(
315+
[is_saturated](Network::ConnectionHandler::ActiveListener& active_listener) {
316+
if (active_listener.listener() != nullptr &&
317+
!active_listener.listener()->shouldBypassOverloadManager()) {
318+
active_listener.onCloseIdleHttpConnections(is_saturated);
319+
}
320+
});
321+
}
322+
}
323+
312324
Network::InternalListenerOptRef
313325
ConnectionHandlerImpl::findByAddress(const Network::Address::InstanceConstSharedPtr& address) {
314326
ASSERT(address->type() == Network::Address::Type::EnvoyInternal);

source/common/listener_manager/connection_handler_impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class ConnectionHandlerImpl : public ConnectionHandler,
5757
void enableListeners() override;
5858
void setListenerRejectFraction(UnitFloat reject_fraction) override;
5959
const std::string& statPrefix() const override { return per_handler_stat_prefix_; }
60+
void closeIdleHttpConnections(bool is_saturated) override;
6061

6162
// Network::TcpConnectionHandler
6263
Event::Dispatcher& dispatcher() override { return dispatcher_; }

source/common/quic/active_quic_listener.cc

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
#include "source/common/quic/active_quic_listener.h"
22

3+
#include <memory>
34
#include <utility>
45
#include <vector>
56

7+
#include "envoy/common/optref.h"
68
#include "envoy/extensions/quic/connection_id_generator/v3/envoy_deterministic_connection_id_generator.pb.h"
79
#include "envoy/extensions/quic/crypto_stream/v3/crypto_stream.pb.h"
810
#include "envoy/extensions/quic/proof_source/v3/proof_source.pb.h"
911
#include "envoy/network/exception.h"
12+
#include "envoy/server/overload/overload_manager.h"
1013

1114
#include "source/common/common/logger.h"
1215
#include "source/common/config/utility.h"
16+
#include "source/common/http/session_idle_list.h"
1317
#include "source/common/http/utility.h"
1418
#include "source/common/network/socket_option_impl.h"
1519
#include "source/common/network/udp_listener_impl.h"
@@ -39,7 +43,7 @@ ActiveQuicListener::ActiveQuicListener(
3943
EnvoyQuicProofSourceFactoryInterface& proof_source_factory,
4044
QuicConnectionIdGeneratorPtr&& cid_generator, QuicConnectionIdWorkerSelector worker_selector,
4145
EnvoyQuicConnectionDebugVisitorFactoryInterfaceOptRef debug_visitor_factory,
42-
bool reject_new_connections)
46+
bool reject_new_connections, bool enable_session_idle_list)
4347
: Server::ActiveUdpListenerBase(
4448
worker_index, concurrency, parent, *listen_socket,
4549
std::make_unique<Network::UdpListenerImpl>(
@@ -91,12 +95,12 @@ ActiveQuicListener::ActiveQuicListener(
9195
listen_socket_.setSocketOption(IPPROTO_IP, IP_RECVTOS, &optval, optlen);
9296
}
9397
}
94-
// TODO(panting): Pass in a non-null session_idle_list when configured.
9598
quic_dispatcher_ = std::make_unique<EnvoyQuicDispatcher>(
9699
crypto_config_.get(), quic_config, &version_manager_, std::move(connection_helper),
97100
std::move(alarm_factory), quic::kQuicDefaultConnectionIdLength, parent, *config_, stats_,
98101
per_worker_stats_, dispatcher, listen_socket_, quic_stat_names, crypto_server_stream_factory_,
99-
*connection_id_generator_, debug_visitor_factory, /*session_idle_list=*/nullptr);
102+
*connection_id_generator_, debug_visitor_factory,
103+
enable_session_idle_list ? std::make_unique<Http::SessionIdleList>(dispatcher) : nullptr);
100104

101105
absl::AnyInvocable<void() &&> on_can_write_cb = [&]() { quic_dispatcher_->OnCanWrite(); };
102106

@@ -272,6 +276,10 @@ void ActiveQuicListener::closeConnectionsWithFilterChain(const Network::FilterCh
272276
quic_dispatcher_->closeConnectionsWithFilterChain(filter_chain);
273277
}
274278

279+
void ActiveQuicListener::onCloseIdleHttpConnections(bool is_saturated) {
280+
quic_dispatcher_->closeIdleQuicConnections(is_saturated);
281+
}
282+
275283
ActiveQuicListenerFactory::ActiveQuicListenerFactory(
276284
const envoy::config::listener::v3::QuicProtocolOptions& config, uint32_t concurrency,
277285
QuicStatNames& quic_stat_names, ProtobufMessage::ValidationVisitor& validation_visitor,
@@ -447,12 +455,21 @@ ActiveQuicListenerFactory::createActiveQuicListener(
447455
EnvoyQuicCryptoServerStreamFactoryInterface& crypto_server_stream_factory,
448456
EnvoyQuicProofSourceFactoryInterface& proof_source_factory,
449457
QuicConnectionIdGeneratorPtr&& cid_generator) {
458+
bool enable_session_idle_list = false;
459+
for (const auto& action :
460+
context_.serverFactoryContext().bootstrap().overload_manager().actions()) {
461+
if (action.name() == Server::OverloadActionNames::get().CloseIdleHttpConnections) {
462+
enable_session_idle_list = true;
463+
break;
464+
}
465+
}
450466
return std::make_unique<ActiveQuicListener>(
451467
runtime, worker_index, concurrency, dispatcher, parent, std::move(listen_socket),
452468
listener_config, quic_config, kernel_worker_routing, enabled, quic_stat_names,
453469
packets_to_read_to_connection_count_ratio, crypto_server_stream_factory, proof_source_factory,
454470
std::move(cid_generator), worker_selector_,
455-
makeOptRefFromPtr(connection_debug_visitor_factory_.get()), reject_new_connections_);
471+
makeOptRefFromPtr(connection_debug_visitor_factory_.get()), reject_new_connections_,
472+
enable_session_idle_list);
456473
}
457474

458475
} // namespace Quic

source/common/quic/active_quic_listener.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class ActiveQuicListener : public Envoy::Server::ActiveUdpListenerBase,
4242
QuicConnectionIdGeneratorPtr&& cid_generator,
4343
QuicConnectionIdWorkerSelector worker_selector,
4444
EnvoyQuicConnectionDebugVisitorFactoryInterfaceOptRef debug_visitor_factory,
45-
bool reject_new_connections = false);
45+
bool reject_new_connections = false, bool enable_session_idle_list = false);
4646

4747
~ActiveQuicListener() override;
4848

@@ -73,6 +73,8 @@ class ActiveQuicListener : public Envoy::Server::ActiveUdpListenerBase,
7373
void onFilterChainDraining(
7474
const std::list<const Network::FilterChain*>& draining_filter_chains) override;
7575

76+
void onCloseIdleHttpConnections(bool is_saturated) override;
77+
7678
protected:
7779
Event::Dispatcher& dispatcher() { return dispatcher_; }
7880

source/common/quic/envoy_quic_dispatcher.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ void EnvoyQuicDispatcher::updateListenerConfig(Network::ListenerConfig& new_list
215215
void EnvoyQuicDispatcher::closeIdleQuicConnections(bool is_saturated) {
216216
// This method is called from the worker thread, triggered by the
217217
// Overload Manager.
218+
ASSERT(session_idle_list_ != nullptr);
218219
session_idle_list_->MaybeTerminateIdleSessions(is_saturated);
219220
}
220221

0 commit comments

Comments
 (0)