summaryrefslogtreecommitdiff
path: root/src/mongo/db/repl/delayable_timeout_callback_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/repl/delayable_timeout_callback_test.cpp')
-rw-r--r--src/mongo/db/repl/delayable_timeout_callback_test.cpp360
1 files changed, 360 insertions, 0 deletions
diff --git a/src/mongo/db/repl/delayable_timeout_callback_test.cpp b/src/mongo/db/repl/delayable_timeout_callback_test.cpp
new file mode 100644
index 00000000000..b293d2793bb
--- /dev/null
+++ b/src/mongo/db/repl/delayable_timeout_callback_test.cpp
@@ -0,0 +1,360 @@
+/**
+ * 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
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 <typename T>
+class DelayableTimeoutCallbackBaseTest : public unittest::Test {
+protected:
+ void setUp() override {
+ auto network = std::make_unique<executor::NetworkInterfaceMock>();
+ _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<T> _delayableTimeoutCallback;
+ executor::NetworkInterfaceMock* _net;
+ std::shared_ptr<executor::TaskExecutor> _executor;
+ int callbackRan = 0;
+ int callbackRanWithError = 0;
+};
+
+template <>
+void DelayableTimeoutCallbackBaseTest<DelayableTimeoutCallback>::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<DelayableTimeoutCallback> DelayableTimeoutCallbackTest;
+typedef DelayableTimeoutCallbackBaseTest<DelayableTimeoutCallbackWithJitter>
+ 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