50bd12ce0c
Previously addnodes were in competition with outbound connections for access to the eight outbound slots. One result of this is that frequently a node with several addnode configured peers would end up connected to none of them, because while the addnode loop was in its two minute sleep the automatic connection logic would fill any free slots with random peers. This is particularly unwelcome to users trying to maintain links to specific nodes for fast block relay or purposes. Another result is that a group of nine or more nodes which are have addnode configured towards each other can become partitioned from the public network. This commit introduces a new limit of eight connections just for addnode peers which is not subject to any of the other connection limitations (including maxconnections). The choice of eight is sufficient so that under no condition would a user find themselves connected to fewer addnoded peers than previously. It is also low enough that users who are confused about the significance of more connections and have gotten too copy-and-paste happy will not consume more than twice the slot usage of a typical user. Any additional load on the network resulting from this will likely be offset by a reduction in users applying even more wasteful workaround for the prior behavior. The retry delays are reduced to avoid nodes sitting around without their added peers up, but are still sufficient to prevent overly aggressive repeated connections. The reduced delays also make the system much more responsive to the addnode RPC. Ban-disconnects are also exempted for peers added via addnode since the outbound addnode logic ignores bans. Previously it would ban an addnode then immediately reconnect to it. A minor change was also made to CSemaphoreGrant so that it is possible to re-acquire via an object whos grant was moved.
291 lines
7.3 KiB
C++
291 lines
7.3 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2016 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#ifndef BITCOIN_SYNC_H
|
|
#define BITCOIN_SYNC_H
|
|
|
|
#include "threadsafety.h"
|
|
|
|
#include <boost/thread/condition_variable.hpp>
|
|
#include <boost/thread/locks.hpp>
|
|
#include <boost/thread/mutex.hpp>
|
|
#include <boost/thread/recursive_mutex.hpp>
|
|
|
|
|
|
////////////////////////////////////////////////
|
|
// //
|
|
// THE SIMPLE DEFINITION, EXCLUDING DEBUG CODE //
|
|
// //
|
|
////////////////////////////////////////////////
|
|
|
|
/*
|
|
CCriticalSection mutex;
|
|
boost::recursive_mutex mutex;
|
|
|
|
LOCK(mutex);
|
|
boost::unique_lock<boost::recursive_mutex> criticalblock(mutex);
|
|
|
|
LOCK2(mutex1, mutex2);
|
|
boost::unique_lock<boost::recursive_mutex> criticalblock1(mutex1);
|
|
boost::unique_lock<boost::recursive_mutex> criticalblock2(mutex2);
|
|
|
|
TRY_LOCK(mutex, name);
|
|
boost::unique_lock<boost::recursive_mutex> name(mutex, boost::try_to_lock_t);
|
|
|
|
ENTER_CRITICAL_SECTION(mutex); // no RAII
|
|
mutex.lock();
|
|
|
|
LEAVE_CRITICAL_SECTION(mutex); // no RAII
|
|
mutex.unlock();
|
|
*/
|
|
|
|
///////////////////////////////
|
|
// //
|
|
// THE ACTUAL IMPLEMENTATION //
|
|
// //
|
|
///////////////////////////////
|
|
|
|
/**
|
|
* Template mixin that adds -Wthread-safety locking
|
|
* annotations to a subset of the mutex API.
|
|
*/
|
|
template <typename PARENT>
|
|
class LOCKABLE AnnotatedMixin : public PARENT
|
|
{
|
|
public:
|
|
void lock() EXCLUSIVE_LOCK_FUNCTION()
|
|
{
|
|
PARENT::lock();
|
|
}
|
|
|
|
void unlock() UNLOCK_FUNCTION()
|
|
{
|
|
PARENT::unlock();
|
|
}
|
|
|
|
bool try_lock() EXCLUSIVE_TRYLOCK_FUNCTION(true)
|
|
{
|
|
return PARENT::try_lock();
|
|
}
|
|
};
|
|
|
|
#ifdef DEBUG_LOCKORDER
|
|
void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false);
|
|
void LeaveCritical();
|
|
std::string LocksHeld();
|
|
void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs);
|
|
void DeleteLock(void* cs);
|
|
#else
|
|
void static inline EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false) {}
|
|
void static inline LeaveCritical() {}
|
|
void static inline AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) {}
|
|
void static inline DeleteLock(void* cs) {}
|
|
#endif
|
|
#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs)
|
|
|
|
/**
|
|
* Wrapped boost mutex: supports recursive locking, but no waiting
|
|
* TODO: We should move away from using the recursive lock by default.
|
|
*/
|
|
class CCriticalSection : public AnnotatedMixin<boost::recursive_mutex>
|
|
{
|
|
public:
|
|
~CCriticalSection() {
|
|
DeleteLock((void*)this);
|
|
}
|
|
};
|
|
|
|
typedef CCriticalSection CDynamicCriticalSection;
|
|
/** Wrapped boost mutex: supports waiting but not recursive locking */
|
|
typedef AnnotatedMixin<boost::mutex> CWaitableCriticalSection;
|
|
|
|
/** Just a typedef for boost::condition_variable, can be wrapped later if desired */
|
|
typedef boost::condition_variable CConditionVariable;
|
|
|
|
#ifdef DEBUG_LOCKCONTENTION
|
|
void PrintLockContention(const char* pszName, const char* pszFile, int nLine);
|
|
#endif
|
|
|
|
/** Wrapper around boost::unique_lock<Mutex> */
|
|
template <typename Mutex>
|
|
class SCOPED_LOCKABLE CMutexLock
|
|
{
|
|
private:
|
|
boost::unique_lock<Mutex> lock;
|
|
|
|
void Enter(const char* pszName, const char* pszFile, int nLine)
|
|
{
|
|
EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex()));
|
|
#ifdef DEBUG_LOCKCONTENTION
|
|
if (!lock.try_lock()) {
|
|
PrintLockContention(pszName, pszFile, nLine);
|
|
#endif
|
|
lock.lock();
|
|
#ifdef DEBUG_LOCKCONTENTION
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool TryEnter(const char* pszName, const char* pszFile, int nLine)
|
|
{
|
|
EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex()), true);
|
|
lock.try_lock();
|
|
if (!lock.owns_lock())
|
|
LeaveCritical();
|
|
return lock.owns_lock();
|
|
}
|
|
|
|
public:
|
|
CMutexLock(Mutex& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(mutexIn) : lock(mutexIn, boost::defer_lock)
|
|
{
|
|
if (fTry)
|
|
TryEnter(pszName, pszFile, nLine);
|
|
else
|
|
Enter(pszName, pszFile, nLine);
|
|
}
|
|
|
|
CMutexLock(Mutex* pmutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(pmutexIn)
|
|
{
|
|
if (!pmutexIn) return;
|
|
|
|
lock = boost::unique_lock<Mutex>(*pmutexIn, boost::defer_lock);
|
|
if (fTry)
|
|
TryEnter(pszName, pszFile, nLine);
|
|
else
|
|
Enter(pszName, pszFile, nLine);
|
|
}
|
|
|
|
~CMutexLock() UNLOCK_FUNCTION()
|
|
{
|
|
if (lock.owns_lock())
|
|
LeaveCritical();
|
|
}
|
|
|
|
operator bool()
|
|
{
|
|
return lock.owns_lock();
|
|
}
|
|
};
|
|
|
|
typedef CMutexLock<CCriticalSection> CCriticalBlock;
|
|
|
|
#define PASTE(x, y) x ## y
|
|
#define PASTE2(x, y) PASTE(x, y)
|
|
|
|
#define LOCK(cs) CCriticalBlock PASTE2(criticalblock, __COUNTER__)(cs, #cs, __FILE__, __LINE__)
|
|
#define LOCK2(cs1, cs2) CCriticalBlock criticalblock1(cs1, #cs1, __FILE__, __LINE__), criticalblock2(cs2, #cs2, __FILE__, __LINE__)
|
|
#define TRY_LOCK(cs, name) CCriticalBlock name(cs, #cs, __FILE__, __LINE__, true)
|
|
|
|
#define ENTER_CRITICAL_SECTION(cs) \
|
|
{ \
|
|
EnterCritical(#cs, __FILE__, __LINE__, (void*)(&cs)); \
|
|
(cs).lock(); \
|
|
}
|
|
|
|
#define LEAVE_CRITICAL_SECTION(cs) \
|
|
{ \
|
|
(cs).unlock(); \
|
|
LeaveCritical(); \
|
|
}
|
|
|
|
class CSemaphore
|
|
{
|
|
private:
|
|
boost::condition_variable condition;
|
|
boost::mutex mutex;
|
|
int value;
|
|
|
|
public:
|
|
CSemaphore(int init) : value(init) {}
|
|
|
|
void wait()
|
|
{
|
|
boost::unique_lock<boost::mutex> lock(mutex);
|
|
while (value < 1) {
|
|
condition.wait(lock);
|
|
}
|
|
value--;
|
|
}
|
|
|
|
bool try_wait()
|
|
{
|
|
boost::unique_lock<boost::mutex> lock(mutex);
|
|
if (value < 1)
|
|
return false;
|
|
value--;
|
|
return true;
|
|
}
|
|
|
|
void post()
|
|
{
|
|
{
|
|
boost::unique_lock<boost::mutex> lock(mutex);
|
|
value++;
|
|
}
|
|
condition.notify_one();
|
|
}
|
|
};
|
|
|
|
/** RAII-style semaphore lock */
|
|
class CSemaphoreGrant
|
|
{
|
|
private:
|
|
CSemaphore* sem;
|
|
bool fHaveGrant;
|
|
|
|
public:
|
|
void Acquire()
|
|
{
|
|
if (fHaveGrant)
|
|
return;
|
|
sem->wait();
|
|
fHaveGrant = true;
|
|
}
|
|
|
|
void Release()
|
|
{
|
|
if (!fHaveGrant)
|
|
return;
|
|
sem->post();
|
|
fHaveGrant = false;
|
|
}
|
|
|
|
bool TryAcquire()
|
|
{
|
|
if (!fHaveGrant && sem->try_wait())
|
|
fHaveGrant = true;
|
|
return fHaveGrant;
|
|
}
|
|
|
|
void MoveTo(CSemaphoreGrant& grant)
|
|
{
|
|
grant.Release();
|
|
grant.sem = sem;
|
|
grant.fHaveGrant = fHaveGrant;
|
|
fHaveGrant = false;
|
|
}
|
|
|
|
CSemaphoreGrant() : sem(NULL), fHaveGrant(false) {}
|
|
|
|
CSemaphoreGrant(CSemaphore& sema, bool fTry = false) : sem(&sema), fHaveGrant(false)
|
|
{
|
|
if (fTry)
|
|
TryAcquire();
|
|
else
|
|
Acquire();
|
|
}
|
|
|
|
~CSemaphoreGrant()
|
|
{
|
|
Release();
|
|
}
|
|
|
|
operator bool()
|
|
{
|
|
return fHaveGrant;
|
|
}
|
|
};
|
|
|
|
#endif // BITCOIN_SYNC_H
|