-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
http2: Remove RELEASE_ASSERTs in sendPendingFrames() error handling #13546
Merged
mattklein123
merged 5 commits into
envoyproxy:master
from
yanavlasov:remove-release-assert
Oct 16, 2020
Merged
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
9076165
Remove RELEASE_ASSERTs in sendPendingFrames() error handling
yanavlasov 94e5fe0
Address comments
yanavlasov 303e430
Merge branch 'master' into remove-release-assert
yanavlasov b6004ee
Merge branch 'master' into remove-release-assert
yanavlasov fe520ad
Merge branch 'master' into remove-release-assert
yanavlasov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -166,17 +166,10 @@ void ConnectionImpl::StreamImpl::encodeHeadersBase(const std::vector<nghttp2_nv> | |
|
||
local_end_stream_ = end_stream; | ||
submitHeaders(final_headers, end_stream ? nullptr : &provider); | ||
auto status = parent_.sendPendingFrames(); | ||
// The RELEASE_ASSERT below does not change the existing behavior of `sendPendingFrames()`. | ||
// The `sendPendingFrames()` used to throw on errors and the only method that was catching | ||
// these exceptions was the `dispatch()`. The `dispatch()` method still checks and handles | ||
// errors returned by the `sendPendingFrames()`. | ||
// Other callers of `sendPendingFrames()` do not catch exceptions from this method and | ||
// would cause abnormal process termination in error cases. This change replaces abnormal | ||
// process termination from unhandled exception with the RELEASE_ASSERT. | ||
// Further work will replace this RELEASE_ASSERT with proper error handling. | ||
RELEASE_ASSERT(status.ok(), "sendPendingFrames() failure in non dispatching context"); | ||
parent_.checkProtocolConstraintViolation(); | ||
if (parent_.sendPendingFramesAndHandleError()) { | ||
// Intended to check through coverage that this error case is tested | ||
return; | ||
} | ||
} | ||
|
||
void ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& headers, | ||
|
@@ -244,10 +237,10 @@ void ConnectionImpl::StreamImpl::encodeTrailersBase(const HeaderMap& trailers) { | |
} | ||
} else { | ||
submitTrailers(trailers); | ||
auto status = parent_.sendPendingFrames(); | ||
// See comment in the `encodeHeadersBase()` method about this RELEASE_ASSERT. | ||
RELEASE_ASSERT(status.ok(), "sendPendingFrames() failure in non dispatching context"); | ||
parent_.checkProtocolConstraintViolation(); | ||
if (parent_.sendPendingFramesAndHandleError()) { | ||
// Intended to check through coverage that this error case is tested | ||
return; | ||
} | ||
} | ||
} | ||
|
||
|
@@ -260,10 +253,10 @@ void ConnectionImpl::StreamImpl::encodeMetadata(const MetadataMapVector& metadat | |
for (uint8_t flags : metadata_encoder.payloadFrameFlagBytes()) { | ||
submitMetadata(flags); | ||
} | ||
auto status = parent_.sendPendingFrames(); | ||
// See comment in the `encodeHeadersBase()` method about this RELEASE_ASSERT. | ||
RELEASE_ASSERT(status.ok(), "sendPendingFrames() failure in non dispatching context"); | ||
parent_.checkProtocolConstraintViolation(); | ||
if (parent_.sendPendingFramesAndHandleError()) { | ||
// Intended to check through coverage that this error case is tested | ||
return; | ||
} | ||
} | ||
|
||
void ConnectionImpl::StreamImpl::readDisable(bool disable) { | ||
|
@@ -278,10 +271,10 @@ void ConnectionImpl::StreamImpl::readDisable(bool disable) { | |
if (!buffersOverrun()) { | ||
nghttp2_session_consume(parent_.session_, stream_id_, unconsumed_bytes_); | ||
unconsumed_bytes_ = 0; | ||
auto status = parent_.sendPendingFrames(); | ||
// See comment in the `encodeHeadersBase()` method about this RELEASE_ASSERT. | ||
RELEASE_ASSERT(status.ok(), "sendPendingFrames() failure in non dispatching context"); | ||
parent_.checkProtocolConstraintViolation(); | ||
if (parent_.sendPendingFramesAndHandleError()) { | ||
// Intended to check through coverage that this error case is tested | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -407,7 +400,7 @@ ssize_t ConnectionImpl::StreamImpl::onDataSourceRead(uint64_t length, uint32_t* | |
} | ||
} | ||
|
||
Status ConnectionImpl::StreamImpl::onDataSourceSend(const uint8_t* framehd, size_t length) { | ||
void ConnectionImpl::StreamImpl::onDataSourceSend(const uint8_t* framehd, size_t length) { | ||
// In this callback we are writing out a raw DATA frame without copying. nghttp2 assumes that we | ||
// "just know" that the frame header is 9 bytes. | ||
// https://nghttp2.org/documentation/types.html#c.nghttp2_send_data_callback | ||
|
@@ -416,18 +409,16 @@ Status ConnectionImpl::StreamImpl::onDataSourceSend(const uint8_t* framehd, size | |
parent_.protocol_constraints_.incrementOutboundDataFrameCount(); | ||
|
||
Buffer::OwnedImpl output; | ||
auto status = parent_.addOutboundFrameFragment(output, framehd, FRAME_HEADER_SIZE); | ||
if (!status.ok()) { | ||
parent_.addOutboundFrameFragment(output, framehd, FRAME_HEADER_SIZE); | ||
if (!parent_.protocol_constraints_.checkOutboundFrameLimits().ok()) { | ||
ENVOY_CONN_LOG(debug, "error sending data frame: Too many frames in the outbound queue", | ||
parent_.connection_); | ||
setDetails(Http2ResponseCodeDetails::get().outbound_frame_flood); | ||
return status; | ||
} | ||
|
||
parent_.stats_.pending_send_bytes_.sub(length); | ||
output.move(pending_send_data_, length); | ||
parent_.connection_.write(output, false); | ||
return status; | ||
} | ||
|
||
void ConnectionImpl::ClientStreamImpl::submitHeaders(const std::vector<nghttp2_nv>& final_headers, | ||
|
@@ -463,10 +454,10 @@ void ConnectionImpl::StreamImpl::onPendingFlushTimer() { | |
// This will emit a reset frame for this stream and close the stream locally. No reset callbacks | ||
// will be run because higher layers think the stream is already finished. | ||
resetStreamWorker(StreamResetReason::LocalReset); | ||
auto status = parent_.sendPendingFrames(); | ||
// See comment in the `encodeHeadersBase()` method about this RELEASE_ASSERT. | ||
RELEASE_ASSERT(status.ok(), "sendPendingFrames() failure in non dispatching context"); | ||
parent_.checkProtocolConstraintViolation(); | ||
if (parent_.sendPendingFramesAndHandleError()) { | ||
// Intended to check through coverage that this error case is tested | ||
return; | ||
} | ||
} | ||
|
||
void ConnectionImpl::StreamImpl::encodeData(Buffer::Instance& data, bool end_stream) { | ||
|
@@ -490,11 +481,10 @@ void ConnectionImpl::StreamImpl::encodeDataHelper(Buffer::Instance& data, bool e | |
data_deferred_ = false; | ||
} | ||
|
||
auto status = parent_.sendPendingFrames(); | ||
// See comment in the `encodeHeadersBase()` method about this RELEASE_ASSERT. | ||
RELEASE_ASSERT(status.ok(), "sendPendingFrames() failure in non dispatching context"); | ||
parent_.checkProtocolConstraintViolation(); | ||
|
||
if (parent_.sendPendingFramesAndHandleError()) { | ||
// Intended to check through coverage that this error case is tested | ||
return; | ||
} | ||
if (local_end_stream_ && pending_send_data_.length() > 0) { | ||
createPendingFlushTimer(); | ||
} | ||
|
@@ -518,10 +508,10 @@ void ConnectionImpl::StreamImpl::resetStream(StreamResetReason reason) { | |
// We must still call sendPendingFrames() in both the deferred and not deferred path. This forces | ||
// the cleanup logic to run which will reset the stream in all cases if all data frames could not | ||
// be sent. | ||
auto status = parent_.sendPendingFrames(); | ||
// See comment in the `encodeHeadersBase()` method about this RELEASE_ASSERT. | ||
RELEASE_ASSERT(status.ok(), "sendPendingFrames() failure in non dispatching context"); | ||
parent_.checkProtocolConstraintViolation(); | ||
if (parent_.sendPendingFramesAndHandleError()) { | ||
// Intended to check through coverage that this error case is tested | ||
return; | ||
} | ||
} | ||
|
||
void ConnectionImpl::StreamImpl::resetStreamWorker(StreamResetReason reason) { | ||
|
@@ -602,11 +592,10 @@ void ConnectionImpl::sendKeepalive() { | |
int rc = nghttp2_submit_ping(session_, 0 /*flags*/, reinterpret_cast<uint8_t*>(&ms_since_epoch)); | ||
ASSERT(rc == 0); | ||
|
||
auto status = sendPendingFrames(); | ||
// See comment in the `encodeHeadersBase()` method about this RELEASE_ASSERT. | ||
RELEASE_ASSERT(status.ok(), "sendPendingFrames() failure in non dispatching context"); | ||
checkProtocolConstraintViolation(); | ||
|
||
if (sendPendingFramesAndHandleError()) { | ||
// Intended to check through coverage that this error case is tested | ||
return; | ||
} | ||
keepalive_timeout_timer_->enableTimer(keepalive_timeout_); | ||
} | ||
void ConnectionImpl::onKeepaliveResponse() { | ||
|
@@ -698,20 +687,20 @@ void ConnectionImpl::goAway() { | |
NGHTTP2_NO_ERROR, nullptr, 0); | ||
ASSERT(rc == 0); | ||
|
||
auto status = sendPendingFrames(); | ||
// See comment in the `encodeHeadersBase()` method about this RELEASE_ASSERT. | ||
RELEASE_ASSERT(status.ok(), "sendPendingFrames() failure in non dispatching context"); | ||
checkProtocolConstraintViolation(); | ||
if (sendPendingFramesAndHandleError()) { | ||
// Intended to check through coverage that this error case is tested | ||
return; | ||
} | ||
} | ||
|
||
void ConnectionImpl::shutdownNotice() { | ||
int rc = nghttp2_submit_shutdown_notice(session_); | ||
ASSERT(rc == 0); | ||
|
||
auto status = sendPendingFrames(); | ||
// See comment in the `encodeHeadersBase()` method about this RELEASE_ASSERT. | ||
RELEASE_ASSERT(status.ok(), "sendPendingFrames() failure in non dispatching context"); | ||
checkProtocolConstraintViolation(); | ||
if (sendPendingFramesAndHandleError()) { | ||
// Intended to check through coverage that this error case is tested | ||
return; | ||
} | ||
} | ||
|
||
Status ConnectionImpl::onBeforeFrameReceived(const nghttp2_frame_hd* hd) { | ||
|
@@ -929,31 +918,21 @@ int ConnectionImpl::onBeforeFrameSend(const nghttp2_frame* frame) { | |
return 0; | ||
} | ||
|
||
Status ConnectionImpl::addOutboundFrameFragment(Buffer::OwnedImpl& output, const uint8_t* data, | ||
size_t length) { | ||
void ConnectionImpl::addOutboundFrameFragment(Buffer::OwnedImpl& output, const uint8_t* data, | ||
size_t length) { | ||
// Reset the outbound frame type (set in the onBeforeFrameSend callback) since the | ||
// onBeforeFrameSend callback is not called for DATA frames. | ||
bool is_outbound_flood_monitored_control_frame = false; | ||
std::swap(is_outbound_flood_monitored_control_frame, is_outbound_flood_monitored_control_frame_); | ||
auto status_or_releasor = trackOutboundFrames(is_outbound_flood_monitored_control_frame); | ||
if (!status_or_releasor.ok()) { | ||
return status_or_releasor.status(); | ||
} | ||
|
||
auto releasor = trackOutboundFrames(is_outbound_flood_monitored_control_frame); | ||
output.add(data, length); | ||
output.addDrainTracker(status_or_releasor.value()); | ||
return okStatus(); | ||
output.addDrainTracker(releasor); | ||
} | ||
|
||
StatusOr<ssize_t> ConnectionImpl::onSend(const uint8_t* data, size_t length) { | ||
ssize_t ConnectionImpl::onSend(const uint8_t* data, size_t length) { | ||
ENVOY_CONN_LOG(trace, "send data: bytes={}", connection_, length); | ||
Buffer::OwnedImpl buffer; | ||
auto status = addOutboundFrameFragment(buffer, data, length); | ||
if (!status.ok()) { | ||
ENVOY_CONN_LOG(debug, "error sending frame: Too many frames in the outbound queue.", | ||
connection_); | ||
return status; | ||
} | ||
addOutboundFrameFragment(buffer, data, length); | ||
|
||
// While the buffer is transient the fragment it contains will be moved into the | ||
// write_buffer_ of the underlying connection_ by the write method below. | ||
|
@@ -1087,15 +1066,6 @@ Status ConnectionImpl::sendPendingFrames() { | |
const int rc = nghttp2_session_send(session_); | ||
if (rc != 0) { | ||
ASSERT(rc == NGHTTP2_ERR_CALLBACK_FAILURE); | ||
|
||
if (!nghttp2_callback_status_.ok()) { | ||
return nghttp2_callback_status_; | ||
} | ||
|
||
// Protocol constrain violations should set the nghttp2_callback_status_ error, and return at | ||
// the statement above. | ||
ASSERT(protocol_constraints_.status().ok()); | ||
|
||
return codecProtocolError(nghttp2_strerror(rc)); | ||
} | ||
|
||
|
@@ -1120,7 +1090,23 @@ Status ConnectionImpl::sendPendingFrames() { | |
} | ||
RETURN_IF_ERROR(sendPendingFrames()); | ||
} | ||
return okStatus(); | ||
|
||
// After all pending frames have been written into the outbound buffer check if any of | ||
// protocol constraints had been violated. | ||
Status status = protocol_constraints_.checkOutboundFrameLimits(); | ||
if (!status.ok()) { | ||
ENVOY_CONN_LOG(debug, "error sending frames: Too many frames in the outbound queue.", | ||
connection_); | ||
} | ||
return status; | ||
} | ||
|
||
bool ConnectionImpl::sendPendingFramesAndHandleError() { | ||
if (!sendPendingFrames().ok()) { | ||
scheduleProtocolConstraintViolationCallback(); | ||
return true; | ||
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. returning true on error is a bit unusual, but you are documenting the return value so it seems fine. |
||
} | ||
return false; | ||
} | ||
|
||
void ConnectionImpl::sendSettings( | ||
|
@@ -1212,23 +1198,16 @@ ConnectionImpl::Http2Callbacks::Http2Callbacks() { | |
nghttp2_session_callbacks_set_send_callback( | ||
callbacks_, | ||
[](nghttp2_session*, const uint8_t* data, size_t length, int, void* user_data) -> ssize_t { | ||
auto status_or_len = static_cast<ConnectionImpl*>(user_data)->onSend(data, length); | ||
if (status_or_len.ok()) { | ||
return status_or_len.value(); | ||
} | ||
auto status = status_or_len.status(); | ||
return static_cast<ConnectionImpl*>(user_data)->setAndCheckNghttp2CallbackStatus( | ||
std::move(status)); | ||
return static_cast<ConnectionImpl*>(user_data)->onSend(data, length); | ||
}); | ||
|
||
nghttp2_session_callbacks_set_send_data_callback( | ||
callbacks_, | ||
[](nghttp2_session*, nghttp2_frame* frame, const uint8_t* framehd, size_t length, | ||
nghttp2_data_source* source, void*) -> int { | ||
ASSERT(frame->data.padlen == 0); | ||
auto status = static_cast<StreamImpl*>(source->ptr)->onDataSourceSend(framehd, length); | ||
return static_cast<StreamImpl*>(source->ptr) | ||
->parent_.setAndCheckNghttp2CallbackStatus(std::move(status)); | ||
static_cast<StreamImpl*>(source->ptr)->onDataSourceSend(framehd, length); | ||
return 0; | ||
}); | ||
|
||
nghttp2_session_callbacks_set_on_begin_headers_callback( | ||
|
@@ -1505,20 +1484,10 @@ Status ServerConnectionImpl::trackInboundFrames(const nghttp2_frame_hd* hd, | |
return result; | ||
} | ||
|
||
StatusOr<ProtocolConstraints::ReleasorProc> | ||
ProtocolConstraints::ReleasorProc | ||
ServerConnectionImpl::trackOutboundFrames(bool is_outbound_flood_monitored_control_frame) { | ||
auto releasor = | ||
protocol_constraints_.incrementOutboundFrameCount(is_outbound_flood_monitored_control_frame); | ||
if (dispatching_downstream_data_ && !protocol_constraints_.checkOutboundFrameLimits().ok()) { | ||
return protocol_constraints_.status(); | ||
} | ||
return releasor; | ||
} | ||
|
||
void ServerConnectionImpl::checkProtocolConstraintViolation() { | ||
if (!protocol_constraints_.checkOutboundFrameLimits().ok()) { | ||
scheduleProtocolConstraintViolationCallback(); | ||
} | ||
return protocol_constraints_.incrementOutboundFrameCount( | ||
is_outbound_flood_monitored_control_frame); | ||
} | ||
|
||
Http::Status ServerConnectionImpl::dispatch(Buffer::Instance& data) { | ||
|
@@ -1530,12 +1499,6 @@ Http::Status ServerConnectionImpl::dispatch(Buffer::Instance& data) { | |
} | ||
|
||
Http::Status ServerConnectionImpl::innerDispatch(Buffer::Instance& data) { | ||
ASSERT(!dispatching_downstream_data_); | ||
dispatching_downstream_data_ = true; | ||
|
||
// Make sure the dispatching_downstream_data_ is set to false when innerDispatch ends. | ||
Cleanup cleanup([this]() { dispatching_downstream_data_ = false; }); | ||
|
||
// Make sure downstream outbound queue was not flooded by the upstream frames. | ||
RETURN_IF_ERROR(protocol_constraints_.checkOutboundFrameLimits()); | ||
return ConnectionImpl::innerDispatch(data); | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Should there be an error return for this case, or should this be void and the call site always return 0?
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.
The error will be handled in the
sendPendingFrames()
. I've made this function void.