-
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
connection: skip read activate call when reading from transport socket if the connection is read disabled #14043
Merged
antoniovicente
merged 7 commits into
envoyproxy:master
from
antoniovicente:high_watermark_skip_read_activate
Dec 3, 2020
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
3d9e14d
connection: skip read activate if the connection is read disabled.
antoniovicente 83ba7b1
expand comments
antoniovicente 0584585
fix typo
antoniovicente 8fbec6c
fix clang-tidy
antoniovicente fe52f31
Merge remote-tracking branch 'upstream/master' into high_watermark_sk…
antoniovicente b1b3df0
fix build
antoniovicente 69615f2
Merge remote-tracking branch 'upstream/master' into high_watermark_sk…
antoniovicente 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
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 |
---|---|---|
|
@@ -194,6 +194,19 @@ Connection::State ConnectionImpl::state() const { | |
|
||
void ConnectionImpl::closeConnectionImmediately() { closeSocket(ConnectionEvent::LocalClose); } | ||
|
||
void ConnectionImpl::setTransportSocketIsReadable() { | ||
// Remember that the transport requested read resumption, in case the resumption event is not | ||
// scheduled immediately or is "lost" because read was disabled. | ||
transport_wants_read_ = true; | ||
// Only schedule a read activation if the connection is not read disabled to avoid spurious | ||
// wakeups. When read disabled, the connection will not read from the transport, and limit | ||
// dispatch to the current contents of the read buffer if its high-watermark is triggered and | ||
// dispatch_buffered_data_ is set. | ||
if (read_disable_count_ == 0) { | ||
ioHandle().activateFileEvents(Event::FileReadyType::Read); | ||
} | ||
} | ||
|
||
bool ConnectionImpl::filterChainWantsData() { | ||
return read_disable_count_ == 0 || | ||
(read_disable_count_ == 1 && read_buffer_.highWatermarkTriggered()); | ||
|
@@ -358,18 +371,19 @@ void ConnectionImpl::readDisable(bool disable) { | |
} | ||
|
||
if (filterChainWantsData() && (read_buffer_.length() > 0 || transport_wants_read_)) { | ||
// If the read_buffer_ is not empty or transport_wants_read_ is true, the connection may be | ||
// able to process additional bytes even if there is no data in the kernel to kick off the | ||
// filter chain. Alternately if the read buffer has data the fd could be read disabled. To | ||
// handle these cases, fake an event to make sure the buffered data in the read buffer or in | ||
// transport socket internal buffers gets processed regardless and ensure that we dispatch it | ||
// via onRead. | ||
|
||
// Sanity check: resumption with read_disable_count_ > 0 should only happen if the read | ||
// buffer's high watermark has triggered. | ||
ASSERT(read_buffer_.length() > 0 || read_disable_count_ == 0); | ||
|
||
// If the read_buffer_ is not empty or transport_wants_read_ is true, the connection may be | ||
// able to process additional bytes even if there is no data in the kernel to kick off the | ||
// filter chain. Alternately the connection may need read resumption while read disabled and | ||
// not registered for read events because the read buffer's high-watermark has triggered. To | ||
// handle these cases, directly schedule a fake read event to make sure the buffered data in | ||
// the read buffer or in transport socket internal buffers gets processed regardless and | ||
// ensure that we dispatch it via onRead. | ||
dispatch_buffered_data_ = true; | ||
setReadBufferReady(); | ||
ioHandle().activateFileEvents(Event::FileReadyType::Read); | ||
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. Think it's worth a comment on why this case is different? 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. Rearranged and expanded comments. |
||
} | ||
} | ||
} | ||
|
@@ -560,8 +574,9 @@ void ConnectionImpl::onReadReady() { | |
ASSERT(!connecting_); | ||
|
||
// We get here while read disabled in two ways. | ||
// 1) There was a call to setReadBufferReady(), for example if a raw buffer socket ceded due to | ||
// shouldDrainReadBuffer(). In this case we defer the event until the socket is read enabled. | ||
// 1) There was a call to setTransportSocketIsReadable(), for example if a raw buffer socket ceded | ||
// due to shouldDrainReadBuffer(). In this case we defer the event until the socket is read | ||
// enabled. | ||
// 2) The consumer of connection data called readDisable(true), and instead of reading from the | ||
// socket we simply need to dispatch already read data. | ||
if (read_disable_count_ != 0) { | ||
|
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
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
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
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
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
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
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
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
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.
So I see a test change to ensure that when read_disabled we don't activate here.
there's one other change in this PR, which is that in filterChainWantsData we no longer set transport_wants_read_ true. Is that behavior regression tested as well?
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.
It stroke me odd as well so I dug a bit deeper into that and I think the current and the previous code are equivalent:
In the previous code we set
transport_wants_read_
via callingsetReadBufferReady
inReadDisable
if:(*)
transport_wants_read_
is true already orbuffer.length() > 0
. This comes from the if-statement above.if (filterChainWantsData() && (read_buffer_.length() > 0 || transport_wants_read_))
Looking at the two cases individually:
transport_wants_read_
is true already then it doesn't really matter that we do not settransport_wants_read_
to true.buffer.length() > 0
then the next call toReadDisable(false)
will trigger the read activation regardless of the value oftransport_wants_read_
because of (*).This means that even in the case where that we have a cycle of ReadDisable(true)/ReadEnable(false) (this used to leave connections at zombie state because the read activation was canceled) we will re-activate reads correctly.
+1 on testing though, the more the merrier.
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 think you're referring to the case where we check filterChainWantsData() in readDisable(false) below. Setting transport_wants_read_ when resuming from readDisable(false) does not matter because it is either already set when the method is called or onReadReady clears it before it is possible to drain the read buffer and call readDisable(false) from within that method.
Also, setting transport_wants_read_ in readDisable(false) was known to be redundant as seen in this comment thread: #13772 (comment)
Longer version:
After a read from the transport socket the connection read buffer can be in one of the following states:
transport_wants_read_ == true in the cases above since shouldDrainReadBuffer() == true triggers a call to setTransportSocketIsReadable() from the transport.
Resumption on readDisable(false) can happen if:
a. read_buffer_.highWatermarkTriggered() is true and read_disable_count_ == 1
b. read_buffer_ is not empty and read_disable_count_ == 0
c. read_buffer_ is empty and transport_wants_read_ == true
Additional considerations:
Bytes are only ever added or removed from the read buffer under the call stack of onReadReady
Resumption case (a) can only happen after transport socket read case (1), so transport_wants_read_ == true in this case. The next call to onReadReady will return early because read_disable_count_ >= 1, so transport_wants_read_ remains == true.
The next call to onReadReady after resumption case (b) will perform a read from the transport because read_disable_count_ == 0. onReadReady will set transport_wants_read_ to false before doing reads from the transport or processing the contents of the read buffer. Nothing reads the value of transport_wants_read_ between the time the read resumption is scheduled until onReadReady sets it to false.
transport_wants_read_ is already set to true in case (c)
While explaining this I noticed an edge case that I hadn't considered before: If the read buffer is exactly at the buffer_limit_, shouldDrainReadBuffer() will return true but highWatermarkTriggered() returns false. In fact, this edge case is an optimization for the common case where the read limit is configured to a multiple of 16KB, so transport reads end up exactly hitting the configured buffer limit and narrowly avoid the expensive calls to readDisable(true)/readDisable(false). I'ld love to get rid of this edge case, but in order to do that we need to reduce the cost of going through a readDisable(true)/readDisable(false) cycle.