/** * Copyright (C) 2022-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/db/repl/delayable_timeout_callback.h" #include "mongo/executor/thread_pool_task_executor_test_fixture.h" namespace mongo { namespace repl { template class DelayableTimeoutCallbackBaseTest : public unittest::Test { protected: void setUp() override { auto network = std::make_unique(); _net = network.get(); _executor = makeSharedThreadPoolTestExecutor(std::move(network)); _executor->startup(); createDelayableTimeoutCallback(); } void tearDown() override { _delayableTimeoutCallback = boost::none; _executor->shutdown(); _executor->join(); _executor.reset(); } void callback(const mongo::executor::TaskExecutor::CallbackArgs& cbData) { callbackRan++; if (!cbData.status.isOK()) { callbackRanWithError++; } } void createDelayableTimeoutCallback() { MONGO_UNREACHABLE; } protected: boost::optional _delayableTimeoutCallback; executor::NetworkInterfaceMock* _net; std::shared_ptr _executor; int callbackRan = 0; int callbackRanWithError = 0; }; template <> void DelayableTimeoutCallbackBaseTest::createDelayableTimeoutCallback() { _delayableTimeoutCallback.emplace( _executor.get(), [this](const mongo::executor::TaskExecutor::CallbackArgs& cbData) { this->callback(cbData); }); } template <> void DelayableTimeoutCallbackBaseTest< DelayableTimeoutCallbackWithJitter>::createDelayableTimeoutCallback() { _delayableTimeoutCallback.emplace( _executor.get(), [this](const mongo::executor::TaskExecutor::CallbackArgs& cbData) { this->callback(cbData); }, [](int64_t limit) { static int64_t notVeryRandom = 0; notVeryRandom += 10; return notVeryRandom % limit; }); } typedef DelayableTimeoutCallbackBaseTest DelayableTimeoutCallbackTest; typedef DelayableTimeoutCallbackBaseTest DelayableTimeoutCallbackWithJitterTest; TEST_F(DelayableTimeoutCallbackTest, ScheduleAtSchedulesFirstCallback) { executor::NetworkInterfaceMock::InNetworkGuard guard(_net); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); ASSERT_OK(_delayableTimeoutCallback->scheduleAt(_net->now() + Seconds(2))); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_EQ(1, callbackRan); ASSERT_EQ(0, callbackRanWithError); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); } TEST_F(DelayableTimeoutCallbackTest, DelayUntilSchedulesFirstCallback) { executor::NetworkInterfaceMock::InNetworkGuard guard(_net); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); ASSERT_OK(_delayableTimeoutCallback->delayUntil(_net->now() + Seconds(2))); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_EQ(1, callbackRan); ASSERT_EQ(0, callbackRanWithError); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); } TEST_F(DelayableTimeoutCallbackTest, ScheduleAtMovesCallbackLater) { executor::NetworkInterfaceMock::InNetworkGuard guard(_net); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); ASSERT_OK(_delayableTimeoutCallback->scheduleAt(_net->now() + Seconds(2))); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); ASSERT_OK(_delayableTimeoutCallback->scheduleAt(_net->now() + Seconds(2))); _net->runUntil(_net->now() + Seconds(1)); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_EQ(1, callbackRan); ASSERT_EQ(0, callbackRanWithError); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); } TEST_F(DelayableTimeoutCallbackTest, DelayUntilMovesCallbackLater) { executor::NetworkInterfaceMock::InNetworkGuard guard(_net); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); ASSERT_OK(_delayableTimeoutCallback->delayUntil(_net->now() + Seconds(2))); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); ASSERT_OK(_delayableTimeoutCallback->delayUntil(_net->now() + Seconds(2))); _net->runUntil(_net->now() + Seconds(1)); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_EQ(1, callbackRan); ASSERT_EQ(0, callbackRanWithError); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); } TEST_F(DelayableTimeoutCallbackTest, ScheduleAtMovesCallbackEarlier) { executor::NetworkInterfaceMock::InNetworkGuard guard(_net); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); ASSERT_OK(_delayableTimeoutCallback->scheduleAt(_net->now() + Seconds(3))); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); ASSERT_OK(_delayableTimeoutCallback->scheduleAt(_net->now() + Seconds(1))); _net->runUntil(_net->now() + Seconds(1)); ASSERT_EQ(1, callbackRan); ASSERT_EQ(0, callbackRanWithError); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); } TEST_F(DelayableTimeoutCallbackTest, DelayUntilDoesNotMoveCallbackEarlier) { executor::NetworkInterfaceMock::InNetworkGuard guard(_net); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); ASSERT_OK(_delayableTimeoutCallback->delayUntil(_net->now() + Seconds(3))); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); ASSERT_OK(_delayableTimeoutCallback->delayUntil(_net->now() + Seconds(1))); _net->runUntil(_net->now() + Seconds(1)); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_EQ(1, callbackRan); ASSERT_EQ(0, callbackRanWithError); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); } TEST_F(DelayableTimeoutCallbackTest, ScheduleAtInPastRunsImmediately) { executor::NetworkInterfaceMock::InNetworkGuard guard(_net); // Make sure there's a past to schedule in. _net->runUntil(_net->now() + Days(1)); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); ASSERT_OK(_delayableTimeoutCallback->scheduleAt(_net->now() - Seconds(1))); // Needed to trigger anything scheduled. _net->runReadyNetworkOperations(); ASSERT_EQ(1, callbackRan); ASSERT_EQ(0, callbackRanWithError); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); } TEST_F(DelayableTimeoutCallbackTest, DelayUntilInPastRunsImmediately) { executor::NetworkInterfaceMock::InNetworkGuard guard(_net); // Make sure there's a past to schedule in. _net->runUntil(_net->now() + Days(1)); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); ASSERT_OK(_delayableTimeoutCallback->delayUntil(_net->now() - Seconds(1))); // Needed to trigger anything scheduled. _net->runReadyNetworkOperations(); ASSERT_EQ(1, callbackRan); ASSERT_EQ(0, callbackRanWithError); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); } TEST_F(DelayableTimeoutCallbackTest, Cancellation) { executor::NetworkInterfaceMock::InNetworkGuard guard(_net); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); ASSERT_OK(_delayableTimeoutCallback->scheduleAt(_net->now() + Seconds(2))); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); _delayableTimeoutCallback->cancel(); ASSERT_EQ(0, callbackRan); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); _net->runUntil(_net->now() + Seconds(1)); ASSERT_EQ(0, callbackRan); ASSERT_EQ(0, callbackRanWithError); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); } TEST_F(DelayableTimeoutCallbackTest, Shutdown) { executor::NetworkInterfaceMock::InNetworkGuard guard(_net); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); ASSERT_OK(_delayableTimeoutCallback->scheduleAt(_net->now() + Seconds(2))); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Seconds(1)); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); _executor->shutdown(); // This makes sure the executor processes the shutdown. _net->runReadyNetworkOperations(); ASSERT_EQ(0, callbackRan); ASSERT_EQ(0, callbackRanWithError); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); } TEST_F(DelayableTimeoutCallbackWithJitterTest, DelayUntilWithJitter) { executor::NetworkInterfaceMock::InNetworkGuard guard(_net); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); ASSERT_OK(_delayableTimeoutCallback->delayUntilWithJitter(_net->now() + Seconds(10), Milliseconds(100))); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); // Our "random" generator is just 10,20,30,... ASSERT_EQ(_net->now() + Milliseconds(10010), _delayableTimeoutCallback->getNextCall()); ASSERT_EQ(0, callbackRan); // Setting it again in the same time shouldn't change jitter. ASSERT_OK(_delayableTimeoutCallback->delayUntilWithJitter(_net->now() + Seconds(10), Milliseconds(100))); ASSERT_EQ(_net->now() + Milliseconds(10010), _delayableTimeoutCallback->getNextCall()); ASSERT_EQ(0, callbackRan); // Move forward less than the max jitter shouldn't change jitter. for (int i = 0; i < 3; i++) { _net->runUntil(_net->now() + Milliseconds(25)); ASSERT_OK(_delayableTimeoutCallback->delayUntilWithJitter(_net->now() + Seconds(10), Milliseconds(100))); ASSERT_EQ(_net->now() + Milliseconds(10010), _delayableTimeoutCallback->getNextCall()); ASSERT_EQ(0, callbackRan); } // Move forward to the max jitter should recalculate jitter. _net->runUntil(_net->now() + Milliseconds(25)); ASSERT_OK(_delayableTimeoutCallback->delayUntilWithJitter(_net->now() + Seconds(10), Milliseconds(100))); ASSERT_EQ(_net->now() + Milliseconds(10020), _delayableTimeoutCallback->getNextCall()); ASSERT_EQ(0, callbackRan); // Setting max jitter to less than actual jitter should recalculate jitter. ASSERT_OK(_delayableTimeoutCallback->delayUntilWithJitter(_net->now() + Seconds(10), Milliseconds(19))); // Jitter value will be 30 % 19 = 11 ASSERT_EQ(_net->now() + Milliseconds(10011), _delayableTimeoutCallback->getNextCall()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Milliseconds(10011)); ASSERT_EQ(1, callbackRan); ASSERT_EQ(0, callbackRanWithError); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); } TEST_F(DelayableTimeoutCallbackWithJitterTest, DelayUntilWithZeroJitter) { executor::NetworkInterfaceMock::InNetworkGuard guard(_net); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); ASSERT_OK( _delayableTimeoutCallback->delayUntilWithJitter(_net->now() + Seconds(10), Seconds(0))); ASSERT_TRUE(_delayableTimeoutCallback->isActive()); ASSERT_EQ(_net->now() + Milliseconds(10000), _delayableTimeoutCallback->getNextCall()); ASSERT_EQ(0, callbackRan); _net->runUntil(_net->now() + Milliseconds(10000)); ASSERT_EQ(1, callbackRan); ASSERT_EQ(0, callbackRanWithError); ASSERT_FALSE(_delayableTimeoutCallback->isActive()); } } // namespace repl } // namespace mongo