lbrycrd/src/sync.h
Gregory Maxwell 50bd12ce0c Break addnode out from the outbound connection limits.
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.
2017-01-05 19:02:09 +00:00

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