-
Notifications
You must be signed in to change notification settings - Fork 5.5k
network: delayed conn close #4382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
4d2426e
94a835a
e2176aa
3a67ecb
30eb02e
9ddf9d3
8e716eb
e71f798
6130f28
890471e
aae3b52
b8eb565
6071d31
36b6087
d056d7a
52c10d9
9d86332
435a518
f2c81ed
6704243
31bcaaa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,7 +19,7 @@ import "gogoproto/gogo.proto"; | |
| // [#protodoc-title: HTTP connection manager] | ||
| // HTTP connection manager :ref:`configuration overview <config_http_conn_man>`. | ||
|
|
||
| // [#comment:next free field: 25] | ||
| // [#comment:next free field: 26] | ||
| message HttpConnectionManager { | ||
| enum CodecType { | ||
| option (gogoproto.goproto_enum_prefix) = false; | ||
|
|
@@ -175,6 +175,17 @@ message HttpConnectionManager { | |
| // option is not specified. | ||
| google.protobuf.Duration drain_timeout = 12 [(gogoproto.stdduration) = true]; | ||
|
|
||
| // The delayed close timeout is for downstream connections managed by the HTTP connection manager. | ||
| // It is defined as a grace period after connection close processing has been locally initiated | ||
| // during which Envoy will flush the write buffers for the connection and await the peer to close | ||
| // the connection (i.e., issue a TCP FIN/RST). | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the case flush the write buffer and delay close, we should issue TCP FIN but not RST, the expected behavior is issue FIN after flush writing, and RST on timeout.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've clarified this comment since this is referring to the client shutting down the connection and Envoy receiving the FIN/RST. With regards to Envoy issuing FIN vs RST, the behavior is for a What benefit are we trying to get out of always issuing RST on timeout? It will likely lead to faster reclamation of resources which is nice, but I think Envoy's behavior regarding FIN vs RST should be applied uniformly across timeout/error conditions and therefore should be tracked as a new issue. |
||
| // | ||
| // If the timeout triggers, Envoy will close the connection's socket. | ||
| // | ||
| // A value of 0 will completely disable delayed close processing, and the downstream connection's | ||
| // socket will be closed immediately after the write flush is completed. | ||
| google.protobuf.Duration delayed_close_timeout = 25 [(gogoproto.stdduration) = true]; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a default value? What happens if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a default timeout value of 1000 ms. I've amended the comment to note this. |
||
|
|
||
| // Configuration for :ref:`HTTP access logs <arch_overview_access_logs>` | ||
| // emitted by the connection manager. | ||
| repeated envoy.config.filter.accesslog.v2.AccessLog access_log = 13; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -63,7 +63,8 @@ class ConnectionCallbacks { | |
| */ | ||
| enum class ConnectionCloseType { | ||
| FlushWrite, // Flush pending write data before raising ConnectionEvent::LocalClose | ||
| NoFlush // Do not flush any pending data and immediately raise ConnectionEvent::LocalClose | ||
| NoFlush, // Do not flush any pending data and immediately raise ConnectionEvent::LocalClose | ||
| FlushWriteAndDelay | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a similar comment to the above two lines?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| }; | ||
|
|
||
| /** | ||
|
|
@@ -86,6 +87,8 @@ class Connection : public Event::DeferredDeletable, public FilterManager { | |
| Stats::Gauge& write_current_; | ||
| // Counter* as this is an optional counter. Bind errors will not be tracked if this is nullptr. | ||
| Stats::Counter* bind_errors_; | ||
| // Optional counter. Delayed close semantics are only used by HTTP connections. | ||
| Stats::Counter* delayed_close_timeouts_; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove the reference to HTTP, since this isn't necessarily HTTP-specific. I think the comment can just mirror the one for bind_errors_.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed the delayed close timeout is not inherently HTTP specific, but the intent of the comment is to point out that as currently implemented it only affects HTTP connections, since the H{1,2} codecs are the only callers of I'm fine removing this however if you don't think the note/clarification is required.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My feeling is that it's just a comment we'll have to delete (or will be incorrect) when someone inevitably enables this for a different purpose.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. Cleaned up the comment. |
||
| }; | ||
|
|
||
| virtual ~Connection() {} | ||
|
|
@@ -233,6 +236,18 @@ class Connection : public Event::DeferredDeletable, public FilterManager { | |
| * Get the socket options set on this connection. | ||
| */ | ||
| virtual const ConnectionSocket::OptionsSharedPtr& socketOptions() const PURE; | ||
|
|
||
| /** | ||
| * Set the timeout for delayed connection close()s. | ||
| * This is only used for downstream connections processing HTTP/1 and HTTP/2. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likewise, remove this reference (or at least the "only" qualifier).
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please document (probably on |
||
| * @param timeout The timeout value in milliseconds | ||
| */ | ||
| virtual void setDelayedCloseTimeout(std::chrono::milliseconds timeout) PURE; | ||
|
|
||
| /** | ||
| * @return std::chrono::milliseconds The delayed close timeout value. | ||
| */ | ||
| virtual std::chrono::milliseconds delayedCloseTimeout() const PURE; | ||
| }; | ||
|
|
||
| typedef std::unique_ptr<Connection> ConnectionPtr; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -450,8 +450,11 @@ void ConnectionImpl::onResetStreamBase(StreamResetReason reason) { | |
|
|
||
| ServerConnectionImpl::ServerConnectionImpl(Network::Connection& connection, | ||
| ServerConnectionCallbacks& callbacks, | ||
| Http1Settings settings) | ||
| : ConnectionImpl(connection, HTTP_REQUEST), callbacks_(callbacks), codec_settings_(settings) {} | ||
| Http1Settings settings, | ||
| std::chrono::milliseconds delayed_close_timeout) | ||
| : ConnectionImpl(connection, HTTP_REQUEST), callbacks_(callbacks), codec_settings_(settings) { | ||
| connection_.setDelayedCloseTimeout(delayed_close_timeout); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the reasoning for doing this set in the codec? It seems like something that is unrelated to the codec itself and should be done by higher layer code? Same question for the HTTP/2 codec below.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not related to the codec directly but this particular delayed_close_timeout is HTTP only since it is managed via the HCM configuration. This seemed to be the most appropriate place to set the timeout for HTTP connections just after they are constructed. Since the timeout value is intended to be network filter specific, I wouldn't expect any layer higher than the corresponding filter to issue the call to setDelayedCloseTimeout().
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree it should be done in the filter. Why not do it in the HCM filter and avoid codec modifications?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed it's best to contain it within the HCM. I revisited the code and moved setting the timeout to the HCM config. |
||
| } | ||
|
|
||
| void ServerConnectionImpl::onEncodeComplete() { | ||
| ASSERT(active_request_); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,12 @@ ConnectionImpl::ConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPt | |
| ConnectionImpl::~ConnectionImpl() { | ||
| ASSERT(fd() == -1, "ConnectionImpl was unexpectedly torn down without being closed."); | ||
|
|
||
| if (delayed_close_timer_) { | ||
| // It's ok to disable even if the timer has already fired. | ||
| delayed_close_timer_->disableTimer(); | ||
| delayed_close_timer_.reset(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: don't need to reset -- it's about to be destroyed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| } | ||
|
|
||
| // In general we assume that owning code has called close() previously to the destructor being | ||
| // run. This generally must be done so that callbacks run in the correct context (vs. deferred | ||
| // deletion). Hence the assert above. However, call close() here just to be completely sure that | ||
|
|
@@ -106,10 +112,29 @@ void ConnectionImpl::close(ConnectionCloseType type) { | |
|
|
||
| closeSocket(ConnectionEvent::LocalClose); | ||
| } else { | ||
| // TODO(mattklein123): We need a flush timer here. We might never get open socket window. | ||
| ASSERT(type == ConnectionCloseType::FlushWrite); | ||
| close_with_flush_ = true; | ||
| RELEASE_ASSERT(type == ConnectionCloseType::FlushWrite || | ||
| type == ConnectionCloseType::FlushWriteAndDelay, | ||
| ""); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the rationale behind making this a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm using the RELEASE_ASSERT to provide runtime protection against bugs introduced in this logic, since they could lead to issues such as resource leaks and/or memory corruption. I just re-read the style guide and it does mention that ASSERT should be the common case for this type of check, so I can certainly change it if that's the convention.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The convention is generally to use ASSERT for this, if you feel strongly about RELEASE_ASSERT I'll defer to senior maintainer for the decision.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed to ASSERT() per style guidelines. |
||
| delayed_close_ = true; | ||
| bool delayed_close_timeout_set = delayedCloseTimeout().count(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer to see the explicit boolean expression here, ie,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
|
||
| // All close types that follow do not actually close() the socket immediately so that buffered | ||
| // data can be written. However, we do want to stop reading to apply TCP backpressure. | ||
| read_enabled_ = false; | ||
|
|
||
| // Force a closeSocket() after the write buffer is flushed if the close_type calls for it or if | ||
| // no delayed close timeout is set. | ||
| close_after_flush_ = type == ConnectionCloseType::FlushWrite || !delayed_close_timeout_set; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Super nit, but I think this expression is more readable if you reverse the two halves of the or, since it separates the = and == more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
|
||
| // Create and activate a timer which will immediately close the connection if triggered. | ||
| // A config value of 0 disables the timeout. | ||
| if (delayed_close_timeout_set) { | ||
| delayed_close_timer_ = dispatcher_.createTimer([this]() -> void { onDelayedCloseTimeout(); }); | ||
| ENVOY_CONN_LOG(debug, "setting delayed close timer with timeout {}", *this, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add the units (ms right?) to the log message?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| delayedCloseTimeout().count()); | ||
| delayed_close_timer_->enableTimer(delayedCloseTimeout()); | ||
| } | ||
|
zuercher marked this conversation as resolved.
|
||
|
|
||
| file_event_->setEnabled(Event::FileReadyType::Write | | ||
| (enable_half_close_ ? 0 : Event::FileReadyType::Closed)); | ||
| } | ||
|
|
@@ -118,7 +143,7 @@ void ConnectionImpl::close(ConnectionCloseType type) { | |
| Connection::State ConnectionImpl::state() const { | ||
| if (fd() == -1) { | ||
| return State::Closed; | ||
| } else if (close_with_flush_) { | ||
| } else if (delayed_close_) { | ||
| return State::Closing; | ||
| } else { | ||
| return State::Open; | ||
|
|
@@ -488,7 +513,7 @@ void ConnectionImpl::onWriteReady() { | |
| // write callback. This can happen if we manage to complete the SSL handshake in the write | ||
| // callback, raise a connected event, and close the connection. | ||
| closeSocket(ConnectionEvent::RemoteClose); | ||
| } else if ((close_with_flush_ && new_buffer_size == 0) || bothSidesHalfClosed()) { | ||
| } else if ((close_after_flush_ && new_buffer_size == 0) || bothSidesHalfClosed()) { | ||
| ENVOY_CONN_LOG(debug, "write flush complete", *this); | ||
| closeSocket(ConnectionEvent::LocalClose); | ||
| } else if (result.action_ == PostIoAction::KeepOpen && result.bytes_processed_ > 0) { | ||
|
|
@@ -535,6 +560,13 @@ bool ConnectionImpl::bothSidesHalfClosed() { | |
| return read_end_stream_ && write_end_stream_ && write_buffer_->length() == 0; | ||
| } | ||
|
|
||
| void ConnectionImpl::onDelayedCloseTimeout() { | ||
| ENVOY_CONN_LOG(debug, "triggered delayed close", *this); | ||
| ASSERT(connection_stats_->delayed_close_timeouts_); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer that you test if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| connection_stats_->delayed_close_timeouts_->inc(); | ||
| closeSocket(ConnectionEvent::LocalClose); | ||
| } | ||
|
|
||
| ClientConnectionImpl::ClientConnectionImpl( | ||
| Event::Dispatcher& dispatcher, const Address::InstanceConstSharedPtr& remote_address, | ||
| const Network::Address::InstanceConstSharedPtr& source_address, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -136,7 +136,8 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( | |
| date_provider_(date_provider), | ||
| listener_stats_(Http::ConnectionManagerImpl::generateListenerStats(stats_prefix_, | ||
| context_.listenerScope())), | ||
| proxy_100_continue_(config.proxy_100_continue()) { | ||
| proxy_100_continue_(config.proxy_100_continue()), | ||
| delayed_close_timeout_(PROTOBUF_GET_MS_OR_DEFAULT(config, delayed_close_timeout, 1000)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like the default is 1 second if the proto field isn't set - we should document that above.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
|
||
| route_config_provider_ = Router::RouteConfigProviderUtil::create(config, context_, stats_prefix_, | ||
| route_config_provider_manager_); | ||
|
|
@@ -303,19 +304,19 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, | |
| Http::ServerConnectionCallbacks& callbacks) { | ||
| switch (codec_type_) { | ||
| case CodecType::HTTP1: | ||
| return Http::ServerConnectionPtr{ | ||
| new Http::Http1::ServerConnectionImpl(connection, callbacks, http1_settings_)}; | ||
| return Http::ServerConnectionPtr{new Http::Http1::ServerConnectionImpl( | ||
| connection, callbacks, http1_settings_, delayed_close_timeout_)}; | ||
| case CodecType::HTTP2: | ||
| return Http::ServerConnectionPtr{new Http::Http2::ServerConnectionImpl( | ||
| connection, callbacks, context_.scope(), http2_settings_)}; | ||
| connection, callbacks, context_.scope(), http2_settings_, delayed_close_timeout_)}; | ||
| case CodecType::AUTO: | ||
| if (HttpConnectionManagerConfigUtility::determineNextProtocol(connection, data) == | ||
| Http::Http2::ALPN_STRING) { | ||
| return Http::ServerConnectionPtr{new Http::Http2::ServerConnectionImpl( | ||
| connection, callbacks, context_.scope(), http2_settings_)}; | ||
| connection, callbacks, context_.scope(), http2_settings_, delayed_close_timeout_)}; | ||
| } else { | ||
| return Http::ServerConnectionPtr{ | ||
| new Http::Http1::ServerConnectionImpl(connection, callbacks, http1_settings_)}; | ||
| return Http::ServerConnectionPtr{new Http::Http1::ServerConnectionImpl( | ||
| connection, callbacks, http1_settings_, delayed_close_timeout_)}; | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see some documentation on why one would change this setting. It's talked about in the linked issue, but from just reading this documentation, I wouldn't know what could go wrong if I change this setting to 0/disabled here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.