-
Notifications
You must be signed in to change notification settings - Fork 5.5k
connection: Remember transport socket read resumption requests and replay them when re-enabling read. #13772
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 2 commits
3d4555a
d949e38
de9fce0
7d7f311
6647411
b9d8ac8
5776193
4298807
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 |
|---|---|---|
|
|
@@ -57,7 +57,8 @@ ConnectionImpl::ConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPt | |
| []() -> void { /* TODO(adisuissa): Handle overflow watermark */ })), | ||
| write_buffer_above_high_watermark_(false), detect_early_close_(true), | ||
| enable_half_close_(false), read_end_stream_raised_(false), read_end_stream_(false), | ||
| write_end_stream_(false), current_write_end_stream_(false), dispatch_buffered_data_(false) { | ||
| write_end_stream_(false), current_write_end_stream_(false), dispatch_buffered_data_(false), | ||
| want_read_(false) { | ||
|
|
||
| if (!connected) { | ||
| connecting_ = true; | ||
|
|
@@ -349,13 +350,16 @@ void ConnectionImpl::readDisable(bool disable) { | |
| file_event_->setEnabled(Event::FileReadyType::Read | Event::FileReadyType::Write); | ||
| } | ||
|
|
||
| if (consumerWantsToRead() && read_buffer_.length() > 0) { | ||
| // If the connection has data buffered there's no guarantee there's also data in the kernel | ||
| // which will 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 gets | ||
| // processed regardless and ensure that we dispatch it via onRead. | ||
| if ((consumerWantsToRead() && read_buffer_.length() > 0) || | ||
| (read_disable_count_ == 0 && want_read_)) { | ||
| // If the read_buffer_ is not empty or want_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. | ||
| dispatch_buffered_data_ = true; | ||
| setReadBufferReady(); | ||
| file_event_->activate(Event::FileReadyType::Read); | ||
|
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. For clarity I might revert this change, even though it redundantly sets want_read_, but nbd either way. Up to you.
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. Reverted. |
||
| } | ||
| } | ||
| } | ||
|
|
@@ -554,6 +558,11 @@ void ConnectionImpl::onReadReady() { | |
| return; | ||
| } | ||
|
|
||
| // Clear want_read_ just before the call to doRead. This is the only way to ensure that the | ||
| // transport socket read resumption happens as requested; onReadReady() returns early without | ||
| // reading from the transport if the read buffer is above high watermark at the start of the | ||
| // method. | ||
| want_read_ = false; | ||
|
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. should we also set this false for the early return onRead above? If not please comment.
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. Commented. MockTransportConnectionImplTest.ResumeWhileAndAfterReadDisable covers the early return case which requires us to clear want_read_ just before doRead.
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. Sorry how is want_read_ cleared before onRead above? Maybe update the comment above also as to why we don't need to clear or how it's already cleared?
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. Added comment. |
||
| IoResult result = transport_socket_->doRead(read_buffer_); | ||
| uint64_t new_buffer_size = read_buffer_.length(); | ||
| updateReadBufferStats(result.bytes_processed_, new_buffer_size); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1796,6 +1796,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. | ||
|
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.
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. 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; | ||
|
|
||
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.
Sorry one other question from another look: is it possible to somehow fold all of this logic into
consumerWantsToRead()? It seems kind of odd that we have a function that is tracking the consumer wants to read and then a different wants to read boolean. If it's too hard to merge them maybe some better names for both the function and the boolean?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.
Tried to improve on names and the if predicate.