-
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: Remember transport socket read resumption requests and replay them when re-enabling read. #13772
Merged
antoniovicente
merged 8 commits into
envoyproxy:master
from
antoniovicente:connection_impl_want_read
Oct 28, 2020
Merged
Changes from 7 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
3d4555a
connection: Remember transport socket read resumption requests and re…
antoniovicente d949e38
address review comments
antoniovicente de9fce0
add comment
antoniovicente 7d7f311
revert unnecessary change
antoniovicente 6647411
refine comment
antoniovicente b9d8ac8
Improve naming of data member and member function
antoniovicente 5776193
Merge remote-tracking branch 'upstream/master' into connection_impl_w…
antoniovicente 4298807
fix comments
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
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 |
---|---|---|
|
@@ -1857,6 +1857,197 @@ TEST_F(MockTransportConnectionImplTest, ObjectDestructOrder) { | |
file_ready_cb_(Event::FileReadyType::Read); | ||
} | ||
|
||
// Verify that read resumptions requested via setReadBufferReady() are scheduled once read is | ||
// re-enabled. | ||
TEST_F(MockTransportConnectionImplTest, ReadBufferReadyResumeAfterReadDisable) { | ||
InSequence s; | ||
|
||
std::shared_ptr<MockReadFilter> read_filter(new StrictMock<MockReadFilter>()); | ||
connection_->enableHalfClose(true); | ||
connection_->addReadFilter(read_filter); | ||
|
||
EXPECT_CALL(*file_event_, setEnabled(Event::FileReadyType::Write)); | ||
connection_->readDisable(true); | ||
EXPECT_CALL(*file_event_, setEnabled(Event::FileReadyType::Read | Event::FileReadyType::Write)); | ||
// No calls to activate when re-enabling if there are no pending read requests. | ||
EXPECT_CALL(*file_event_, activate(_)).Times(0); | ||
connection_->readDisable(false); | ||
|
||
// setReadBufferReady triggers an immediate call to activate. | ||
EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Read)); | ||
connection_->setReadBufferReady(); | ||
|
||
// When processing a sequence of read disable/read enable, changes to the enabled event mask | ||
// happen only when the disable count transitions to/from 0. | ||
EXPECT_CALL(*file_event_, setEnabled(Event::FileReadyType::Write)); | ||
connection_->readDisable(true); | ||
connection_->readDisable(true); | ||
connection_->readDisable(true); | ||
connection_->readDisable(false); | ||
connection_->readDisable(false); | ||
EXPECT_CALL(*file_event_, setEnabled(Event::FileReadyType::Read | Event::FileReadyType::Write)); | ||
// Expect a read activation since there have been no transport doRead calls since the call to | ||
// setReadBufferReady. | ||
EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Read)); | ||
connection_->readDisable(false); | ||
|
||
// No calls to doRead when file_ready_cb is invoked while read disabled. | ||
EXPECT_CALL(*file_event_, setEnabled(_)); | ||
connection_->readDisable(true); | ||
EXPECT_CALL(*transport_socket_, doRead(_)).Times(0); | ||
file_ready_cb_(Event::FileReadyType::Read); | ||
|
||
// Expect a read activate when re-enabling since the file ready cb has not done a read. | ||
EXPECT_CALL(*file_event_, setEnabled(_)); | ||
EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Read)); | ||
connection_->readDisable(false); | ||
|
||
// Do a read to clear the want_read_ flag, verify that no read activation is scheduled. | ||
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 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. Nice catch. |
||
EXPECT_CALL(*transport_socket_, doRead(_)) | ||
.WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false})); | ||
file_ready_cb_(Event::FileReadyType::Read); | ||
EXPECT_CALL(*file_event_, setEnabled(_)); | ||
connection_->readDisable(true); | ||
EXPECT_CALL(*file_event_, setEnabled(_)); | ||
// No read activate call. | ||
EXPECT_CALL(*file_event_, activate(_)).Times(0); | ||
connection_->readDisable(false); | ||
} | ||
|
||
// Verify that read resumption is scheduled when read is re-enabled while the read buffer is | ||
// non-empty. | ||
TEST_F(MockTransportConnectionImplTest, ReadBufferResumeAfterReadDisable) { | ||
InSequence s; | ||
|
||
std::shared_ptr<MockReadFilter> read_filter(new StrictMock<MockReadFilter>()); | ||
connection_->setBufferLimits(5); | ||
connection_->enableHalfClose(true); | ||
connection_->addReadFilter(read_filter); | ||
|
||
// Add some data to the read buffer to trigger read activate calls when re-enabling read. | ||
EXPECT_CALL(*transport_socket_, doRead(_)) | ||
.WillOnce(Invoke([](Buffer::Instance& buffer) -> IoResult { | ||
buffer.add("0123456789"); | ||
return {PostIoAction::KeepOpen, 10, false}; | ||
})); | ||
// Expect a change to the event mask when hitting the read buffer high-watermark. | ||
EXPECT_CALL(*file_event_, setEnabled(Event::FileReadyType::Write)); | ||
EXPECT_CALL(*read_filter, onNewConnection()).WillOnce(Return(FilterStatus::Continue)); | ||
EXPECT_CALL(*read_filter, onData(_, false)).WillOnce(Return(FilterStatus::Continue)); | ||
file_ready_cb_(Event::FileReadyType::Read); | ||
|
||
// Already read disabled, expect no changes to enabled events mask. | ||
EXPECT_CALL(*file_event_, setEnabled(_)).Times(0); | ||
connection_->readDisable(true); | ||
connection_->readDisable(true); | ||
connection_->readDisable(false); | ||
// Read buffer is at the high watermark so read_disable_count should be == 1. Expect a read | ||
// activate but no call to setEnable to change the registration mask. | ||
EXPECT_CALL(*file_event_, setEnabled(_)).Times(0); | ||
EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Read)); | ||
connection_->readDisable(false); | ||
|
||
// Invoke the file event cb while read_disable_count_ == 1 to partially drain the read buffer. | ||
// Expect no transport reads. | ||
EXPECT_CALL(*transport_socket_, doRead(_)).Times(0); | ||
EXPECT_CALL(*read_filter, onData(_, _)) | ||
.WillRepeatedly(Invoke([&](Buffer::Instance& data, bool) -> FilterStatus { | ||
EXPECT_EQ(10, data.length()); | ||
data.drain(data.length() - 1); | ||
return FilterStatus::Continue; | ||
})); | ||
// Partial drain of the read buffer below low watermark triggers an update to the fd enabled mask | ||
// and a read activate since the read buffer is not empty. | ||
EXPECT_CALL(*file_event_, setEnabled(Event::FileReadyType::Read | Event::FileReadyType::Write)); | ||
EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Read)); | ||
file_ready_cb_(Event::FileReadyType::Read); | ||
|
||
// Drain the rest of the buffer and verify there are no spurious read activate calls. | ||
EXPECT_CALL(*transport_socket_, doRead(_)) | ||
.WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false})); | ||
EXPECT_CALL(*read_filter, onData(_, _)) | ||
.WillRepeatedly(Invoke([&](Buffer::Instance& data, bool) -> FilterStatus { | ||
EXPECT_EQ(1, data.length()); | ||
data.drain(1); | ||
return FilterStatus::Continue; | ||
})); | ||
file_ready_cb_(Event::FileReadyType::Read); | ||
|
||
EXPECT_CALL(*file_event_, setEnabled(_)); | ||
connection_->readDisable(true); | ||
EXPECT_CALL(*file_event_, setEnabled(_)); | ||
// read buffer is empty, no read activate call. | ||
EXPECT_CALL(*file_event_, activate(_)).Times(0); | ||
connection_->readDisable(false); | ||
} | ||
|
||
// Verify that want_read_ read resumption is not lost when processing read buffer high-watermark | ||
// resumptions. | ||
TEST_F(MockTransportConnectionImplTest, ResumeWhileAndAfterReadDisable) { | ||
InSequence s; | ||
|
||
std::shared_ptr<MockReadFilter> read_filter(new StrictMock<MockReadFilter>()); | ||
connection_->setBufferLimits(5); | ||
connection_->enableHalfClose(true); | ||
connection_->addReadFilter(read_filter); | ||
|
||
// Add some data to the read buffer and also call setReadBufferReady to mimic what transport | ||
// sockets are expected to do when the read buffer high watermark is hit. | ||
EXPECT_CALL(*transport_socket_, doRead(_)) | ||
.WillOnce(Invoke([this](Buffer::Instance& buffer) -> IoResult { | ||
buffer.add("0123456789"); | ||
connection_->setReadBufferReady(); | ||
return {PostIoAction::KeepOpen, 10, false}; | ||
})); | ||
// Expect a change to the event mask when hitting the read buffer high-watermark. | ||
EXPECT_CALL(*file_event_, setEnabled(Event::FileReadyType::Write)); | ||
// The setReadBufferReady call adds a spurious read activation. | ||
// TODO(antoniovicente) Skip the read activate in setReadBufferReady when read_disable_count_ > 0. | ||
EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Read)); | ||
EXPECT_CALL(*read_filter, onNewConnection()).WillOnce(Return(FilterStatus::Continue)); | ||
EXPECT_CALL(*read_filter, onData(_, false)).WillOnce(Return(FilterStatus::Continue)); | ||
file_ready_cb_(Event::FileReadyType::Read); | ||
|
||
// Already read disabled, expect no changes to enabled events mask. | ||
EXPECT_CALL(*file_event_, setEnabled(_)).Times(0); | ||
connection_->readDisable(true); | ||
connection_->readDisable(true); | ||
connection_->readDisable(false); | ||
// Read buffer is at the high watermark so read_disable_count should be == 1. Expect a read | ||
// activate but no call to setEnable to change the registration mask. | ||
EXPECT_CALL(*file_event_, setEnabled(_)).Times(0); | ||
EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Read)); | ||
connection_->readDisable(false); | ||
|
||
// Invoke the file event cb while read_disable_count_ == 1 and fully drain the read buffer. | ||
// Expect no transport reads. Expect a read resumption due to want_read_ being true when read is | ||
// re-enabled due to going under the low watermark. | ||
EXPECT_CALL(*transport_socket_, doRead(_)).Times(0); | ||
EXPECT_CALL(*read_filter, onData(_, _)) | ||
.WillRepeatedly(Invoke([&](Buffer::Instance& data, bool) -> FilterStatus { | ||
EXPECT_EQ(10, data.length()); | ||
data.drain(data.length()); | ||
return FilterStatus::Continue; | ||
})); | ||
// The buffer is fully drained. Expect a read activation because setReadBufferReady set want_read_ | ||
// and no transport doRead calls have happened. | ||
EXPECT_CALL(*file_event_, setEnabled(Event::FileReadyType::Read | Event::FileReadyType::Write)); | ||
EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Read)); | ||
file_ready_cb_(Event::FileReadyType::Read); | ||
|
||
EXPECT_CALL(*transport_socket_, doRead(_)) | ||
.WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false})); | ||
file_ready_cb_(Event::FileReadyType::Read); | ||
|
||
// Verify there are no read activate calls the event callback does a transport read and clears the | ||
// want_read_ state. | ||
EXPECT_CALL(*file_event_, setEnabled(_)); | ||
connection_->readDisable(true); | ||
EXPECT_CALL(*file_event_, setEnabled(_)); | ||
EXPECT_CALL(*file_event_, activate(_)).Times(0); | ||
connection_->readDisable(false); | ||
} | ||
|
||
// Test that BytesSentCb is invoked at the correct times | ||
TEST_F(MockTransportConnectionImplTest, BytesSentCallback) { | ||
uint64_t bytes_sent = 0; | ||
|
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.
Stupid question: why do we bother with this extra logic to read when the disable count is still 1 and high watermark triggered? Is the idea that we already have the data so we might as well flush it? If the high watermark triggered it's probably going to be stuck on the other side, so mostly just wondering if this extra logic is actually worth it.
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.
This is part of the implementation of high-watermarks on the read buffer introduced in PR #11170
The parser is able to consume input from the read buffer, we just don't want to read additional bytes from the transport into the read buffer.
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 the idea here is that we allow reading data out in the hope that it will get us below the low watermark? OK thanks that makes sense.
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.
Yes.
The common scenario is when the H1 parser stops consuming bytes from the read buffer and calls readDisable(true) once it detects the end of the current request message. When request processing is complete, the H1 parser re-enables read. When H1 pipelining is in use, a full second request message could be in the read buffer and the read buffer may have triggered the high-watermark.
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.
Make sense, though note that we don't do pipelining.