/**
* 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
#include
#include "mongo/base/string_data.h"
#include "mongo/config.h"
#include "mongo/platform/cstdint.h"
#include "mongo/platform/hash_namespace.h"
namespace mongo {
class Locker;
struct LockHead;
struct PartitionedLockHead;
/**
* Lock modes.
*
* Compatibility Matrix
* Granted mode
* ---------------.--------------------------------------------------------.
* Requested Mode | MODE_NONE MODE_IS MODE_IX MODE_S MODE_X |
* MODE_IS | + + + + - |
* MODE_IX | + + + - - |
* MODE_S | + + - + - |
* MODE_X | + - - - - |
*/
enum LockMode {
MODE_NONE = 0,
MODE_IS = 1,
MODE_IX = 2,
MODE_S = 3,
MODE_X = 4,
// Counts the lock modes. Used for array size allocations, etc. Always insert new lock
// modes above this entry.
LockModesCount
};
/**
* Returns a human-readable name for the specified lock mode.
*/
const char* modeName(LockMode mode);
/**
* Legacy lock mode names in parity for 2.6 reports.
*/
const char* legacyModeName(LockMode mode);
/**
* Mode A is covered by mode B if the set of conflicts for mode A is a subset of the set of
* conflicts for mode B. For example S is covered by X. IS is covered by S. However, IX is not
* covered by S or IS.
*/
bool isModeCovered(LockMode mode, LockMode coveringMode);
/**
* Returns whether the passed in mode is S or IS. Used for validation checks.
*/
inline bool isSharedLockMode(LockMode mode) {
return (mode == MODE_IS || mode == MODE_S);
}
/**
* Return values for the locking functions of the lock manager.
*/
enum LockResult {
/**
* The lock request was granted and is now on the granted list for the specified resource.
*/
LOCK_OK,
/**
* The lock request was not granted because of conflict. If this value is returned, the
* request was placed on the conflict queue of the specified resource and a call to the
* LockGrantNotification::notify callback should be expected with the resource whose lock
* was requested.
*/
LOCK_WAITING,
/**
* The lock request waited, but timed out before it could be granted. This value is never
* returned by the LockManager methods here, but by the Locker class, which offers
* capability to block while waiting for locks.
*/
LOCK_TIMEOUT,
/**
* The lock request was not granted because it would result in a deadlock. No changes to
* the state of the Locker would be made if this value is returned (i.e., it will not be
* killed due to deadlock). It is up to the caller to decide how to recover from this
* return value - could be either release some locks and try again, or just bail with an
* error and have some upper code handle it.
*/
LOCK_DEADLOCK,
/**
* This is used as an initialiser value. Should never be returned.
*/
LOCK_INVALID
};
/**
* Hierarchy of resource types. The lock manager knows nothing about this hierarchy, it is
* purely logical. Resources of different types will never conflict with each other.
*
* While the lock manager does not know or care about ordering, the general policy is that
* resources are acquired in the order below. For example, one might first acquire a
* RESOURCE_GLOBAL and then the desired RESOURCE_DATABASE, both using intent modes, and
* finally a RESOURCE_COLLECTION in exclusive mode. When locking multiple resources of the
* same type, the canonical order is by resourceId order.
*
* It is OK to lock resources out of order, but it is the users responsibility to ensure
* ordering is consistent so deadlock cannot occur.
*/
enum ResourceType {
// Types used for special resources, use with a hash id from ResourceId::SingletonHashIds.
RESOURCE_INVALID = 0,
RESOURCE_GLOBAL, // Used for mode changes or global exclusive operations
RESOURCE_MMAPV1_FLUSH, // Necessary only for the MMAPv1 engine
// Generic resources
RESOURCE_DATABASE,
RESOURCE_COLLECTION,
RESOURCE_METADATA,
// Counts the rest. Always insert new resource types above this entry.
ResourceTypesCount
};
/**
* Returns a human-readable name for the specified resource type.
*/
const char* resourceTypeName(ResourceType resourceType);
/**
* Uniquely identifies a lockable resource.
*/
class ResourceId {
// We only use 3 bits for the resource type in the ResourceId hash
enum {resourceTypeBits = 3};
BOOST_STATIC_ASSERT(ResourceTypesCount <= (1 << resourceTypeBits));
public:
/**
* Assign hash ids for special resources to avoid accidental reuse of ids. For ids used
* with the same ResourceType, the order here must be the same as the locking order.
*/
enum SingletonHashIds {
SINGLETON_INVALID = 0,
SINGLETON_PARALLEL_BATCH_WRITER_MODE,
SINGLETON_GLOBAL,
SINGLETON_MMAPV1_FLUSH
};
ResourceId() : _fullHash(0) { }
ResourceId(ResourceType type, StringData ns);
ResourceId(ResourceType type, const std::string& ns);
ResourceId(ResourceType type, uint64_t hashId);
bool isValid() const { return getType() != RESOURCE_INVALID; }
operator uint64_t() const {
return _fullHash;
}
// This defines the canonical locking order, first by type and then hash id
bool operator<(const ResourceId& rhs) const {
return _fullHash < rhs._fullHash;
}
ResourceType getType() const {
return static_cast(_fullHash >> (64 - resourceTypeBits));
}
uint64_t getHashId() const {
return _fullHash & (std::numeric_limits::max() >> resourceTypeBits);
}
std::string toString() const;
private:
/**
* The top 'resourceTypeBits' bits of '_fullHash' represent the resource type,
* while the remaining bits contain the bottom bits of the hashId. This avoids false
* conflicts between resources of different types, which is necessary to prevent deadlocks.
*/
uint64_t _fullHash;
static uint64_t fullHash(ResourceType type, uint64_t hashId);
#ifdef MONGO_CONFIG_DEBUG_BUILD
// Keep the complete namespace name for debugging purposes (TODO: this will be
// removed once we are confident in the robustness of the lock manager).
std::string _nsCopy;
#endif
};
#ifndef MONGO_CONFIG_DEBUG_BUILD
// Treat the resource ids as 64-bit integers in release mode in order to ensure we do
// not spend too much time doing comparisons for hashing.
BOOST_STATIC_ASSERT(sizeof(ResourceId) == sizeof(uint64_t));
#endif
// Type to uniquely identify a given locker object
typedef uint64_t LockerId;
// Hardcoded resource id for the oplog collection, which is special-cased both for resource
// acquisition purposes and for statistics reporting.
extern const ResourceId resourceIdLocalDB;
extern const ResourceId resourceIdOplog;
// Hardcoded resource id for admin db. This is to ensure direct writes to auth collections
// are serialized (see SERVER-16092)
extern const ResourceId resourceIdAdminDB;
// Hardcoded resource id for ParallelBatchWriterMode. We use the same resource type
// as resourceIdGlobal. This will also ensure the waits are reported as global, which
// is appropriate. The lock will never be contended unless the parallel batch writers
// must stop all other accesses globally. This resource must be locked before all other
// resources (including resourceIdGlobal). Replication applier threads don't take this
// lock.
// TODO: Merge this with resourceIdGlobal
extern const ResourceId resourceIdParallelBatchWriterMode;
/**
* Interface on which granted lock requests will be notified. See the contract for the notify
* method for more information and also the LockManager::lock call.
*
* The default implementation of this method would simply block on an event until notify has
* been invoked (see CondVarLockGrantNotification).
*
* Test implementations could just count the number of notifications and their outcome so that
* they can validate locks are granted as desired and drive the test execution.
*/
class LockGrantNotification {
public:
virtual ~LockGrantNotification() {}
/**
* This method is invoked at most once for each lock request and indicates the outcome of
* the lock acquisition for the specified resource id.
*
* Cases where it won't be called are if a lock acquisition (be it in waiting or converting
* state) is cancelled through a call to unlock.
*
* IMPORTANT: This callback runs under a spinlock for the lock manager, so the work done
* inside must be kept to a minimum and no locks or operations which may block
* should be run. Also, no methods which call back into the lock manager should
* be invoked from within this methods (LockManager is not reentrant).
*
* @resId ResourceId for which a lock operation was previously called.
* @result Outcome of the lock operation.
*/
virtual void notify(ResourceId resId, LockResult result) = 0;
};
/**
* There is one of those entries per each request for a lock. They hang on a linked list off
* the LockHead or off a PartitionedLockHead and also are in a map for each Locker. This
* structure is not thread-safe.
*
* LockRequest are owned by the Locker class and it controls their lifetime. They should not
* be deleted while on the LockManager though (see the contract for the lock/unlock methods).
*/
struct LockRequest {
enum Status {
STATUS_NEW,
STATUS_GRANTED,
STATUS_WAITING,
STATUS_CONVERTING,
// Counts the rest. Always insert new status types above this entry.
StatusCount
};
/**
* Used for initialization of a LockRequest, which might have been retrieved from cache.
*/
void initNew(Locker* locker, LockGrantNotification* notify);
//
// These fields are maintained by the Locker class
//
// This is the Locker, which created this LockRequest. Pointer is not owned, just
// referenced. Must outlive the LockRequest.
Locker* locker;
// Not owned, just referenced. If a request is in the WAITING or CONVERTING state, must
// live at least until LockManager::unlock is cancelled or the notification has been
// invoked.
LockGrantNotification* notify;
//
// These fields are maintained by both the LockManager and Locker class
//
// If the request cannot be granted right away, whether to put it at the front or at the
// end of the queue. By default, requests are put at the back. If a request is requested
// to be put at the front, this effectively bypasses fairness. Default is FALSE.
bool enqueueAtFront;
// When this request is granted and as long as it is on the granted queue, the particular
// resource's policy will be changed to "compatibleFirst". This means that even if there
// are pending requests on the conflict queue, if a compatible request comes in it will be
// granted immediately. This effectively turns off fairness.
bool compatibleFirst;
// When set, an attempt is made to execute this request using partitioned lockheads.
// This speeds up the common case where all requested locking modes are compatible with
// each other, at the cost of extra overhead for conflicting modes.
bool partitioned;
// How many times has LockManager::lock been called for this request. Locks are released
// when their recursive count drops to zero.
unsigned recursiveCount;
//
// These fields are owned and maintained by the LockManager class exclusively
//
// Pointer to the lock to which this request belongs, or null if this request has not yet
// been assigned to a lock or if it belongs to the PartitionedLockHead for locker. The
// LockHead should be alive as long as there are LockRequests on it, so it is safe to have
// this pointer hanging around.
LockHead* lock;
// Pointer to the partitioned lock to which this request belongs, or null if it is not
// partitioned. Only one of 'lock' and 'partitionedLock' is non-NULL, and a request can
// only transition from 'partitionedLock' to 'lock', never the other way around.
PartitionedLockHead* partitionedLock;
// The reason intrusive linked list is used instead of the std::list class is to allow
// for entries to be removed from the middle of the list in O(1) time, if they are known
// instead of having to search for them and we cannot persist iterators, because the list
// can be modified while an iterator is held.
LockRequest* prev;
LockRequest* next;
// Current status of this request.
Status status;
// If not granted, the mode which has been requested for this lock. If granted, the mode
// in which it is currently granted.
LockMode mode;
// This value is different from MODE_NONE only if a conversion is requested for a lock and
// that conversion cannot be immediately granted.
LockMode convertMode;
};
/**
* Returns a human readable status name for the specified LockRequest status.
*/
const char* lockRequestStatusName(LockRequest::Status status);
} // namespace mongo
MONGO_HASH_NAMESPACE_START
template <> struct hash {
size_t operator()(const mongo::ResourceId& resource) const {
return resource;
}
};
MONGO_HASH_NAMESPACE_END