Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions source/common/network/connection_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,15 @@ void ConnectionImpl::raiseEvent(ConnectionEvent event) {
// connected events.
callback->onEvent(event);
}
// We may have pending data in the write buffer on transport handshake
// completion, which may also have completed in the context of onReadReady(),
// where no check of the write buffer is made. Provide an opportunity to flush
// here. If connection write is not ready, this is harmless. We should only do
// this if we're still open (the above callbacks may have closed).
if (state() == State::Open && event == ConnectionEvent::Connected &&
Copy link
Member

Choose a reason for hiding this comment

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

@htuch @ggreenway I'm late here, but I think it's slightly strange that in the plaintext case we effectively recurse into onWriteReady() which already handles this case by trying to write after a connection happens. Did we consider any other variant of this change? It's not a big deal just curious.

Copy link
Member

Choose a reason for hiding this comment

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

That is weird; I didn't catch that. No, we didn't try anything else. We should look into if there's a better way to handle this. It seems to work, but it is strange, and hard to reason about.

Copy link
Member Author

Choose a reason for hiding this comment

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

It works because we flip connecting_ status before re-entry. I agree this is harder to reason about than would be ideal. Should we just return unconditionally in raw buffer socket onWriteReady() following a new connection and defer to the new write check?

Copy link
Member

Choose a reason for hiding this comment

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

@htuch agreed the code works as is. To make it clearer yes I would either return unconditionally with a comment here https://github.com/envoyproxy/envoy/blob/master/source/common/network/connection_impl.cc#L477 or another option is to factor out the 2nd part of onWriteReady() into a separate function and call that in the SSL case when handshake completes during a read callback. I think the former is simpler IMO.

write_buffer_->length() > 0) {
onWriteReady();
}
}

bool ConnectionImpl::readEnabled() const { return read_enabled_; }
Expand Down
34 changes: 33 additions & 1 deletion test/common/network/connection_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,10 @@ class MockTransportConnectionImplTest : public testing::Test {
EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _))
.WillOnce(DoAll(SaveArg<1>(&file_ready_cb_), Return(file_event_)));
transport_socket_ = new NiceMock<MockTransportSocket>;
EXPECT_CALL(*transport_socket_, setTransportSocketCallbacks(_))
.WillOnce(Invoke([this](TransportSocketCallbacks& callbacks) {
transport_socket_callbacks_ = &callbacks;
}));
connection_.reset(
new ConnectionImpl(dispatcher_, std::make_unique<ConnectionSocketImpl>(0, nullptr, nullptr),
TransportSocketPtr(transport_socket_), true));
Expand All @@ -861,9 +865,10 @@ class MockTransportConnectionImplTest : public testing::Test {
std::unique_ptr<ConnectionImpl> connection_;
Event::MockDispatcher dispatcher_;
NiceMock<MockConnectionCallbacks> callbacks_;
NiceMock<MockTransportSocket>* transport_socket_;
MockTransportSocket* transport_socket_;
Event::MockFileEvent* file_event_;
Event::FileReadyCb file_ready_cb_;
TransportSocketCallbacks* transport_socket_callbacks_;
};

// Test that BytesSentCb is invoked at the correct times
Expand Down Expand Up @@ -1122,6 +1127,33 @@ TEST_F(MockTransportConnectionImplTest, WriteEndStreamStopIteration) {
connection_->write(buffer, true);
}

// Validate that when the transport signals ConnectionEvent::Connected, that we
// check for pending write buffer content.
TEST_F(MockTransportConnectionImplTest, WriteReadyOnConnected) {
InSequence s;

// Queue up some data in write buffer, simulating what happens in SSL handshake.
const std::string val("some data");
Buffer::OwnedImpl buffer(val);
EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Write)).WillOnce(Invoke(file_ready_cb_));
EXPECT_CALL(*transport_socket_, doWrite(BufferStringEqual(val), false))
.WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false}));
connection_->write(buffer, false);

// A read event happens, resulting in handshake completion and
// raiseEvent(Network::ConnectionEvent::Connected). Since we have data queued
// in the write buffer, we should see a doWrite with this data.
EXPECT_CALL(*transport_socket_, doRead(_)).WillOnce(InvokeWithoutArgs([this] {
transport_socket_callbacks_->raiseEvent(Network::ConnectionEvent::Connected);
return IoResult{PostIoAction::KeepOpen, 0, false};
}));
EXPECT_CALL(*transport_socket_, doWrite(BufferStringEqual(val), false))
.WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false}));
file_ready_cb_(Event::FileReadyType::Read);
EXPECT_CALL(*transport_socket_, doWrite(_, true))
.WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, true}));
}

class ReadBufferLimitTest : public ConnectionImplTest {
public:
void readBufferLimitTest(uint32_t read_buffer_limit, uint32_t expected_chunk_size) {
Expand Down