diff options
Diffstat (limited to 'src/mongo/client/distlock.h')
| -rw-r--r-- | src/mongo/client/distlock.h | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/src/mongo/client/distlock.h b/src/mongo/client/distlock.h new file mode 100644 index 00000000000..70aa3bd158c --- /dev/null +++ b/src/mongo/client/distlock.h @@ -0,0 +1,256 @@ +// distlock.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "mongo/pch.h" +#include "connpool.h" +#include "syncclusterconnection.h" + +#define LOCK_TIMEOUT (15 * 60 * 1000) +#define LOCK_SKEW_FACTOR (30) +#define LOCK_PING (LOCK_TIMEOUT / LOCK_SKEW_FACTOR) +#define MAX_LOCK_NET_SKEW (LOCK_TIMEOUT / LOCK_SKEW_FACTOR) +#define MAX_LOCK_CLOCK_SKEW (LOCK_TIMEOUT / LOCK_SKEW_FACTOR) +#define NUM_LOCK_SKEW_CHECKS (3) + +// The maximum clock skew we need to handle between config servers is +// 2 * MAX_LOCK_NET_SKEW + MAX_LOCK_CLOCK_SKEW. + +// Net effect of *this* clock being slow is effectively a multiplier on the max net skew +// and a linear increase or decrease of the max clock skew. + +namespace mongo { + + /** + * Exception class to encapsulate exceptions while managing distributed locks + */ + class LockException : public DBException { + public: + LockException( const char * msg , int code ) : DBException( msg, code ) {} + LockException( const string& msg, int code ) : DBException( msg, code ) {} + virtual ~LockException() throw() { } + }; + + /** + * Indicates an error in retrieving time values from remote servers. + */ + class TimeNotFoundException : public LockException { + public: + TimeNotFoundException( const char * msg , int code ) : LockException( msg, code ) {} + TimeNotFoundException( const string& msg, int code ) : LockException( msg, code ) {} + virtual ~TimeNotFoundException() throw() { } + }; + + /** + * The distributed lock is a configdb backed way of synchronizing system-wide tasks. A task must be identified by a + * unique name across the system (e.g., "balancer"). A lock is taken by writing a document in the configdb's locks + * collection with that name. + * + * To be maintained, each taken lock needs to be revalidaded ("pinged") within a pre-established amount of time. This + * class does this maintenance automatically once a DistributedLock object was constructed. + */ + class DistributedLock { + public: + + static LabeledLevel logLvl; + + struct PingData { + + PingData( const string& _id , Date_t _lastPing , Date_t _remote , OID _ts ) + : id(_id), lastPing(_lastPing), remote(_remote), ts(_ts){ + } + + PingData() + : id(""), lastPing(0), remote(0), ts(){ + } + + string id; + Date_t lastPing; + Date_t remote; + OID ts; + }; + + class LastPings { + public: + LastPings() : _mutex( "DistributedLock::LastPings" ) {} + ~LastPings(){} + + PingData getLastPing( const ConnectionString& conn, const string& lockName ); + void setLastPing( const ConnectionString& conn, const string& lockName, const PingData& pd ); + + mongo::mutex _mutex; + map< std::pair<string, string>, PingData > _lastPings; + }; + + static LastPings lastPings; + + /** + * The constructor does not connect to the configdb yet and constructing does not mean the lock was acquired. + * Construction does trigger a lock "pinging" mechanism, though. + * + * @param conn address of config(s) server(s) + * @param name identifier for the lock + * @param lockTimeout how long can the log go "unpinged" before a new attempt to lock steals it (in minutes). + * @param lockPing how long to wait between lock pings + * @param legacy use legacy logic + * + */ + DistributedLock( const ConnectionString& conn , const string& name , unsigned long long lockTimeout = 0, bool asProcess = false ); + ~DistributedLock(){}; + + /** + * Attempts to acquire 'this' lock, checking if it could or should be stolen from the previous holder. Please + * consider using the dist_lock_try construct to acquire this lock in an exception safe way. + * + * @param why human readable description of why the lock is being taken (used to log) + * @param whether this is a lock re-entry or a new lock + * @param other configdb's lock document that is currently holding the lock, if lock is taken, or our own lock + * details if not + * @return true if it managed to grab the lock + */ + bool lock_try( const string& why , bool reenter = false, BSONObj * other = 0 ); + + /** + * Releases a previously taken lock. + */ + void unlock( BSONObj* oldLockPtr = NULL ); + + Date_t getRemoteTime(); + + bool isRemoteTimeSkewed(); + + const string& getProcessId(); + + const ConnectionString& getRemoteConnection(); + + /** + * Check the skew between a cluster of servers + */ + static bool checkSkew( const ConnectionString& cluster, unsigned skewChecks = NUM_LOCK_SKEW_CHECKS, unsigned long long maxClockSkew = MAX_LOCK_CLOCK_SKEW, unsigned long long maxNetSkew = MAX_LOCK_NET_SKEW ); + + /** + * Get the remote time from a server or cluster + */ + static Date_t remoteTime( const ConnectionString& cluster, unsigned long long maxNetSkew = MAX_LOCK_NET_SKEW ); + + static bool killPinger( DistributedLock& lock ); + + /** + * Namespace for lock pings + */ + static const string lockPingNS; + + /** + * Namespace for locks + */ + static const string locksNS; + + const ConnectionString _conn; + const string _name; + const BSONObj _id; + const string _processId; + + // Timeout for lock, usually LOCK_TIMEOUT + const unsigned long long _lockTimeout; + const unsigned long long _maxClockSkew; + const unsigned long long _maxNetSkew; + const unsigned long long _lockPing; + + private: + + void resetLastPing(){ lastPings.setLastPing( _conn, _name, PingData() ); } + void setLastPing( const PingData& pd ){ lastPings.setLastPing( _conn, _name, pd ); } + PingData getLastPing(){ return lastPings.getLastPing( _conn, _name ); } + + // May or may not exist, depending on startup + mongo::mutex _mutex; + string _threadId; + + }; + + class dist_lock_try { + public: + + dist_lock_try() : _lock(NULL), _got(false) {} + + dist_lock_try( const dist_lock_try& that ) : _lock(that._lock), _got(that._got), _other(that._other) { + _other.getOwned(); + + // Make sure the lock ownership passes to this object, + // so we only unlock once. + ((dist_lock_try&) that)._got = false; + ((dist_lock_try&) that)._lock = NULL; + ((dist_lock_try&) that)._other = BSONObj(); + } + + // Needed so we can handle lock exceptions in context of lock try. + dist_lock_try& operator=( const dist_lock_try& that ){ + + if( this == &that ) return *this; + + _lock = that._lock; + _got = that._got; + _other = that._other; + _other.getOwned(); + _why = that._why; + + // Make sure the lock ownership passes to this object, + // so we only unlock once. + ((dist_lock_try&) that)._got = false; + ((dist_lock_try&) that)._lock = NULL; + ((dist_lock_try&) that)._other = BSONObj(); + + return *this; + } + + dist_lock_try( DistributedLock * lock , string why ) + : _lock(lock), _why(why) { + _got = _lock->lock_try( why , false , &_other ); + } + + ~dist_lock_try() { + if ( _got ) { + verify( ! _other.isEmpty() ); + _lock->unlock( &_other ); + } + } + + bool reestablish(){ + return retry(); + } + + bool retry() { + verify( _lock ); + verify( _got ); + verify( ! _other.isEmpty() ); + + return _got = _lock->lock_try( _why , true, &_other ); + } + + bool got() const { return _got; } + BSONObj other() const { return _other; } + + private: + DistributedLock * _lock; + bool _got; + BSONObj _other; + string _why; + }; + +} + |
