mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Refactor Application shutdown using new Service, AsyncService interfaces
This commit is contained in:
@@ -93,21 +93,22 @@ void logTimedDestroy (
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Log a timed function call if the time exceeds a threshold. */
|
||||
template <typename PartitionKey, typename Function>
|
||||
void logTimedCall (String description, char const* fileName, int lineNumber,
|
||||
template <typename Function>
|
||||
void logTimedCall (Journal::Stream stream,
|
||||
String description,
|
||||
char const* fileName,
|
||||
int lineNumber,
|
||||
Function f, double thresholdSeconds = 1)
|
||||
{
|
||||
double const seconds = measureFunctionCallTime (f);
|
||||
|
||||
if (seconds > thresholdSeconds)
|
||||
{
|
||||
LogSeverity const severity = lsWARNING;
|
||||
|
||||
Log (severity, LogPartition::get <PartitionKey> ()) <<
|
||||
stream <<
|
||||
description << " took "<<
|
||||
String (detail::cleanElapsed (seconds)) <<
|
||||
" seconds to execute at " <<
|
||||
Debug::getSourceLocation (fileName, lineNumber);
|
||||
String (detail::cleanElapsed (seconds)) <<
|
||||
" seconds to execute at " <<
|
||||
Debug::getSourceLocation (fileName, lineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ namespace ripple
|
||||
#include "utility/CountedObject.cpp"
|
||||
#include "utility/DiffieHellmanUtil.cpp"
|
||||
#include "utility/IniFile.cpp"
|
||||
#include "utility/Service.cpp"
|
||||
#include "utility/StringUtilities.cpp"
|
||||
#include "utility/Sustain.cpp"
|
||||
#include "utility/ThreadName.cpp"
|
||||
|
||||
@@ -108,6 +108,7 @@ using namespace beast;
|
||||
#include "utility/IniFile.h"
|
||||
#include "utility/PlatformMacros.h"
|
||||
#include "utility/RandomNumbers.h"
|
||||
#include "utility/Service.h"
|
||||
#include "utility/StringUtilities.h"
|
||||
#include "utility/Sustain.h"
|
||||
#include "utility/ThreadName.h"
|
||||
|
||||
@@ -35,5 +35,7 @@ typedef UnsignedInteger <33> RipplePublicKey;
|
||||
/** A container holding the hash of a public key in binary format. */
|
||||
typedef UnsignedInteger <20> RipplePublicKeyHash;
|
||||
|
||||
/** A callback used to check for canceling an operation. */
|
||||
typedef SharedFunction <bool(void)> CancelCallback;
|
||||
|
||||
#endif
|
||||
|
||||
200
src/ripple_basics/utility/Service.cpp
Normal file
200
src/ripple_basics/utility/Service.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
Service::Service (char const* name)
|
||||
: m_name (name)
|
||||
, m_root (true)
|
||||
, m_child (this)
|
||||
, m_calledServiceStop (false)
|
||||
, m_stopped (false)
|
||||
, m_childrenStopped (false)
|
||||
{
|
||||
}
|
||||
|
||||
Service::Service (char const* name, Service* parent)
|
||||
: m_name (name)
|
||||
, m_root (parent != nullptr)
|
||||
, m_child (this)
|
||||
, m_calledServiceStop (false)
|
||||
, m_stopped (false)
|
||||
, m_childrenStopped (false)
|
||||
{
|
||||
if (parent != nullptr)
|
||||
{
|
||||
// must not have had stop called
|
||||
bassert (! parent->isServiceStopping());
|
||||
|
||||
parent->m_children.push_front (&m_child);
|
||||
}
|
||||
}
|
||||
|
||||
Service::Service (char const* name, Service& parent)
|
||||
: m_name (name)
|
||||
, m_root (false)
|
||||
, m_child (this)
|
||||
, m_calledServiceStop (false)
|
||||
, m_stopped (false)
|
||||
, m_childrenStopped (false)
|
||||
{
|
||||
// must not have had stop called
|
||||
bassert (! parent.isServiceStopping());
|
||||
|
||||
parent.m_children.push_front (&m_child);
|
||||
}
|
||||
|
||||
Service::~Service ()
|
||||
{
|
||||
// must be stopped
|
||||
bassert (m_stopped);
|
||||
|
||||
// children must be stopped
|
||||
bassert (m_childrenStopped);
|
||||
}
|
||||
|
||||
char const* Service::serviceName () const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void Service::serviceStop (Journal::Stream stream)
|
||||
{
|
||||
// may only be called once
|
||||
if (m_calledServiceStop)
|
||||
return;
|
||||
|
||||
m_calledServiceStop = true;
|
||||
|
||||
// must be called from a root service
|
||||
bassert (m_root);
|
||||
|
||||
// send the notification
|
||||
serviceStopAsync ();
|
||||
|
||||
// now block on the tree of Service objects from the leaves up.
|
||||
stopRecursive (stream);
|
||||
}
|
||||
|
||||
void Service::serviceStopAsync ()
|
||||
{
|
||||
// must be called from a root service
|
||||
bassert (m_root);
|
||||
|
||||
stopAsyncRecursive ();
|
||||
}
|
||||
|
||||
bool Service::isServiceStopping ()
|
||||
{
|
||||
return m_calledStopAsync.get() != 0;
|
||||
}
|
||||
|
||||
bool Service::isServiceStopped ()
|
||||
{
|
||||
return m_stopped;
|
||||
}
|
||||
|
||||
bool Service::areServiceChildrenStopped ()
|
||||
{
|
||||
return m_childrenStopped;
|
||||
}
|
||||
|
||||
void Service::serviceStopped ()
|
||||
{
|
||||
m_stoppedEvent.signal();
|
||||
}
|
||||
|
||||
void Service::onServiceStop()
|
||||
{
|
||||
serviceStopped();
|
||||
}
|
||||
|
||||
void Service::onServiceChildrenStopped ()
|
||||
{
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void Service::stopAsyncRecursive ()
|
||||
{
|
||||
// make sure we only do this once
|
||||
if (m_root)
|
||||
{
|
||||
// if this fails, some other thread got to it first
|
||||
if (! m_calledStopAsync.compareAndSetBool (1, 0))
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// can't possibly already be set
|
||||
bassert (m_calledStopAsync.get() == 0);
|
||||
|
||||
m_calledStopAsync.set (1);
|
||||
}
|
||||
|
||||
// notify this service
|
||||
onServiceStop ();
|
||||
|
||||
// notify children
|
||||
for (Children::const_iterator iter (m_children.cbegin ());
|
||||
iter != m_children.cend(); ++iter)
|
||||
{
|
||||
iter->service->stopAsyncRecursive();
|
||||
}
|
||||
}
|
||||
|
||||
void Service::stopRecursive (Journal::Stream stream)
|
||||
{
|
||||
// Block on each child recursively. Thinking of the Service
|
||||
// hierarchy as a tree with the root at the top, we will block
|
||||
// first on leaves, and then at each successivly higher level.
|
||||
//
|
||||
for (Children::const_iterator iter (m_children.cbegin ());
|
||||
iter != m_children.cend(); ++iter)
|
||||
{
|
||||
iter->service->stopRecursive (stream);
|
||||
}
|
||||
|
||||
// Once we get here, we either have no children, or all of
|
||||
// our children have stopped, so update state accordingly.
|
||||
//
|
||||
m_childrenStopped = true;
|
||||
|
||||
// Notify derived class that children have stopped.
|
||||
onServiceChildrenStopped ();
|
||||
|
||||
// Block until this service stops. First we do a timed wait of 1 second, and
|
||||
// if that times out we report to the Journal and then do an infinite wait.
|
||||
//
|
||||
bool const timedOut (! m_stoppedEvent.wait (1 * 1000)); // milliseconds
|
||||
if (timedOut)
|
||||
{
|
||||
stream << "Service: Waiting for '" << serviceName() << "' to stop";
|
||||
m_stoppedEvent.wait ();
|
||||
}
|
||||
|
||||
// once we get here, we know the service has stopped.
|
||||
m_stopped = true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
ScopedService::ScopedService (char const* name)
|
||||
: Service (name)
|
||||
{
|
||||
}
|
||||
|
||||
ScopedService::~ScopedService ()
|
||||
{
|
||||
serviceStop();
|
||||
}
|
||||
|
||||
void ScopedService::onServiceStop ()
|
||||
{
|
||||
serviceStopped();
|
||||
}
|
||||
|
||||
void ScopedService::onServiceChildrenStopped ()
|
||||
{
|
||||
}
|
||||
275
src/ripple_basics/utility/Service.h
Normal file
275
src/ripple_basics/utility/Service.h
Normal file
@@ -0,0 +1,275 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_BASICS_SERVICE_H_INCLUDED
|
||||
#define RIPPLE_BASICS_SERVICE_H_INCLUDED
|
||||
|
||||
/** Abstraction for organizing partitioned support code.
|
||||
|
||||
The main thing a service can do, is to stop. Once it stops it cannot be
|
||||
reused, it can only be destroyed. This interface is used to coordinate
|
||||
the complex activities required for a clean exit in the presence of
|
||||
pending asynchronous i/o and multiple theads.
|
||||
|
||||
This is the sequence of events involved in stopping a service:
|
||||
|
||||
1. serviceStopAsync() [optional]
|
||||
|
||||
This notifies the Service and all its children that a stop is requested.
|
||||
|
||||
2. serviceStop ()
|
||||
|
||||
This first calls serviceStopAsync(), and then blocks on each Service
|
||||
in the tree from the bottom up, until the Service indicates it has
|
||||
stopped. This will usually be called from the main thread of execution
|
||||
when some external signal indicates that the process should stop.
|
||||
FOr example, an RPC 'stop' command, or a SIGINT POSIX signal.
|
||||
|
||||
3. onServiceStop ()
|
||||
|
||||
This is called for the root Service and all its children when a stop
|
||||
is requested. Derived classes should cancel pending I/O and timers,
|
||||
signal that threads should exit, queue cleanup jobs, and perform any
|
||||
other necessary clean up in preparation for exit.
|
||||
|
||||
4. onServiceChildrenStopped ()
|
||||
|
||||
When all the children of a service have stopped, this will be called.
|
||||
This informs the Service that there should not be any more dependents
|
||||
making calls into the derived class member functions. A Service that
|
||||
has no children will have this function called immediately.
|
||||
|
||||
5. serviceStopped ()
|
||||
|
||||
The derived class calls this function to inform the Service API that
|
||||
it has completed the stop. This unblocks the caller of serviceStop().
|
||||
|
||||
For services which are only considered stopped when all of their children
|
||||
have stopped, and their own internal logic indicates a stop, it will be
|
||||
necessary to perform special actions in onServiceChildrenStopped(). The
|
||||
funtion areServiceChildrenStopped() can be used after children have
|
||||
stopped, but before the Service logic itself has stopped, to determine
|
||||
if the stopped service logic is a true stop.
|
||||
|
||||
Pseudo code for this process is as follows:
|
||||
|
||||
@code
|
||||
|
||||
// Returns `true` if derived logic has stopped.
|
||||
//
|
||||
// When the logic stops, logicProcessingStop() is no longer called.
|
||||
// If children are still active we need to wait until we get a
|
||||
// notification that the children have stopped.
|
||||
//
|
||||
bool logicHasStopped ();
|
||||
|
||||
// Called when children have stopped
|
||||
void onServiceChildrenStopped ()
|
||||
{
|
||||
// We have stopped when the derived logic stops and children stop.
|
||||
if (logicHasStopped)
|
||||
serviceStopped();
|
||||
}
|
||||
|
||||
// derived-specific logic that executes periodically
|
||||
void logicProcessingStep ()
|
||||
{
|
||||
// do the step
|
||||
// ...
|
||||
|
||||
// now see if we've stopped
|
||||
if (logicHasStopped() && areServiceChildrenStopped())
|
||||
serviceStopped();
|
||||
}
|
||||
|
||||
@endcode
|
||||
*/
|
||||
class Service
|
||||
{
|
||||
public:
|
||||
/** Create a service.
|
||||
Services are always created in a non-stopped state.
|
||||
A service without a parent is a root service.
|
||||
*/
|
||||
/** @{ */
|
||||
explicit Service (char const* name);
|
||||
Service (char const* name, Service* parent);
|
||||
Service (char const* name, Service& parent);
|
||||
/** @} */
|
||||
|
||||
/** Destroy the service.
|
||||
Undefined behavior results if the service is not first stopped.
|
||||
|
||||
In general, services are not allowed to be created and destroyed
|
||||
dynamically. The set of services should be static at some point
|
||||
after the initialization of the process. If you need a dynamic
|
||||
service, consider having a static Service which marshals service
|
||||
calls to a second custom interface.
|
||||
*/
|
||||
virtual ~Service ();
|
||||
|
||||
/** Returns the name of the service. */
|
||||
char const* serviceName () const;
|
||||
|
||||
/** Notify a root service and its children to stop, and block until stopped.
|
||||
If the service was already notified, it is not notified again.
|
||||
The call blocks until the service and all of its children have stopped.
|
||||
|
||||
Thread safety:
|
||||
Safe to call from any thread not associated with a Service.
|
||||
This function may only be called once.
|
||||
|
||||
@param stream An optional Journal stream on which to log progress.
|
||||
*/
|
||||
void serviceStop (Journal::Stream stream = Journal::Stream());
|
||||
|
||||
/** Notify a root service and children to stop, without waiting.
|
||||
If the service was already notified, it is not notified again.
|
||||
While this is safe to call more than once, only the first call
|
||||
has any effect.
|
||||
|
||||
Thread safety:
|
||||
Safe to call from any thread at any time.
|
||||
*/
|
||||
void serviceStopAsync ();
|
||||
|
||||
/** Returns `true` if the service should stop.
|
||||
Call from the derived class to determine if a long-running
|
||||
operation should be canceled.
|
||||
|
||||
Note that this is not appropriate for either threads, or asynchronous
|
||||
I/O. For threads, use the thread-specific facilities available to
|
||||
inform the thread that it should exi. For asynchronous I/O, cancel
|
||||
all pending operations inside the onServiceStop overide.
|
||||
|
||||
Thread safety:
|
||||
Safe to call from any thread at any time.
|
||||
|
||||
@see onServiceStop
|
||||
*/
|
||||
bool isServiceStopping ();
|
||||
|
||||
/** Returns `true` if the service has stopped.
|
||||
|
||||
Thread safety:
|
||||
Safe to call from any thread at any time.
|
||||
*/
|
||||
bool isServiceStopped ();
|
||||
|
||||
/** Returns `true` if all children have stopped.
|
||||
Children of services with no children are considered stopped if
|
||||
the service has been notified.
|
||||
|
||||
Thread safety:
|
||||
Safe to call from any thread at any time.
|
||||
*/
|
||||
bool areServiceChildrenStopped ();
|
||||
|
||||
/** Called by derived classes to indicate that the service has stopped.
|
||||
The derived class must call this either after isServiceStopping
|
||||
returns `true`, or when onServiceStop is called, or else a call
|
||||
to serviceStop will never return.
|
||||
|
||||
Thread safety:
|
||||
Safe to call from any thread at any time.
|
||||
*/
|
||||
void serviceStopped ();
|
||||
|
||||
/** Called when the stop notification is issued.
|
||||
|
||||
The call is made on an unspecified, implementation-specific thread.
|
||||
onServiceStop and onServiceChildrenStopped will never be called
|
||||
concurrently, across all Service objects descended from the same root,
|
||||
inclusive of the root.
|
||||
|
||||
It is safe to call isServiceStopping, isServiceStopped, and
|
||||
areServiceChildrenStopped from within this function; The values
|
||||
returned will always be valid and never change during the callback.
|
||||
|
||||
The default implementation simply calls serviceStopped(). This is
|
||||
applicable when the Service has a trivial stop operation (or no
|
||||
stop operation), and we are merely using the Service API to position
|
||||
it as a dependency of some parent service.
|
||||
|
||||
Thread safety:
|
||||
May not block for long periods.
|
||||
Guaranteed only to be called once.
|
||||
Must be safe to call from any thread at any time.
|
||||
*/
|
||||
virtual void onServiceStop ();
|
||||
|
||||
/** Called when all children of a service have stopped.
|
||||
|
||||
The call is made on an unspecified, implementation-specific thread.
|
||||
onServiceStop and onServiceChildrenStopped will never be called
|
||||
concurrently, across all Service objects descended from the same root,
|
||||
inclusive of the root.
|
||||
|
||||
It is safe to call isServiceStopping, isServiceStopped, and
|
||||
areServiceChildrenStopped from within this function; The values
|
||||
returned will always be valid and never change during the callback.
|
||||
|
||||
Thread safety:
|
||||
May not block for long periods.
|
||||
Guaranteed only to be called once.
|
||||
Must be safe to call from any thread at any time.
|
||||
*/
|
||||
virtual void onServiceChildrenStopped ();
|
||||
|
||||
private:
|
||||
struct Child;
|
||||
typedef LockFreeStack <Child> Children;
|
||||
|
||||
struct Child : Children::Node
|
||||
{
|
||||
Child (Service* service_) : service (service_)
|
||||
{
|
||||
}
|
||||
|
||||
Service* service;
|
||||
};
|
||||
|
||||
void stopAsyncRecursive ();
|
||||
void stopRecursive (Journal::Stream stream);
|
||||
|
||||
char const* m_name;
|
||||
bool m_root;
|
||||
Child m_child;
|
||||
Children m_children;
|
||||
|
||||
// Flag that we called serviceStop. This is for diagnostics.
|
||||
bool m_calledServiceStop;
|
||||
|
||||
// Atomic flag to make sure we only call serviceStopAsync once.
|
||||
Atomic <int> m_calledStopAsync;
|
||||
|
||||
// Flag that this service stopped. Never goes back to false.
|
||||
bool volatile m_stopped;
|
||||
|
||||
// Flag that all children have stopped (recursive). Never goes back to false.
|
||||
bool volatile m_childrenStopped;
|
||||
|
||||
// serviceStop() blocks on this event until serviceStopped() is called.
|
||||
WaitableEvent m_stoppedEvent;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** A root Service with a short scope.
|
||||
This Service takes care of stopping automatically, no additional
|
||||
action is required.
|
||||
*/
|
||||
class ScopedService : public Service
|
||||
{
|
||||
public:
|
||||
explicit ScopedService (char const* name);
|
||||
~ScopedService ();
|
||||
|
||||
void onServiceStop ();
|
||||
void onServiceChildrenStopped ();
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user