diff --git a/beast/threads/Stoppable.h b/beast/threads/Stoppable.h index 8f78871d7..70ee0ae92 100644 --- a/beast/threads/Stoppable.h +++ b/beast/threads/Stoppable.h @@ -62,9 +62,10 @@ class RootStoppable; 3. onPrepare() This override is called for all Stoppable objects in the hierarchy - during the prepare stage. Objects are called from the bottom up. - It is guaranteed that all child Stoppable objects have already been - prepared when this is called. + during the prepare stage. It is guaranteed that all child Stoppable + objects have already been prepared when this is called. + + Objects are called children first. 4. start() @@ -77,9 +78,10 @@ class RootStoppable; 5. onStart() This override is called for all Stoppable objects in the hierarchy - during the start stage. Objects are called from the bottom up. - It is guaranteed that all child Stoppable objects have already been - started when this is called. + during the start stage. It is guaranteed that no child Stoppable + objects have been started when this is called. + + Objects are called parent first. This is the sequence of events involved in stopping: @@ -103,6 +105,8 @@ class RootStoppable; timers, signal that threads should exit, queue cleanup jobs, and perform any other necessary final actions in preparation for exit. + Objects are called parent first. + 9. onChildrenStopped() This override is called when all the children have stopped. This informs @@ -110,6 +114,8 @@ class RootStoppable; into its member functions. A Stoppable that has no children will still have this function called. + Objects are called children first. + 10. stopped() The derived class calls this function to inform the Stoppable API that @@ -183,9 +189,11 @@ public: /** Returns `true` if all children have stopped. */ bool areChildrenStopped () const; +protected: /** Called by derived classes to indicate that the stoppable has stopped. */ void stopped (); +private: /** Override called during preparation. Since all other Stoppable objects in the tree have already been constructed, this provides an opportunity to perform initialization which @@ -242,7 +250,6 @@ public: */ virtual void onChildrenStopped (); -private: friend class RootStoppable; struct Child; @@ -262,13 +269,12 @@ private: void stopAsyncRecursive (); void stopRecursive (Journal journal); -protected: - char const* m_name; + std::string m_name; RootStoppable& m_root; Child m_child; - std::atomic m_started; - bool volatile m_stopped; - bool volatile m_childrenStopped; + std::atomic m_started; + std::atomic m_stopped; + std::atomic m_childrenStopped; Children m_children; WaitableEvent m_stoppedEvent; }; @@ -309,7 +315,7 @@ public: Safe to call from any thread not associated with a Stoppable. */ void stop (Journal journal = Journal()); - +private: /** Notify a root stoppable and children to stop, without waiting. Has no effect if the stoppable was already notified. @@ -318,10 +324,9 @@ public: */ void stopAsync (); -private: - std::atomic m_prepared; - std::atomic m_calledStop; - std::atomic m_calledStopAsync; + std::atomic m_prepared; + std::atomic m_calledStop; + std::atomic m_calledStopAsync; }; /** @} */ diff --git a/beast/threads/Threads.unity.cpp b/beast/threads/Threads.unity.cpp index 57241ca78..4348d1baa 100644 --- a/beast/threads/Threads.unity.cpp +++ b/beast/threads/Threads.unity.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include diff --git a/beast/threads/impl/Stoppable.cpp b/beast/threads/impl/Stoppable.cpp index 81ab1f5cf..bbaedc611 100644 --- a/beast/threads/impl/Stoppable.cpp +++ b/beast/threads/impl/Stoppable.cpp @@ -25,7 +25,7 @@ Stoppable::Stoppable (char const* name, RootStoppable& root) : m_name (name) , m_root (root) , m_child (this) - , m_started (0) + , m_started (false) , m_stopped (false) , m_childrenStopped (false) { @@ -35,7 +35,7 @@ Stoppable::Stoppable (char const* name, Stoppable& parent) : m_name (name) , m_root (parent.m_root) , m_child (this) - , m_started (0) + , m_started (false) , m_stopped (false) , m_childrenStopped (false) { @@ -48,7 +48,7 @@ Stoppable::Stoppable (char const* name, Stoppable& parent) Stoppable::~Stoppable () { // Children must be stopped. - bassert (m_started.load () == 0 || m_childrenStopped); + bassert (!m_started || m_childrenStopped); } bool Stoppable::isStopping() const @@ -124,7 +124,6 @@ void Stoppable::stopRecursive (Journal journal) // if we get here then all children have stopped // - memoryBarrier (); m_childrenStopped = true; onChildrenStopped (); @@ -145,39 +144,39 @@ void Stoppable::stopRecursive (Journal journal) RootStoppable::RootStoppable (char const* name) : Stoppable (name, *this) - , m_prepared (0) - , m_calledStop (0) - , m_calledStopAsync (0) + , m_prepared (false) + , m_calledStop (false) + , m_calledStopAsync (false) { } bool RootStoppable::isStopping() const { - return m_calledStopAsync.load () != 0; + return m_calledStopAsync; } void RootStoppable::prepare () { - if (m_prepared.exchange (1) == 0) + if (m_prepared.exchange (true) == false) prepareRecursive (); } void RootStoppable::start () { // Courtesy call to prepare. - if (m_prepared.exchange (1) == 0) + if (m_prepared.exchange (true) == false) prepareRecursive (); - if (m_started.exchange (1) == 0) + if (m_started.exchange (true) == false) startRecursive (); } void RootStoppable::stop (Journal journal) { // Must have a prior call to start() - bassert (m_started.load () != 0); + bassert (m_started); - if (m_calledStop.exchange (1) == 1) + if (m_calledStop.exchange (true) == true) { journal.warning << "Stoppable::stop called again"; return; @@ -189,7 +188,7 @@ void RootStoppable::stop (Journal journal) void RootStoppable::stopAsync () { - if (m_calledStopAsync.exchange (1) == 0) + if (m_calledStopAsync.exchange (true) == false) stopAsyncRecursive (); } diff --git a/beast/threads/impl/Stoppable.test.cpp b/beast/threads/impl/Stoppable.test.cpp new file mode 100644 index 000000000..3baa2ef87 --- /dev/null +++ b/beast/threads/impl/Stoppable.test.cpp @@ -0,0 +1,445 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2012, 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. +*/ +//============================================================================== + +#include + +namespace beast { + +class Stoppable_test + : public unit_test::suite +{ +/* + R + / | \ + / | \ + A B C + / | \ /\ | + D E F G H I + | + J +*/ + unsigned count = 0; + + class D + : public Stoppable + { + Stoppable_test& test_; + public: + D(Stoppable& parent, Stoppable_test& test) + : Stoppable("D", parent) + , test_(test) + {} + + void onPrepare() override + { + test_.expect(++test_.count == 9, "D::onPrepare called out of order"); + } + + void onStart() override + { + test_.expect(--test_.count == 0, "D::onStart called out of order"); + } + + void onStop() override + { + test_.expect(++test_.count == 11, "D::onStop called out of order"); + } + + void onChildrenStopped() override + { + Stoppable::stopped(); + test_.expect(--test_.count == 2, "D::onChildrenStopped called out of order"); + } + }; + + class J + : public Stoppable + { + Stoppable_test& test_; + public: + J(Stoppable& parent, Stoppable_test& test) + : Stoppable("J", parent) + , test_(test) + {} + + void onPrepare() override + { + test_.expect(++test_.count == 7, "J::onPrepare called out of order"); + } + + void onStart() override + { + test_.expect(--test_.count == 1, "J::onStart called out of order"); + } + + void onStop() override + { + test_.expect(++test_.count == 10, "J::onStop called out of order"); + } + + void onChildrenStopped() override + { + Stoppable::stopped(); + test_.expect(--test_.count == 4, "J::onChildrenStopped called out of order"); + } + }; + + class E + : public Stoppable + { + J j_; + Stoppable_test& test_; + public: + E(Stoppable& parent, Stoppable_test& test) + : Stoppable("E", parent) + , j_(*this, test) + , test_(test) + {} + + void onPrepare() override + { + test_.expect(++test_.count == 8, "E::onPrepare called out of order"); + } + + void onStart() override + { + test_.expect(--test_.count == 2, "E::onStart called out of order"); + } + + void onStop() override + { + test_.expect(++test_.count == 9, "E::onStop called out of order"); + } + + void onChildrenStopped() override + { + Stoppable::stopped(); + test_.expect(--test_.count == 3, "E::onChildrenStopped called out of order"); + } + }; + + class F + : public Stoppable + { + Stoppable_test& test_; + public: + F(Stoppable& parent, Stoppable_test& test) + : Stoppable("F", parent) + , test_(test) + {} + + void onPrepare() override + { + test_.expect(++test_.count == 6, "F::onPrepare called out of order"); + } + + void onStart() override + { + test_.expect(--test_.count == 3, "F::onStart called out of order"); + } + + void onStop() override + { + test_.expect(++test_.count == 8, "F::onStop called out of order"); + } + + void onChildrenStopped() override + { + Stoppable::stopped(); + test_.expect(--test_.count == 5, "F::onChildrenStopped called out of order"); + } + }; + + class A + : public Stoppable + { + enum {running, please_stop, have_stopped}; + D d_; + E e_; + F f_; + Stoppable_test& test_; + std::atomic stop_; + public: + A(Stoppable& parent, Stoppable_test& test) + : Stoppable("A", parent) + , d_(*this, test) + , e_(*this, test) + , f_(*this, test) + , test_(test) + , stop_(running) + {} + + void run() + { + while (stop_ == running) + ; + stop_ = have_stopped; + } + + void onPrepare() override + { + test_.expect(++test_.count == 10, "A::onPrepare called out of order"); + } + + void onStart() override + { + test_.expect(--test_.count == 4, "A::onStart called out of order"); + } + + void onStop() override + { + test_.expect(++test_.count == 7, "A::onStop called out of order"); + } + + void onChildrenStopped() override + { + stop_ = please_stop; + while (stop_ != have_stopped) + ; + Stoppable::stopped(); + test_.expect(--test_.count == 1, "A::onChildrenStopped called out of order"); + } + }; + + class G + : public Stoppable + { + Stoppable_test& test_; + public: + G(Stoppable& parent, Stoppable_test& test) + : Stoppable("G", parent) + , test_(test) + {} + + void onPrepare() override + { + test_.expect(++test_.count == 4, "G::onPrepare called out of order"); + } + + void onStart() override + { + test_.expect(--test_.count == 5, "G::onStart called out of order"); + } + + void onStop() override + { + test_.expect(++test_.count == 6, "G::onStop called out of order"); + } + + void onChildrenStopped() override + { + Stoppable::stopped(); + test_.expect(--test_.count == 7, "G::onChildrenStopped called out of order"); + } + }; + + class H + : public Stoppable + { + Stoppable_test& test_; + public: + H(Stoppable& parent, Stoppable_test& test) + : Stoppable("H", parent) + , test_(test) + {} + + void onPrepare() override + { + test_.expect(++test_.count == 3, "H::onPrepare called out of order"); + } + + void onStart() override + { + test_.expect(--test_.count == 6, "H::onStart called out of order"); + } + + void onStop() override + { + test_.expect(++test_.count == 5, "H::onStop called out of order"); + } + + void onChildrenStopped() override + { + Stoppable::stopped(); + test_.expect(--test_.count == 8, "H::onChildrenStopped called out of order"); + } + }; + + class B + : public Stoppable + { + G g_; + H h_; + Stoppable_test& test_; + public: + B(Stoppable& parent, Stoppable_test& test) + : Stoppable("B", parent) + , g_(*this, test) + , h_(*this, test) + , test_(test) + {} + + void onPrepare() override + { + test_.expect(++test_.count == 5, "B::onPrepare called out of order"); + } + + void onStart() override + { + test_.expect(--test_.count == 7, "B::onStart called out of order"); + } + + void onStop() override + { + test_.expect(++test_.count == 4, "B::onStop called out of order"); + } + + void onChildrenStopped() override + { + Stoppable::stopped(); + test_.expect(--test_.count == 6, "B::onChildrenStopped called out of order"); + } + }; + + class I + : public Stoppable + { + Stoppable_test& test_; + public: + I(Stoppable& parent, Stoppable_test& test) + : Stoppable("I", parent) + , test_(test) + {} + + void onPrepare() override + { + test_.expect(++test_.count == 1, "I::onPrepare called out of order"); + } + + void onStart() override + { + test_.expect(--test_.count == 8, "I::onStart called out of order"); + } + + void onStop() override + { + test_.expect(++test_.count == 3, "I::onStop called out of order"); + } + + void onChildrenStopped() override + { + Stoppable::stopped(); + test_.expect(--test_.count == 10, "I::onChildrenStopped called out of order"); + } + }; + + class C + : public Stoppable + { + I i_; + Stoppable_test& test_; + public: + C(Stoppable& parent, Stoppable_test& test) + : Stoppable("C", parent) + , i_(*this, test) + , test_(test) + {} + + void onPrepare() override + { + test_.expect(++test_.count == 2, "C::onPrepare called out of order"); + } + + void onStart() override + { + test_.expect(--test_.count == 9, "C::onStart called out of order"); + } + + void onStop() override + { + test_.expect(++test_.count == 2, "C::onStop called out of order"); + } + + void onChildrenStopped() override + { + Stoppable::stopped(); + test_.expect(--test_.count == 9, "C::onChildrenStopped called out of order"); + } + }; + + class Root + : public RootStoppable + { + std::thread a_; + B b_; + C c_; + Stoppable_test& test_; + public: + Root(Stoppable_test& test) + : RootStoppable("R") + , a_(&A::run, std::make_unique(*this, test)) + , b_(*this, test) + , c_(*this, test) + , test_(test) + {} + + void run() + { + prepare(); + start(); + stop(); + } + + void onPrepare() override + { + test_.expect(++test_.count == 11, "Root::onPrepare called out of order"); + } + + void onStart() override + { + test_.expect(--test_.count == 10, "Root::onStart called out of order"); + } + + void onStop() override + { + test_.expect(++test_.count == 1, "Root::onStop called out of order"); + } + + void onChildrenStopped() override + { + a_.join(); + Stoppable::stopped(); + test_.expect(--test_.count == 0, "Root::onChildrenStopped called out of order"); + } + }; + +public: + void run() + { + { + Root rt(*this); + rt.run(); + } + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(Stoppable,beast_core,beast); + +}