diff options
-rw-r--r-- | jstests/slowNightly/index_retry.js | 15 | ||||
-rw-r--r-- | src/mongo/SConscript | 4 | ||||
-rw-r--r-- | src/mongo/db/cloner.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/commands.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/commands.h | 4 | ||||
-rw-r--r-- | src/mongo/db/compact.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/curop.cpp | 38 | ||||
-rw-r--r-- | src/mongo/db/curop.h | 9 | ||||
-rw-r--r-- | src/mongo/db/db.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/dbcommands.cpp | 121 | ||||
-rw-r--r-- | src/mongo/db/index_builder.cpp | 74 | ||||
-rw-r--r-- | src/mongo/db/index_builder.h | 59 | ||||
-rw-r--r-- | src/mongo/db/index_rebuilder.cpp | 25 | ||||
-rw-r--r-- | src/mongo/db/index_rebuilder.h | 9 | ||||
-rw-r--r-- | src/mongo/db/index_update.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/index_update.h | 1 | ||||
-rw-r--r-- | src/mongo/db/pdfile.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/repl/oplog.cpp | 19 | ||||
-rw-r--r-- | src/mongo/dbtests/replsettests.cpp | 560 |
19 files changed, 964 insertions, 43 deletions
diff --git a/jstests/slowNightly/index_retry.js b/jstests/slowNightly/index_retry.js index d6632c20543..64561d60f87 100644 --- a/jstests/slowNightly/index_retry.js +++ b/jstests/slowNightly/index_retry.js @@ -1,5 +1,3 @@ - -if (0) { // SERVER-8536 / SERVER-8344 // Check index rebuild when MongoDB is killed var ports = allocatePorts(1); @@ -14,7 +12,7 @@ t.drop(); // Insert a large number of documents, enough to ensure that an index build on these documents can // be interrupted before complete. -for (i = 0; i < 1e6; ++i) { +for (i = 0; i < 1e5; ++i) { t.save( { a:i } ); if (i % 10000 == 0) { print("i: " + i); @@ -57,7 +55,8 @@ function indexBuildInProgress() { function abortDuringIndexBuild(options) { // Create an index asynchronously by using a new connection. - new Mongo(test.getMongo().host ).getCollection( t.toString() ).createIndex( { a:1 }, options); + new Mongo(test.getMongo().host).getCollection(t.toString()).createIndex( + { a:1 }, { background:true } ); // Wait for the index build to start. var times = 0; @@ -71,13 +70,12 @@ function abortDuringIndexBuild(options) { stopMongod(ports[0], /* signal */ 9); } -abortDuringIndexBuild({background:true}); - -print("sleeping"); -sleep(2000); +abortDuringIndexBuild(); conn = mongod.start(/* reuseData */ true); + + assert.soon( function() { try { @@ -96,4 +94,3 @@ print("Index built"); stopMongod(ports[0]); print("SUCCESS!"); -} diff --git a/src/mongo/SConscript b/src/mongo/SConscript index 160db497a9d..e575cecb1fb 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -421,6 +421,7 @@ serverOnlyFiles = [ "db/curop.cpp", "db/query_plan_selection_policy.cpp", "db/extsort.cpp", "db/index.cpp", + "db/index_builder.cpp", "db/index_update.cpp", "db/index_rebuilder.cpp", "db/scanandorder.cpp", @@ -440,6 +441,7 @@ serverOnlyFiles = [ "db/curop.cpp", "db/ops/update_internal.cpp", "db/parsed_query.cpp", "db/dbcommands.cpp", + "db/compact.cpp", "db/dbcommands_admin.cpp", # most commands are only for mongod @@ -603,7 +605,7 @@ mongodAndMongosFiles = [ ] env.StaticLibrary("mongodandmongos", mongodAndMongosFiles) -mongodOnlyFiles = [ "db/db.cpp", "db/compact.cpp", "db/commands/touch.cpp" ] +mongodOnlyFiles = [ "db/db.cpp", "db/commands/touch.cpp" ] # ----- TARGETS ------ diff --git a/src/mongo/db/cloner.cpp b/src/mongo/db/cloner.cpp index d22ef10d894..2dbacd03ddc 100644 --- a/src/mongo/db/cloner.cpp +++ b/src/mongo/db/cloner.cpp @@ -26,6 +26,7 @@ #include "mongo/db/commands/rename_collection.h" #include "mongo/db/db.h" #include "mongo/db/dbhelpers.h" +#include "mongo/db/index_builder.h" #include "mongo/db/instance.h" #include "mongo/db/jsobj.h" #include "mongo/db/kill_current_op.h" @@ -848,6 +849,30 @@ namespace mongo { virtual void help( stringstream &help ) const { help << " example: { renameCollection: foo.a, to: bar.b }"; } + + virtual std::vector<BSONObj> stopIndexBuilds(const std::string& dbname, + const BSONObj& cmdObj) { + string source = cmdObj.getStringField( name.c_str() ); + string target = cmdObj.getStringField( "to" ); + + BSONObj criteria = BSON("op" << "insert" << "ns" << dbname+".system.indexes" << + "insert.ns" << source); + + std::vector<BSONObj> prelim = IndexBuilder::killMatchingIndexBuilds(criteria); + std::vector<BSONObj> indexes; + + for (int i = 0; i < static_cast<int>(prelim.size()); i++) { + // Change the ns + BSONObj stripped = prelim[i].removeField("ns"); + BSONObjBuilder builder; + builder.appendElements(stripped); + builder.append("ns", target); + indexes.push_back(builder.done()); + } + + return indexes; + } + virtual bool run(const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { string source = cmdObj.getStringField( name.c_str() ); string target = cmdObj.getStringField( "to" ); @@ -881,10 +906,12 @@ namespace mongo { bool capped = false; long long size = 0; + std::vector<BSONObj> indexesInProg; { Client::Context ctx( source ); NamespaceDetails *nsd = nsdetails( source ); uassert( 10026 , "source namespace does not exist", nsd ); + indexesInProg = stopIndexBuilds(dbname, cmdObj); capped = nsd->isCapped(); if ( capped ) for( DiskLoc i = nsd->firstExtent; !i.isNull(); i = i.ext()->xnext ) @@ -970,6 +997,7 @@ namespace mongo { { Client::Context ctx( source ); dropCollection( source, errmsg, result ); + IndexBuilder::restoreIndexes(targetIndexes, indexesInProg); } return true; } diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp index 87e9129e612..9616eca8650 100644 --- a/src/mongo/db/commands.cpp +++ b/src/mongo/db/commands.cpp @@ -166,6 +166,11 @@ namespace mongo { help << "no help defined";
}
+ std::vector<BSONObj> Command::stopIndexBuilds(const std::string& dbname, + const BSONObj& cmdObj) {
+ return std::vector<BSONObj>();
+ }
+
Command* Command::findCommand( const string& name ) {
map<string,Command*>::iterator i = _commands->find( name );
if ( i == _commands->end() )
diff --git a/src/mongo/db/commands.h b/src/mongo/db/commands.h index 3897308a720..d1049f78c1a 100644 --- a/src/mongo/db/commands.h +++ b/src/mongo/db/commands.h @@ -149,6 +149,10 @@ namespace mongo { static map<string,Command*> * _webCommands; public: + // Stop all index builds required to run this command and return index builds killed. + virtual std::vector<BSONObj> stopIndexBuilds(const std::string& dbname, + const BSONObj& cmdObj); + static const map<string,Command*>* commandsByBestName() { return _commandsByBestName; } static const map<string,Command*>* webCommands() { return _webCommands; } /** @return if command was found */ diff --git a/src/mongo/db/compact.cpp b/src/mongo/db/compact.cpp index de684f0112b..31a091aad4d 100644 --- a/src/mongo/db/compact.cpp +++ b/src/mongo/db/compact.cpp @@ -33,6 +33,7 @@ #include "mongo/db/curop-inl.h" #include "mongo/db/extsort.h" #include "mongo/db/index.h" +#include "mongo/db/index_builder.h" #include "mongo/db/index_update.h" #include "mongo/db/jsobj.h" #include "mongo/db/kill_current_op.h" @@ -364,6 +365,16 @@ namespace mongo { } CompactCmd() : Command("compact") { } + virtual std::vector<BSONObj> stopIndexBuilds(const std::string& dbname, + const BSONObj& cmdObj) { + std::string systemIndexes = dbname+".system.indexes"; + std::string coll = cmdObj.firstElement().valuestr(); + std::string ns = dbname + "." + coll; + BSONObj criteria = BSON("ns" << systemIndexes << "op" << "insert" << "insert.ns" << ns); + + return IndexBuilder::killMatchingIndexBuilds(criteria); + } + virtual bool run(const string& db, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { string coll = cmdObj.firstElement().valuestr(); if( coll.empty() || db.empty() ) { @@ -414,8 +425,13 @@ namespace mongo { verify( pb >= 0 && pb <= 1024 * 1024 ); } + std::vector<BSONObj> indexesInProg = stopIndexBuilds(db, cmdObj); + bool validate = !cmdObj.hasElement("validate") || cmdObj["validate"].trueValue(); // default is true at the moment bool ok = compact(ns, errmsg, validate, result, pf, pb); + + IndexBuilder::restoreIndexes(db+".system.indexes", indexesInProg); + return ok; } }; diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp index 68e24e2b171..66e0138ee0f 100644 --- a/src/mongo/db/curop.cpp +++ b/src/mongo/db/curop.cpp @@ -67,6 +67,44 @@ namespace mongo { _active = true; // this should be last for ui clarity } + CurOp* CurOp::getOp(const BSONObj& criteria) { + // Regarding Matcher: This is not quite the right hammer to use here. + // Future: use an actual property of CurOp to flag index builds + // and use that to filter. + // This will probably need refactoring once we change index builds + // to be a real command instead of an insert into system.indexes + Matcher matcher(criteria); + + Client& me = cc(); + + scoped_lock client_lock(Client::clientsMutex); + for (std::set<Client*>::iterator it = Client::clients.begin(); + it != Client::clients.end(); + it++) { + + Client *client = *it; + verify(client); + + CurOp* curop = client->curop(); + if (client == &me || curop == NULL) { + continue; + } + + if ( !curop->active() ) + continue; + + if ( curop->killPendingStrict() ) + continue; + + BSONObj info = curop->info(); + if (matcher.matches(info)) { + return curop; + } + } + + return NULL; + } + void CurOp::reset( const HostAndPort& remote, int op ) { reset(); if( _remote != remote ) { diff --git a/src/mongo/db/curop.h b/src/mongo/db/curop.h index 8721484b684..04c916119b9 100644 --- a/src/mongo/db/curop.h +++ b/src/mongo/db/curop.h @@ -245,6 +245,15 @@ namespace mongo { LockStat& lockStat() { return _lockStat; } void setKillWaiterFlags(); + + /** + * Find a currently running operation matching the given criteria. This assumes that you're + * going to kill the operation, so it must be called multiple times to get multiple matching + * operations. + * @param criteria the search to do against the infoNoauth() BSONObj + * @return a pointer to a matching op or NULL if no ops match + */ + static CurOp* getOp(const BSONObj& criteria); private: friend class Client; void _reset(); diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp index 7ff4bc7e49a..f7eec8cd35d 100644 --- a/src/mongo/db/db.cpp +++ b/src/mongo/db/db.cpp @@ -672,6 +672,9 @@ namespace mongo { Client::WriteContext c("admin", dbpath); } + // Starts a background thread that rebuilds all incomplete indices. + indexRebuilder.go(); + listen(listenPort); // listen() will return when exit code closes its socket. @@ -778,8 +781,7 @@ static void buildOptionsDescriptions(po::options_description *pVisible, ("jsonp","allow JSONP access via http (has security implications)") ("noauth", "run without security") ("nohttpinterface", "disable http interface") - // SERVER-8536 - // ("noIndexBuildRetry", "don't retry any index builds that were interrupted by shutdown") + ("noIndexBuildRetry", "don't retry any index builds that were interrupted by shutdown") ("nojournal", "disable journaling (journaling is on by default for 64 bit)") ("noprealloc", "disable data file preallocation - will often hurt performance") ("noscripting", "disable scripting engine") diff --git a/src/mongo/db/dbcommands.cpp b/src/mongo/db/dbcommands.cpp index a94dca2f8ef..94477e68b98 100644 --- a/src/mongo/db/dbcommands.cpp +++ b/src/mongo/db/dbcommands.cpp @@ -40,6 +40,7 @@ #include "mongo/db/commands/server_status.h" #include "mongo/db/db.h" #include "mongo/db/dur_stats.h" +#include "mongo/db/index_builder.h" #include "mongo/db/index_update.h" #include "mongo/db/instance.h" #include "mongo/db/introspect.h" @@ -382,6 +383,17 @@ namespace mongo { virtual bool lockGlobally() const { return true; } virtual LockType locktype() const { return WRITE; } + + virtual std::vector<BSONObj> stopIndexBuilds(const std::string& dbname, + const BSONObj& cmdObj) { + std::string systemIndexes = dbname+".system.indexes"; + std::string toDeleteRegex = "^"+dbname+"\\."; + BSONObj criteria = BSON("ns" << systemIndexes << + "op" << "insert" << + "insert.ns" << BSON("$regex" << toDeleteRegex)); + return IndexBuilder::killMatchingIndexBuilds(criteria); + } + CmdDropDatabase() : Command("dropDatabase") {} bool run(const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { // disallow dropping the config database @@ -394,6 +406,7 @@ namespace mongo { int p = (int) e.number(); if ( p != 1 ) return false; + stopIndexBuilds(dbname, cmdObj); dropDatabase(dbname); result.append( "dropped" , dbname ); log() << "dropDatabase " << dbname << " finished" << endl; @@ -424,19 +437,37 @@ namespace mongo { out->push_back(Privilege(dbname, actions)); } CmdRepairDatabase() : Command("repairDatabase") {} + + virtual std::vector<BSONObj> stopIndexBuilds(const std::string& dbname, + const BSONObj& cmdObj) { + std::string systemIndexes = dbname+".system.indexes"; + BSONObj criteria = BSON("ns" << systemIndexes << "op" << "insert"); + return IndexBuilder::killMatchingIndexBuilds(criteria); + } + bool run(const string& dbname , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { BSONElement e = cmdObj.firstElement(); log() << "repairDatabase " << dbname << endl; int p = (int) e.number(); + + std::vector<BSONObj> indexesInProg; if ( p != 1 ) { errmsg = "bad option"; return false; } + + std::vector<BSONObj> indexes = stopIndexBuilds(dbname, cmdObj); + e = cmdObj.getField( "preserveClonedFilesOnFailure" ); bool preserveClonedFilesOnFailure = e.isBoolean() && e.boolean(); e = cmdObj.getField( "backupOriginalFiles" ); bool backupOriginalFiles = e.isBoolean() && e.boolean(); - return repairDatabase( dbname, errmsg, preserveClonedFilesOnFailure, backupOriginalFiles ); + bool ok = + repairDatabase( dbname, errmsg, preserveClonedFilesOnFailure, backupOriginalFiles ); + + IndexBuilder::restoreIndexes(dbname+".system.indexes", indexesInProg); + + return ok; } } cmdRepairDatabase; @@ -567,6 +598,17 @@ namespace mongo { } virtual void help( stringstream& help ) const { help << "drop a collection\n{drop : <collectionName>}"; } virtual LockType locktype() const { return WRITE; } + + virtual std::vector<BSONObj> stopIndexBuilds(const std::string& dbname, + const BSONObj& cmdObj) { + std::string nsToDrop = dbname + '.' + cmdObj.firstElement().valuestr(); + std::string systemIndexes = dbname+".system.indexes"; + BSONObj criteria = BSON("ns" << systemIndexes << "op" << "insert" << + "insert.ns" << nsToDrop); + + return IndexBuilder::killMatchingIndexBuilds(criteria); + } + virtual bool run(const string& dbname , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) { string nsToDrop = dbname + '.' + cmdObj.firstElement().valuestr(); NamespaceDetails *d = nsdetails(nsToDrop); @@ -576,7 +618,12 @@ namespace mongo { errmsg = "ns not found"; return false; } - uassert( 10039 , "can't drop collection with reserved $ character in name", strchr(nsToDrop.c_str(), '$') == 0 ); + + uassert(10039, "can't drop collection with reserved $ character in name", + strchr(nsToDrop.c_str(), '$') == 0); + + stopIndexBuilds(dbname, cmdObj); + dropCollection( nsToDrop, errmsg, result ); return true; } @@ -698,6 +745,42 @@ namespace mongo { actions.addAction(ActionType::dropIndexes); out->push_back(Privilege(parseNs(dbname, cmdObj), actions)); } + + virtual std::vector<BSONObj> stopIndexBuilds(const std::string& dbname, + const BSONObj& cmdObj) { + std::string systemIndexes = dbname+".system.indexes"; + std::string toDeleteNs = dbname+"."+cmdObj.firstElement().valuestr(); + BSONObjBuilder builder; + builder.append("ns", systemIndexes); + builder.append("op", "insert"); + builder.append("insert.ns", toDeleteNs); + + // Get index name to drop + BSONElement toDrop = cmdObj.getField("index"); + + if (toDrop.type() == String) { + // Kill all in-progress indexes + if (strcmp("*", toDrop.valuestr()) == 0) { + BSONObj criteria = builder.done(); + return IndexBuilder::killMatchingIndexBuilds(criteria); + } + // Kill an in-progress index by name + else { + builder.append("insert.name", toDrop.valuestr()); + BSONObj criteria = builder.done(); + return IndexBuilder::killMatchingIndexBuilds(criteria); + } + } + // Kill an in-progress index build by index key + else if (toDrop.type() == Object) { + builder.append("insert.key", toDrop.Obj()); + BSONObj criteria = builder.done(); + return IndexBuilder::killMatchingIndexBuilds(criteria); + } + + return std::vector<BSONObj>(); + } + CmdDropIndexes() : Command("dropIndexes", false, "deleteIndexes") { } bool run(const string& dbname, BSONObj& jsobj, int, string& errmsg, BSONObjBuilder& anObjBuilder, bool /*fromRepl*/) { BSONElement e = jsobj.firstElement(); @@ -706,6 +789,8 @@ namespace mongo { if ( !cmdLine.quiet ) tlog() << "CMD: dropIndexes " << toDeleteNs << endl; if ( d ) { + stopIndexBuilds(dbname, jsobj); + BSONElement f = jsobj.getField("index"); if ( f.type() == String ) { return dropIndexes( d, toDeleteNs.c_str(), f.valuestr(), errmsg, anObjBuilder, false ); @@ -751,6 +836,16 @@ namespace mongo { out->push_back(Privilege(parseNs(dbname, cmdObj), actions)); } CmdReIndex() : Command("reIndex") { } + + virtual std::vector<BSONObj> stopIndexBuilds(const std::string& dbname, + const BSONObj& cmdObj) { + std::string systemIndexes = dbname+".system.indexes"; + std::string ns = dbname + '.' + cmdObj["reIndex"].valuestrsafe(); + BSONObj criteria = BSON("ns" << systemIndexes << "op" << "insert" << "insert.ns" << ns); + + return IndexBuilder::killMatchingIndexBuilds(criteria); + } + bool run(const string& dbname , BSONObj& jsobj, int, string& errmsg, BSONObjBuilder& result, bool /*fromRepl*/) { static DBDirectClient db; @@ -765,6 +860,9 @@ namespace mongo { return false; } + std::vector<BSONObj> indexesInProg; + stopIndexBuilds(dbname, jsobj); + list<BSONObj> all; auto_ptr<DBClientCursor> i = db.query( dbname + ".system.indexes" , BSON( "ns" << toDeleteNs ) , 0 , 0 , 0 , QueryOption_SlaveOk ); BSONObjBuilder b; @@ -791,6 +889,8 @@ namespace mongo { result.append( "nIndexes" , (int)all.size() ); result.appendArray( "indexes" , b.obj() ); + + IndexBuilder::restoreIndexes(dbname+".system.indexes", indexesInProg); return true; } } cmdReIndex; @@ -1862,13 +1962,30 @@ namespace mongo { virtual void addRequiredPrivileges(const std::string& dbname, const BSONObj& cmdObj, std::vector<Privilege>* out) {} + + virtual std::vector<BSONObj> stopIndexBuilds(const std::string& dbname, + const BSONObj& cmdObj) { + std::string systemIndexes = dbname+".system.indexes"; + std::string coll = cmdObj[ "emptycapped" ].valuestrsafe(); + std::string ns = dbname + '.' + coll; + BSONObj criteria = BSON("ns" << systemIndexes << "op" << "insert" << "insert.ns" << ns); + + return IndexBuilder::killMatchingIndexBuilds(criteria); + } + virtual bool run(const string& dbname , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) { string coll = cmdObj[ "emptycapped" ].valuestrsafe(); uassert( 13428, "emptycapped must specify a collection", !coll.empty() ); string ns = dbname + "." + coll; NamespaceDetails *nsd = nsdetails( ns ); massert( 13429, "emptycapped no such collection", nsd ); + + std::vector<BSONObj> indexes = stopIndexBuilds(dbname, cmdObj); + nsd->emptyCappedCollection( ns.c_str() ); + + IndexBuilder::restoreIndexes(dbname+".system.indexes", indexes); + return true; } }; diff --git a/src/mongo/db/index_builder.cpp b/src/mongo/db/index_builder.cpp new file mode 100644 index 00000000000..090fbcd4508 --- /dev/null +++ b/src/mongo/db/index_builder.cpp @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2012 10gen 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/>. + */ + +#include "mongo/db/index_builder.h" + +#include "mongo/db/client.h" +#include "mongo/db/kill_current_op.h" +#include "mongo/db/repl/rs.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + + AtomicUInt IndexBuilder::_indexBuildCount = 0; + + IndexBuilder::IndexBuilder(const std::string ns, const BSONObj index) : + BackgroundJob(true /* self-delete */), _ns(ns), _index(index.getOwned()), + _name(str::stream() << "repl index builder " << (_indexBuildCount++).get()) { + } + + IndexBuilder::~IndexBuilder() {} + + std::string IndexBuilder::name() const { + return _name; + } + + void IndexBuilder::run() { + LOG(2) << "building index " << _index << " on " << _ns << endl; + Client::initThread(name().c_str()); + replLocalAuth(); + + Client::WriteContext ctx(_ns); + build(); + + cc().shutdown(); + } + + void IndexBuilder::build() const { + theDataFileMgr.insert(_ns.c_str(), _index.objdata(), _index.objsize(), + true /* mayInterrupt */); + } + + std::vector<BSONObj> IndexBuilder::killMatchingIndexBuilds(const BSONObj& criteria) { + std::vector<BSONObj> indexes; + CurOp* op = NULL; + while ((op = CurOp::getOp(criteria)) != NULL) { + BSONObj index = op->query(); + killCurrentOp.blockingKill(op->opNum()); + indexes.push_back(index); + } + return indexes; + } + + void IndexBuilder::restoreIndexes(const std::string& ns, const std::vector<BSONObj>& indexes) { + for (int i = 0; i < static_cast<int>(indexes.size()); i++) { + IndexBuilder* indexBuilder = new IndexBuilder(ns, indexes[i]); + // This looks like a memory leak, but indexBuilder deletes itself when it finishes + indexBuilder->go(); + } + } +} + diff --git a/src/mongo/db/index_builder.h b/src/mongo/db/index_builder.h new file mode 100644 index 00000000000..0b5de273c84 --- /dev/null +++ b/src/mongo/db/index_builder.h @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2012 10gen 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/>. + */ + +#pragma once + +#include <string> + +#include "mongo/db/jsobj.h" +#include "mongo/util/background.h" + +/** + * Forks off a thread to build an index. + */ +namespace mongo { + + class IndexBuilder : public BackgroundJob { + public: + IndexBuilder(const std::string ns, const BSONObj index); + virtual ~IndexBuilder(); + + virtual void run(); + virtual std::string name() const; + + void build() const; + + /** + * Kill all in-progress indexes matching criteria and, optionally, store them in the + * indexes list. + */ + static std::vector<BSONObj> killMatchingIndexBuilds(const BSONObj& criteria); + + /** + * Retry all index builds in the list. Builds each index in a separate thread. If ns does + * not match the ns field in the indexes list, the BSONObj's ns field is changed before the + * index is built (to handle rename). + */ + static void restoreIndexes(const std::string& ns, const std::vector<BSONObj>& indexes); + + private: + const std::string _ns; + const BSONObj _index; + std::string _name; + static AtomicUInt _indexBuildCount; + }; + +} diff --git a/src/mongo/db/index_rebuilder.cpp b/src/mongo/db/index_rebuilder.cpp index ac2132ed56e..924882c80c4 100644 --- a/src/mongo/db/index_rebuilder.cpp +++ b/src/mongo/db/index_rebuilder.cpp @@ -22,12 +22,14 @@ namespace mongo { - // Disabled until locking at startup can be ironed out. - // See SERVER-8344 and SERVER-8536 - //IndexRebuilder indexRebuilder; + IndexRebuilder indexRebuilder; IndexRebuilder::IndexRebuilder() {} + std::string IndexRebuilder::name() const { + return "IndexRebuilder"; + } + /** * This resets memory tracking to its original value after all indexes are rebuilt. * @@ -46,8 +48,8 @@ namespace mongo { ON_BLOCK_EXIT(resetMemoryTracking, Record::MemoryTrackingEnabled); Record::MemoryTrackingEnabled = false; + Client::initThread(name().c_str()); Client::GodScope gs; - Lock::GlobalWrite lk; bool firstTime = true; std::vector<std::string> dbNames; @@ -72,6 +74,8 @@ namespace mongo { BSONObj nsDoc = cursor->next(); const char* ns = nsDoc["name"].valuestrsafe(); + // This write lock is held throughout the index building process + // for this namespace. Client::WriteContext ctx(ns); NamespaceDetails* nsd = nsdetails(ns); @@ -79,9 +83,9 @@ namespace mongo { continue; } - log() << "Found interrupted index build on " << ns << endl; + log() << "found interrupted index build(s) on " << ns << endl; if (*firstTime) { - log() << "Restart the server with --noIndexBuildRetry to skip index rebuilds" + log() << "note: restart the server with --noIndexBuildRetry to skip index rebuilds" << endl; *firstTime = false; } @@ -91,6 +95,7 @@ namespace mongo { // If we crash between unsetting the inProg flag and cleaning up the index, the // index space will be lost. int inProg = nsd->indexBuildsInProgress; + getDur().writingInt(nsd->indexBuildsInProgress) = 0; for (int i = 0; i < inProg; i++) { @@ -123,23 +128,19 @@ namespace mongo { // Clean up the in-progress index build getDur().writingInt(nsd->indexBuildsInProgress) -= 1; details.kill_idx(); + // The index has now been removed from system.indexes, so the only record of it is in- // memory. If there is a journal commit between now and when insert() rewrites the entry and // the db crashes before the new system.indexes entry is journalled, the index will be lost // forever. Thus, we're assuming no journaling will happen between now and the entry being // re-written. - // We need to force a foreground index build to prevent replication from replaying an - // incompatible op (like a drop) during a yield. - // TODO: once commands can interrupt/wait for index builds, this can be removed. - indexObj = indexObj.removeField("background"); - try { const std::string ns = dbName + ".system.indexes"; theDataFileMgr.insert(ns.c_str(), indexObj.objdata(), indexObj.objsize(), false, true); } catch (const DBException& e) { - log() << "Rebuilding index failed: " << e.what() << " (" << e.getCode() << ")" + log() << "building index failed: " << e.what() << " (" << e.getCode() << ")" << endl; } } diff --git a/src/mongo/db/index_rebuilder.h b/src/mongo/db/index_rebuilder.h index 2b327fbc048..4abc2d133ee 100644 --- a/src/mongo/db/index_rebuilder.h +++ b/src/mongo/db/index_rebuilder.h @@ -17,10 +17,13 @@ #pragma once #include "mongo/db/namespace_details.h" +#include "mongo/util/background.h" namespace mongo { - class IndexRebuilder { + // This is a job that's only run at startup. It finds all incomplete indices and + // finishes rebuilding them. After they complete rebuilding, the thread terminates. + class IndexRebuilder : public BackgroundJob { public: IndexRebuilder(); @@ -40,7 +43,9 @@ namespace mongo { * @param nsd the namespace details of the namespace building the index * @param index the offset into nsd's index array of the partially-built index */ - void retryIndexBuild(const std::string& dbName, NamespaceDetails* nsd, const int index); + void retryIndexBuild(const std::string& dbName, + NamespaceDetails* nsd, + const int index); }; extern IndexRebuilder indexRebuilder; diff --git a/src/mongo/db/index_update.cpp b/src/mongo/db/index_update.cpp index 741b1285c51..a6be38c6431 100644 --- a/src/mongo/db/index_update.cpp +++ b/src/mongo/db/index_update.cpp @@ -553,8 +553,10 @@ namespace mongo { void buildAnIndex(const std::string& ns, NamespaceDetails* d, IndexDetails& idx, - bool background, bool mayInterrupt) { + + bool background = idx.info.obj()["background"].trueValue(); + tlog() << "build index " << ns << ' ' << idx.keyPattern() << ( background ? " background" : "" ) << endl; Timer t; unsigned long long n; diff --git a/src/mongo/db/index_update.h b/src/mongo/db/index_update.h index ad71d293ce0..d0f3bd0608e 100644 --- a/src/mongo/db/index_update.h +++ b/src/mongo/db/index_update.h @@ -34,7 +34,6 @@ namespace mongo { void buildAnIndex(const std::string& ns, NamespaceDetails *d, IndexDetails& idx, - bool background, bool mayInterrupt); // add index keys for a newly inserted record diff --git a/src/mongo/db/pdfile.cpp b/src/mongo/db/pdfile.cpp index 3f9e22c6af3..93a948ad666 100644 --- a/src/mongo/db/pdfile.cpp +++ b/src/mongo/db/pdfile.cpp @@ -1456,14 +1456,6 @@ namespace mongo { NamespaceString(tabletoidxns).coll != "system.indexes"); BSONObj info = loc.obj(); - bool background = info["background"].trueValue(); - if (background && !isMasterNs(tabletoidxns.c_str())) { - /* don't do background indexing on slaves. there are nuances. this could be added later - but requires more code. - */ - log() << "info: indexing in foreground on this replica; was a background index build on the primary" << endl; - background = false; - } // The total number of indexes right before we write to the collection int oldNIndexes = -1; @@ -1485,7 +1477,7 @@ namespace mongo { try { getDur().writingInt(tableToIndex->indexBuildsInProgress) += 1; - buildAnIndex(tabletoidxns, tableToIndex, idx, background, mayInterrupt); + buildAnIndex(tabletoidxns, tableToIndex, idx, mayInterrupt); } catch (DBException& e) { // save our error msg string as an exception or dropIndexes will overwrite our message diff --git a/src/mongo/db/repl/oplog.cpp b/src/mongo/db/repl/oplog.cpp index 969c8ba530d..6e149994cdf 100644 --- a/src/mongo/db/repl/oplog.cpp +++ b/src/mongo/db/repl/oplog.cpp @@ -26,6 +26,7 @@ #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/commands.h" +#include "mongo/db/index_builder.h" #include "mongo/db/index_update.h" #include "mongo/db/instance.h" #include "mongo/db/namespacestring.h" @@ -95,6 +96,8 @@ namespace mongo { theReplSet->lastOpTimeWritten = ts; theReplSet->lastH = h; ctx.getClient()->setLastOp( ts ); + + replset::BackgroundSync::notify(); } } @@ -439,10 +442,18 @@ namespace mongo { if ( *opType == 'i' ) { opCounters->gotInsert(); - if (NamespaceString(ns).coll == "system.indexes") { - // updates aren't allowed for indexes -- so we will do a regular insert. if index already - // exists, that is ok. - theDataFileMgr.insert(ns, (void*) o.objdata(), o.objsize()); + const char *p = strchr(ns, '.'); + if ( p && strcmp(p, ".system.indexes") == 0 ) { + if (o["background"].trueValue()) { + IndexBuilder* builder = new IndexBuilder(ns, o); + // This spawns a new thread and returns immediately. + builder->go(); + } + else { + IndexBuilder builder(ns, o); + // Finish the foreground build before returning + builder.build(); + } } else { // do upserts for inserts as we might get replayed more than once diff --git a/src/mongo/dbtests/replsettests.cpp b/src/mongo/dbtests/replsettests.cpp index 4c679628fb7..a11b0913219 100644 --- a/src/mongo/dbtests/replsettests.cpp +++ b/src/mongo/dbtests/replsettests.cpp @@ -20,12 +20,16 @@ #include "mongo/pch.h" #include "mongo/db/db.h" +#include "mongo/db/index_builder.h" #include "mongo/db/instance.h" #include "mongo/db/json.h" +#include "mongo/db/kill_current_op.h" #include "mongo/db/repl/bgsync.h" #include "mongo/db/repl/oplog.h" #include "mongo/db/repl/rs.h" #include "mongo/dbtests/dbtests.h" +#include "mongo/util/time_support.h" + namespace mongo { void createOplog(); @@ -172,6 +176,555 @@ namespace ReplSetTests { DBDirectClient Base::client_; + class IndexBuildThread : public BackgroundJob { + public: + IndexBuildThread(const BSONObj index) : + _done(false), _index(index.getOwned()), _client(NULL), _curop(NULL) { + } + + std::string name() const { + return "index build helper"; + } + + void run() { + std::string ns = nsToDatabase(_index.getStringField("ns"))+".system.indexes"; + + Client::initThread("in progress idx build"); + Client::WriteContext ctx(ns); + _client = currentClient.get(); + + // This spins to mimic the db building an index. Yield the read lock so that other + // dbs can be opened (which requires the write lock) + while (!_curop || !_curop->killPendingStrict()) { + dbtemprelease temp; + sleepmillis(0); + } + + killCurrentOp.notifyAllWaiters(); + cc().shutdown(); + _done = true; + } + + // Make sure the test doesn't end while this "index build" is still spinning + void finish() { + while (!_curop) { + sleepmillis(0); + } + + _curop->kill(); + + while (!_done) { + sleepmillis(0); + } + } + + bool finished() { + return _done; + } + + CurOp* curop() { + if (_curop != NULL) { + return _curop; + } + + while (_client == NULL) { + sleepmillis(0); + } + + // On the first time through, make sure the curop is set up correctly + _client->curop()->reset(HostAndPort::me(), dbInsert); + _client->curop()->enter(_client->getContext()); + _client->curop()->setQuery(_index); + + _curop = _client->curop(); + return _curop; + } + + private: + bool _done; + BSONObj _index; + Client* _client; + CurOp* _curop; + }; + + class TestDropDB : public Base { + public: + void run() { + drop(); + + std::string dbname = nsToDatabase(ns()); + Command *dropDB = Command::findCommand("dropDatabase"); + BSONObj cmdObj = BSON("dropDatabase" << 1); + + BSONObj indexOp1 = BSON("ns" << ns() << "name" << "foo_1" << "key" << + BSON("foo" << 1)); + BSONObj indexOp2 = BSON("ns" << (dbname+".something.else") << "name" << "baz_1" << + "key" << BSON("baz" << 1)); + // Different database - foo + BSONObj indexOp3 = BSON("ns" << "foo.something.else" << "name" << "baz_1" << + "key" << BSON("baz" << 1)); + // Different database - unittestsx + BSONObj indexOp4 = BSON("ns" << (dbname+"x.something.else") << "name" << "baz_1" << + "key" << BSON("baz" << 1)); + // Different database - xunittests + BSONObj indexOp5 = BSON("ns" << ("x"+dbname+".something.else") << "name" << "baz_1" << + "key" << BSON("baz" << 1)); + + IndexBuildThread t1(indexOp1); + IndexBuildThread t2(indexOp2); + IndexBuildThread t3(indexOp3); + IndexBuildThread t4(indexOp4); + IndexBuildThread t5(indexOp5); + + t1.go(); + t2.go(); + t3.go(); + t4.go(); + t5.go(); + + ASSERT(!t1.curop()->killPending()); + ASSERT(!t2.curop()->killPending()); + ASSERT(!t3.curop()->killPending()); + ASSERT(!t4.curop()->killPending()); + ASSERT(!t5.curop()->killPending()); + + dropDB->stopIndexBuilds(dbname, cmdObj); + + sleepsecs(1); + + ASSERT(t1.finished()); + ASSERT(t2.finished()); + ASSERT(!t3.finished()); + ASSERT(!t4.finished()); + ASSERT(!t5.finished()); + + t3.finish(); + t4.finish(); + t5.finish(); + } + }; + + class TestDrop : public Base { + public: + void run() { + drop(); + + std::string dbname = nsToDatabase(ns()); + Command *drop = Command::findCommand("drop"); + + BSONObj indexOp1 = BSON("ns" << ns() << "name" << "foo_1" << + "key" << BSON("foo" << 1)); + BSONObj indexOp2 = BSON("ns" << ns() << "name" << "bar_1" << + "key" << BSON("bar" << 1) << "background" << true); + // Different collection + BSONObj indexOp3 = BSON("ns" << (dbname+".something.else") << "name" << "baz_1" << + "key" << BSON("baz" << 1)); + + IndexBuildThread t1(indexOp1); + IndexBuildThread t2(indexOp2); + IndexBuildThread t3(indexOp3); + + t1.go(); + t2.go(); + t3.go(); + + ASSERT(!t1.curop()->killPending()); + ASSERT(!t2.curop()->killPending()); + ASSERT(!t3.curop()->killPending()); + + std::string coll(strchr(ns(), '.')+1); + BSONObj cmdObj = BSON("drop" << coll); + drop->stopIndexBuilds(dbname, cmdObj); + + sleepsecs(1); + + ASSERT(t1.finished()); + ASSERT(t2.finished()); + ASSERT(!t3.finished()); + + t3.finish(); + } + }; + + class TestDropIndexes : public Base { + + void testDropIndexes1() { + drop(); + + std::string dbname = nsToDatabase(ns()); + Command *c = Command::findCommand("dropIndexes"); + + BSONObj indexOp1 = BSON("ns" << ns() << "name" << "x_1" << "key" << BSON("x" << 1)); + BSONObj indexOp2 = BSON("ns" << ns() << "name" << "y_1" << "key" << BSON("y" << 1)); + BSONObj indexOp3 = BSON("ns" << ns() << "name" << "z_1" << "key" << BSON("z" << 1)); + + std::string coll(strchr(ns(), '.')+1); + BSONObj cmd1 = BSON("dropIndexes" << coll << "index" << "*"); + + IndexBuildThread t1(indexOp1); + IndexBuildThread t2(indexOp2); + IndexBuildThread t3(indexOp3); + + t1.go(); + t2.go(); + t3.go(); + + ASSERT(!t1.curop()->killPending()); + ASSERT(!t2.curop()->killPending()); + ASSERT(!t3.curop()->killPending()); + + c->stopIndexBuilds(dbname, cmd1); + + sleepsecs(1); + + ASSERT(t1.finished()); + ASSERT(t2.finished()); + ASSERT(t3.finished()); + } + + void testDropIndexes2() { + drop(); + + std::string dbname = nsToDatabase(ns()); + Command *c = Command::findCommand("dropIndexes"); + + BSONObj indexOp1 = BSON("ns" << ns() << "name" << "x_1" << "key" << BSON("x" << 1)); + BSONObj indexOp2 = BSON("ns" << ns() << "name" << "y_1" << "key" << BSON("y" << 1)); + BSONObj indexOp3 = BSON("ns" << ns() << "name" << "z_1" << "key" << BSON("z" << 1)); + + std::string coll(strchr(ns(), '.')+1); + BSONObj cmd2 = BSON("dropIndexes" << coll << "index" << "y_1"); + + IndexBuildThread t1(indexOp1); + IndexBuildThread t2(indexOp2); + IndexBuildThread t3(indexOp3); + + t1.go(); + t2.go(); + t3.go(); + + ASSERT(!t1.curop()->killPending()); + ASSERT(!t2.curop()->killPending()); + ASSERT(!t3.curop()->killPending()); + + c->stopIndexBuilds(dbname, cmd2); + + sleepsecs(1); + + ASSERT(!t1.finished()); + ASSERT(t2.finished()); + ASSERT(!t3.finished()); + + t1.finish(); + t3.finish(); + } + + void testDropIndexes3() { + drop(); + + std::string dbname = nsToDatabase(ns()); + std::string coll(strchr(ns(), '.')+1); + BSONObj cmd3 = BSON("dropIndexes" << coll << "index" << BSON("z" << 1)); + Command *c = Command::findCommand("dropIndexes"); + + BSONObj indexOp1 = BSON("ns" << ns() << "name" << "x_1" << "key" << BSON("x" << 1)); + BSONObj indexOp2 = BSON("ns" << ns() << "name" << "y_1" << "key" << BSON("y" << 1)); + BSONObj indexOp3 = BSON("ns" << ns() << "name" << "z_1" << "key" << BSON("z" << 1)); + + IndexBuildThread t1(indexOp1); + IndexBuildThread t2(indexOp2); + IndexBuildThread t3(indexOp3); + + t1.go(); + t2.go(); + t3.go(); + + ASSERT(!t1.curop()->killPending()); + ASSERT(!t2.curop()->killPending()); + ASSERT(!t3.curop()->killPending()); + + c->stopIndexBuilds(dbname, cmd3); + + sleepsecs(1); + + ASSERT(!t1.finished()); + ASSERT(!t2.finished()); + ASSERT(t3.finished()); + + t1.finish(); + t2.finish(); + } + + public: + void run() { + testDropIndexes1(); + testDropIndexes2(); + testDropIndexes3(); + } + }; + + class TestRename : public Base { + public: + void run() { + drop(); + + std::string dbname = nsToDatabase(ns()); + std::string coll(strchr(ns(), '.')+1); + BSONObj cmdObj = BSON("renameCollection" << ns() << "to" << dbname+".bar"); + Command *c = Command::findCommand("renameCollection"); + + BSONObj indexOp1 = BSON("ns" << ns() << "name" << "x_1" << "key" << BSON("x" << 1)); + BSONObj indexOp2 = BSON("ns" << ns() << "name" << "y_1" << "key" << BSON("y" << 1)); + BSONObj indexOp3 = BSON("ns" << (dbname+".bar") << "name" << "z_1" << "key" << + BSON("z" << 1)); + BSONObj indexOp4 = BSON("ns" << ("x."+coll) << "name" << "z_1" << "key" << + BSON("z" << 1)); + + IndexBuildThread t1(indexOp1); + IndexBuildThread t2(indexOp2); + IndexBuildThread t3(indexOp3); + IndexBuildThread t4(indexOp4); + + t1.go(); + t2.go(); + t3.go(); + t4.go(); + + ASSERT(!t1.curop()->killPending()); + ASSERT(!t2.curop()->killPending()); + ASSERT(!t3.curop()->killPending()); + ASSERT(!t4.curop()->killPending()); + + std::vector<BSONObj> indexes = c->stopIndexBuilds(dbname, cmdObj); + + ASSERT(t1.finished()); + ASSERT(t2.finished()); + ASSERT(!t3.finished()); + ASSERT(!t4.finished()); + + t3.finish(); + t4.finish(); + + // Build indexes + IndexBuilder::restoreIndexes(dbname+".system.indexes", indexes); + + DBDirectClient cli; + time_t max = time(0)+10; + // assert.soon + while (time(0) < max) { + std::string ns = dbname+".system.indexes"; + if (cli.count(ns) == 3) { + return; + } + }; + + ASSERT(false); + } + }; + + class TestReIndex : public Base { + public: + void run() { + drop(); + + std::string dbname = nsToDatabase(ns()); + std::string coll(strchr(ns(), '.')+1); + BSONObj cmdObj = BSON("reIndex" << coll); + Command *c = Command::findCommand("reIndex"); + + DBDirectClient cli; + int originalIndexCount = cli.count(dbname+".system.indexes"); + + BSONObj indexOp1 = BSON("ns" << ns() << "name" << "foo_1" << + "key" << BSON("foo" << 1)); + BSONObj indexOp2 = BSON("ns" << (dbname+".something.else") << "name" << "baz_1" << + "key" << BSON("baz" << 1)); + + IndexBuildThread t1(indexOp1); + IndexBuildThread t2(indexOp2); + + t1.go(); + t2.go(); + + ASSERT(!t1.curop()->killPending()); + ASSERT(!t2.curop()->killPending()); + + std::vector<BSONObj> indexes = c->stopIndexBuilds(dbname, cmdObj); + ASSERT_EQUALS(1U, indexes.size()); + IndexBuilder::restoreIndexes(dbname+".system.indexes", indexes); + + ASSERT(!t2.finished()); + t2.finish(); + + time_t max = time(0)+10; + // assert.soon(t1.finished()) + while (time(0) < max) { + std::string ns = dbname+".system.indexes"; + if (static_cast<int>(cli.count(ns)) == originalIndexCount+2) { + return; + } + }; + + ASSERT(false); + } + }; + + class TestTruncateCapped : public Base { + public: + void run() { + drop(); + + std::string dbname = nsToDatabase(ns()); + std::string coll(strchr(ns(), '.')+1); + BSONObj cmdObj = BSON("emptycapped" << coll); + Command *c = Command::findCommand("emptycapped"); + + BSONObj indexOp1 = BSON("ns" << ns() << "name" << "foo_1" << + "key" << BSON("foo" << 1)); + BSONObj indexOp2 = BSON("ns" << (dbname+".something.else") << "name" << "baz_1" << + "key" << BSON("baz" << 1)); + + IndexBuildThread t1(indexOp1); + IndexBuildThread t2(indexOp2); + + t1.go(); + t2.go(); + + ASSERT(!t1.curop()->killPending()); + ASSERT(!t2.curop()->killPending()); + + c->stopIndexBuilds(dbname, cmdObj); + + sleepsecs(1); + + ASSERT(t1.finished()); + ASSERT(!t2.finished()); + + t2.finish(); + } + }; + + class TestCompact : public Base { + public: + void run() { + drop(); + + std::string dbname = nsToDatabase(ns()); + std::string coll(strchr(ns(), '.')+1); + BSONObj cmdObj = BSON("compact" << coll); + Command *c = Command::findCommand("compact"); + DBDirectClient cli; + int originalIndexCount = cli.count(dbname+".system.indexes"); + + BSONObj indexOp1 = BSON("ns" << ns() << "name" << "foo_1" << + "key" << BSON("foo" << 1)); + BSONObj indexOp2 = BSON("ns" << (dbname+".something.else") << "name" << "baz_1" << + "key" << BSON("baz" << 1)); + + IndexBuildThread t1(indexOp1); + IndexBuildThread t2(indexOp2); + + t1.go(); + t2.go(); + + ASSERT(!t1.curop()->killPending()); + ASSERT(!t2.curop()->killPending()); + + std::vector<BSONObj> indexes = c->stopIndexBuilds(dbname, cmdObj); + IndexBuilder::restoreIndexes(dbname+".system.indexes", indexes); + + ASSERT(!t2.finished()); + t2.finish(); + + time_t max = time(0)+10; + // assert.soon(t1.finished()) + while (time(0) < max) { + std::string ns = dbname+".system.indexes"; + if (static_cast<int>(cli.count(ns)) == originalIndexCount+2) { + return; + } + }; + + ASSERT(false); + } + }; + + class TestRepair : public Base { + public: + void run() { + drop(); + + std::string dbname = nsToDatabase(ns()); + Command *c = Command::findCommand("repairDatabase"); + BSONObj cmdObj = BSON("repairDatabase" << 1); + DBDirectClient cli; + int originalIndexCount = cli.count(dbname+".system.indexes"); + + BSONObj indexOp1 = BSON("ns" << ns() << "name" << "foo_1" << "key" << + BSON("foo" << 1)); + BSONObj indexOp2 = BSON("ns" << (dbname+".something.else") << "name" << "baz_1" << + "key" << BSON("baz" << 1)); + // Different database - foo + BSONObj indexOp3 = BSON("ns" << "foo.something.else" << "name" << "baz_1" << + "key" << BSON("baz" << 1)); + // Different database - unittestsx + BSONObj indexOp4 = BSON("ns" << (dbname+"x.something.else") << "name" << "baz_1" << + "key" << BSON("baz" << 1)); + // Different database - xunittests + BSONObj indexOp5 = BSON("ns" << ("x"+dbname+".something.else") << "name" << "baz_1" << + "key" << BSON("baz" << 1)); + + IndexBuildThread t1(indexOp1); + IndexBuildThread t2(indexOp2); + IndexBuildThread t3(indexOp3); + IndexBuildThread t4(indexOp4); + IndexBuildThread t5(indexOp5); + + t1.go(); + t2.go(); + t3.go(); + t4.go(); + t5.go(); + + ASSERT(!t1.curop()->killPending()); + ASSERT(!t2.curop()->killPending()); + ASSERT(!t3.curop()->killPending()); + ASSERT(!t4.curop()->killPending()); + ASSERT(!t5.curop()->killPending()); + + std::vector<BSONObj> indexes = c->stopIndexBuilds(dbname, cmdObj); + + sleepsecs(1); + + ASSERT(t1.finished()); + ASSERT(t2.finished()); + ASSERT(!t3.finished()); + ASSERT(!t4.finished()); + ASSERT(!t5.finished()); + + IndexBuilder::restoreIndexes(dbname+".system.indexes", indexes); + + ASSERT(!t3.finished()); + ASSERT(!t4.finished()); + ASSERT(!t5.finished()); + + t3.finish(); + t4.finish(); + t5.finish(); + + time_t max = time(0)+10; + // assert.soon(t1.finished() && t2.finished()) + while (time(0) < max) { + std::string ns = dbname+".system.indexes"; + if (static_cast<int>(cli.count(ns)) == originalIndexCount+4) { + return; + } + }; + + ASSERT(false); + } + }; + class MockInitialSync : public replset::InitialSync { int step; public: @@ -545,6 +1098,13 @@ namespace ReplSetTests { add< CappedUpdate >(); add< CappedInsert >(); add< TestRSSync >(); + add< TestDropDB >(); + add< TestDrop >(); + add< TestDropIndexes >(); + add< TestTruncateCapped >(); + add< TestReIndex >(); + add< TestRepair >(); + add< TestCompact >(); } } myall; } |