diff --git a/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj b/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj index 9cd94464ea..2b91845bd7 100644 --- a/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj +++ b/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj @@ -66,6 +66,7 @@ + @@ -237,6 +238,12 @@ true true + + true + true + true + true + true true diff --git a/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj.filters b/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj.filters index 292a02ca42..51db6d1515 100644 --- a/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj.filters +++ b/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj.filters @@ -608,6 +608,9 @@ beast_basics\threads + + beast_basics\events + @@ -946,6 +949,9 @@ beast_core\time + + beast_basics\events + diff --git a/Subtrees/beast/TODO.txt b/Subtrees/beast/TODO.txt index dd7f0570ad..f69e94d55d 100644 --- a/Subtrees/beast/TODO.txt +++ b/Subtrees/beast/TODO.txt @@ -2,6 +2,14 @@ BEAST TODO -------------------------------------------------------------------------------- +- Implement beast::Bimap? + +- Use Bimap for storage in the DeadlineTimer::Manager, to support + thousands of timers. + +- Think about adding a shouldStop bool to InterruptibleThread, along + with a shouldStop () function returning bool, and a stop() method. + - Make OwnedArray add routines return a pointer instead of reference - Tidy up CacheLine, MemoryAlignment diff --git a/Subtrees/beast/modules/beast_basics/beast_basics.cpp b/Subtrees/beast/modules/beast_basics/beast_basics.cpp index 76fd7a7e85..05d51a7630 100644 --- a/Subtrees/beast/modules/beast_basics/beast_basics.cpp +++ b/Subtrees/beast/modules/beast_basics/beast_basics.cpp @@ -42,6 +42,7 @@ namespace beast #include "diagnostic/beast_CatchAny.cpp" +#include "events/beast_DeadlineTimer.cpp" #include "events/beast_OncePerSecond.cpp" #include "math/beast_MurmurHash.cpp" diff --git a/Subtrees/beast/modules/beast_basics/beast_basics.h b/Subtrees/beast/modules/beast_basics/beast_basics.h index bb533fd6d6..2dea753bb9 100644 --- a/Subtrees/beast/modules/beast_basics/beast_basics.h +++ b/Subtrees/beast/modules/beast_basics/beast_basics.h @@ -247,6 +247,7 @@ namespace beast #include "functor/beast_Function.h" #include "diagnostic/beast_CatchAny.h" +#include "events/beast_DeadlineTimer.h" #include "events/beast_OncePerSecond.h" #include "math/beast_Math.h" #include "math/beast_MurmurHash.h" diff --git a/Subtrees/beast/modules/beast_basics/events/beast_DeadlineTimer.cpp b/Subtrees/beast/modules/beast_basics/events/beast_DeadlineTimer.cpp new file mode 100644 index 0000000000..671d2ff0dc --- /dev/null +++ b/Subtrees/beast/modules/beast_basics/events/beast_DeadlineTimer.cpp @@ -0,0 +1,247 @@ +//------------------------------------------------------------------------------ +/* + 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. +*/ +//============================================================================== + +class DeadlineTimer::Manager + : public SharedSingleton + , public InterruptibleThread::EntryPoint +{ +private: + typedef CriticalSection LockType; + typedef List Items; + +public: + Manager () + : SharedSingleton (SingletonLifetime::persistAfterCreation) + , m_shouldStop (false) + , m_thread ("DeadlineTimer::Manager") + { + m_thread.start (this); + } + + ~Manager () + { + m_shouldStop = true; + + m_thread.interrupt (); + + bassert (m_items.empty ()); + } + + void activate (DeadlineTimer* timer) + { + LockType::ScopedLockType lock (m_mutex); + + bassert (! timer->m_isActive); + + insertSorted (*timer); + timer->m_isActive = true; + + m_thread.interrupt (); + } + + // Okay to call this on an inactive timer. + // This can happen naturally based on concurrency. + // + void deactivate (DeadlineTimer* timer) + { + LockType::ScopedLockType lock (m_mutex); + + if (timer->m_isActive) + { + m_items.erase (m_items.iterator_to (*timer)); + + timer->m_isActive = false; + } + + m_thread.interrupt (); + } + + void threadRun () + { + while (! m_shouldStop) + { + Time const currentTime = Time::getCurrentTime (); + double seconds = 0; + + { + LockType::ScopedLockType lock (m_mutex); + + // Notify everyone whose timer has expired + // + if (! m_items.empty ()) + { + for (;;) + { + Items::iterator const iter = m_items.begin (); + + // Has this timer expired? + if (iter->m_notificationTime <= currentTime) + { + // Yes, so call the listener. + // + // Note that this happens while the lock is held. + // + iter->m_listener->onDeadlineTimer (); + + // Remove it from the list. + m_items.erase (iter); + + // Is the timer recurring? + if (iter->m_secondsRecurring > 0) + { + // Yes so set the timer again. + iter->m_notificationTime = + currentTime + RelativeTime (iter->m_secondsRecurring); + + // Keep it active. + insertSorted (*iter); + } + else + { + // Not a recurring timer, deactivate it. + iter->m_isActive = false; + } + } + else + { + break; + } + } + } + + // Figure out how long we need to wait. + // This has to be done while holding the lock. + // + if (! m_items.empty ()) + { + seconds = (m_items.front ().m_notificationTime - currentTime).inSeconds (); + } + else + { + seconds = 0; + } + } + + // Note that we have released the lock here. + // + if (seconds > 0) + { + // Wait until interrupt or next timer. + // + m_thread.wait (static_cast (seconds * 1000 + 0.5)); + } + else if (seconds == 0) + { + // Wait until interrupt + // + m_thread.wait (); + } + else + { + // Do not wait. This can happen if the recurring timer duration + // is extremely short, or if a listener wastes too much time in + // their callback. + } + } + } + + // Caller is responsible for locking + void insertSorted (DeadlineTimer& item) + { + if (! m_items.empty ()) + { + Items::iterator before = m_items.begin (); + + for (;;) + { + if (before->m_notificationTime >= item.m_notificationTime) + { + m_items.insert (before, item); + break; + } + + ++before; + + if (before == m_items.end ()) + { + m_items.push_back (item); + break; + } + } + } + else + { + m_items.push_back (item); + } + } + + static Manager* createInstance () + { + return new Manager; + } + +private: + CriticalSection m_mutex; + bool volatile m_shouldStop; + InterruptibleThread m_thread; + Items m_items; +}; + +//------------------------------------------------------------------------------ + +DeadlineTimer::DeadlineTimer (Listener* listener) + : m_listener (listener) + , m_manager (Manager::getInstance ()) + , m_isActive (false) +{ +} + +DeadlineTimer::~DeadlineTimer () +{ + m_manager->deactivate (this); +} + +void DeadlineTimer::setExpiration (double secondsUntilDeadline) +{ + m_secondsRecurring = 0; + m_notificationTime = Time::getCurrentTime () + RelativeTime (secondsUntilDeadline); + + m_manager->activate (this); +} + +void DeadlineTimer::setRecurringExpiration (double secondsUntilDeadline) +{ + m_secondsRecurring = secondsUntilDeadline; + m_notificationTime = Time::getCurrentTime () + RelativeTime (secondsUntilDeadline); + + m_manager->activate (this); +} + +void DeadlineTimer::setExpirationTime (Time absoluteDeadline) +{ + m_secondsRecurring = 0; + m_notificationTime = absoluteDeadline; + + m_manager->activate (this); +} + +void DeadlineTimer::reset () +{ + m_manager->deactivate (this); +} diff --git a/Subtrees/beast/modules/beast_basics/events/beast_DeadlineTimer.h b/Subtrees/beast/modules/beast_basics/events/beast_DeadlineTimer.h new file mode 100644 index 0000000000..cc4d2d631f --- /dev/null +++ b/Subtrees/beast/modules/beast_basics/events/beast_DeadlineTimer.h @@ -0,0 +1,77 @@ +//------------------------------------------------------------------------------ +/* + 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_DEADLINETIMER_H_INCLUDED +#define BEAST_DEADLINETIMER_H_INCLUDED + +/** Provides periodic or one time notifications at a specified time interval. +*/ +class DeadlineTimer : public List ::Node +{ +public: + /** 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 Allow construction with a specific ThreadWithCallQueue& + // on which to notify the listener. + class Listener + { + public: + virtual void onDeadlineTimer () { } + }; + +public: + /** Create a deadline timer with the specified listener attached. + */ + explicit DeadlineTimer (Listener* listener); + + ~DeadlineTimer (); + + /** Set the timer to go off once in the future. + */ + void setExpiration (double secondsUntilDeadline); + + /** Set the timer to go off repeatedly with the specified frequency. + */ + void setRecurringExpiration (double secondsUntilDeadline); + + /** Set the timer to go off at a specific time. + + @note If the time is in the past, the timer will go off + immediately. + */ + void setExpirationTime (Time absoluteDeadline); + + /** Reset the timer so that no more notifications are sent. + */ + void reset (); + +private: + class Manager; + + Listener* const m_listener; + ReferenceCountedObjectPtr m_manager; + bool m_isActive; + Time m_notificationTime; + double m_secondsRecurring; // non zero if recurring +}; + +#endif diff --git a/Subtrees/beast/modules/beast_basics/events/beast_OncePerSecond.cpp b/Subtrees/beast/modules/beast_basics/events/beast_OncePerSecond.cpp index 7f5bbda7e6..6b436ec51c 100644 --- a/Subtrees/beast/modules/beast_basics/events/beast_OncePerSecond.cpp +++ b/Subtrees/beast/modules/beast_basics/events/beast_OncePerSecond.cpp @@ -41,7 +41,7 @@ private: { for (;;) { - const bool interrupted = m_thread.wait (1000); + bool const interrupted = m_thread.wait (1000); if (interrupted) break; diff --git a/Subtrees/beast/modules/beast_basics/threads/beast_ThreadWithCallQueue.h b/Subtrees/beast/modules/beast_basics/threads/beast_ThreadWithCallQueue.h index ef6f153b6b..1db2021f76 100644 --- a/Subtrees/beast/modules/beast_basics/threads/beast_ThreadWithCallQueue.h +++ b/Subtrees/beast/modules/beast_basics/threads/beast_ThreadWithCallQueue.h @@ -114,18 +114,17 @@ public: void stop (bool const wait); - /** - Determine if the thread needs interruption. + /** Determine if the thread needs interruption. - Should be called periodically by the idle function. If interruptionPoint - returns true or throws, it must not be called again until the idle function - returns and is re-entered. + Should be called periodically by the idle function. If interruptionPoint + returns true or throws, it must not be called again until the idle function + returns and is re-entered. - @invariant No previous calls to interruptionPoint() made after the idle - function entry point returned `true`. + @invariant No previous calls to interruptionPoint() made after the idle + function entry point returned `true`. - @return `false` if the idle function may continue, or `true` if the - idle function must return as soon as possible. + @return `false` if the idle function may continue, or `true` if the + idle function must return as soon as possible. */ bool interruptionPoint ();