Replace DeadlineTimer with asio::steadyTimer (RIPD-1356):

The two active users of DeadlineTimer, NetworkOPs and Application,
now use asio::steady_timers rather than DeadlineTimer.
DeadlineTimer is removed since it is no longer used.

To assure that all in-flight closures on timers are done before
Stoppables call stopped(), the JobCounter is made more generic.
It's now a ClosureCounter.  The ClosureCounter is currently used
to count closures in flight for the JobQueue, NetworkOPs, and the
Application.
This commit is contained in:
Scott Schurr
2017-06-07 13:18:32 -07:00
committed by seelabs
parent efe3700f70
commit 1a56b9c5f2
17 changed files with 733 additions and 1019 deletions

View File

@@ -1879,6 +1879,8 @@
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\Validations.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\ClosureCounter.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\Config.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\ConfigSections.h">
@@ -1887,8 +1889,6 @@
</None>
<ClInclude Include="..\..\src\ripple\core\DatabaseCon.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\DeadlineTimer.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\core\impl\Config.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -1901,12 +1901,6 @@
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<ClCompile Include="..\..\src\ripple\core\impl\DeadlineTimer.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<ClCompile Include="..\..\src\ripple\core\impl\DummySociDynamicBackend.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -1981,8 +1975,6 @@
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\Job.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\JobCounter.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\JobQueue.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\JobTypeData.h">
@@ -4491,6 +4483,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\core\ClosureCounter_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\core\Config_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -4503,14 +4499,6 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\core\DeadlineTimer_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\core\JobCounter_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\core\JobQueue_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>

View File

@@ -2529,6 +2529,9 @@
<ClInclude Include="..\..\src\ripple\consensus\Validations.h">
<Filter>ripple\consensus</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\ClosureCounter.h">
<Filter>ripple\core</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\Config.h">
<Filter>ripple\core</Filter>
</ClInclude>
@@ -2541,18 +2544,12 @@
<ClInclude Include="..\..\src\ripple\core\DatabaseCon.h">
<Filter>ripple\core</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\DeadlineTimer.h">
<Filter>ripple\core</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\core\impl\Config.cpp">
<Filter>ripple\core\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\core\impl\DatabaseCon.cpp">
<Filter>ripple\core\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\core\impl\DeadlineTimer.cpp">
<Filter>ripple\core\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\core\impl\DummySociDynamicBackend.cpp">
<Filter>ripple\core\impl</Filter>
</ClCompile>
@@ -2598,9 +2595,6 @@
<ClInclude Include="..\..\src\ripple\core\Job.h">
<Filter>ripple\core</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\JobCounter.h">
<Filter>ripple\core</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\JobQueue.h">
<Filter>ripple\core</Filter>
</ClInclude>
@@ -5250,6 +5244,9 @@
<ClCompile Include="..\..\src\test\consensus\Validations_test.cpp">
<Filter>test\consensus</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\core\ClosureCounter_test.cpp">
<Filter>test\core</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\core\Config_test.cpp">
<Filter>test\core</Filter>
</ClCompile>
@@ -5259,12 +5256,6 @@
<ClCompile Include="..\..\src\test\core\CryptoPRNG_test.cpp">
<Filter>test\core</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\core\DeadlineTimer_test.cpp">
<Filter>test\core</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\core\JobCounter_test.cpp">
<Filter>test\core</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\core\JobQueue_test.cpp">
<Filter>test\core</Filter>
</ClCompile>

View File

@@ -48,7 +48,6 @@
#include <ripple/basics/ResolverAsio.h>
#include <ripple/basics/Sustain.h>
#include <ripple/json/json_reader.h>
#include <ripple/core/DeadlineTimer.h>
#include <ripple/nodestore/DummyScheduler.h>
#include <ripple/overlay/Cluster.h>
#include <ripple/overlay/make_Overlay.h>
@@ -56,6 +55,7 @@
#include <ripple/resource/Fees.h>
#include <ripple/beast/asio/io_latency_probe.h>
#include <ripple/beast/core/LexicalCast.h>
#include <boost/asio/steady_timer.hpp>
#include <fstream>
namespace ripple {
@@ -218,7 +218,6 @@ supportedAmendments ();
class ApplicationImp
: public Application
, public RootStoppable
, public DeadlineTimer::Listener
, public BasicApp
{
private:
@@ -334,8 +333,9 @@ public:
RCLValidations mValidations;
std::unique_ptr <LoadManager> m_loadManager;
std::unique_ptr <TxQ> txQ_;
DeadlineTimer m_sweepTimer;
DeadlineTimer m_entropyTimer;
ClosureCounter<void, boost::system::error_code const&> waitHandlerCounter_;
boost::asio::steady_timer sweepTimer_;
boost::asio::steady_timer entropyTimer_;
bool startTimers_;
std::unique_ptr <DatabaseCon> mTxnDB;
@@ -448,7 +448,7 @@ public:
, m_networkOPs (make_NetworkOPs (*this, stopwatch(),
config_->standalone(), config_->NETWORK_QUORUM, config_->START_VALID,
*m_jobQueue, *m_ledgerMaster, *m_jobQueue, validatorKeys_,
logs_->journal("NetworkOPs")))
get_io_service(), logs_->journal("NetworkOPs")))
, cluster_ (std::make_unique<Cluster> (
logs_->journal("Overlay")))
@@ -481,9 +481,9 @@ public:
, txQ_(make_TxQ(setup_TxQ(*config_), logs_->journal("TxQ")))
, m_sweepTimer (this)
, sweepTimer_ (get_io_service())
, m_entropyTimer (this)
, entropyTimer_ (get_io_service())
, startTimers_ (false)
@@ -827,8 +827,8 @@ public:
using namespace std::chrono_literals;
if(startTimers_)
{
m_sweepTimer.setExpiration (10s);
m_entropyTimer.setRecurringExpiration (5min);
setSweepTimer();
setEntropyTimer();
}
m_io_latency_sampler.start();
@@ -858,11 +858,28 @@ public:
// things will happen.
m_resolver->stop ();
if(startTimers_)
{
m_sweepTimer.cancel ();
m_entropyTimer.cancel ();
boost::system::error_code ec;
sweepTimer_.cancel (ec);
if (ec)
{
JLOG (m_journal.error())
<< "Application: sweepTimer cancel error: "
<< ec.message();
}
ec.clear();
entropyTimer_.cancel (ec);
if (ec)
{
JLOG (m_journal.error())
<< "Application: entropyTimer cancel error: "
<< ec.message();
}
}
// Make sure that any waitHandlers pending in our timers are done
// before we declare ourselves stopped.
waitHandlerCounter_.join("Application", 1s, m_journal);
mValidations.flush ();
@@ -884,7 +901,7 @@ public:
stopped ();
}
//------------------------------------------------------------------------------
//--------------------------------------------------------------------------
//
// PropertyStream
//
@@ -893,20 +910,64 @@ public:
{
}
//------------------------------------------------------------------------------
//--------------------------------------------------------------------------
void onDeadlineTimer (DeadlineTimer& timer) override
void setSweepTimer ()
{
if (timer == m_entropyTimer)
// Only start the timer if waitHandlerCounter_ is not yet joined.
if (auto optionalCountedHandler = waitHandlerCounter_.wrap (
[this] (boost::system::error_code const& e)
{
if ((e.value() == boost::system::errc::success) &&
(! m_jobQueue->isStopped()))
{
m_jobQueue->addJob(
jtSWEEP, "sweep", [this] (Job&) { doSweep(); });
}
// Recover as best we can if an unexpected error occurs.
if (e.value() != boost::system::errc::success &&
e.value() != boost::asio::error::operation_aborted)
{
// Try again later and hope for the best.
JLOG (m_journal.error())
<< "Sweep timer got error '" << e.message()
<< "'. Restarting timer.";
setSweepTimer();
}
}))
{
crypto_prng().mix_entropy ();
return;
sweepTimer_.expires_from_now (
std::chrono::seconds {config_->getSize (siSweepInterval)});
sweepTimer_.async_wait (std::move (*optionalCountedHandler));
}
}
if (timer == m_sweepTimer)
void setEntropyTimer ()
{
// Only start the timer if waitHandlerCounter_ is not yet joined.
if (auto optionalCountedHandler = waitHandlerCounter_.wrap (
[this] (boost::system::error_code const& e)
{
if (e.value() == boost::system::errc::success)
{
crypto_prng().mix_entropy();
setEntropyTimer();
}
// Recover as best we can if an unexpected error occurs.
if (e.value() != boost::system::errc::success &&
e.value() != boost::asio::error::operation_aborted)
{
// Try again later and hope for the best.
JLOG (m_journal.error())
<< "Entropy timer got error '" << e.message()
<< "'. Restarting timer.";
setEntropyTimer();
}
}))
{
m_jobQueue->addJob(
jtSWEEP, "sweep", [this] (Job&) { doSweep(); });
using namespace std::chrono_literals;
entropyTimer_.expires_from_now (5min);
entropyTimer_.async_wait (std::move (*optionalCountedHandler));
}
}
@@ -942,8 +1003,7 @@ public:
cachedSLEs_.expire();
// Set timer to do another sweep later.
m_sweepTimer.setExpiration (
std::chrono::seconds {config_->getSize (siSweepInterval)});
setSweepTimer();
}

View File

@@ -43,7 +43,6 @@
#include <ripple/basics/mulDiv.h>
#include <ripple/basics/UptimeTimer.h>
#include <ripple/core/ConfigSections.h>
#include <ripple/core/DeadlineTimer.h>
#include <ripple/crypto/csprng.h>
#include <ripple/crypto/RFC1751.h>
#include <ripple/json/to_string.h>
@@ -58,12 +57,12 @@
#include <ripple/beast/utility/rngfill.h>
#include <ripple/basics/make_lock.h>
#include <beast/core/detail/base64.hpp>
#include <boost/asio/steady_timer.hpp>
namespace ripple {
class NetworkOPsImp final
: public NetworkOPs
, public DeadlineTimer::Listener
{
/**
* Transaction with input flags and results to be applied in batches.
@@ -183,11 +182,11 @@ class NetworkOPsImp final
public:
// VFALCO TODO Make LedgerMaster a SharedPtr or a reference.
//
NetworkOPsImp (
Application& app, clock_type& clock, bool standalone,
std::size_t network_quorum, bool start_valid, JobQueue& job_queue,
LedgerMaster& ledgerMaster, Stoppable& parent,
ValidatorKeys const & validatorKeys, beast::Journal journal)
NetworkOPsImp (Application& app, NetworkOPs::clock_type& clock,
bool standalone, std::size_t network_quorum, bool start_valid,
JobQueue& job_queue, LedgerMaster& ledgerMaster, Stoppable& parent,
ValidatorKeys const & validatorKeys, boost::asio::io_service& io_svc,
beast::Journal journal)
: NetworkOPs (parent)
, app_ (app)
, m_clock (clock)
@@ -196,8 +195,8 @@ public:
, mMode (start_valid ? omFULL : omDISCONNECTED)
, mNeedNetworkLedger (false)
, m_amendmentBlocked (false)
, m_heartbeatTimer (this)
, m_clusterTimer (this)
, heartbeatTimer_ (io_svc)
, clusterTimer_ (io_svc)
, mConsensus (app,
make_FeeVote(setup_FeeVote (app_.config().section ("voting")),
app_.logs().journal("FeeVote")),
@@ -481,16 +480,35 @@ public:
void onStop () override
{
mAcquiringLedger.reset();
m_heartbeatTimer.cancel();
m_clusterTimer.cancel();
{
boost::system::error_code ec;
heartbeatTimer_.cancel (ec);
if (ec)
{
JLOG (m_journal.error())
<< "NetworkOPs: heartbeatTimer cancel error: "
<< ec.message();
}
ec.clear();
clusterTimer_.cancel (ec);
if (ec)
{
JLOG (m_journal.error())
<< "NetworkOPs: clusterTimer cancel error: "
<< ec.message();
}
}
// Make sure that any waitHandlers pending in our timers are done
// before we declare ourselves stopped.
using namespace std::chrono_literals;
waitHandlerCounter_.join("NetworkOPs", 1s, m_journal);
stopped ();
}
private:
void setHeartbeatTimer ();
void setClusterTimer ();
void onDeadlineTimer (DeadlineTimer& timer) override;
void processHeartbeatTimer ();
void processClusterTimer ();
@@ -533,8 +551,9 @@ private:
std::atomic <bool> mNeedNetworkLedger;
bool m_amendmentBlocked;
DeadlineTimer m_heartbeatTimer;
DeadlineTimer m_clusterTimer;
ClosureCounter<void, boost::system::error_code const&> waitHandlerCounter_;
boost::asio::steady_timer heartbeatTimer_;
boost::asio::steady_timer clusterTimer_;
RCLConsensus mConsensus;
@@ -638,28 +657,61 @@ void NetworkOPsImp::setStateTimer ()
void NetworkOPsImp::setHeartbeatTimer ()
{
m_heartbeatTimer.setExpiration (mConsensus.parms().ledgerGRANULARITY);
// Only start the timer if waitHandlerCounter_ is not yet joined.
if (auto optionalCountedHandler = waitHandlerCounter_.wrap (
[this] (boost::system::error_code const& e)
{
if ((e.value() == boost::system::errc::success) &&
(! m_job_queue.isStopped()))
{
m_job_queue.addJob (jtNETOP_TIMER, "NetOPs.heartbeat",
[this] (Job&) { processHeartbeatTimer(); });
}
// Recover as best we can if an unexpected error occurs.
if (e.value() != boost::system::errc::success &&
e.value() != boost::asio::error::operation_aborted)
{
// Try again later and hope for the best.
JLOG (m_journal.error())
<< "Heartbeat timer got error '" << e.message()
<< "'. Restarting timer.";
setHeartbeatTimer();
}
}))
{
heartbeatTimer_.expires_from_now (
mConsensus.parms().ledgerGRANULARITY);
heartbeatTimer_.async_wait (std::move (*optionalCountedHandler));
}
}
void NetworkOPsImp::setClusterTimer ()
{
using namespace std::chrono_literals;
m_clusterTimer.setExpiration (10s);
}
void NetworkOPsImp::onDeadlineTimer (DeadlineTimer& timer)
{
if (timer == m_heartbeatTimer)
// Only start the timer if waitHandlerCounter_ is not yet joined.
if (auto optionalCountedHandler = waitHandlerCounter_.wrap (
[this] (boost::system::error_code const& e)
{
if ((e.value() == boost::system::errc::success) &&
(! m_job_queue.isStopped()))
{
m_job_queue.addJob (jtNETOP_CLUSTER, "NetOPs.cluster",
[this] (Job&) { processClusterTimer(); });
}
// Recover as best we can if an unexpected error occurs.
if (e.value() != boost::system::errc::success &&
e.value() != boost::asio::error::operation_aborted)
{
// Try again later and hope for the best.
JLOG (m_journal.error())
<< "Cluster timer got error '" << e.message()
<< "'. Restarting timer.";
setClusterTimer();
}
}))
{
m_job_queue.addJob (
jtNETOP_TIMER, "NetOPs.heartbeat",
[this] (Job&) { processHeartbeatTimer(); });
}
else if (timer == m_clusterTimer)
{
m_job_queue.addJob (
jtNETOP_CLUSTER, "NetOPs.cluster",
[this] (Job&) { processClusterTimer(); });
using namespace std::chrono_literals;
clusterTimer_.expires_from_now (10s);
clusterTimer_.async_wait (std::move (*optionalCountedHandler));
}
}
@@ -3347,13 +3399,15 @@ Json::Value NetworkOPsImp::StateAccounting::json() const
//------------------------------------------------------------------------------
std::unique_ptr<NetworkOPs>
make_NetworkOPs (Application& app, NetworkOPs::clock_type& clock, bool standalone,
std::size_t network_quorum, bool startvalid,
JobQueue& job_queue, LedgerMaster& ledgerMaster,
Stoppable& parent, ValidatorKeys const & validatorKeys, beast::Journal journal)
make_NetworkOPs (Application& app, NetworkOPs::clock_type& clock,
bool standalone, std::size_t network_quorum, bool startvalid,
JobQueue& job_queue, LedgerMaster& ledgerMaster, Stoppable& parent,
ValidatorKeys const & validatorKeys, boost::asio::io_service& io_svc,
beast::Journal journal)
{
return std::make_unique<NetworkOPsImp> (app, clock, standalone, network_quorum,
startvalid, job_queue, ledgerMaster, parent, validatorKeys, journal);
return std::make_unique<NetworkOPsImp> (app, clock, standalone,
network_quorum, startvalid, job_queue, ledgerMaster, parent,
validatorKeys, io_svc, journal);
}
} // ripple

View File

@@ -237,10 +237,11 @@ public:
//------------------------------------------------------------------------------
std::unique_ptr<NetworkOPs>
make_NetworkOPs (Application& app, NetworkOPs::clock_type& clock, bool standalone,
std::size_t network_quorum, bool start_valid,
JobQueue& job_queue, LedgerMaster& ledgerMaster,
Stoppable& parent, ValidatorKeys const & validatorKeys, beast::Journal journal);
make_NetworkOPs (Application& app, NetworkOPs::clock_type& clock,
bool standalone, std::size_t network_quorum, bool start_valid,
JobQueue& job_queue, LedgerMaster& ledgerMaster, Stoppable& parent,
ValidatorKeys const & validatorKeys, boost::asio::io_service& io_svc,
beast::Journal journal);
} // ripple

View File

@@ -0,0 +1,207 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2017 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_CORE_CLOSURE_COUNTER_H_INCLUDED
#define RIPPLE_CORE_CLOSURE_COUNTER_H_INCLUDED
#include <ripple/basics/Log.h>
#include <boost/optional.hpp>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <type_traits>
namespace ripple {
// A class that does reference counting for postponed closures -- a closure
// who's execution is delayed by a timer or queue. The reference counting
// allows a Stoppable to assure that all such postponed closures are
// completed before the Stoppable declares itself stopped().
//
// Ret_t is the type that the counted closure returns.
// Args_t are the types of the arguments that will be passed to the closure.
template <typename Ret_t, typename... Args_t>
class ClosureCounter
{
private:
std::mutex mutable mutex_ {};
std::condition_variable allClosuresDoneCond_ {}; // guard with mutex_
bool waitForClosures_ {false}; // guard with mutex_
std::atomic<int> closureCount_ {0};
// Increment the count.
ClosureCounter& operator++()
{
++closureCount_;
return *this;
}
// Decrement the count. If we're stopping and the count drops to zero
// notify allClosuresDoneCond_.
ClosureCounter& operator--()
{
// Even though closureCount_ is atomic, we decrement its value under
// a lock. This removes a small timing window that occurs if the
// waiting thread is handling a spurious wakeup when closureCount_
// drops to zero.
std::lock_guard<std::mutex> lock {mutex_};
// Update closureCount_. Notify if stopping and closureCount_ == 0.
if ((--closureCount_ == 0) && waitForClosures_)
allClosuresDoneCond_.notify_all();
return *this;
}
// A private template class that helps count the number of closures
// in flight. This allows Stoppables to hold off declaring stopped()
// until all their postponed closures are dispatched.
template <typename Closure>
class Wrapper
{
private:
ClosureCounter& counter_;
std::remove_reference_t<Closure> closure_;
static_assert (
std::is_same<decltype(
closure_(std::declval<Args_t>()...)), Ret_t>::value,
"Closure arguments don't match ClosureCounter Ret_t or Args_t");
public:
Wrapper() = delete;
Wrapper (Wrapper const& rhs)
: counter_ (rhs.counter_)
, closure_ (rhs.closure_)
{
++counter_;
}
Wrapper (Wrapper&& rhs)
: counter_ (rhs.counter_)
, closure_ (std::move (rhs.closure_))
{
++counter_;
}
Wrapper (ClosureCounter& counter, Closure&& closure)
: counter_ (counter)
, closure_ (std::forward<Closure> (closure))
{
++counter_;
}
Wrapper& operator=(Wrapper const& rhs) = delete;
Wrapper& operator=(Wrapper&& rhs) = delete;
~Wrapper()
{
--counter_;
}
// Note that Args_t is not deduced, it is explicit. So Args_t&&
// would be an rvalue reference, not a forwarding reference. We
// want to forward exactly what the user declared.
Ret_t operator ()(Args_t... args)
{
return closure_ (std::forward<Args_t>(args)...);
}
};
public:
ClosureCounter() = default;
// Not copyable or movable. Outstanding counts would be hard to sort out.
ClosureCounter (ClosureCounter const&) = delete;
ClosureCounter& operator=(ClosureCounter const&) = delete;
/** Destructor verifies all in-flight closures are complete. */
~ClosureCounter()
{
using namespace std::chrono_literals;
join ("ClosureCounter", 1s, debugLog());
}
/** Returns once all counted in-flight closures are destroyed.
@param name Name reported if join time exceeds wait.
@param wait If join() exceeds this duration report to Journal.
@param j Journal written to if wait is exceeded.
*/
void join (char const* name,
std::chrono::milliseconds wait, beast::Journal j)
{
std::unique_lock<std::mutex> lock {mutex_};
waitForClosures_ = true;
if (closureCount_ > 0)
{
if (! allClosuresDoneCond_.wait_for (
lock, wait, [this] { return closureCount_ == 0; }))
{
if (auto stream = j.error())
stream << name
<< " waiting for ClosureCounter::join().";
allClosuresDoneCond_.wait (
lock, [this] { return closureCount_ == 0; });
}
}
}
/** Wrap the passed closure with a reference counter.
@param closure Closure that accepts Args_t parameters and returns Ret_t.
@return If join() has been called returns boost::none. Otherwise
returns a boost::optional that wraps closure with a
reference counter.
*/
template <class Closure>
boost::optional<Wrapper<Closure>>
wrap (Closure&& closure)
{
boost::optional<Wrapper<Closure>> ret;
std::lock_guard<std::mutex> lock {mutex_};
if (! waitForClosures_)
ret.emplace (*this, std::forward<Closure> (closure));
return ret;
}
/** Current number of Closures outstanding. Only useful for testing. */
int count() const
{
return closureCount_;
}
/** Returns true if this has been joined.
Even if true is returned, counted closures may still be in flight.
However if (joined() && (count() == 0)) there should be no more
counted closures in flight.
*/
bool joined() const
{
std::lock_guard<std::mutex> lock {mutex_};
return waitForClosures_;
}
};
} // ripple
#endif // RIPPLE_CORE_CLOSURE_COUNTER_H_INCLUDED

View File

@@ -1,119 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_CORE_DEADLINETIMER_H_INCLUDED
#define RIPPLE_CORE_DEADLINETIMER_H_INCLUDED
#include <ripple/beast/core/List.h>
#include <chrono>
namespace ripple {
/** Provides periodic or one time notifications at a specified time interval.
*/
class DeadlineTimer
: public beast::List <DeadlineTimer>::Node
{
public:
using clock = std::chrono::steady_clock; ///< DeadlineTimer clock.
using duration = std::chrono::milliseconds; ///< DeadlineTimer duration.
/** DeadlineTimer time_point. */
using time_point = std::chrono::time_point<clock, duration>;
/** Listener for a deadline timer.
The listener is called on an auxiliary thread. It is suggested
not to perform any time consuming operations during the call.
*/
// VFALCO TODO Perhaps allow construction using a ServiceQueue to use
// for notifications.
//
class Listener
{
public:
/** Entry point called by DeadlineTimer when a deadline elapses. */
virtual void onDeadlineTimer (DeadlineTimer&) = 0;
};
/** Create a deadline timer with the specified listener attached.
@param listener pointer to Listener that is called at the deadline.
*/
explicit DeadlineTimer (Listener* listener);
/// @cond INTERNAL
DeadlineTimer (DeadlineTimer const&) = delete;
DeadlineTimer& operator= (DeadlineTimer const&) = delete;
/// @endcond
/** Destructor. */
~DeadlineTimer ();
/** Cancel all notifications.
It is okay to call this on an inactive timer.
@note It is guaranteed that no notifications will occur after this
function returns.
*/
void cancel ();
/** Set the timer to go off once in the future.
If the timer is already active, this will reset it.
@note If the timer is already active, the old one might go off
before this function returns.
@param delay duration until the timer will send a notification.
This must be greater than zero.
*/
void setExpiration (duration delay);
/** Set the timer to go off repeatedly with the specified period.
If the timer is already active, this will reset it.
@note If the timer is already active, the old one might go off
before this function returns.
@param interval duration until the timer will send a notification.
This must be greater than zero.
*/
void setRecurringExpiration (duration interval);
/** Equality comparison.
Timers are equal if they have the same address.
*/
inline bool operator== (DeadlineTimer const& other) const
{
return this == &other;
}
/** Inequality comparison. */
inline bool operator!= (DeadlineTimer const& other) const
{
return this != &other;
}
private:
class Manager;
Listener* const m_listener;
bool m_isActive;
time_point notificationTime_;
duration recurring_; // > 0ms if recurring.
};
}
#endif

View File

@@ -1,205 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2017 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_CORE_JOB_COUNTER_H_INCLUDED
#define RIPPLE_CORE_JOB_COUNTER_H_INCLUDED
#include <ripple/basics/Log.h>
#include <ripple/core/Job.h>
#include <boost/optional.hpp>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <type_traits>
namespace ripple {
// A class that does reference counting for Jobs. The reference counting
// allows a Stoppable to assure that all child Jobs in the JobQueue are
// completed before the Stoppable declares itself stopped().
class JobCounter
{
private:
std::mutex mutable mutex_ {};
std::condition_variable allJobsDoneCond_ {}; // guard with mutex_
bool waitForJobs_ {false}; // guard with mutex_
std::atomic<int> jobCount_ {0};
// Increment the count.
JobCounter& operator++()
{
++jobCount_;
return *this;
}
// Decrement the count. If we're stopping and the count drops to zero
// notify allJobsDoneCond_.
JobCounter& operator--()
{
// Even though jobCount_ is atomic, we decrement its value under a
// lock. This removes a small timing window that occurs if the
// waiting thread is handling a spurious wakeup when jobCount_
// drops to zero.
std::lock_guard<std::mutex> lock {mutex_};
// Update jobCount_. Notify if we're stopping and all jobs are done.
if ((--jobCount_ == 0) && waitForJobs_)
allJobsDoneCond_.notify_all();
return *this;
}
// A private template class that helps count the number of Jobs
// in flight. This allows Stoppables to hold off declaring stopped()
// until all their JobQueue Jobs are dispatched.
template <typename JobHandler>
class CountedJob
{
private:
JobCounter& counter_;
JobHandler handler_;
static_assert (
std::is_same<decltype(
handler_(std::declval<Job&>())), void>::value,
"JobHandler must be callable with Job&");
public:
CountedJob() = delete;
CountedJob (CountedJob const& rhs)
: counter_ (rhs.counter_)
, handler_ (rhs.handler_)
{
++counter_;
}
CountedJob (CountedJob&& rhs)
: counter_ (rhs.counter_)
, handler_ (std::move (rhs.handler_))
{
++counter_;
}
CountedJob (JobCounter& counter, JobHandler&& handler)
: counter_ (counter)
, handler_ (std::move (handler))
{
++counter_;
}
CountedJob& operator=(CountedJob const& rhs) = delete;
CountedJob& operator=(CountedJob&& rhs) = delete;
~CountedJob()
{
--counter_;
}
void operator ()(Job& job)
{
return handler_ (job);
}
};
public:
JobCounter() = default;
// Not copyable or movable. Outstanding counts would be hard to sort out.
JobCounter (JobCounter const&) = delete;
JobCounter& operator=(JobCounter const&) = delete;
/** Destructor verifies all in-flight jobs are complete. */
~JobCounter()
{
using namespace std::chrono_literals;
join ("JobCounter", 1s, debugLog());
}
/** Returns once all counted in-flight Jobs are destroyed.
@param name Name reported if join time exceeds wait.
@param wait If join() exceeds this duration report to Journal.
@param j Journal written to if wait is exceeded.
*/
void join (char const* name,
std::chrono::milliseconds wait, beast::Journal j)
{
std::unique_lock<std::mutex> lock {mutex_};
waitForJobs_ = true;
if (jobCount_ > 0)
{
if (! allJobsDoneCond_.wait_for (
lock, wait, [this] { return jobCount_ == 0; }))
{
if (auto stream = j.error())
stream << name << " waiting for JobCounter::join().";
allJobsDoneCond_.wait (lock, [this] { return jobCount_ == 0; });
}
}
}
/** Wrap the passed lambda with a reference counter.
@param handler Lambda that accepts a Job& parameter and returns void.
@return If join() has been called returns boost::none. Otherwise
returns a boost::optional that wraps handler with a
reference counter.
*/
template <class JobHandler>
boost::optional<CountedJob<JobHandler>>
wrap (JobHandler&& handler)
{
// The current intention is that wrap() may only be called with an
// rvalue lambda. That can be adjusted in the future if needed,
// but the following static_assert covers current expectations.
static_assert (std::is_rvalue_reference<decltype (handler)>::value,
"JobCounter::wrap() only supports rvalue lambdas.");
boost::optional<CountedJob<JobHandler>> ret;
std::lock_guard<std::mutex> lock {mutex_};
if (! waitForJobs_)
ret.emplace (*this, std::move (handler));
return ret;
}
/** Current number of Jobs outstanding. Only useful for testing. */
int count() const
{
return jobCount_;
}
/** Returns true if this has been joined.
Even if true is returned, counted Jobs may still be in flight.
However if (joined() && (count() == 0)) there should be no more
counted Jobs in flight.
*/
bool joined() const
{
std::lock_guard<std::mutex> lock {mutex_};
return waitForJobs_;
}
};
} // ripple
#endif // RIPPLE_CORE_JOB_COUNTER_H_INCLUDED

View File

@@ -22,7 +22,6 @@
#include <ripple/basics/LocalValue.h>
#include <ripple/basics/win32_workaround.h>
#include <ripple/core/JobCounter.h>
#include <ripple/core/JobTypes.h>
#include <ripple/core/JobTypeData.h>
#include <ripple/core/Stoppable.h>

View File

@@ -23,7 +23,8 @@
#include <ripple/beast/core/LockFreeStack.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/beast/core/WaitableEvent.h>
#include <ripple/core/JobCounter.h>
#include <ripple/core/Job.h>
#include <ripple/core/ClosureCounter.h>
#include <atomic>
#include <chrono>
#include <condition_variable>
@@ -31,6 +32,9 @@
namespace ripple {
// Give a reasonable name for the JobCounter
using JobCounter = ClosureCounter<void, Job&>;
class RootStoppable;
/** Provides an interface for starting and stopping.

View File

@@ -1,338 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/core/DeadlineTimer.h>
#include <ripple/basics/contract.h>
#include <ripple/beast/core/CurrentThreadName.h>
#include <cassert>
#include <condition_variable>
#include <mutex>
#include <thread>
namespace ripple {
class DeadlineTimer::Manager
{
private:
using Items = beast::List <DeadlineTimer>;
// Use RAII to manage our recursion counter.
//
// NOTE: Creation of any lock(mutex_) should be immediately followed
// by constructing a named CountRecursion. Otherwise the mutex recursion
// tracking will be faulty.
class CountRecursion
{
int& counter_;
public:
CountRecursion (CountRecursion const&) = delete;
CountRecursion& operator=(CountRecursion const&) = delete;
explicit CountRecursion (int& counter)
: counter_ {counter}
{
++counter_;
}
~CountRecursion()
{
--counter_;
}
};
Manager ()
{
thread_ = std::thread {&Manager::run, this};
}
~Manager ()
{
{
std::lock_guard<std::recursive_mutex> lock {mutex_};
CountRecursion c {recursionCount_};
shouldExit_ = true;
wakeup_.notify_one();
}
thread_.join();
assert (m_items.empty ());
}
public:
static
Manager&
instance()
{
static Manager m;
return m;
}
// Okay to call on an active timer.
// However, an extra notification may still happen due to concurrency.
//
void activate (DeadlineTimer& timer,
duration recurring,
time_point when)
{
using namespace std::chrono_literals;
assert (recurring >= 0ms);
std::lock_guard <std::recursive_mutex> lock {mutex_};
CountRecursion c {recursionCount_};
if (timer.m_isActive)
{
m_items.erase (m_items.iterator_to (timer));
timer.m_isActive = false;
}
timer.recurring_ = recurring;
timer.notificationTime_ = when;
insertSorted (timer);
timer.m_isActive = true;
wakeup_.notify_one();
}
// Okay to call this on an inactive timer.
// This can happen naturally based on concurrency.
//
void deactivate (DeadlineTimer& timer)
{
std::lock_guard <std::recursive_mutex> lock {mutex_};
CountRecursion c {recursionCount_};
if (timer.m_isActive)
{
m_items.erase (m_items.iterator_to (timer));
timer.m_isActive = false;
wakeup_.notify_one();
}
}
void run ()
{
using namespace std::chrono;
beast::setCurrentThreadName ("DeadlineTimer");
bool shouldExit = true;
do
{
{
auto const currentTime =
time_point_cast<duration>(clock::now());
auto nextDeadline = currentTime;
std::unique_lock <std::recursive_mutex> lock {mutex_};
CountRecursion c {recursionCount_};
// See if a timer expired
if (!shouldExit_ && !m_items.empty ())
{
DeadlineTimer* const timer = &m_items.front ();
// Has this timer expired?
if (timer->notificationTime_ <= currentTime)
{
// Expired, remove it from the list.
assert (timer->m_isActive);
m_items.pop_front ();
// Is the timer recurring?
if (timer->recurring_ > 0ms)
{
// Yes so set the timer again.
timer->notificationTime_ =
currentTime + timer->recurring_;
// Put it back into the list as active
insertSorted (*timer);
}
else
{
// Not a recurring timer, deactivate it.
timer->m_isActive = false;
}
// Given the current code structure this call must
// happen inside the lock. Once the lock is released
// the timer might be canceled and it would be invalid
// to call timer->m_listener.
timer->m_listener->onDeadlineTimer (*timer);
// re-loop
nextDeadline = currentTime - 1s;
}
else
{
// Timer has not yet expired.
nextDeadline = timer->notificationTime_;
// Can't be zero and come into the else clause.
assert (nextDeadline > currentTime);
}
}
if (!shouldExit_)
{
// It's bad news to invoke std::condition_variable_any
// wait() or wait_until() on a recursive_mutex if the
// recursion depth is greater than one. That's because
// wait() and wait_until() will only release one level
// of lock.
//
// We believe that the lock recursion depth can only be
// one at this point in the code, given the current code
// structure (December 2016). Here's why:
//
// 1. The DeadlineTimer::Manager runs exclusively on its
// own dedicated thread. This is the only thread where
// wakeup_.wait() or wakeup_.wait_until() are called.
//
// 2. So in order for the recursive_mutex to be called
// recursively, it must result from the call through
// timer->m_listener->onDeadlineTimer (*timer).
//
// 3. Any callback into DeadlineTimer from a Listener
// may do one of two things: a call to activate() or
// a call to deactivate(). Either of these will invoke
// the lock recursively. Then they both invoke
// condition_variable_any wakeup_.notify_one() under
// the recursive lock. Then they release the recursive
// lock. Once this local lock release occurs the
// recursion depth should be back to one.
//
// 4. So, once the Listener callback completes then the
// recursive_lock is no longer recursively held. That
// means when we enter the wakeup_.wait() or the
// wakeup_.wait_until() the lock is never held
// recursively.
//
// In case that analysis is, or becomes, incorrect the
// following LogicError should fire.
if (recursionCount_ != 1)
LogicError ("DeadlineTimer mutex recursion violation.");
if (nextDeadline > currentTime)
// Wake up at the next deadline or next notify.
// Cast to clock::duration to work around VS-2015 bug.
// Harmless on other platforms.
wakeup_.wait_until (lock,
time_point_cast<clock::duration>(nextDeadline));
else if (nextDeadline == currentTime)
// There is no deadline. Wake up at the next notify.
wakeup_.wait (lock);
else;
// Do not wait. This can happen if the recurring
// timer duration is extremely short or if a listener
// burns lots of time in their callback.
}
// shouldExit is used outside the lock.
shouldExit = shouldExit_;
} // Note that we release the lock here.
} while (!shouldExit);
}
// Caller is responsible for locking
void insertSorted (DeadlineTimer& timer)
{
if (! m_items.empty ())
{
Items::iterator before {m_items.begin()};
for (;;)
{
if (before->notificationTime_ >= timer.notificationTime_)
{
m_items.insert (before, timer);
break;
}
++before;
if (before == m_items.end ())
{
m_items.push_back (timer);
break;
}
}
}
else
{
m_items.push_back (timer);
}
}
private:
std::recursive_mutex mutex_;
std::condition_variable_any wakeup_; // Works with std::recursive_mutex.
std::thread thread_;
bool shouldExit_ {false};
int recursionCount_ {0};
Items m_items;
};
//------------------------------------------------------------------------------
DeadlineTimer::DeadlineTimer (Listener* listener)
: m_listener (listener)
, m_isActive (false)
{
}
DeadlineTimer::~DeadlineTimer ()
{
Manager::instance().deactivate (*this);
}
void DeadlineTimer::cancel ()
{
Manager::instance().deactivate (*this);
}
void DeadlineTimer::setExpiration (std::chrono::milliseconds delay)
{
using namespace std::chrono;
assert (delay > 0ms);
auto const when = time_point_cast<duration>(clock::now() + delay);
Manager::instance().activate (*this, 0ms, when);
}
void DeadlineTimer::setRecurringExpiration (std::chrono::milliseconds interval)
{
using namespace std::chrono;
assert (interval > 0ms);
auto const when = time_point_cast<duration>(clock::now() + interval);
Manager::instance().activate (*this, interval, when);
}
} // ripple

View File

@@ -21,7 +21,6 @@
#include <ripple/core/impl/Config.cpp>
#include <ripple/core/impl/DatabaseCon.cpp>
#include <ripple/core/impl/DeadlineTimer.cpp>
#include <ripple/core/impl/LoadEvent.cpp>
#include <ripple/core/impl/LoadMonitor.cpp>
#include <ripple/core/impl/Job.cpp>

View File

@@ -0,0 +1,329 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2017 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/core/ClosureCounter.h>
#include <ripple/beast/unit_test.h>
#include <test/jtx/Env.h>
#include <atomic>
#include <chrono>
#include <thread>
namespace ripple {
namespace test {
//------------------------------------------------------------------------------
class ClosureCounter_test : public beast::unit_test::suite
{
// We're only using Env for its Journal.
jtx::Env env {*this};
beast::Journal j {env.app().journal ("ClosureCounter_test")};
void testConstruction()
{
// Build different kinds of ClosureCounters.
{
// Count closures that return void and take no arguments.
ClosureCounter<void> voidCounter;
BEAST_EXPECT (voidCounter.count() == 0);
int evidence = 0;
// Make sure voidCounter.wrap works with an rvalue closure.
auto wrapped = voidCounter.wrap ([&evidence] () { ++evidence; });
BEAST_EXPECT (voidCounter.count() == 1);
BEAST_EXPECT (evidence == 0);
BEAST_EXPECT (wrapped);
// wrapped() should be callable with no arguments.
(*wrapped)();
BEAST_EXPECT (evidence == 1);
(*wrapped)();
BEAST_EXPECT (evidence == 2);
// Destroying the contents of wrapped should decrement voidCounter.
wrapped = boost::none;
BEAST_EXPECT (voidCounter.count() == 0);
}
{
// Count closures that return void and take one int argument.
ClosureCounter<void, int> setCounter;
BEAST_EXPECT (setCounter.count() == 0);
int evidence = 0;
// Make sure setCounter.wrap works with a non-const lvalue closure.
auto setInt = [&evidence] (int i) { evidence = i; };
auto wrapped = setCounter.wrap (setInt);
BEAST_EXPECT (setCounter.count() == 1);
BEAST_EXPECT (evidence == 0);
BEAST_EXPECT (wrapped);
// wrapped() should be callable with one integer argument.
(*wrapped)(5);
BEAST_EXPECT (evidence == 5);
(*wrapped)(11);
BEAST_EXPECT (evidence == 11);
// Destroying the contents of wrapped should decrement setCounter.
wrapped = boost::none;
BEAST_EXPECT (setCounter.count() == 0);
}
{
// Count closures that return int and take two int arguments.
ClosureCounter<int, int, int> sumCounter;
BEAST_EXPECT (sumCounter.count() == 0);
// Make sure sumCounter.wrap works with a const lvalue closure.
auto const sum = [] (int i, int j) { return i + j; };
auto wrapped = sumCounter.wrap (sum);
BEAST_EXPECT (sumCounter.count() == 1);
BEAST_EXPECT (wrapped);
// wrapped() should be callable with two integers.
BEAST_EXPECT ((*wrapped)(5, 2) == 7);
BEAST_EXPECT ((*wrapped)(2, -8) == -6);
// Destroying the contents of wrapped should decrement sumCounter.
wrapped = boost::none;
BEAST_EXPECT (sumCounter.count() == 0);
}
}
// A class used to test argument passing.
class TrackedString
{
public:
int copies = {0};
int moves = {0};
std::string str;
TrackedString() = delete;
explicit TrackedString(char const* rhs)
: str (rhs) {}
// Copy constructor
TrackedString (TrackedString const& rhs)
: copies (rhs.copies + 1)
, moves (rhs.moves)
, str (rhs.str) {}
// Move constructor
TrackedString (TrackedString&& rhs)
: copies (rhs.copies)
, moves (rhs.moves + 1)
, str (std::move(rhs.str)) {}
// Delete copy and move assignment.
TrackedString& operator=(TrackedString const& rhs) = delete;
// String concatenation
TrackedString& operator+=(char const* rhs)
{
str += rhs;
return *this;
}
friend
TrackedString operator+(TrackedString const& str, char const* rhs)
{
TrackedString ret {str};
ret.str += rhs;
return ret;
}
};
void testArgs()
{
// Make sure a wrapped closure handles rvalue reference arguments
// correctly.
{
// Pass by value.
ClosureCounter<TrackedString, TrackedString> strCounter;
BEAST_EXPECT (strCounter.count() == 0);
auto wrapped = strCounter.wrap (
[] (TrackedString in) { return in += "!"; });
BEAST_EXPECT (strCounter.count() == 1);
BEAST_EXPECT (wrapped);
TrackedString const strValue ("value");
TrackedString const result = (*wrapped)(strValue);
BEAST_EXPECT (result.copies == 2);
BEAST_EXPECT (result.moves == 1);
BEAST_EXPECT (result.str == "value!");
BEAST_EXPECT (strValue.str.size() == 5);
}
{
// Use a const lvalue argument.
ClosureCounter<TrackedString, TrackedString const&> strCounter;
BEAST_EXPECT (strCounter.count() == 0);
auto wrapped = strCounter.wrap (
[] (TrackedString const& in) { return in + "!"; });
BEAST_EXPECT (strCounter.count() == 1);
BEAST_EXPECT (wrapped);
TrackedString const strConstLValue ("const lvalue");
TrackedString const result = (*wrapped)(strConstLValue);
BEAST_EXPECT (result.copies == 1);
// BEAST_EXPECT (result.moves == ?); // moves VS == 1, gcc == 0
BEAST_EXPECT (result.str == "const lvalue!");
BEAST_EXPECT (strConstLValue.str.size() == 12);
}
{
// Use a non-const lvalue argument.
ClosureCounter<TrackedString, TrackedString&> strCounter;
BEAST_EXPECT (strCounter.count() == 0);
auto wrapped = strCounter.wrap (
[] (TrackedString& in) { return in += "!"; });
BEAST_EXPECT (strCounter.count() == 1);
BEAST_EXPECT (wrapped);
TrackedString strLValue ("lvalue");
TrackedString const result = (*wrapped)(strLValue);
BEAST_EXPECT (result.copies == 1);
BEAST_EXPECT (result.moves == 0);
BEAST_EXPECT (result.str == "lvalue!");
BEAST_EXPECT (strLValue.str == result.str);
}
{
// Use an rvalue argument.
ClosureCounter<TrackedString, TrackedString&&> strCounter;
BEAST_EXPECT (strCounter.count() == 0);
auto wrapped = strCounter.wrap (
[] (TrackedString&& in) {
// Note that none of the compilers noticed that in was
// leaving scope. So, without intervention, they would
// do a copy for the return (June 2017). An explicit
// std::move() was required.
return std::move(in += "!");
});
BEAST_EXPECT (strCounter.count() == 1);
BEAST_EXPECT (wrapped);
// Make the string big enough to (probably) avoid the small string
// optimization.
TrackedString strRValue ("rvalue abcdefghijklmnopqrstuvwxyz");
TrackedString const result = (*wrapped)(std::move(strRValue));
BEAST_EXPECT (result.copies == 0);
BEAST_EXPECT (result.moves == 1);
BEAST_EXPECT (result.str == "rvalue abcdefghijklmnopqrstuvwxyz!");
BEAST_EXPECT (strRValue.str.size() == 0);
}
}
void testWrap()
{
// Verify reference counting.
ClosureCounter<void> voidCounter;
BEAST_EXPECT (voidCounter.count() == 0);
{
auto wrapped1 = voidCounter.wrap ([] () {});
BEAST_EXPECT (voidCounter.count() == 1);
{
// Copy should increase reference count.
auto wrapped2 (wrapped1);
BEAST_EXPECT (voidCounter.count() == 2);
{
// Move should increase reference count.
auto wrapped3 (std::move(wrapped2));
BEAST_EXPECT (voidCounter.count() == 3);
{
// An additional closure also increases count.
auto wrapped4 = voidCounter.wrap ([] () {});
BEAST_EXPECT (voidCounter.count() == 4);
}
BEAST_EXPECT (voidCounter.count() == 3);
}
BEAST_EXPECT (voidCounter.count() == 2);
}
BEAST_EXPECT (voidCounter.count() == 1);
}
BEAST_EXPECT (voidCounter.count() == 0);
// Join with 0 count should not stall.
using namespace std::chrono_literals;
voidCounter.join("testWrap", 1ms, j);
// Wrapping a closure after join() should return boost::none.
BEAST_EXPECT (voidCounter.wrap ([] () {}) == boost::none);
}
void testWaitOnJoin()
{
// Verify reference counting.
ClosureCounter<void> voidCounter;
BEAST_EXPECT (voidCounter.count() == 0);
auto wrapped = (voidCounter.wrap ([] () {}));
BEAST_EXPECT (voidCounter.count() == 1);
// Calling join() now should stall, so do it on a different thread.
std::atomic<bool> threadExited {false};
std::thread localThread ([&voidCounter, &threadExited, this] ()
{
// Should stall after calling join.
using namespace std::chrono_literals;
voidCounter.join("testWaitOnJoin", 1ms, j);
threadExited.store (true);
});
// Wait for the thread to call voidCounter.join().
while (! voidCounter.joined());
// The thread should still be active after waiting 5 milliseconds.
// This is not a guarantee that join() stalled the thread, but it
// improves confidence.
using namespace std::chrono_literals;
std::this_thread::sleep_for (5ms);
BEAST_EXPECT (threadExited == false);
// Destroy the contents of wrapped and expect the thread to exit
// (asynchronously).
wrapped = boost::none;
BEAST_EXPECT (voidCounter.count() == 0);
// Wait for the thread to exit.
while (threadExited == false);
localThread.join();
}
public:
void run()
{
testConstruction();
testArgs();
testWrap();
testWaitOnJoin();
}
};
BEAST_DEFINE_TESTSUITE(ClosureCounter, core, ripple);
} // test
} // ripple

View File

@@ -1,123 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2016 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/core/DeadlineTimer.h>
#include <ripple/beast/unit_test.h>
#include <atomic>
#include <chrono>
#include <thread>
namespace ripple {
//------------------------------------------------------------------------------
class DeadlineTimer_test : public beast::unit_test::suite
{
public:
struct TestCallback : DeadlineTimer::Listener
{
TestCallback() = default;
void onDeadlineTimer (DeadlineTimer&) override
{
++count;
}
std::atomic<int> count {0};
};
void testExpiration()
{
using clock = DeadlineTimer::clock;
using namespace std::chrono_literals;
using namespace std::this_thread;
TestCallback cb;
DeadlineTimer dt {&cb};
// There are parts of this test that are somewhat race conditional.
// The test is designed to avoid spurious failures, rather than
// fail occasionally but randomly, where ever possible. So there may
// be occasional gratuitous passes. Unfortunately, since it is a
// time-based test, there may also be occasional spurious failures
// on low-powered continuous integration platforms.
{
testcase("Expiration");
// Set a deadline timer that should only fire once in 5ms.
cb.count = 0;
auto const startTime = clock::now();
dt.setExpiration (5ms);
// Make sure the timer didn't fire immediately.
int const count = cb.count.load();
if (clock::now() < startTime + 4ms)
{
BEAST_EXPECT (count == 0);
}
// Wait until the timer should have fired and check that it did.
// In fact, we wait long enough that if it were to fire multiple
// times we'd see that.
sleep_until (startTime + 50ms);
BEAST_EXPECT (cb.count.load() == 1);
}
{
testcase("RecurringExpiration");
// Set a deadline timer that should fire once every 5ms.
cb.count = 0;
auto const startTime = clock::now();
dt.setRecurringExpiration (5ms);
// Make sure the timer didn't fire immediately.
{
int const count = cb.count.load();
if (clock::now() < startTime + 4ms)
{
BEAST_EXPECT (count == 0);
}
}
// Wait until the timer should have fired several times and
// check that it did.
sleep_until (startTime + 100ms);
{
auto const count = cb.count.load();
BEAST_EXPECT ((count > 1) && (count < 21));
}
// Cancel the recurring timer and it should not fire any more.
dt.cancel();
auto const count = cb.count.load();
sleep_for (50ms);
BEAST_EXPECT (count == cb.count.load());
}
}
void run()
{
testExpiration();
}
};
BEAST_DEFINE_TESTSUITE(DeadlineTimer, core, ripple);
}

View File

@@ -1,131 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2017 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/core/JobCounter.h>
#include <ripple/beast/unit_test.h>
#include <test/jtx/Env.h>
#include <atomic>
#include <chrono>
#include <thread>
namespace ripple {
namespace test {
//------------------------------------------------------------------------------
class JobCounter_test : public beast::unit_test::suite
{
// We're only using Env for its Journal.
jtx::Env env {*this};
beast::Journal j {env.app().journal ("JobCounter_test")};
void testWrap()
{
// Verify reference counting.
JobCounter jobCounter;
BEAST_EXPECT (jobCounter.count() == 0);
{
auto wrapped1 = jobCounter.wrap ([] (Job&) {});
BEAST_EXPECT (jobCounter.count() == 1);
// wrapped1 should be callable with a Job.
{
Job job;
(*wrapped1)(job);
}
{
// Copy should increase reference count.
auto wrapped2 (wrapped1);
BEAST_EXPECT (jobCounter.count() == 2);
{
// Move should increase reference count.
auto wrapped3 (std::move(wrapped2));
BEAST_EXPECT (jobCounter.count() == 3);
{
// An additional Job also increases count.
auto wrapped4 = jobCounter.wrap ([] (Job&) {});
BEAST_EXPECT (jobCounter.count() == 4);
}
BEAST_EXPECT (jobCounter.count() == 3);
}
BEAST_EXPECT (jobCounter.count() == 2);
}
BEAST_EXPECT (jobCounter.count() == 1);
}
BEAST_EXPECT (jobCounter.count() == 0);
// Join with 0 count should not stall.
using namespace std::chrono_literals;
jobCounter.join("testWrap", 1ms, j);
// Wrapping a Job after join() should return boost::none.
BEAST_EXPECT (jobCounter.wrap ([] (Job&) {}) == boost::none);
}
void testWaitOnJoin()
{
// Verify reference counting.
JobCounter jobCounter;
BEAST_EXPECT (jobCounter.count() == 0);
auto job = (jobCounter.wrap ([] (Job&) {}));
BEAST_EXPECT (jobCounter.count() == 1);
// Calling join() now should stall, so do it on a different thread.
std::atomic<bool> threadExited {false};
std::thread localThread ([&jobCounter, &threadExited, this] ()
{
// Should stall after calling join.
using namespace std::chrono_literals;
jobCounter.join("testWaitOnJoin", 1ms, j);
threadExited.store (true);
});
// Wait for the thread to call jobCounter.join().
while (! jobCounter.joined());
// The thread should still be active after waiting 5 milliseconds.
// This is not a guarantee that join() stalled the thread, but it
// improves confidence.
using namespace std::chrono_literals;
std::this_thread::sleep_for (5ms);
BEAST_EXPECT (threadExited == false);
// Destroy the Job and expect the thread to exit (asynchronously).
job = boost::none;
BEAST_EXPECT (jobCounter.count() == 0);
// Wait for the thread to exit.
while (threadExited == false);
localThread.join();
}
public:
void run()
{
testWrap();
testWaitOnJoin();
}
};
BEAST_DEFINE_TESTSUITE(JobCounter, core, ripple);
} // test
} // ripple

View File

@@ -25,7 +25,6 @@
#include <test/jtx/envconfig.h>
#include <test/jtx/WSClient.h>
#include <test/jtx/JSONRPCClient.h>
#include <ripple/core/DeadlineTimer.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <beast/http.hpp>

View File

@@ -21,8 +21,7 @@
#include <test/core/Config_test.cpp>
#include <test/core/Coroutine_test.cpp>
#include <test/core/CryptoPRNG_test.cpp>
#include <test/core/DeadlineTimer_test.cpp>
#include <test/core/JobCounter_test.cpp>
#include <test/core/ClosureCounter_test.cpp>
#include <test/core/JobQueue_test.cpp>
#include <test/core/SociDB_test.cpp>
#include <test/core/Stoppable_test.cpp>