mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
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:
207
src/ripple/core/ClosureCounter.h
Normal file
207
src/ripple/core/ClosureCounter.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user