diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/commands/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/commands/invalidate_view_catalog_command.cpp | 93 | ||||
-rw-r--r-- | src/mongo/db/views/view_catalog.cpp | 103 |
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 |