/**
* Copyright (C) 2014 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 .
*
* 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.
*/
#pragma once
#include
#include "mongo/db/concurrency/fast_map_noalloc.h"
#include "mongo/db/concurrency/locker.h"
#include "mongo/util/concurrency/spin_lock.h"
namespace mongo {
/**
* Notfication callback, which stores the last notification result and signals a condition
* variable, which can be waited on.
*/
class CondVarLockGrantNotification : public LockGrantNotification {
MONGO_DISALLOW_COPYING(CondVarLockGrantNotification);
public:
CondVarLockGrantNotification();
/**
* Clears the object so it can be reused.
*/
void clear();
/**
* Uninterruptible blocking method, which waits for the notification to fire.
*
* @param timeoutMs How many milliseconds to wait before returning LOCK_TIMEOUT.
*/
LockResult wait(unsigned timeoutMs);
private:
virtual void notify(ResourceId resId, LockResult result);
// These two go together to implement the conditional variable pattern.
stdx::mutex _mutex;
stdx::condition_variable _cond;
// Result from the last call to notify
LockResult _result;
};
/**
* Interface for acquiring locks. One of those objects will have to be instantiated for each
* request (transaction).
*
* Lock/unlock methods must always be called from a single thread.
*
* All instances reference a single global lock manager.
*
* @param IsForMMAPV1 Whether to compile-in the flush lock functionality, which is specific to
* the way the MMAP V1 (legacy) storag engine does commit concurrency control.
*/
template
class LockerImpl : public Locker {
public:
/**
* Instantiates new locker. Must be given a unique identifier for disambiguation. Lockers
* having the same identifier will not conflict on lock acquisition.
*/
LockerImpl();
virtual ~LockerImpl();
virtual LockerId getId() const {
return _id;
}
virtual LockResult lockGlobal(LockMode mode, unsigned timeoutMs = UINT_MAX);
virtual LockResult lockGlobalBegin(LockMode mode);
virtual LockResult lockGlobalComplete(unsigned timeoutMs);
virtual void lockMMAPV1Flush();
virtual void downgradeGlobalXtoSForMMAPV1();
virtual bool unlockAll();
virtual void beginWriteUnitOfWork();
virtual void endWriteUnitOfWork();
virtual bool inAWriteUnitOfWork() const {
return _wuowNestingLevel > 0;
}
virtual LockResult lock(ResourceId resId,
LockMode mode,
unsigned timeoutMs = UINT_MAX,
bool checkDeadlock = false);
virtual void downgrade(ResourceId resId, LockMode newMode);
virtual bool unlock(ResourceId resId);
virtual LockMode getLockMode(ResourceId resId) const;
virtual bool isLockHeldForMode(ResourceId resId, LockMode mode) const;
virtual bool isDbLockedForMode(StringData dbName, LockMode mode) const;
virtual bool isCollectionLockedForMode(StringData ns, LockMode mode) const;
virtual ResourceId getWaitingResource() const;
virtual void getLockerInfo(LockerInfo* lockerInfo) const;
virtual bool saveLockStateAndUnlock(LockSnapshot* stateOut);
virtual void restoreLockState(const LockSnapshot& stateToRestore);
/**
* Allows for lock requests to be requested in a non-blocking way. There can be only one
* outstanding pending lock request per locker object.
*
* lockBegin posts a request to the lock manager for the specified lock to be acquired,
* which either immediately grants the lock, or puts the requestor on the conflict queue
* and returns immediately with the result of the acquisition. The result can be one of:
*
* LOCK_OK - Nothing more needs to be done. The lock is granted.
* LOCK_WAITING - The request has been queued up and will be granted as soon as the lock
* is free. If this result is returned, typically lockComplete needs to be called in
* order to wait for the actual grant to occur. If the caller no longer needs to wait
* for the grant to happen, unlock needs to be called with the same resource passed
* to lockBegin.
*
* In other words for each call to lockBegin, which does not return LOCK_OK, there needs to
* be a corresponding call to either lockComplete or unlock.
*
* NOTE: These methods are not public and should only be used inside the class
* implementation and for unit-tests and not called directly.
*/
LockResult lockBegin(ResourceId resId, LockMode mode);
/**
* Waits for the completion of a lock, previously requested through lockBegin or
* lockGlobalBegin. Must only be called, if lockBegin returned LOCK_WAITING.
*
* @param resId Resource id which was passed to an earlier lockBegin call. Must match.
* @param mode Mode which was passed to an earlier lockBegin call. Must match.
* @param timeoutMs How long to wait for the lock acquisition to complete.
* @param checkDeadlock whether to perform deadlock detection while waiting.
*/
LockResult lockComplete(ResourceId resId,
LockMode mode,
unsigned timeoutMs,
bool checkDeadlock);
private:
friend class AutoYieldFlushLockForMMAPV1Commit;
typedef FastMapNoAlloc LockRequestsMap;
/**
* The main functionality of the unlock method, except accepts iterator in order to avoid
* additional lookups during unlockAll.
*/
bool _unlockImpl(LockRequestsMap::Iterator& it);
/**
* MMAP V1 locking code yields and re-acquires the flush lock occasionally in order to
* allow the flush thread proceed. This call returns in what mode the flush lock should be
* acquired. It is based on the type of the operation (IS for readers, IX for writers).
*/
LockMode _getModeForMMAPV1FlushLock() const;
// Used to disambiguate different lockers
const LockerId _id;
// The only reason we have this spin lock here is for the diagnostic tools, which could
// iterate through the LockRequestsMap on a separate thread and need it to be stable.
// Apart from that, all accesses to the LockerImpl are always from a single thread.
//
// This has to be locked inside const methods, hence the mutable.
mutable SpinLock _lock;
LockRequestsMap _requests;
// Reuse the notification object across requests so we don't have to create a new mutex
// and condition variable every time.
CondVarLockGrantNotification _notify;
// Per-locker locking statistics. Reported in the slow-query log message and through
// db.currentOp. Complementary to the per-instance locking statistics.
SingleThreadedLockStats _stats;
// Delays release of exclusive/intent-exclusive locked resources until the write unit of
// work completes. Value of 0 means we are not inside a write unit of work.
int _wuowNestingLevel;
std::queue _resourcesToUnlockAtEndOfUnitOfWork;
// Mode for which the Locker acquired a ticket, or MODE_NONE if no ticket was acquired.
LockMode _modeForTicket = MODE_NONE;
//////////////////////////////////////////////////////////////////////////////////////////
//
// Methods merged from LockState, which should eventually be removed or changed to methods
// on the LockerImpl interface.
//
public:
virtual void dump() const;
virtual bool isW() const;
virtual bool isR() const;
virtual bool isLocked() const;
virtual bool isWriteLocked() const;
virtual bool isReadLocked() const;
virtual void assertEmptyAndReset();
virtual bool hasLockPending() const {
return getWaitingResource().isValid();
}
virtual void setIsBatchWriter(bool newValue) {
_batchWriter = newValue;
}
virtual bool isBatchWriter() const {
return _batchWriter;
}
virtual bool hasStrongLocks() const;
private:
bool _batchWriter;
};
typedef LockerImpl DefaultLockerImpl;
typedef LockerImpl MMAPV1LockerImpl;
/**
* At global synchronization points, such as drop database we are running under a global
* exclusive lock and without an active write unit of work, doing changes which require global
* commit. This utility allows the flush lock to be temporarily dropped so the flush thread
* could run in such circumstances. Should not be used where write units of work are used,
* because these have different mechanism of yielding the flush lock.
*/
class AutoYieldFlushLockForMMAPV1Commit {
public:
AutoYieldFlushLockForMMAPV1Commit(Locker* locker);
~AutoYieldFlushLockForMMAPV1Commit();
private:
MMAPV1LockerImpl* const _locker;
};
/**
* This explains how the MMAP V1 durability system is implemented.
*
* Every server operation (OperationContext), must call Locker::lockGlobal as the first lock
* action (it is illegal to acquire any other locks without calling this first). This action
* acquires the global and flush locks in the appropriate modes (IS for read operations, IX
* for write operations). Having the flush lock in one of these modes indicates to the flush
* thread that there is an active reader or writer.
*
* Whenever the flush thread(dur.cpp) activates, it goes through the following steps :
*
* Acquire the flush lock in S mode using AutoAcquireFlushLockForMMAPV1Commit. This waits until
* all current write activity on the system completes and does not allow any new operations to
* start.
*
* Once the S lock is granted, the flush thread writes the journal entries to disk (it is
* guaranteed that there will not be any modifications) and applies them to the shared view.
*
* After that, it upgrades the S lock to X and remaps the private view.
*
* NOTE: There should be only one usage of this class and this should be in dur.cpp
*/
class AutoAcquireFlushLockForMMAPV1Commit {
public:
AutoAcquireFlushLockForMMAPV1Commit(Locker* locker);
~AutoAcquireFlushLockForMMAPV1Commit();
/**
* We need the exclusive lock in order to do the shared view remap.
*/
void upgradeFlushLockToExclusive();
/**
* Allows the acquired flush lock to be prematurely released. This is helpful for the case
* where we know that we won't be doing a remap after gathering the write intents, so the
* rest can be done outside of flush lock.
*/
void release();
private:
Locker* const _locker;
bool _released;
};
/**
* Retrieves the global lock manager instance.
*/
LockManager* getGlobalLockManager();
} // namespace mongo