diff --git a/Builds/VisualStudio2012/beast.vcxproj b/Builds/VisualStudio2012/beast.vcxproj index b6934c6d..6f9e5f3c 100644 --- a/Builds/VisualStudio2012/beast.vcxproj +++ b/Builds/VisualStudio2012/beast.vcxproj @@ -307,6 +307,7 @@ + @@ -1138,6 +1139,12 @@ true true + + true + true + true + true + true true diff --git a/Builds/VisualStudio2012/beast.vcxproj.filters b/Builds/VisualStudio2012/beast.vcxproj.filters index aad5b086..b725af4e 100644 --- a/Builds/VisualStudio2012/beast.vcxproj.filters +++ b/Builds/VisualStudio2012/beast.vcxproj.filters @@ -1101,6 +1101,9 @@ beast\mpl + + beast_core\thread + @@ -1631,6 +1634,9 @@ beast_asio\http + + beast_core\thread + diff --git a/modules/beast_core/beast_core.cpp b/modules/beast_core/beast_core.cpp index f585d9ac..7658f795 100644 --- a/modules/beast_core/beast_core.cpp +++ b/modules/beast_core/beast_core.cpp @@ -206,6 +206,7 @@ namespace beast #include "thread/impl/TrackedMutex.cpp" #include "thread/DeadlineTimer.cpp" #include "thread/InterruptibleThread.cpp" +#include "thread/Stoppable.cpp" #include "thread/Semaphore.cpp" #include "thread/CallQueue.cpp" #include "thread/Listeners.cpp" diff --git a/modules/beast_core/beast_core.h b/modules/beast_core/beast_core.h index d403109c..b0fedbe0 100644 --- a/modules/beast_core/beast_core.h +++ b/modules/beast_core/beast_core.h @@ -90,6 +90,7 @@ extern BEAST_API void BEAST_CALLTYPE logAssertion (char const* file, int line) n #include "../../beast/Net.h" #include "../../beast/TypeTraits.h" #include "../../beast/Thread.h" +#include "../../beast/Utility.h" namespace beast { @@ -269,6 +270,7 @@ class FileOutputStream; #include "thread/Semaphore.h" #include "thread/InterruptibleThread.h" +#include "thread/Stoppable.h" #include "thread/CallQueue.h" #include "thread/Listeners.h" #include "thread/ManualCallQueue.h" diff --git a/modules/beast_core/thread/Stoppable.cpp b/modules/beast_core/thread/Stoppable.cpp new file mode 100644 index 00000000..bd6febd4 --- /dev/null +++ b/modules/beast_core/thread/Stoppable.cpp @@ -0,0 +1,164 @@ +//------------------------------------------------------------------------------ +/* + Copyright (c) 2011-2013, OpenCoin, Inc. +*/ +//============================================================================== + +Stoppable::Stoppable (char const* name, Stoppable& parent) + : m_name (name) + , m_root (false) + , m_child (this) + , m_calledStop (false) + , m_stopped (false) + , m_childrenStopped (false) +{ + // must not have had stop called + bassert (! parent.isStopping()); + + parent.m_children.push_front (&m_child); +} + +Stoppable::Stoppable (char const* name, Stoppable* parent) + : m_name (name) + , m_root (parent == nullptr) + , m_child (this) + , m_calledStop (false) + , m_stopped (false) + , m_childrenStopped (false) +{ + if (parent != nullptr) + { + // must not have had stop called + bassert (! parent->isStopping()); + + parent->m_children.push_front (&m_child); + } +} + +Stoppable::~Stoppable () +{ + // must be stopped + bassert (m_stopped); + + // children must be stopped + bassert (m_childrenStopped); +} + +void Stoppable::stop (Journal::Stream stream) +{ + // may only be called once + if (m_calledStop) + return; + + m_calledStop = true; + + // must be called from a root stoppable + bassert (m_root); + + // send the notification + stopAsync (); + + // now block on the tree of Stoppable objects from the leaves up. + stopRecursive (stream); +} + +void Stoppable::stopAsync () +{ + // must be called from a root stoppable + bassert (m_root); + + stopAsyncRecursive (); +} + +bool Stoppable::isStopping () +{ + return m_calledStopAsync.get() != 0; +} + +bool Stoppable::isStopped () +{ + return m_stopped; +} + +bool Stoppable::areChildrenStopped () +{ + return m_childrenStopped; +} + +void Stoppable::stopped () +{ + m_stoppedEvent.signal(); +} + +void Stoppable::onStop() +{ + stopped(); +} + +void Stoppable::onChildrenStopped () +{ +} + +//------------------------------------------------------------------------------ + +void Stoppable::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 stoppable + onStop (); + + // notify children + for (Children::const_iterator iter (m_children.cbegin ()); + iter != m_children.cend(); ++iter) + { + iter->stoppable->stopAsyncRecursive(); + } +} + +void Stoppable::stopRecursive (Journal::Stream stream) +{ + // Block on each child recursively. Thinking of the Stoppable + // 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->stoppable->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. + onChildrenStopped (); + + // Block until this stoppable 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 << "Waiting for '" << m_name << "' to stop"; + m_stoppedEvent.wait (); + } + + // once we get here, we know the stoppable has stopped. + m_stopped = true; +} diff --git a/modules/beast_core/thread/Stoppable.h b/modules/beast_core/thread/Stoppable.h new file mode 100644 index 00000000..5aa2cb1f --- /dev/null +++ b/modules/beast_core/thread/Stoppable.h @@ -0,0 +1,264 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + 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 BEAST_CORE_STOPPABLE_H_INCLUDED +#define BEAST_CORE_STOPPABLE_H_INCLUDED + +/** Provides an interface for stopping. + + This is the sequence of events involved in stopping: + + 1. stopAsync() [optional] + + This notifies the root Stoppable and all its children that a stop is + requested. + + 2. stop() + + This first calls stopAsync(), and then blocks on each child Stoppable in + the in the tree from the bottom up, until the Stoppable 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. onStop() + + This override is called for the root Stoppable and all its children when + stopAsync() is called. Derived classes should cancel pending I/O and + timers, signal that threads should exit, queue cleanup jobs, and perform + any other necessary final actions in preparation for exit. + + 4. onChildrenStopped() + + This override is called when all the children have stopped. This informs + the Stoppable that there should not be any more dependents making calls + into its member functions. A Stoppable that has no children will still + have this function called. + + 5. stopped() + + The derived class calls this function to inform the Stoppable API that + it has completed the stop. This unblocks the caller of stop(). + + For stoppables 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 onChildrenStopped(). The + funtion areChildrenStopped() can be used after children have stopped, + but before the Stoppable logic itself has stopped, to determine if the + stoppable's 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 onChildrenStopped () + { + // We have stopped when the derived logic stops and children stop. + if (logicHasStopped) + stopped(); + } + + // derived-specific logic that executes periodically + void logicProcessingStep () + { + // process + // ... + + // now see if we've stopped + if (logicHasStopped() && areChildrenStopped()) + stopped(); + } + + @endcode + + Derived class that manage one or more threads should typically notify + those threads in onStop that they should exit. In the thread function, + when the last thread is about to exit it would call stopped(). + + @note A Stoppable may not be restarted. +*/ +class Stoppable +{ +public: + /** Create the stoppable. + A stoppable without a parent is a root stoppable. + @param name A name used in log output. + @param parent Optional parent of this stoppable. + */ + /** @{ */ + Stoppable (char const* name, Stoppable& parent); + explicit Stoppable (char const* name, Stoppable* parent = nullptr); + /** @} */ + + /** Destroy the stoppable. + Undefined behavior results if the object is not stopped first. + Stoppable objects should not be created and destroyed dynamically during + the process lifetime. Rather, the set of stoppables should be static and + well-defined after initialization. If the set of domain-specific objects + which need to stop is dynamic, use a single parent Stoppable to manage + those objects. For example, make an HTTP server implementation a + Stoppable, rather than each of its active connections. + */ + virtual ~Stoppable (); + + /** Notify a root stoppable and children to stop, and block until stopped. + Has no effect if the stoppable was already notified. + This blocks until the stoppable and all of its children have stopped. + @param stream An optional Journal::Stream on which to log progress. + + Thread safety: + Safe to call from any thread not associated with a Stoppable. + */ + void stop (Journal::Stream stream = Journal::Stream()); + + /** Notify a root stoppable and children to stop, without waiting. + Has no effect if the stoppable was already notified. + + Thread safety: + Safe to call from any thread at any time. + */ + void stopAsync (); + + /** Returns `true` if the stoppable should stop. + Call from the derived class to determine if a long-running operation + should be canceled. 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 exit. For asynchronous + I/O, cancel all pending operations inside the onStop override. + @see onStop + + Thread safety: + Safe to call from any thread at any time. + */ + bool isStopping (); + + /** Returns `true` if the stoppable has completed its stop. + Thread safety: + Safe to call from any thread at any time. + */ + bool isStopped (); + + /** Returns `true` if all children have stopped. + For stoppables without children, this returns `true` immediately + after a stop notification is received. + + Thread safety: + Safe to call from any thread at any time. + */ + bool areChildrenStopped (); + + /** Called by derived classes to indicate that the stoppable has stopped. + The derived class must call this either after isStopping returns `true`, + or when onStop is called, or else the call to stop will never unblock. + + Thread safety: + Safe to call from any thread at any time. + */ + void stopped (); + + /** Override called when the stop notification is issued. + + The call is made on an unspecified, implementation-specific thread. + onStop and onChildrenStopped will never be called concurrently, across + all Stoppable objects descended from the same root, inclusive of the + root. + + It is safe to call isStopping, isStopped, and areChildrenStopped from + within this function; The values returned will always be valid and never + change during the callback. + + The default implementation simply calls stopped(). This is applicable + when the Stoppable has a trivial stop operation (or no stop operation), + and we are merely using the Stoppable 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 onStop (); + + /** Override called when all children have stopped. + + The call is made on an unspecified, implementation-specific thread. + onStop and onChildrenStopped will never be called concurrently, across + all Stoppable objects descended from the same root, inclusive of the + root. + + It is safe to call isStopping, isStopped, and areChildrenStopped from + within this function; The values returned will always be valid and never + change during the callback. + + The default implementation does nothing. + + 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 onChildrenStopped (); + +private: + struct Child; + typedef LockFreeStack Children; + + struct Child : Children::Node + { + Child (Stoppable* stoppable_) : stoppable (stoppable_) + { + } + + Stoppable* stoppable; + }; + + void stopAsyncRecursive (); + void stopRecursive (Journal::Stream stream); + + char const* m_name; + bool m_root; + Child m_child; + Children m_children; + + // Flag that we called stop. This is for diagnostics. + bool m_calledStop; + + // Atomic flag to make sure we only call stopAsync once. + Atomic 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; + + // stop() blocks on this event until stopped() is called. + WaitableEvent m_stoppedEvent; +}; + +#endif