Skip to content
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

Allow multiplexed upstream servers to half close the stream before the downstream #34461

Merged
merged 37 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7263c53
Allow multiplexed upstream servers to half close the stream before th…
yanavlasov May 31, 2024
fd17760
Address comments
yanavlasov Jun 21, 2024
723ed65
Merge branch 'main' into allow-upstream-half-close
yanavlasov Jun 21, 2024
1d8172c
Address comments
yanavlasov Jun 22, 2024
3ba7cc4
Clarify comment
yanavlasov Jun 24, 2024
6133efc
Reuse isHalfCloseEnabled callback
yanavlasov Jun 24, 2024
fcf2a76
Add stopDecoding
yanavlasov Jun 24, 2024
f24bc34
Merge branch 'main' into allow-upstream-half-close
yanavlasov Jun 25, 2024
0e881dc
Merge branch 'main' into allow-upstream-half-close
yanavlasov Jun 28, 2024
b7b746f
Fixing tests
yanavlasov Jul 26, 2024
af60f0b
Address post merge test failures
yanavlasov Jul 30, 2024
01ae68b
Merge branch 'main' into allow-upstream-half-close
yanavlasov Jul 30, 2024
1f269a4
Post merge fix
yanavlasov Jul 30, 2024
1b961df
Fix gcc build error
yanavlasov Jul 30, 2024
8ae88a0
WiP
yanavlasov Jul 30, 2024
d8aabba
Merge branch 'main' into allow-upstream-half-close
yanavlasov Aug 2, 2024
2166fcb
Post merge fixes
yanavlasov Aug 2, 2024
e2deca2
WiP
yanavlasov Aug 8, 2024
9e9428f
Make ending filters in the middle of the filter chain work. p1
yanavlasov Aug 9, 2024
71ce1f0
Make encoding filters in the middle of the filter chain work. p2
yanavlasov Aug 9, 2024
68c807e
Update comments
yanavlasov Aug 12, 2024
9c308ba
Merge branch 'main' into allow-upstream-half-close
yanavlasov Aug 12, 2024
2d8cb15
post merge fixes
yanavlasov Aug 12, 2024
e1c00d0
Update comment
yanavlasov Aug 12, 2024
018a3e4
Update comments
yanavlasov Aug 12, 2024
9a692f3
Address comments
yanavlasov Aug 15, 2024
bd30f81
Merge branch 'main' into allow-upstream-half-close
yanavlasov Aug 15, 2024
b9948ab
Post merge fix
yanavlasov Aug 15, 2024
e224738
Update comment
yanavlasov Aug 15, 2024
e4ae6b4
Merge branch 'main' into allow-upstream-half-close
yanavlasov Aug 16, 2024
d5320da
Fix docs
yanavlasov Aug 16, 2024
b804cb5
Disable upstream filter tests
yanavlasov Aug 16, 2024
4b305d9
Add coverage
yanavlasov Aug 16, 2024
9155e49
Address comments
yanavlasov Aug 21, 2024
49b6162
Merge branch 'main' into allow-upstream-half-close
yanavlasov Aug 23, 2024
c02ac47
Add asserts
yanavlasov Aug 23, 2024
d965ff6
Merge branch 'main' into allow-upstream-half-close
yanavlasov Aug 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ behavior_changes:
change: |
Removed support for (long deprecated) opentracing. See `issue 27401
<https://github.com/envoyproxy/envoy/issues/27401>`_ for details.
- area: http
change: |
Allow HTTP/2 (and HTTP/3) upstream servers to half close the stream before the downstream. This enables bidirectional
gRPC streams where server completes streaming before the client. Behavior of HTTP/1 or TCP proxy upstream servers is
unchanged and the stream is reset if the upstream server completes response before the downstream. The stream is also
reset if the upstream server responds with an error status before the downstream. This behavior is disabled by default
and can be enabled by setting the ``envoy.reloadable_features.allow_multiplexed_upstream_half_close`` runtime key to true.
- area: http
change: |
Added HTTP1-safe option for :ref:`max_connection_duration
Expand Down
6 changes: 6 additions & 0 deletions docs/root/configuration/observability/access_log/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,12 @@ The following command operators are supported:
* ``us``: Microsecond precision.
* ``ns``: Nanosecond precision.

NOTE: enabling independent half-close behavior for H/2 and H/3 protocols can produce
``*_TX_END`` values lower than ``*_RX_END`` values, in cases where upstream peer has half-closed
its stream before downstream peer. In these cases ``COMMON_DURATION`` value will become negative.
Independent half-close behavior is enabled by setting the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional but I tend to leave runtime guards out of docs as it's easy to forget to clean them up

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

``envoy.reloadable_features.allow_multiplexed_upstream_half_close`` runtime value to ``true``.

TCP/UDP
Not implemented ("-").

Expand Down
4 changes: 3 additions & 1 deletion envoy/http/stream_reset_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ enum class StreamResetReason {
// Received payload did not conform to HTTP protocol.
ProtocolError,
// If the stream was locally reset by the Overload Manager.
OverloadManager
OverloadManager,
// If stream was locally reset due to HTTP/1 upstream half closing before downstream.
Http1PrematureUpstreamHalfClose,
KBaichoo marked this conversation as resolved.
Show resolved Hide resolved
};

/**
Expand Down
15 changes: 8 additions & 7 deletions source/common/http/async_client_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,14 @@ void AsyncStreamImpl::encodeHeaders(ResponseHeaderMapPtr&& headers, bool end_str
encoded_response_headers_ = true;
stream_callbacks_.onHeaders(std::move(headers), end_stream);
closeRemote(end_stream);
// At present, the router cleans up stream state as soon as the remote is closed, making a
// half-open local stream unsupported and dangerous. Ensure we close locally to trigger completion
// and keep things consistent. Another option would be to issue a stream reset here if local isn't
// yet closed, triggering cleanup along a more standardized path. However, this would require
// additional logic to handle the response completion and subsequent reset, and run the risk of
// being interpreted as a failure, when in fact no error has necessarily occurred. Gracefully
// closing seems most in-line with behavior elsewhere in Envoy for now.
// At present, the AsyncStream is always fully closed when the server half closes the stream.
//
// Always ensure we close locally to trigger completion. Another option would be to issue a stream
// reset here if local isn't yet closed, triggering cleanup along a more standardized path.
// However, this would require additional logic to handle the response completion and subsequent
// reset, and run the risk of being interpreted as a failure, when in fact no error has
// necessarily occurred. Gracefully closing seems most in-line with behavior elsewhere in Envoy
// for now.
closeLocal(end_stream);
}

Expand Down
6 changes: 4 additions & 2 deletions source/common/http/conn_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,10 @@ ConnectionManagerImpl::ConnectionManagerImpl(ConnectionManagerConfigSharedPtr co
/*node_id=*/local_info_.node().id(),
/*server_name=*/config_->serverName(),
/*proxy_status_config=*/config_->proxyStatusConfig())),
max_requests_during_dispatch_(runtime_.snapshot().getInteger(
ConnectionManagerImpl::MaxRequestsPerIoCycle, UINT32_MAX)) {
max_requests_during_dispatch_(
runtime_.snapshot().getInteger(ConnectionManagerImpl::MaxRequestsPerIoCycle, UINT32_MAX)),
allow_upstream_half_close_(Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.allow_multiplexed_upstream_half_close")) {
ENVOY_LOG_ONCE_IF(
trace, accept_new_http_stream_ == nullptr,
"LoadShedPoint envoy.load_shed_points.http_connection_manager_decode_headers is not "
Expand Down
3 changes: 2 additions & 1 deletion source/common/http/conn_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ class ConnectionManagerImpl : Logger::Loggable<Logger::Id::http>,
OptRef<const Tracing::Config> tracingConfig() const override;
const ScopeTrackedObject& scope() override;
OptRef<DownstreamStreamFilterCallbacks> downstreamCallbacks() override { return *this; }
bool isHalfCloseEnabled() override { return false; }
bool isHalfCloseEnabled() override { return connection_manager_.allow_upstream_half_close_; }

// DownstreamStreamFilterCallbacks
void setRoute(Router::RouteConstSharedPtr route) override;
Expand Down Expand Up @@ -640,6 +640,7 @@ class ConnectionManagerImpl : Logger::Loggable<Logger::Id::http>,
uint32_t requests_during_dispatch_count_{0};
const uint32_t max_requests_during_dispatch_{UINT32_MAX};
Event::SchedulableCallbackPtr deferred_request_processing_callback_;
const bool allow_upstream_half_close_{};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have half close behavior commented anywhere? comment link to it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added docs and more comments.

};

} // namespace Http
Expand Down
1 change: 1 addition & 0 deletions source/common/http/conn_pool_base.cc
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ void MultiplexedActiveClientBase::onStreamReset(Http::StreamResetReason reason)
case StreamResetReason::RemoteRefusedStreamReset:
case StreamResetReason::Overflow:
case StreamResetReason::ConnectError:
case StreamResetReason::Http1PrematureUpstreamHalfClose:
break;
}
}
Expand Down
95 changes: 91 additions & 4 deletions source/common/http/filter_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -466,19 +466,35 @@ void ActiveStreamDecoderFilter::encode1xxHeaders(ResponseHeaderMapPtr&& headers)
}
}

void ActiveStreamDecoderFilter::maybeMarkDecoderFilterTerminal(bool encoded_end_stream) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so I think this is fine, as discussed, but if we don't really want to support half close for anything but router (and maybe codec filter upstream) responses with half-close, do we need to support other filters being terminal? If any other filter generates the response we could treat it as full close, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed code so that only terminal filter can work with independent half-close. One runtime key is removed the FM will agnostic to the half-close behavior - it will always assume independent half-close. And semantics of closing incomplete streams will move into codecs.

// If this filter encoded end_stream and the decoder filter chain has not yet been finished
// then make this filter terminal. Decoding will not go past this filter.
filter_encoded_end_stream_ = encoded_end_stream;

// If the filter that encoded end_stream has also already observed request (downstream)
// end_stream, but the decoder filter chain has not been completed, then decoding is aborted as
// there is no need to iterate over remaining decoder filters any more.
if (encoded_end_stream && end_stream_ && !parent_.state_.decoder_filter_chain_complete_) {
parent_.state_.decoder_filter_chain_aborted_ = true;
}
}

void ActiveStreamDecoderFilter::encodeHeaders(ResponseHeaderMapPtr&& headers, bool end_stream,
absl::string_view details) {
maybeMarkDecoderFilterTerminal(end_stream);
parent_.streamInfo().setResponseCodeDetails(details);
parent_.filter_manager_callbacks_.setResponseHeaders(std::move(headers));
parent_.encodeHeaders(nullptr, *parent_.filter_manager_callbacks_.responseHeaders(), end_stream);
}

void ActiveStreamDecoderFilter::encodeData(Buffer::Instance& data, bool end_stream) {
maybeMarkDecoderFilterTerminal(end_stream);
parent_.encodeData(nullptr, data, end_stream,
FilterManager::FilterIterationStartState::CanStartFromCurrent);
}

void ActiveStreamDecoderFilter::encodeTrailers(ResponseTrailerMapPtr&& trailers) {
maybeMarkDecoderFilterTerminal(true);
parent_.filter_manager_callbacks_.setResponseTrailers(std::move(trailers));
parent_.encodeTrailers(nullptr, *parent_.filter_manager_callbacks_.responseTrailers());
}
Expand Down Expand Up @@ -526,6 +542,8 @@ void FilterManager::decodeHeaders(ActiveStreamDecoderFilter* filter, RequestHead
std::list<ActiveStreamDecoderFilterPtr>::iterator entry =
commonDecodePrefix(filter, FilterIterationStartState::AlwaysStartFromNext);
std::list<ActiveStreamDecoderFilterPtr>::iterator continue_data_entry = decoder_filters_.end();
// Terminal filter is either the last one or filter that encoded end_stream.
bool terminal_filter_decoded_end_stream = false;

for (; entry != decoder_filters_.end(); entry++) {
ASSERT(!(state_.filter_call_state_ & FilterCallState::DecodeHeaders));
Expand Down Expand Up @@ -599,13 +617,17 @@ void FilterManager::decodeHeaders(ActiveStreamDecoderFilter* filter, RequestHead
if (end_stream && buffered_request_data_ && continue_data_entry == decoder_filters_.end()) {
continue_data_entry = entry;
}
terminal_filter_decoded_end_stream =
(end_stream && continue_data_entry == decoder_filters_.end()) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you comment what this check does? it's unclear at a glance why we need the continue_data_entry here but not below. If it's for the body added case can't we have decodeData handle it and put this check in an else block for "no body added"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

(std::next(entry) == decoder_filters_.end() || (*entry)->filter_encoded_end_stream_);
}

maybeContinueDecoding(continue_data_entry);

if (end_stream) {
disarmRequestTimeout();
}
maybeEndDecode(terminal_filter_decoded_end_stream);
}

void FilterManager::decodeData(ActiveStreamDecoderFilter* filter, Buffer::Instance& data,
Expand All @@ -625,6 +647,7 @@ void FilterManager::decodeData(ActiveStreamDecoderFilter* filter, Buffer::Instan
// Filter iteration may start at the current filter.
std::list<ActiveStreamDecoderFilterPtr>::iterator entry =
commonDecodePrefix(filter, filter_iteration_start_state);
bool terminal_filter_decoded_end_stream = false;
KBaichoo marked this conversation as resolved.
Show resolved Hide resolved

for (; entry != decoder_filters_.end(); entry++) {
// If the filter pointed by entry has stopped for all frame types, return now.
Expand Down Expand Up @@ -702,6 +725,10 @@ void FilterManager::decodeData(ActiveStreamDecoderFilter* filter, Buffer::Instan
trailers_added_entry = entry;
}

terminal_filter_decoded_end_stream =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's definitely comment each of these blocks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

end_stream &&
(std::next(entry) == decoder_filters_.end() || (*entry)->filter_encoded_end_stream_);

if (!(*entry)->commonHandleAfterDataCallback(status, data, state_.decoder_filters_streaming_) &&
std::next(entry) != decoder_filters_.end()) {
// Stop iteration IFF this is not the last filter. If it is the last filter, continue with
Expand All @@ -720,6 +747,7 @@ void FilterManager::decodeData(ActiveStreamDecoderFilter* filter, Buffer::Instan
if (end_stream) {
disarmRequestTimeout();
}
maybeEndDecode(terminal_filter_decoded_end_stream);
}

RequestTrailerMap& FilterManager::addDecodedTrailers() {
Expand Down Expand Up @@ -764,6 +792,7 @@ void FilterManager::decodeTrailers(ActiveStreamDecoderFilter* filter, RequestTra
// Filter iteration may start at the current filter.
std::list<ActiveStreamDecoderFilterPtr>::iterator entry =
commonDecodePrefix(filter, FilterIterationStartState::CanStartFromCurrent);
bool terminal_filter_decoded_end_stream = false;

for (; entry != decoder_filters_.end(); entry++) {
// If the filter pointed by entry has stopped for all frame type, return now.
Expand All @@ -787,12 +816,20 @@ void FilterManager::decodeTrailers(ActiveStreamDecoderFilter* filter, RequestTra
}

processNewlyAddedMetadata();
terminal_filter_decoded_end_stream =
std::next(entry) == decoder_filters_.end() || (*entry)->filter_encoded_end_stream_;

if (!(*entry)->commonHandleAfterTrailersCallback(status)) {
if (terminal_filter_decoded_end_stream) {
break;
KBaichoo marked this conversation as resolved.
Show resolved Hide resolved
}

if (!(*entry)->commonHandleAfterTrailersCallback(status) &&
std::next(entry) != decoder_filters_.end()) {
return;
}
}
disarmRequestTimeout();
maybeEndDecode(terminal_filter_decoded_end_stream);
}

void FilterManager::decodeMetadata(ActiveStreamDecoderFilter* filter, MetadataMap& metadata_map) {
Expand Down Expand Up @@ -922,9 +959,9 @@ void DownstreamFilterManager::sendLocalReply(

// Stop filter chain iteration if local reply was sent while filter decoding or encoding callbacks
// are running.
if (state_.filter_call_state_ & FilterCallState::IsDecodingMask) {
state_.decoder_filter_chain_aborted_ = true;
} else if (state_.filter_call_state_ & FilterCallState::IsEncodingMask) {
// Local reply always stops decoder filter chain.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like a functional change. Should it be behind this or a separate runtime guard? maybe split out and land ahead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made it flag protected. I will create separate PR to make it so that decoder filter is always aborted on local reply.

state_.decoder_filter_chain_aborted_ = true;
if (state_.filter_call_state_ & FilterCallState::IsEncodingMask) {
state_.encoder_filter_chain_aborted_ = true;
}

Expand Down Expand Up @@ -1448,6 +1485,56 @@ void FilterManager::maybeEndEncode(bool end_stream) {
if (end_stream) {
ASSERT(!state_.encoder_filter_chain_complete_);
state_.encoder_filter_chain_complete_ = true;
if (filter_manager_callbacks_.isHalfCloseEnabled()) {
// If independent half close is enabled the stream is closed when both decoder and encoder
// filter chains has completed or were aborted.
checkAndCloseStreamIfFullyClosed();
} else {
// Otherwise encoding end_stream always closes the stream (and resets it if request was not
// complete yet).
filter_manager_callbacks_.endStream();
}
}
}

void FilterManager::maybeEndDecode(bool terminal_filter_decoded_end_stream) {
if (terminal_filter_decoded_end_stream) {
ASSERT(!state_.decoder_filter_chain_complete_);
state_.decoder_filter_chain_complete_ = true;
// If the decoder filter chain was aborted (i.e. due to local reply)
// we rely on the encoding of end_stream to close the stream.
if (filter_manager_callbacks_.isHalfCloseEnabled() && !stopDecoderFilterChain()) {
checkAndCloseStreamIfFullyClosed();
}
}
}

void FilterManager::checkAndCloseStreamIfFullyClosed() {
// This function is only used when half close semantics are enabled.
if (!filter_manager_callbacks_.isHalfCloseEnabled()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right now it's only called if half close is enabled right? should it be an envoy bug if we early return?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added ASSERT

return;
}
// When the independent half close is enabled the stream is always closed on error responses
// from the server.
if (filter_manager_callbacks_.responseHeaders().has_value()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should definitely have a doc section on how half close works as I don't recall this being mentioned before (but IMO sounds reasonable)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

const uint64_t response_status =
Http::Utility::getResponseStatus(filter_manager_callbacks_.responseHeaders().ref());
bool error_response =
KBaichoo marked this conversation as resolved.
Show resolved Hide resolved
!(Http::CodeUtility::is2xx(response_status) || Http::CodeUtility::is1xx(response_status));
// Abort the decoder filter if it has not yet been completed.
if (error_response && !state_.decoder_filter_chain_complete_) {
state_.decoder_filter_chain_aborted_ = true;
}
}

// If independent half close is enabled then close the stream when
// 1. Both encoder and decoder filter chains has completed.
// 2. Encoder filter chain has completed and decoder filter chain was aborted (i.e. local reply).
// There is no need to check for aborted encoder filter chain as the filter will either be
// completed or stream is reset.
if (state_.encoder_filter_chain_complete_ &&
(state_.decoder_filter_chain_complete_ || state_.decoder_filter_chain_aborted_)) {
ENVOY_STREAM_LOG(trace, "closing stream", *this);
filter_manager_callbacks_.endStream();
}
}
Expand Down
46 changes: 38 additions & 8 deletions source/common/http/filter_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,20 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase,

void requestDataTooLarge();
void requestDataDrained();
// Check if the filter that encoded end_stream has also decoded end_stream and if true
// stop the decoder filter chain. This will end the request after encoder filter chain
// is completed.
// This allows non-terminal filters (i.e. cache filter) to encode responses when independent
// half-close is enabled. Encoding end_stream effectively makes the filter terminal - decoder
// filer chain will not go past this filter.
void maybeMarkDecoderFilterTerminal(bool encoded_end_stream);

StreamDecoderFilterSharedPtr handle_;
bool is_grpc_request_{};
// Indicates that this filter called an encodeXXX method with end_stream == true.
// When independent half close is enabled this filter becomes the terminal filter
// in the decoder filter chain.
bool filter_encoded_end_stream_{false};
};

using ActiveStreamDecoderFilterPtr = std::unique_ptr<ActiveStreamDecoderFilter>;
Expand Down Expand Up @@ -656,7 +667,11 @@ class FilterManager : public ScopeTrackedObject,
// ScopeTrackedObject
void dumpState(std::ostream& os, int indent_level = 0) const override {
const char* spaces = spacesForLevel(indent_level);
os << spaces << "FilterManager " << this << DUMP_MEMBER(state_.has_1xx_headers_) << "\n";
os << spaces << "FilterManager " << this << DUMP_MEMBER(state_.has_1xx_headers_)
<< DUMP_MEMBER(state_.decoder_filter_chain_complete_)
<< DUMP_MEMBER(state_.encoder_filter_chain_complete_)
<< DUMP_MEMBER(state_.observed_decode_end_stream_)
<< DUMP_MEMBER(state_.observed_encode_end_stream_) << "\n";

DUMP_DETAILS(filter_manager_callbacks_.requestHeaders());
DUMP_DETAILS(filter_manager_callbacks_.requestTrailers());
Expand Down Expand Up @@ -782,6 +797,15 @@ class FilterManager : public ScopeTrackedObject,
*/
void maybeEndEncode(bool end_stream);

/**
* If terminal_filter_decoded_end_stream is true, marks decoding as complete. This is a noop if
* terminal_filter_decoded_end_stream is false.
* @param end_stream whether decoding is complete.
*/
void maybeEndDecode(bool terminal_filter_decoded_end_stream);

void checkAndCloseStreamIfFullyClosed();

virtual void sendLocalReply(Code code, absl::string_view body,
const std::function<void(ResponseHeaderMap& headers)>& modify_headers,
const absl::optional<Grpc::Status::GrpcStatus> grpc_status,
Expand Down Expand Up @@ -868,16 +892,22 @@ class FilterManager : public ScopeTrackedObject,
protected:
struct State {
State()
: encoder_filter_chain_complete_(false), observed_decode_end_stream_(false),
observed_encode_end_stream_(false), has_1xx_headers_(false), created_filter_chain_(false),
is_head_request_(false), is_grpc_request_(false),
non_100_response_headers_encoded_(false), under_on_local_reply_(false),
decoder_filter_chain_aborted_(false), encoder_filter_chain_aborted_(false),
saw_downstream_reset_(false) {}
: decoder_filter_chain_complete_(false), encoder_filter_chain_complete_(false),
observed_decode_end_stream_(false), observed_encode_end_stream_(false),
has_1xx_headers_(false), created_filter_chain_(false), is_head_request_(false),
is_grpc_request_(false), non_100_response_headers_encoded_(false),
under_on_local_reply_(false), decoder_filter_chain_aborted_(false),
encoder_filter_chain_aborted_(false), saw_downstream_reset_(false) {}
uint32_t filter_call_state_{0};

// Set after decoder filter chain has completed iteration. Prevents further calls to decoder
// filters. This flag is used to determine stream completion when the independent half-close is
// enabled.
bool decoder_filter_chain_complete_ : 1;

// Set after encoder filter chain has completed iteration. Prevents further calls to encoder
// filters.
// filters. This flag is used to determine stream completion when the independent half-close is
// enabled.
bool encoder_filter_chain_complete_ : 1;

// Set `true` when the filter manager observes end stream on the decoder path (from downstream
Expand Down
Loading