Convert LoadManager to use std::thread (RIPD-236)

This commit is contained in:
Scott Schurr
2015-09-09 12:19:54 -07:00
committed by Nik Bougalis
parent 570bb2e139
commit caa4ed31de
2 changed files with 204 additions and 181 deletions

View File

@@ -22,156 +22,146 @@
#include <ripple/app/main/Application.h> #include <ripple/app/main/Application.h>
#include <ripple/app/misc/NetworkOPs.h> #include <ripple/app/misc/NetworkOPs.h>
#include <ripple/basics/UptimeTimer.h> #include <ripple/basics/UptimeTimer.h>
#include <ripple/core/JobQueue.h>
#include <ripple/core/LoadFeeTrack.h> #include <ripple/core/LoadFeeTrack.h>
#include <ripple/json/to_string.h> #include <ripple/json/to_string.h>
#include <beast/threads/Thread.h> #include <beast/threads/Thread.h>
#include <beast/cxx14/memory.h> // <memory>
#include <mutex>
#include <thread>
namespace ripple { namespace ripple {
class LoadManagerImp LoadManager::LoadManager (
: public LoadManager Application& app, Stoppable& parent, beast::Journal journal)
, public beast::Thread : Stoppable ("LoadManager", parent)
{
public:
using LockType = std::mutex;
using ScopedLockType = std::lock_guard <LockType>;
Application& app_;
beast::Journal m_journal;
LockType mLock;
bool mArmed;
int mDeadLock; // Detect server deadlocks
//--------------------------------------------------------------------------
LoadManagerImp (Application& app,
Stoppable& parent, beast::Journal journal)
: LoadManager (parent)
, Thread ("loadmgr")
, app_ (app) , app_ (app)
, m_journal (journal) , journal_ (journal)
, mArmed (false) , deadLock_ (0)
, mDeadLock (0) , armed_ (false)
{ , stop_ (false)
{
UptimeTimer::getInstance ().beginManualUpdates (); UptimeTimer::getInstance ().beginManualUpdates ();
} }
~LoadManagerImp () LoadManager::~LoadManager ()
{
try
{ {
UptimeTimer::getInstance ().endManualUpdates (); UptimeTimer::getInstance ().endManualUpdates ();
onStop ();
stopThread ();
} }
catch (std::exception const& ex)
//--------------------------------------------------------------------------
//
// Stoppable
//
//--------------------------------------------------------------------------
void onPrepare ()
{ {
// Swallow the exception in a destructor.
JLOG(journal_.warning) << "std::exception in ~LoadManager. "
<< ex.what();
} }
}
void onStart () //------------------------------------------------------------------------------
{
m_journal.debug << "Starting";
startThread ();
}
void onStop () void LoadManager::activateDeadlockDetector ()
{
std::lock_guard<std::mutex> sl (mutex_);
armed_ = true;
}
void LoadManager::resetDeadlockDetector ()
{
auto const elapsedSeconds =
UptimeTimer::getInstance ().getElapsedSeconds ();
std::lock_guard<std::mutex> sl (mutex_);
deadLock_ = elapsedSeconds;
}
//------------------------------------------------------------------------------
void LoadManager::onPrepare ()
{
}
void LoadManager::onStart ()
{
JLOG(journal_.debug) << "Starting";
assert (! thread_.joinable());
thread_ = std::thread {&LoadManager::run, this};
}
void LoadManager::onStop ()
{
if (thread_.joinable())
{ {
if (isThreadRunning ()) JLOG(journal_.debug) << "Stopping";
{ {
m_journal.debug << "Stopping"; std::lock_guard<std::mutex> sl (mutex_);
stopThreadAsync (); stop_ = true;
}
thread_.join();
} }
else
{
stopped (); stopped ();
} }
}
//-------------------------------------------------------------------------- //------------------------------------------------------------------------------
void resetDeadlockDetector () void LoadManager::run ()
{ {
ScopedLockType sl (mLock); beast::Thread::setCurrentThreadName ("LoadManager");
mDeadLock = UptimeTimer::getInstance ().getElapsedSeconds ();
}
void activateDeadlockDetector ()
{
mArmed = true;
}
void logDeadlock (int dlTime)
{
m_journal.warning << "Server stalled for " << dlTime << " seconds.";
}
// VFALCO NOTE Where's the thread object? It's not a data member...
//
void run ()
{
using clock_type = std::chrono::steady_clock; using clock_type = std::chrono::steady_clock;
// Initialize the clock to the current time. // Initialize the clock to the current time.
auto t = clock_type::now(); auto t = clock_type::now();
bool stop = false;
while (! threadShouldExit ()) while (! (stop || isStopping ()))
{ {
{ {
// VFALCO NOTE What is this lock protecting? // VFALCO NOTE I think this is to reduce calls to the operating
ScopedLockType sl (mLock); // system for retrieving the current time.
// VFALCO NOTE I think this is to reduce calls to the operating system
// for retrieving the current time.
// //
// TODO Instead of incrementing can't we just retrieve the current // TODO Instead of incrementing can't we just retrieve the
// time again? // current time again?
// //
// Manually update the timer. // Manually update the timer.
UptimeTimer::getInstance ().incrementElapsedTime (); UptimeTimer::getInstance ().incrementElapsedTime ();
// Copy out shared data under a lock. Use copies outside lock.
std::unique_lock<std::mutex> sl (mutex_);
auto const deadLock = deadLock_;
auto const armed = armed_;
stop = stop_;
sl.unlock();
// Measure the amount of time we have been deadlocked, in seconds. // Measure the amount of time we have been deadlocked, in seconds.
// //
// VFALCO NOTE mDeadLock is a canary for detecting the condition. // VFALCO NOTE deadLock_ is a canary for detecting the condition.
int const timeSpentDeadlocked = UptimeTimer::getInstance ().getElapsedSeconds () - mDeadLock; int const timeSpentDeadlocked =
UptimeTimer::getInstance ().getElapsedSeconds () - deadLock;
// VFALCO NOTE I think that "armed" refers to the deadlock detector // VFALCO NOTE I think that "armed" refers to the deadlock detector.
// //
int const reportingIntervalSeconds = 10; int const reportingIntervalSeconds = 10;
if (mArmed && (timeSpentDeadlocked >= reportingIntervalSeconds)) if (armed && (timeSpentDeadlocked >= reportingIntervalSeconds))
{ {
// Report the deadlocked condition every 10 seconds // Report the deadlocked condition every 10 seconds
if ((timeSpentDeadlocked % reportingIntervalSeconds) == 0) if ((timeSpentDeadlocked % reportingIntervalSeconds) == 0)
{ {
logDeadlock (timeSpentDeadlocked); JLOG(journal_.warning)
<< "Server stalled for "
<< timeSpentDeadlocked << " seconds.";
} }
// If we go over 500 seconds spent deadlocked, it means that the // If we go over 500 seconds spent deadlocked, it means that
// deadlock resolution code has failed, which qualifies as undefined // the deadlock resolution code has failed, which qualifies
// behavior. // as undefined behavior.
// //
assert (timeSpentDeadlocked < 500); assert (timeSpentDeadlocked < 500);
} }
} }
bool change; bool change = false;
// VFALCO TODO Eliminate the dependence on the Application object.
// Choices include constructing with the job queue / feetracker.
// Another option is using an observer pattern to invert the dependency.
if (app_.getJobQueue ().isOverloaded ()) if (app_.getJobQueue ().isOverloaded ())
{ {
if (m_journal.info) JLOG(journal_.info) << app_.getJobQueue ().getJson (0);
m_journal.info << app_.getJobQueue ().getJson (0);
change = app_.getFeeTrack ().raiseLocalFee (); change = app_.getFeeTrack ().raiseLocalFee ();
} }
else else
@@ -181,16 +171,18 @@ public:
if (change) if (change)
{ {
// VFALCO TODO replace this with a Listener / observer and subscribe in NetworkOPs or Application // VFALCO TODO replace this with a Listener / observer and
// subscribe in NetworkOPs or Application.
app_.getOPs ().reportFeeChange (); app_.getOPs ().reportFeeChange ();
} }
t += std::chrono::seconds (1); t += std::chrono::seconds (1);
auto const duration = t - clock_type::now(); auto const duration = t - clock_type::now();
if ((duration < std::chrono::seconds (0)) || (duration > std::chrono::seconds (1))) if ((duration < std::chrono::seconds (0)) ||
(duration > std::chrono::seconds (1)))
{ {
m_journal.warning << "time jump"; JLOG(journal_.warning) << "time jump";
t = clock_type::now(); t = clock_type::now();
} }
else else
@@ -200,14 +192,6 @@ public:
} }
stopped (); stopped ();
}
};
//------------------------------------------------------------------------------
LoadManager::LoadManager (Stoppable& parent)
: Stoppable ("LoadManager", parent)
{
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -216,7 +200,7 @@ std::unique_ptr<LoadManager>
make_LoadManager (Application& app, make_LoadManager (Application& app,
beast::Stoppable& parent, beast::Journal journal) beast::Stoppable& parent, beast::Journal journal)
{ {
return std::make_unique<LoadManagerImp>(app, parent, journal); return std::make_unique<LoadManager>(app, parent, journal);
} }
} // ripple } // ripple

View File

@@ -20,12 +20,15 @@
#ifndef RIPPLE_APP_MAIN_LOADMANAGER_H_INCLUDED #ifndef RIPPLE_APP_MAIN_LOADMANAGER_H_INCLUDED
#define RIPPLE_APP_MAIN_LOADMANAGER_H_INCLUDED #define RIPPLE_APP_MAIN_LOADMANAGER_H_INCLUDED
#include <ripple/app/main/Application.h>
#include <beast/threads/Stoppable.h> #include <beast/threads/Stoppable.h>
#include <beast/cxx14/memory.h> // <memory> #include <beast/cxx14/memory.h> // <memory>
#include <thread>
#include <mutex>
namespace ripple { namespace ripple {
class Application;
/** Manages load sources. /** Manages load sources.
This object creates an associated thread to maintain a clock. This object creates an associated thread to maintain a clock.
@@ -39,15 +42,28 @@ namespace ripple {
*/ */
class LoadManager : public beast::Stoppable class LoadManager : public beast::Stoppable
{ {
protected: public:
explicit LoadManager (Stoppable& parent); // It would be better if the LoadManager constructor could be private
// with std::make_unique as a friend. But Visual Studio can't currently
// swallow the following friend declaration (Microsoft (R) C/C++
// Optimizing Compiler Version 19.00.23026 for x64). So we make the
// constructor public.
// template<class T, class... Args>
// friend std::unique_ptr<T> std::make_unique (Args&&... args);
// Should only be constructible by std::make_unique.
LoadManager (Application& app, Stoppable& parent, beast::Journal journal);
public: public:
LoadManager () = delete;
LoadManager (LoadManager const&) = delete;
LoadManager& operator=(LoadManager const&) = delete;
/** Destroy the manager. /** Destroy the manager.
The destructor returns only after the thread has stopped. The destructor returns only after the thread has stopped.
*/ */
virtual ~LoadManager () = default; ~LoadManager ();
/** Turn on deadlock detection. /** Turn on deadlock detection.
@@ -57,23 +73,46 @@ public:
@see resetDeadlockDetector @see resetDeadlockDetector
*/ */
// VFALCO NOTE it seems that the deadlock detector has an "armed" state to prevent it // VFALCO NOTE it seems that the deadlock detector has an "armed" state
// from going off during program startup if there's a lengthy initialization // to prevent it from going off during program startup if
// operation taking place? // there's a lengthy initialization operation taking place?
// //
virtual void activateDeadlockDetector () = 0; void activateDeadlockDetector ();
/** Reset the deadlock detection timer. /** Reset the deadlock detection timer.
A dedicated thread monitors the deadlock timer, and if too much A dedicated thread monitors the deadlock timer, and if too much
time passes it will produce log warnings. time passes it will produce log warnings.
*/ */
virtual void resetDeadlockDetector () = 0; void resetDeadlockDetector ();
//--------------------------------------------------------------------------
// Stoppable members
void onPrepare () override;
void onStart () override;
void onStop () override;
private:
void run ();
private:
Application& app_;
beast::Journal journal_;
std::thread thread_;
std::mutex mutex_; // Guards deadLock_, armed_, and stop_.
int deadLock_; // Detect server deadlocks.
bool armed_;
bool stop_;
}; };
std::unique_ptr<LoadManager> std::unique_ptr<LoadManager>
make_LoadManager (Application& app, make_LoadManager (
beast::Stoppable& parent, beast::Journal journal); Application& app, beast::Stoppable& parent, beast::Journal journal);
} // ripple } // ripple