summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/commands/SConscript1
-rw-r--r--src/mongo/db/commands/invalidate_view_catalog_command.cpp93
-rw-r--r--src/mongo/db/views/view_catalog.cpp103
3 files changed, 164 insertions, 33 deletions
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript
index 80078126aa1..ed51b4f4ccc 100644
--- a/src/mongo/db/commands/SConscript
+++ b/src/mongo/db/commands/SConscript
@@ -288,6 +288,7 @@ env.Library(
"do_txn_cmd.cpp",
"driverHelpers.cpp",
"haystack.cpp",
+ "invalidate_view_catalog_command.cpp",
"mr.cpp",
"oplog_application_checks.cpp",
"oplog_note.cpp",
diff --git a/src/mongo/db/commands/invalidate_view_catalog_command.cpp b/src/mongo/db/commands/invalidate_view_catalog_command.cpp
new file mode 100644
index 00000000000..37bd15b6af3
--- /dev/null
+++ b/src/mongo/db/commands/invalidate_view_catalog_command.cpp
@@ -0,0 +1,93 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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/platform/basic.h"
+
+#include "mongo/db/catalog_raii.h"
+#include "mongo/db/commands.h"
+#include "mongo/db/commands/test_commands_enabled.h"
+
+namespace mongo {
+/**
+ * Testing-only command that invalidates a database's view catalog.
+ */
+class InvalidateViewCatalogCmd final : public BasicCommand {
+public:
+ InvalidateViewCatalogCmd() : BasicCommand("invalidateViewCatalog") {}
+
+ Status checkAuthForOperation(OperationContext* opCtx,
+ const std::string& dbname,
+ const BSONObj& cmdObj) const final {
+ // No auth checks as this is a testing-only command.
+ return Status::OK();
+ }
+
+ bool adminOnly() const final {
+ return false;
+ }
+
+ bool maintenanceMode() const final {
+ return true;
+ }
+
+ bool maintenanceOk() const final {
+ return true;
+ }
+
+ AllowedOnSecondary secondaryAllowed(ServiceContext*) const final {
+ return AllowedOnSecondary::kAlways;
+ }
+
+ bool supportsWriteConcern(const BSONObj& cmd) const final {
+ return false;
+ }
+
+ std::string help() const final {
+ return "invalidate view catalog\n"
+ "Internal command for testing only. Invalidates a database's view catalog,\n"
+ "forcing a reload on the next view operation.\n";
+ }
+
+ bool run(OperationContext* opCtx,
+ const std::string& dbName,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result) final {
+ AutoGetDb dblock(opCtx, dbName, LockMode::MODE_IS);
+ auto db = dblock.getDb();
+ uassert(ErrorCodes::NamespaceNotFound,
+ str::stream() << "database " << dbName << " does not exist",
+ db);
+
+ db->getViewCatalog()->invalidate();
+ return true;
+ }
+};
+
+MONGO_REGISTER_TEST_COMMAND(InvalidateViewCatalogCmd);
+
+} // namespace mongo
diff --git a/src/mongo/db/views/view_catalog.cpp b/src/mongo/db/views/view_catalog.cpp
index 107249c3d3d..eb1e6dc6bd9 100644
--- a/src/mongo/db/views/view_catalog.cpp
+++ b/src/mongo/db/views/view_catalog.cpp
@@ -53,10 +53,13 @@
#include "mongo/db/views/resolved_view.h"
#include "mongo/db/views/view.h"
#include "mongo/db/views/view_graph.h"
+#include "mongo/util/fail_point_service.h"
#include "mongo/util/log.h"
namespace mongo {
namespace {
+MONGO_FAIL_POINT_DEFINE(hangDuringViewResolution);
+
StatusWith<std::unique_ptr<CollatorInterface>> parseCollator(OperationContext* opCtx,
BSONObj collationSpec) {
// If 'collationSpec' is empty, return the null collator, which represents the "simple"
@@ -428,45 +431,79 @@ std::shared_ptr<ViewDefinition> ViewCatalog::lookup(OperationContext* opCtx, Str
StatusWith<ResolvedView> ViewCatalog::resolveView(OperationContext* opCtx,
const NamespaceString& nss) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- const NamespaceString* resolvedNss = &nss;
- std::vector<BSONObj> resolvedPipeline;
- BSONObj collation;
-
- for (int i = 0; i < ViewGraph::kMaxViewDepth; i++) {
- auto view = _lookup_inlock(opCtx, resolvedNss->ns());
- if (!view) {
- // Return error status if pipeline is too large.
- int pipelineSize = 0;
- for (auto obj : resolvedPipeline) {
- pipelineSize += obj.objsize();
+ stdx::unique_lock<stdx::mutex> lock(_mutex);
+
+ // Keep looping until the resolution completes. If the catalog is invalidated during the
+ // resolution, we start over from the beginning.
+ while (true) {
+ // Points to the name of the most resolved namespace.
+ const NamespaceString* resolvedNss = &nss;
+
+ // Holds the combination of all the resolved views.
+ std::vector<BSONObj> resolvedPipeline;
+
+ // If the catalog has not been tampered with, all views seen during the resolution will have
+ // the same collation. As an optimization, we fill out the collation spec only once.
+ boost::optional<BSONObj> collation;
+
+ // The last seen view definition, which owns the NamespaceString pointed to by
+ // 'resolvedNss'.
+ std::shared_ptr<ViewDefinition> lastViewDefinition;
+
+ int depth = 0;
+ for (; depth < ViewGraph::kMaxViewDepth; depth++) {
+ while (MONGO_FAIL_POINT(hangDuringViewResolution)) {
+ log() << "Yielding mutex and hanging due to 'hangDuringViewResolution' failpoint";
+ lock.unlock();
+ sleepmillis(1000);
+ lock.lock();
}
- if (pipelineSize > ViewGraph::kMaxViewPipelineSizeBytes) {
- return {ErrorCodes::ViewPipelineMaxSizeExceeded,
- str::stream() << "View pipeline exceeds maximum size; maximum size is "
- << ViewGraph::kMaxViewPipelineSizeBytes};
+
+ // If the catalog has been invalidated, bail and restart.
+ if (!_valid.load()) {
+ uassertStatusOK(_reloadIfNeeded_inlock(opCtx));
+ break;
}
- return StatusWith<ResolvedView>(
- {*resolvedNss, std::move(resolvedPipeline), std::move(collation)});
- }
- resolvedNss = &(view->viewOn());
- collation = view->defaultCollator() ? view->defaultCollator()->getSpec().toBSON()
- : CollationSpec::kSimpleSpec;
+ auto view = _lookup_inlock(opCtx, resolvedNss->ns());
+ if (!view) {
+ // Return error status if pipeline is too large.
+ int pipelineSize = 0;
+ for (auto obj : resolvedPipeline) {
+ pipelineSize += obj.objsize();
+ }
+ if (pipelineSize > ViewGraph::kMaxViewPipelineSizeBytes) {
+ return {ErrorCodes::ViewPipelineMaxSizeExceeded,
+ str::stream() << "View pipeline exceeds maximum size; maximum size is "
+ << ViewGraph::kMaxViewPipelineSizeBytes};
+ }
+ return StatusWith<ResolvedView>(
+ {*resolvedNss, std::move(resolvedPipeline), std::move(collation.get())});
+ }
- // Prepend the underlying view's pipeline to the current working pipeline.
- const std::vector<BSONObj>& toPrepend = view->pipeline();
- resolvedPipeline.insert(resolvedPipeline.begin(), toPrepend.begin(), toPrepend.end());
+ resolvedNss = &view->viewOn();
+ if (!collation) {
+ collation = view->defaultCollator() ? view->defaultCollator()->getSpec().toBSON()
+ : CollationSpec::kSimpleSpec;
+ }
- // If the first stage is a $collStats, then we return early with the viewOn namespace.
- if (toPrepend.size() > 0 && !toPrepend[0]["$collStats"].eoo()) {
- return StatusWith<ResolvedView>(
- {*resolvedNss, std::move(resolvedPipeline), std::move(collation)});
+ // Prepend the underlying view's pipeline to the current working pipeline.
+ const std::vector<BSONObj>& toPrepend = view->pipeline();
+ resolvedPipeline.insert(resolvedPipeline.begin(), toPrepend.begin(), toPrepend.end());
+
+ // If the first stage is a $collStats, then we return early with the viewOn namespace.
+ if (toPrepend.size() > 0 && !toPrepend[0]["$collStats"].eoo()) {
+ return StatusWith<ResolvedView>(
+ {*resolvedNss, std::move(resolvedPipeline), std::move(collation.get())});
+ }
}
- }
- return {ErrorCodes::ViewDepthLimitExceeded,
- str::stream() << "View depth too deep or view cycle detected; maximum depth is "
- << ViewGraph::kMaxViewDepth};
+ if (depth >= ViewGraph::kMaxViewDepth) {
+ return {ErrorCodes::ViewDepthLimitExceeded,
+ str::stream() << "View depth too deep or view cycle detected; maximum depth is "
+ << ViewGraph::kMaxViewDepth};
+ }
+ };
+ MONGO_UNREACHABLE;
}
} // namespace mongo