mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-06 22:22:38 +03:00
Improve transport close handling and observability
Summary: The current `close` observer event marks when `closeImpl` is called. However, the actual close of the socket may be delayed for some time while the socket drains. I've changed the existing event to `closeStarted` and added a new event `closing` that marks the close of the underlying `AsyncUDPSocket`. Adding the `closeStarted` event required two other changes which are difficult to separate from this diff: - When a transport is destroyed, `QuicTransportBase` was calling `closeImpl` and also closing the UDP socket. However, because the `folly::ObserverContainer` used to store observers is maintained in classes that derive from `QuicTransportBase`, the observers are gone by the time the UDP socket is closed in the base class destructor. Thus, the UDP socket should be closed by the derived classes in their respective destructors. This requirement is inline with the existing code: `closeImpl` is called by all derived classes in their destructors. Made this change and added `DCHECK` statements in the `QuicTransportBase` destructor to ensure that derived classes cleanup after themselves. - Writing tests with draining enabled and disabled required being able to set the transport settings. However, all of the existing `QuicTypedTransportTest` test cases were designed to operate after the connection was accepted (for the server impls) or established (for client impls), and transport settings cannot be updated at this state. Resolving this required adding new test classes in which the accept/connect operation is delayed until requested by the test. Reviewed By: mjoras Differential Revision: D39249604 fbshipit-source-id: 0ebf8b719c4d3b01d4f9509cf2b9a4fc72c2e737
This commit is contained in:
committed by
Facebook GitHub Bot
parent
ced772c63f
commit
5b8e0de5bd
@@ -247,11 +247,14 @@ class TestQuicTransport
|
||||
~TestQuicTransport() override {
|
||||
resetConnectionCallbacks();
|
||||
// we need to call close in the derived class.
|
||||
resetConnectionCallbacks();
|
||||
closeImpl(
|
||||
QuicError(
|
||||
QuicErrorCode(LocalErrorCode::SHUTTING_DOWN),
|
||||
std::string("shutdown")),
|
||||
false);
|
||||
false /* drainConnection */);
|
||||
// closeImpl may have been called earlier with drain = true, so force close.
|
||||
closeUdpSocket();
|
||||
}
|
||||
|
||||
std::chrono::milliseconds getLossTimeoutRemainingTime() const {
|
||||
@@ -3703,7 +3706,8 @@ TEST_P(QuicTransportImplTestBase, ObserverDestroy) {
|
||||
transport->addObserver(cb.get());
|
||||
EXPECT_THAT(transport->getObservers(), UnorderedElementsAre(cb.get()));
|
||||
InSequence s;
|
||||
EXPECT_CALL(*cb, close(transport.get(), _));
|
||||
EXPECT_CALL(*cb, closeStarted(transport.get(), _));
|
||||
EXPECT_CALL(*cb, closing(transport.get(), _));
|
||||
EXPECT_CALL(*cb, destroy(transport.get()));
|
||||
transport = nullptr;
|
||||
Mock::VerifyAndClearExpectations(cb.get());
|
||||
@@ -3732,7 +3736,8 @@ TEST_P(QuicTransportImplTestBase, ObserverSharedPtrDestroy) {
|
||||
transport->addObserver(cb);
|
||||
EXPECT_THAT(transport->getObservers(), UnorderedElementsAre(cb.get()));
|
||||
InSequence s;
|
||||
EXPECT_CALL(*cb, close(transport.get(), _));
|
||||
EXPECT_CALL(*cb, closeStarted(transport.get(), _));
|
||||
EXPECT_CALL(*cb, closing(transport.get(), _));
|
||||
EXPECT_CALL(*cb, destroy(transport.get()));
|
||||
transport = nullptr;
|
||||
Mock::VerifyAndClearExpectations(cb.get());
|
||||
@@ -3752,7 +3757,8 @@ TEST_P(QuicTransportImplTestBase, ObserverSharedPtrReleasedDestroy) {
|
||||
EXPECT_FALSE(dc.destroyed()); // should still exist
|
||||
|
||||
InSequence s;
|
||||
EXPECT_CALL(*cbRaw, close(transport.get(), _));
|
||||
EXPECT_CALL(*cbRaw, closeStarted(transport.get(), _));
|
||||
EXPECT_CALL(*cbRaw, closing(transport.get(), _));
|
||||
EXPECT_CALL(*cbRaw, destroy(transport.get()));
|
||||
transport = nullptr;
|
||||
Mock::VerifyAndClearExpectations(cb.get());
|
||||
@@ -3764,44 +3770,6 @@ TEST_P(QuicTransportImplTestBase, ObserverSharedPtrRemoveMissing) {
|
||||
EXPECT_THAT(transport->getObservers(), IsEmpty());
|
||||
}
|
||||
|
||||
TEST_P(QuicTransportImplTestBase, ObserverCloseNoErrorThenDestroy) {
|
||||
auto cb = std::make_unique<StrictMock<MockLegacyObserver>>();
|
||||
EXPECT_CALL(*cb, observerAttach(transport.get()));
|
||||
transport->addObserver(cb.get());
|
||||
EXPECT_THAT(transport->getObservers(), UnorderedElementsAre(cb.get()));
|
||||
|
||||
const QuicError defaultError = QuicError(
|
||||
GenericApplicationErrorCode::NO_ERROR,
|
||||
toString(GenericApplicationErrorCode::NO_ERROR));
|
||||
EXPECT_CALL(
|
||||
*cb, close(transport.get(), folly::Optional<QuicError>(defaultError)));
|
||||
transport->close(folly::none);
|
||||
Mock::VerifyAndClearExpectations(cb.get());
|
||||
InSequence s;
|
||||
EXPECT_CALL(*cb, destroy(transport.get()));
|
||||
transport = nullptr;
|
||||
Mock::VerifyAndClearExpectations(cb.get());
|
||||
}
|
||||
|
||||
TEST_P(QuicTransportImplTestBase, ObserverCloseWithErrorThenDestroy) {
|
||||
auto cb = std::make_unique<StrictMock<MockLegacyObserver>>();
|
||||
EXPECT_CALL(*cb, observerAttach(transport.get()));
|
||||
transport->addObserver(cb.get());
|
||||
EXPECT_THAT(transport->getObservers(), UnorderedElementsAre(cb.get()));
|
||||
|
||||
const auto testError = QuicError(
|
||||
QuicErrorCode(LocalErrorCode::CONNECTION_RESET),
|
||||
std::string("testError"));
|
||||
EXPECT_CALL(
|
||||
*cb, close(transport.get(), folly::Optional<QuicError>(testError)));
|
||||
transport->close(testError);
|
||||
Mock::VerifyAndClearExpectations(cb.get());
|
||||
InSequence s;
|
||||
EXPECT_CALL(*cb, destroy(transport.get()));
|
||||
transport = nullptr;
|
||||
Mock::VerifyAndClearExpectations(cb.get());
|
||||
}
|
||||
|
||||
TEST_P(QuicTransportImplTestBase, ObserverDetachImmediately) {
|
||||
auto cb = std::make_unique<StrictMock<MockLegacyObserver>>();
|
||||
EXPECT_CALL(*cb, observerAttach(transport.get()));
|
||||
@@ -3815,12 +3783,20 @@ TEST_P(QuicTransportImplTestBase, ObserverDetachImmediately) {
|
||||
}
|
||||
|
||||
TEST_P(QuicTransportImplTestBase, ObserverDetachAfterClose) {
|
||||
// disable draining to ensure closing() event occurs immediately after close()
|
||||
{
|
||||
auto transportSettings = transport->getTransportSettings();
|
||||
transportSettings.shouldDrain = false;
|
||||
transport->setTransportSettings(transportSettings);
|
||||
}
|
||||
|
||||
auto cb = std::make_unique<StrictMock<MockLegacyObserver>>();
|
||||
EXPECT_CALL(*cb, observerAttach(transport.get()));
|
||||
transport->addObserver(cb.get());
|
||||
EXPECT_THAT(transport->getObservers(), UnorderedElementsAre(cb.get()));
|
||||
|
||||
EXPECT_CALL(*cb, close(transport.get(), _));
|
||||
EXPECT_CALL(*cb, closeStarted(transport.get(), _));
|
||||
EXPECT_CALL(*cb, closing(transport.get(), _));
|
||||
transport->close(folly::none);
|
||||
Mock::VerifyAndClearExpectations(cb.get());
|
||||
|
||||
@@ -3830,14 +3806,33 @@ TEST_P(QuicTransportImplTestBase, ObserverDetachAfterClose) {
|
||||
EXPECT_THAT(transport->getObservers(), IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(QuicTransportImplTest, ObserverDetachOnCloseDuringDestroy) {
|
||||
TEST_F(QuicTransportImplTest, ObserverDetachOnCloseStartedDuringDestroy) {
|
||||
auto cb = std::make_unique<StrictMock<MockLegacyObserver>>();
|
||||
EXPECT_CALL(*cb, observerAttach(transport.get()));
|
||||
transport->addObserver(cb.get());
|
||||
EXPECT_THAT(transport->getObservers(), UnorderedElementsAre(cb.get()));
|
||||
|
||||
InSequence s;
|
||||
EXPECT_CALL(*cb, close(transport.get(), _))
|
||||
|
||||
EXPECT_CALL(*cb, closeStarted(transport.get(), _))
|
||||
.WillOnce(Invoke([&cb](auto callbackTransport, auto /* errorOpt */) {
|
||||
EXPECT_TRUE(callbackTransport->removeObserver(cb.get()));
|
||||
}));
|
||||
EXPECT_CALL(*cb, observerDetach(transport.get()));
|
||||
transport = nullptr;
|
||||
Mock::VerifyAndClearExpectations(cb.get());
|
||||
}
|
||||
|
||||
TEST_F(QuicTransportImplTest, ObserverDetachOnClosingDuringDestroy) {
|
||||
auto cb = std::make_unique<StrictMock<MockLegacyObserver>>();
|
||||
EXPECT_CALL(*cb, observerAttach(transport.get()));
|
||||
transport->addObserver(cb.get());
|
||||
EXPECT_THAT(transport->getObservers(), UnorderedElementsAre(cb.get()));
|
||||
|
||||
InSequence s;
|
||||
|
||||
EXPECT_CALL(*cb, closeStarted(transport.get(), _));
|
||||
EXPECT_CALL(*cb, closing(transport.get(), _))
|
||||
.WillOnce(Invoke([&cb](auto callbackTransport, auto /* errorOpt */) {
|
||||
EXPECT_TRUE(callbackTransport->removeObserver(cb.get()));
|
||||
}));
|
||||
@@ -3938,8 +3933,10 @@ TEST_P(QuicTransportImplTestBase, ObserverMultipleAttachDestroy) {
|
||||
transport->getObservers(), UnorderedElementsAre(cb1.get(), cb2.get()));
|
||||
|
||||
InSequence s;
|
||||
EXPECT_CALL(*cb1, close(transport.get(), _));
|
||||
EXPECT_CALL(*cb2, close(transport.get(), _));
|
||||
EXPECT_CALL(*cb1, closeStarted(transport.get(), _));
|
||||
EXPECT_CALL(*cb2, closeStarted(transport.get(), _));
|
||||
EXPECT_CALL(*cb1, closing(transport.get(), _));
|
||||
EXPECT_CALL(*cb2, closing(transport.get(), _));
|
||||
EXPECT_CALL(*cb1, destroy(transport.get()));
|
||||
EXPECT_CALL(*cb2, destroy(transport.get()));
|
||||
transport = nullptr;
|
||||
|
Reference in New Issue
Block a user