summaryrefslogtreecommitdiff
path: root/src/mongo/executor/mock_network_fixture.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/executor/mock_network_fixture.cpp')
-rw-r--r--src/mongo/executor/mock_network_fixture.cpp148
1 files changed, 148 insertions, 0 deletions
diff --git a/src/mongo/executor/mock_network_fixture.cpp b/src/mongo/executor/mock_network_fixture.cpp
new file mode 100644
index 00000000000..da028d8a6ba
--- /dev/null
+++ b/src/mongo/executor/mock_network_fixture.cpp
@@ -0,0 +1,148 @@
+/**
+ * Copyright (C) 2020-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.
+ */
+
+#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/executor/mock_network_fixture.h"
+
+#include "mongo/db/matcher/matcher.h"
+#include "mongo/executor/network_interface_mock.h"
+#include "mongo/logv2/log.h"
+
+namespace mongo {
+namespace test {
+namespace mock {
+
+MockNetwork::Matcher::Matcher(const BSONObj& matcherQuery) {
+ auto expCtx = make_intrusive<ExpressionContext>(
+ nullptr /* opCtx */, nullptr /* collator */, NamespaceString{"db.coll"} /* dummy nss */);
+ // Expression matcher doesn't have copy constructor, so wrap it in a shared_ptr for capture.
+ auto m = std::make_shared<mongo::Matcher>(matcherQuery, std::move(expCtx));
+ _matcherFunc = [=](const BSONObj& request) { return m->matches(request); };
+}
+
+bool MockNetwork::_allExpectationsSatisfied() const {
+ return std::all_of(_expectations.begin(), _expectations.end(), [](const auto& exp) {
+ return exp->isDefault() || exp->isSatisfied();
+ });
+}
+
+void MockNetwork::_runUntilIdle() {
+ executor::NetworkInterfaceMock::InNetworkGuard guard(_net);
+ do {
+ // The main responsibility of the mock network is to host incoming requests and scheduled
+ // responses. Additionally, the mock network interface is a de facto lock-step scheduler.
+ //
+ // The executor thread and the mock/test thread run in turn. The test thread
+ // (1) triggers the tested behavior, e.g. by simulating a command;
+ // (2) responds to network requests;
+ // (3) advances the mock clock; and
+ // (4) handles some network operations implicitly (explained below).
+ // The executor runs the asynchronous jobs which may schedule network requests.
+ //
+ // The executor thread gets the first turn, then each of them yields at the end of their
+ // turns by enabling and signaling the other. The executor thread yields by calling
+ // waitForWork() on the mock network; the test thread yields by calling
+ // runReadyNetworkOperations().
+ //
+ // runReadyNetworkOperations() also first checks for expired scheduled works (e.g. request
+ // timeout) and executes the expired works. This behavior is the item (4) mentioned above.
+ //
+ // After yielding to the executor thread, it's possible that new expired scheduled works
+ // were added by the executor. That's why we need to double check if there's any ready
+ // network operations before deciding the network is idle.
+ //
+ // External threads may make things more complex. For example, they can schedule new
+ // requests right after we thought the network was idle. However, that's always the case
+ // with or without the mock framework.
+ _net->runReadyNetworkOperations();
+ if (_net->hasReadyRequests()) {
+ // Peek the next request.
+ auto noi = _net->getFrontOfUnscheduledQueue();
+ auto request = noi->getRequest().cmdObj;
+
+ // We ignore the next request if it's not expected (or already satisfied).
+ // Default expectations are always the oldest in the vector, so matching expectations
+ // in LIFO order allows us to always see the overrides first.
+ // (Iterating a vector backwards is much cheaper than pushing to its front.)
+ auto const& exp =
+ std::find_if(_expectations.rbegin(), _expectations.rend(), [&](const auto& exp) {
+ return !exp->isSatisfied() && exp->match(request);
+ });
+
+ if (exp != _expectations.rend()) {
+ // Consume the next request and execute the action.
+ noi = _net->getNextReadyRequest();
+ auto response = (*exp)->run(request);
+ LOGV2_DEBUG(5015401,
+ 1,
+ "mock reply ",
+ "request"_attr = request,
+ "response"_attr = response);
+ _net->scheduleResponse(noi, _net->now(), response);
+
+ // Continue handling network operations and process requests.
+ continue;
+ }
+ }
+
+ // The executor is idle since we just ran it. Check hasReadyNetworkOperations so that no
+ // scheduled work is waiting for the network thread.
+ } while (_net->hasReadyNetworkOperations());
+}
+
+void MockNetwork::runUntilExpectationsSatisfied() {
+ // If there exist extra threads beside the executor and the mock/test thread, when the
+ // network is idle, the extra threads may be running and will schedule new requests. As a
+ // result, the current best practice is to busy-loop to prepare for that.
+ while (!_allExpectationsSatisfied()) {
+ _runUntilIdle();
+ }
+}
+
+void MockNetwork::runUntil(Date_t target) {
+ while (_net->now() < target) {
+ LOGV2_DEBUG(
+ 5015402, 1, "mock advances time", "from"_attr = _net->now(), "to"_attr = target);
+ {
+ executor::NetworkInterfaceMock::InNetworkGuard guard(_net);
+ // Even if we cannot reach target time, we are still making progress in the loop.
+ _net->runUntil(target);
+ }
+ // Run until idle.
+ _runUntilIdle();
+ }
+ LOGV2_DEBUG(5015403, 1, "mock reached time", "target"_attr = target);
+}
+
+} // namespace mock
+} // namespace test
+} // namespace mongo