Database* DatabaseHolderImpl::openDb(OperationContext* opCtx, StringData ns, bool* justCreated) { const StringData dbname = _todb(ns); invariant(opCtx->lockState()->isDbLockedForMode(dbname, MODE_X)); if (justCreated) *justCreated = false; // Until proven otherwise. stdx::unique_lock<SimpleMutex> lk(_m); // The following will insert a nullptr for dbname, which will treated the same as a non- // existant database by the get method, yet still counts in getNamesWithConflictingCasing. if (auto db = _dbs[dbname]) return db; // We've inserted a nullptr entry for dbname: make sure to remove it on unsuccessful exit. auto removeDbGuard = makeGuard([this, &lk, dbname] { if (!lk.owns_lock()) lk.lock(); _dbs.erase(dbname); }); // Check casing in lock to avoid transient duplicates. auto duplicates = _getNamesWithConflictingCasing_inlock(dbname); uassert(ErrorCodes::DatabaseDifferCase, str::stream() << "db already exists with different case already have: [" << *duplicates.cbegin() << "] trying to create [" << dbname.toString() << "]", duplicates.empty()); // Do the catalog lookup and database creation outside of the scoped lock, because these may // block. Only one thread can be inside this method for the same DB name, because of the // requirement for X-lock on the database when we enter. So there is no way we can insert two // different databases for the same name. lk.unlock(); StorageEngine* storageEngine = getGlobalServiceContext()->getStorageEngine(); DatabaseCatalogEntry* entry = storageEngine->getDatabaseCatalogEntry(opCtx, dbname); if (!entry->exists()) { audit::logCreateDatabase(opCtx->getClient(), dbname); if (justCreated) *justCreated = true; } auto newDb = stdx::make_unique<DatabaseImpl>(dbname, entry, ++_epoch); newDb->init(opCtx); // Finally replace our nullptr entry with the new Database pointer. removeDbGuard.dismiss(); lk.lock(); auto it = _dbs.find(dbname); invariant(it != _dbs.end() && it->second == nullptr); it->second = newDb.release(); invariant(_getNamesWithConflictingCasing_inlock(dbname.toString()).empty()); return it->second; }
Database* DatabaseHolder::openDb(OperationContext* txn, StringData ns, bool* justCreated) { const StringData dbname = _todb(ns); invariant(txn->lockState()->isDbLockedForMode(dbname, MODE_X)); Database* db = get(txn, ns); if (db) { if (justCreated) { *justCreated = false; } return db; } // Check casing const string duplicate = Database::duplicateUncasedName(dbname.toString()); if (!duplicate.empty()) { stringstream ss; ss << "db already exists with different case already have: [" << duplicate << "] trying to create [" << dbname.toString() << "]"; uasserted(DatabaseDifferCaseCode, ss.str()); } StorageEngine* storageEngine = getGlobalEnvironment()->getGlobalStorageEngine(); invariant(storageEngine); DatabaseCatalogEntry* entry = storageEngine->getDatabaseCatalogEntry(txn, dbname); invariant(entry); const bool exists = entry->exists(); if (!exists) { audit::logCreateDatabase(currentClient.get(), dbname); } if (justCreated) { *justCreated = !exists; } // Do this outside of the scoped lock, because database creation does transactional // operations which may block. Only one thread can be inside this method for the same DB // name, because of the requirement for X-lock on the database when we enter. So there is // no way we can insert two different databases for the same name. db = new Database(txn, dbname, entry); SimpleMutex::scoped_lock lk(_m); _dbs[dbname] = db; return db; }
Database* DatabaseHolder::getOrCreate(OperationContext* txn, const string& ns, bool& justCreated) { const string dbname = _todb( ns ); invariant(txn->lockState()->isAtLeastReadLocked(dbname)); if (txn->lockState()->isWriteLocked() && FileAllocator::get()->hasFailed()) { uassert(17507, "Can't take a write lock while out of disk space", false); } { SimpleMutex::scoped_lock lk(_m); { DBs::iterator i = _dbs.find(dbname); if( i != _dbs.end() ) { justCreated = false; return i->second; } } // todo: protect against getting sprayed with requests for different db names that DNE - // that would make the DBs map very large. not clear what to do to handle though, // perhaps just log it, which is what we do here with the "> 40" : bool cant = !txn->lockState()->isWriteLocked(ns); if( logger::globalLogDomain()->shouldLog(logger::LogSeverity::Debug(1)) || _dbs.size() > 40 || cant || DEBUG_BUILD ) { log() << "opening db: " << dbname; } massert(15927, "can't open database in a read lock. if db was just closed, consider retrying the query. might otherwise indicate an internal error", !cant); } // we mark our thread as having done writes now as we do not want any exceptions // once we start creating a new database cc().writeHappened(); // this locks _m for defensive checks, so we don't want to be locked right here : StorageEngine* storageEngine = getGlobalEnvironment()->getGlobalStorageEngine(); invariant(storageEngine); DatabaseCatalogEntry* entry = storageEngine->getDatabaseCatalogEntry( txn, dbname ); invariant( entry ); justCreated = !entry->exists(); Database *db = new Database(txn, dbname, entry ); { SimpleMutex::scoped_lock lk(_m); _dbs[dbname] = db; } return db; }
Database* DatabaseHolder::getOrCreate(OperationContext* txn, const StringData& ns, bool& justCreated) { const StringData dbname = _todb( ns ); invariant(txn->lockState()->isAtLeastReadLocked(dbname)); Database* db = get(txn, ns); if (db) { justCreated = false; return db; } // todo: protect against getting sprayed with requests for different db names that DNE - // that would make the DBs map very large. not clear what to do to handle though, // perhaps just log it, which is what we do here with the "> 40" : bool cant = !txn->lockState()->isWriteLocked(ns); if( logger::globalLogDomain()->shouldLog(logger::LogSeverity::Debug(1)) || _dbs.size() > 40 || cant || DEBUG_BUILD ) { log() << "opening db: " << dbname; } massert(15927, "can't open database in a read lock. if db was just closed, consider retrying the query. might otherwise indicate an internal error", !cant); const string duplicate = Database::duplicateUncasedName(dbname.toString()); if ( !duplicate.empty() ) { stringstream ss; ss << "db already exists with different case already have: [" << duplicate << "] trying to create [" << dbname.toString() << "]"; uasserted( DatabaseDifferCaseCode , ss.str() ); } StorageEngine* storageEngine = getGlobalEnvironment()->getGlobalStorageEngine(); invariant(storageEngine); DatabaseCatalogEntry* entry = storageEngine->getDatabaseCatalogEntry(txn, dbname); invariant(entry); justCreated = !entry->exists(); db = new Database(dbname, entry); { SimpleMutex::scoped_lock lk(_m); _dbs[dbname] = db; } return db; }