├── .gitignore ├── CMakeLists.txt ├── src ├── CPPTimerPool │ ├── CMakeLists.txt │ ├── TimerPool.hpp │ └── TimerPool.cpp ├── CMakeLists.txt └── Test.cpp ├── .drone.yml └── Readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | CMakeSettings.json 3 | out/ 4 | build/ 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.16) 2 | 3 | set (CMAKE_CONFIGURATION_TYPES Debug Release) 4 | 5 | project ("TimerPool") 6 | 7 | add_subdirectory ("src") 8 | -------------------------------------------------------------------------------- /src/CPPTimerPool/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | add_library (CPPTimerPool STATIC 4 | TimerPool.cpp 5 | TimerPool.hpp 6 | ) 7 | 8 | target_include_directories (CPPTimerPool INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 9 | target_compile_features (CPPTimerPool PUBLIC cxx_std_14) 10 | 11 | if (NOT MSVC) 12 | target_link_libraries (CPPTimerPool PUBLIC pthread) 13 | endif () 14 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | add_subdirectory("CPPTimerPool") 4 | 5 | add_executable (TestApp 6 | Test.cpp 7 | ) 8 | 9 | target_include_directories (TestApp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 10 | target_compile_features (TestApp PUBLIC cxx_std_14) 11 | target_link_libraries (TestApp PRIVATE CPPTimerPool) 12 | 13 | if (MSVC) 14 | target_compile_options (TestApp PUBLIC /W3 /WX) 15 | else () 16 | target_compile_options (TestApp PUBLIC -Wall -Wextra -Werror -Wno-unused-parameter -Wshadow -Wdouble-promotion) 17 | endif () 18 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | name: default 3 | 4 | steps: 5 | - name: Build (Clang) 6 | image: abcminiuser/docker-ci-ubuntu-buildtools 7 | commands: 8 | - export CC=/usr/bin/clang 9 | - export CXX=/usr/bin/clang++ 10 | - $CC --version 11 | - cmake -B build/clang -DCMAKE_CXX_COMPILER="$CXX" -G "Ninja" . 12 | - cmake --build build/clang 13 | - valgrind ./build/clang/src/TestApp 14 | 15 | - name: Build (GCC) 16 | image: abcminiuser/docker-ci-ubuntu-buildtools 17 | commands: 18 | - export CC=/usr/bin/gcc 19 | - export CXX=/usr/bin/g++ 20 | - $CC --version 21 | - cmake -B build/gcc -DCMAKE_CXX_COMPILER="$CXX" -G "Ninja" . 22 | - cmake --build build/gcc 23 | - valgrind ./build/gcc/src/TestApp 24 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Thread-Safe Timer Pool 2 | ======================== 3 | 4 | 5 | Overview 6 | --------------- 7 | 8 | This is a simple C++14 source library for creating multiple timer pools, each of 9 | which can contain multiple timers with configurable timeout intervals and 10 | callback functions. 11 | 12 | I've made use of shared/weak pointers to ensure that the timers do not leak and 13 | that handles to timers created within a pool do not outlive their parent. 14 | 15 | Callbacks are executed on the pool's thread that created the timer. Timer 16 | intervals are specified in milliseconds, based on wall-clock time (using the 17 | standard libraries' `std::chrono::steady_clock` as the timer pool's timer 18 | reference). 19 | 20 | 21 | Object Lifespan 22 | ---------------- 23 | 24 | Created timers are weakly bound to their parent pools; if the last user-held 25 | reference to the parent pool expires, the pool is destroyed and all child timers 26 | will cease to run. 27 | 28 | Timers will stop automatically if their last user-held reference expires. 29 | 30 | A timer object whose parent pool is no longer valid will not run, however it 31 | will remain a valid object and can be safely modified without deadlocks or 32 | crashes. 33 | 34 | 35 | Compatibility 36 | ---------------- 37 | 38 | Tested on Visual Studio 2019 (Windows) and Clang/GCC (Linux). Only C++14 39 | standard library and compiler support is required, no special libraries, 40 | although on Posix systems this generally assumes `pthreads` is available. 41 | 42 | 43 | License 44 | ---------------- 45 | 46 | This library is released into the public domain, however patches are welcome 47 | (and appreciated!). The full license text is as follows: 48 | 49 | This is free and unencumbered software released into the public domain. 50 | 51 | Anyone is free to copy, modify, publish, use, compile, sell, or 52 | distribute this software, either in source code form or as a compiled 53 | binary, for any purpose, commercial or non-commercial, and by any 54 | means. 55 | 56 | In jurisdictions that recognize copyright laws, the author or authors 57 | of this software dedicate any and all copyright interest in the 58 | software to the public domain. We make this dedication for the benefit 59 | of the public at large and to the detriment of our heirs and 60 | successors. We intend this dedication to be an overt act of 61 | relinquishment in perpetuity of all present and future rights to this 62 | software under copyright law. 63 | 64 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 65 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 66 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 67 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 68 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 69 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 70 | OTHER DEALINGS IN THE SOFTWARE. 71 | 72 | For more information, please refer to 73 | -------------------------------------------------------------------------------- /src/CPPTimerPool/TimerPool.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Thread Safe Timer Pool Library 3 | By Dean Camera, 2023. 4 | 5 | dean [at] fourwalledcubicle [dot] com 6 | www.fourwalledcubicle.com 7 | */ 8 | 9 | /* 10 | This is free and unencumbered software released into the public domain. 11 | 12 | Anyone is free to copy, modify, publish, use, compile, sell, or 13 | distribute this software, either in source code form or as a compiled 14 | binary, for any purpose, commercial or non-commercial, and by any 15 | means. 16 | 17 | In jurisdictions that recognize copyright laws, the author or authors 18 | of this software dedicate any and all copyright interest in the 19 | software to the public domain. We make this dedication for the benefit 20 | of the public at large and to the detriment of our heirs and 21 | successors. We intend this dedication to be an overt act of 22 | relinquishment in perpetuity of all present and future rights to this 23 | software under copyright law. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 28 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 29 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 30 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 31 | OTHER DEALINGS IN THE SOFTWARE. 32 | 33 | For more information, please refer to 34 | */ 35 | 36 | #pragma once 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | 49 | class TimerPool 50 | : public std::enable_shared_from_this 51 | { 52 | public: 53 | class Timer; 54 | 55 | using Clock = std::chrono::steady_clock; 56 | using WeakPoolHandle = std::weak_ptr; 57 | using PoolHandle = std::shared_ptr; 58 | using WeakTimerHandle = std::weak_ptr; 59 | using TimerHandle = std::shared_ptr; 60 | 61 | public: 62 | static PoolHandle Create(const std::string& name = {}); 63 | 64 | virtual ~TimerPool(); 65 | 66 | std::string name() const noexcept { return m_name; } 67 | bool running() const noexcept { return m_running; } 68 | 69 | void stop(); 70 | 71 | void registerTimer(TimerHandle timer); 72 | void unregisterTimer(TimerHandle timer); 73 | 74 | protected: 75 | explicit TimerPool(const std::string& name); 76 | 77 | TimerPool(const TimerPool&) = delete; 78 | TimerPool& operator=(const TimerPool&) = delete; 79 | 80 | private: 81 | void run(); 82 | void wake(); 83 | 84 | private: 85 | mutable std::mutex m_mutex; 86 | 87 | const std::string m_name; 88 | 89 | std::forward_list m_timers; 90 | 91 | std::atomic m_running; 92 | 93 | std::condition_variable m_cond; 94 | std::thread m_thread; 95 | }; 96 | 97 | class TimerPool::Timer 98 | : public std::enable_shared_from_this 99 | { 100 | public: 101 | using Clock = TimerPool::Clock; 102 | using WeakPoolHandle = TimerPool::WeakPoolHandle; 103 | using PoolHandle = TimerPool::PoolHandle; 104 | using WeakTimerHandle = TimerPool::WeakTimerHandle; 105 | using TimerHandle = TimerPool::TimerHandle; 106 | using Callback = std::function; 107 | 108 | public: 109 | static TimerHandle Create(const PoolHandle& pool, const std::string& name = {}); 110 | 111 | virtual ~Timer() = default; 112 | 113 | PoolHandle pool() const { return m_pool.lock(); } 114 | 115 | std::string name() const noexcept { return m_name; } 116 | 117 | void setCallback(Callback callback); 118 | void setInterval(std::chrono::milliseconds ms); 119 | void setRepeated(bool repeated); 120 | 121 | enum class StartMode 122 | { 123 | StartOnly, 124 | RestartIfRunning, 125 | RestartOnly, 126 | }; 127 | 128 | void start(StartMode mode = StartMode::RestartIfRunning); 129 | void stop(); 130 | 131 | bool running() const noexcept; 132 | Clock::time_point nextExpiry() const noexcept; 133 | 134 | void fire(Clock::time_point now = Clock::time_point::min()); 135 | 136 | protected: 137 | explicit Timer(const PoolHandle& pool, const std::string& name = {}); 138 | 139 | Timer(const Timer&) = delete; 140 | Timer& operator=(const Timer&) = delete; 141 | 142 | private: 143 | mutable std::mutex m_mutex; 144 | 145 | const WeakPoolHandle m_pool; 146 | const std::string m_name; 147 | 148 | Clock::time_point m_nextExpiry; 149 | 150 | Callback m_callback; 151 | std::chrono::milliseconds m_interval; 152 | bool m_repeated; 153 | }; 154 | -------------------------------------------------------------------------------- /src/Test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Thread Safe Timer Pool Library 3 | By Dean Camera, 2022. 4 | 5 | dean [at] fourwalledcubicle [dot] com 6 | www.fourwalledcubicle.com 7 | */ 8 | 9 | /* 10 | This is free and unencumbered software released into the public domain. 11 | 12 | Anyone is free to copy, modify, publish, use, compile, sell, or 13 | distribute this software, either in source code form or as a compiled 14 | binary, for any purpose, commercial or non-commercial, and by any 15 | means. 16 | 17 | In jurisdictions that recognize copyright laws, the author or authors 18 | of this software dedicate any and all copyright interest in the 19 | software to the public domain. We make this dedication for the benefit 20 | of the public at large and to the detriment of our heirs and 21 | successors. We intend this dedication to be an overt act of 22 | relinquishment in perpetuity of all present and future rights to this 23 | software under copyright law. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 28 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 29 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 30 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 31 | OTHER DEALINGS IN THE SOFTWARE. 32 | 33 | For more information, please refer to 34 | */ 35 | 36 | #include "TimerPool.hpp" 37 | 38 | #include 39 | #include 40 | 41 | int main() 42 | { 43 | static const auto kPrintTimerCallback = 44 | [](const TimerPool::TimerHandle& t) 45 | { 46 | const auto poolName = t->pool()->name(); 47 | const auto timerName = t->name(); 48 | 49 | std::stringstream message; 50 | message << poolName << " - " << timerName << "\n"; 51 | 52 | std::cout << message.str(); 53 | }; 54 | 55 | // TEST 1: Timer pool is long lived, two long lived timers (both should run) 56 | auto pool1 = TimerPool::Create("Pool 1"); 57 | 58 | auto timer1 = TimerPool::Timer::Create(pool1, "TICK!"); 59 | timer1->setCallback(kPrintTimerCallback); 60 | timer1->setInterval(std::chrono::seconds(1)); 61 | timer1->setRepeated(true); 62 | timer1->start(); 63 | 64 | auto timer2 = TimerPool::Timer::Create(pool1, "TOCK!"); 65 | timer2->setCallback(kPrintTimerCallback); 66 | timer2->setInterval(std::chrono::milliseconds(250)); 67 | timer2->setRepeated(true); 68 | timer2->start(); 69 | 70 | // TEST 2: Timer pool is very long lived, two timers which are also long lived (both should run) 71 | { 72 | static auto pool2 = TimerPool::Create("Pool 2"); 73 | 74 | static auto timer3 = TimerPool::Timer::Create(pool2, "Alpha"); 75 | timer3->setCallback(kPrintTimerCallback); 76 | timer3->setInterval(std::chrono::milliseconds(666)); 77 | timer3->setRepeated(true); 78 | timer3->start(); 79 | 80 | static auto timer4 = TimerPool::Timer::Create(pool2, "Beta"); 81 | timer4->setCallback(kPrintTimerCallback); 82 | timer4->setInterval(std::chrono::milliseconds(333)); 83 | timer4->setRepeated(true); 84 | timer4->start(); 85 | } 86 | 87 | // TEST 3: Timer is created before its parent pool is discarded (should not run) 88 | { 89 | TimerPool::TimerHandle timer5; 90 | { 91 | auto pool3 = TimerPool::Create("Pool 3"); 92 | 93 | timer5 = TimerPool::Timer::Create(pool3, "Discarded Parent Pool 3 Timer"); 94 | } 95 | 96 | timer5->setCallback(kPrintTimerCallback); 97 | timer5->setInterval(std::chrono::milliseconds(100)); 98 | timer5->setRepeated(true); 99 | timer5->start(); 100 | } 101 | 102 | // TEST 4: Timer is created and handle retained, but parent pool is only weakly retained and so is discarded (should not run) 103 | { 104 | static TimerPool::WeakPoolHandle pool4weak; 105 | static TimerPool::TimerHandle timer6; 106 | 107 | { 108 | auto pool4 = TimerPool::Create("Pool 4"); 109 | 110 | timer6 = TimerPool::Timer::Create(pool4, "Discarded Parent Pool 4 Timer"); 111 | 112 | pool4weak = pool4; 113 | } 114 | 115 | timer6->setCallback(kPrintTimerCallback); 116 | timer6->setInterval(std::chrono::milliseconds(100)); 117 | timer6->setRepeated(true); 118 | timer6->start(); 119 | } 120 | 121 | // TEST 5: Timer and its parent pool are retained, but the pool is manually stopped before it can run (should not run) 122 | auto pool5 = TimerPool::Create("Pool 5"); 123 | pool5->stop(); 124 | 125 | auto timer7 = TimerPool::Timer::Create(pool5, "Stopped Parent Pool 5 Timer"); 126 | timer7->setCallback(kPrintTimerCallback); 127 | timer7->setInterval(std::chrono::seconds(1)); 128 | timer7->setRepeated(true); 129 | timer7->start(); 130 | 131 | // TEST 6: Parent pool is very long lived, timer's only user reference is discarded (should not run) 132 | auto pool6 = TimerPool::Create("Pool 6"); 133 | 134 | if (auto timer8 = TimerPool::Timer::Create(pool6, "Discarded Pool 6 Timer")) 135 | { 136 | timer8->setCallback(kPrintTimerCallback); 137 | timer8->setInterval(std::chrono::seconds(1)); 138 | timer8->setRepeated(true); 139 | timer8->start(); 140 | } 141 | 142 | // TEST 7: Pool is very long lived, but timer is only weakly retained (should not run) 143 | { 144 | static TimerPool::PoolHandle pool7 = TimerPool::Create("Pool 7"); 145 | TimerPool::WeakTimerHandle timer9weak; 146 | 147 | if (auto timer9 = TimerPool::Timer::Create(pool7, "Weak Pool 7 Timer")) 148 | { 149 | timer9->setCallback(kPrintTimerCallback); 150 | timer9->setInterval(std::chrono::seconds(1)); 151 | timer9->setRepeated(true); 152 | timer9->start(); 153 | 154 | timer9weak = timer9; 155 | } 156 | } 157 | 158 | // TEST 8: Pool is very long lived, timer is retained strongly by copy (should run) 159 | { 160 | static TimerPool::PoolHandle pool8 = TimerPool::Create("Pool 8"); 161 | static TimerPool::TimerHandle timer10strong; 162 | 163 | if (auto timer10 = TimerPool::Timer::Create(pool8, "GAMMA")) 164 | { 165 | timer10->setCallback(kPrintTimerCallback); 166 | timer10->setInterval(std::chrono::milliseconds(400)); 167 | timer10->setRepeated(true); 168 | timer10->start(); 169 | 170 | timer10strong = timer10; 171 | } 172 | } 173 | 174 | std::this_thread::sleep_for(std::chrono::seconds(10)); 175 | } 176 | -------------------------------------------------------------------------------- /src/CPPTimerPool/TimerPool.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Thread Safe Timer Pool Library 3 | By Dean Camera, 2023. 4 | 5 | dean [at] fourwalledcubicle [dot] com 6 | www.fourwalledcubicle.com 7 | */ 8 | 9 | /* 10 | This is free and unencumbered software released into the public domain. 11 | 12 | Anyone is free to copy, modify, publish, use, compile, sell, or 13 | distribute this software, either in source code form or as a compiled 14 | binary, for any purpose, commercial or non-commercial, and by any 15 | means. 16 | 17 | In jurisdictions that recognize copyright laws, the author or authors 18 | of this software dedicate any and all copyright interest in the 19 | software to the public domain. We make this dedication for the benefit 20 | of the public at large and to the detriment of our heirs and 21 | successors. We intend this dedication to be an overt act of 22 | relinquishment in perpetuity of all present and future rights to this 23 | software under copyright law. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 28 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 29 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 30 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 31 | OTHER DEALINGS IN THE SOFTWARE. 32 | 33 | For more information, please refer to 34 | */ 35 | 36 | #include "TimerPool.hpp" 37 | 38 | #include 39 | 40 | #if defined(_WIN32) 41 | #define NOMINMAX 42 | #define WIN32_LEAN_AND_MEAN 43 | #include 44 | #elif defined(__linux__) || defined(__APPLE__) 45 | #include 46 | #endif 47 | 48 | namespace 49 | { 50 | // Private wrapper, used to expose the normally protected constructors 51 | // internally to the factory methods inside the TimerPool class. 52 | template 53 | class EnableConstructor final 54 | : public BaseClass 55 | { 56 | public: 57 | template 58 | explicit EnableConstructor(Args&&... args) : BaseClass(std::forward(args)...) {} 59 | 60 | ~EnableConstructor() = default; 61 | }; 62 | 63 | // This wrapper classes is the reference-counted object that is shared by 64 | // all created user-timers. It is ref-counted independently to the actual 65 | // timer instance, so that the timer is automatically registered and 66 | // unregistered when the first and last user-application timer handle is 67 | // made. Note that due to the std::share_ptr() aliasing constructor, it will 68 | // transparently dereference as a normal TimerPool::Timer instance. 69 | class UserTimer final 70 | { 71 | public: 72 | explicit UserTimer(const TimerPool::TimerHandle& timer) 73 | : m_timer(timer) 74 | { 75 | if (const auto& pool = m_timer->pool()) 76 | pool->registerTimer(timer); 77 | } 78 | 79 | ~UserTimer() 80 | { 81 | m_timer->stop(); 82 | 83 | if (const auto& pool = m_timer->pool()) 84 | pool->unregisterTimer(m_timer); 85 | } 86 | 87 | private: 88 | const TimerPool::TimerHandle m_timer; 89 | }; 90 | 91 | void NameCurrentThread(const std::string& name) 92 | { 93 | #if defined(_WIN32) 94 | SetThreadDescription(GetCurrentThread(), std::wstring(name.begin(), name.end()).c_str()); 95 | #elif defined(__linux__) 96 | pthread_setname_np(pthread_self(), name.c_str()); 97 | #elif defined(__APPLE__) 98 | pthread_setname_np(name.c_str()); 99 | #endif 100 | } 101 | } 102 | 103 | 104 | TimerPool::PoolHandle TimerPool::Create(const std::string& name) 105 | { 106 | return std::make_shared>(name); 107 | } 108 | 109 | TimerPool::TimerPool(const std::string& name) 110 | : m_mutex{ } 111 | , m_name{ name } 112 | , m_timers{ } 113 | , m_running{ true } 114 | , m_cond{ } 115 | , m_thread{ [this]() { run(); } } 116 | { 117 | 118 | } 119 | 120 | TimerPool::~TimerPool() 121 | { 122 | stop(); 123 | 124 | if (m_thread.joinable()) 125 | m_thread.join(); 126 | } 127 | 128 | void TimerPool::registerTimer(TimerHandle timer) 129 | { 130 | { 131 | std::lock_guard lock(m_mutex); 132 | 133 | m_timers.remove(timer); 134 | m_timers.emplace_front(timer); 135 | } 136 | 137 | m_cond.notify_all(); 138 | } 139 | 140 | void TimerPool::unregisterTimer(TimerHandle timer) 141 | { 142 | { 143 | std::lock_guard lock(m_mutex); 144 | 145 | m_timers.remove(timer); 146 | } 147 | 148 | m_cond.notify_all(); 149 | } 150 | 151 | void TimerPool::run() 152 | { 153 | // Name the current timer pool thread, useful when using a debugger. 154 | { 155 | std::string threadName = "Timer Pool"; 156 | if (! m_name.empty()) 157 | threadName += " '" + m_name + "'"; 158 | 159 | NameCurrentThread(threadName); 160 | } 161 | 162 | std::vector expiredTimers; 163 | 164 | while (m_running) 165 | { 166 | std::unique_lock lock(m_mutex); 167 | 168 | const auto nowTime = Clock::now(); 169 | 170 | auto wakeTime = nowTime + std::chrono::minutes(1); 171 | 172 | for (const auto& timer : m_timers) 173 | { 174 | const auto expiryTime = timer->nextExpiry(); 175 | 176 | if (expiryTime <= nowTime) 177 | expiredTimers.emplace_back(timer); 178 | else if (expiryTime < wakeTime) 179 | wakeTime = expiryTime; 180 | } 181 | 182 | if (! expiredTimers.empty()) 183 | { 184 | // We fire callbacks without the pool modification lock held, so that the timer callbacks can 185 | // safely manipulate the pool if desired (and so other threads can change the pool while callbacks 186 | // are in progress). Note that the timer list modification (recursive) mutex remains held, so that 187 | // we do block if timers are being (de-)registered while the callbacks are run, so that we don't 188 | // e.g. try to fire a callback to a partially destroyed user-object that owned the timer being unregistered. 189 | 190 | lock.unlock(); 191 | 192 | for (const auto& timer : expiredTimers) 193 | timer->fire(nowTime); 194 | 195 | expiredTimers.clear(); 196 | } 197 | else 198 | { 199 | // No timers have expired yet, we can sleep. 200 | 201 | m_cond.wait_until(lock, wakeTime); 202 | } 203 | } 204 | } 205 | 206 | void TimerPool::stop() 207 | { 208 | { 209 | std::lock_guard lock(m_mutex); 210 | 211 | m_running = false; 212 | m_timers.clear(); 213 | } 214 | 215 | m_cond.notify_all(); 216 | } 217 | 218 | void TimerPool::wake() 219 | { 220 | m_cond.notify_all(); 221 | } 222 | 223 | // ================== 224 | 225 | TimerPool::Timer::TimerHandle TimerPool::Timer::Create(const PoolHandle& pool, const std::string& name) 226 | { 227 | const auto timer = std::make_shared>(pool, name); 228 | const auto userHandle = std::make_shared(timer); 229 | 230 | return std::shared_ptr(userHandle, timer.get()); 231 | } 232 | 233 | TimerPool::Timer::Timer(const PoolHandle& pool, const std::string& name) 234 | : m_pool{ pool } 235 | , m_name{ name } 236 | , m_nextExpiry{ Clock::time_point::max() } 237 | , m_callback{ nullptr } 238 | , m_interval{ 0 } 239 | , m_repeated{ false } 240 | { 241 | 242 | } 243 | 244 | void TimerPool::Timer::setCallback(Callback callback) 245 | { 246 | std::lock_guard lock(m_mutex); 247 | 248 | m_callback = std::move(callback); 249 | } 250 | 251 | void TimerPool::Timer::setInterval(std::chrono::milliseconds ms) 252 | { 253 | std::lock_guard lock(m_mutex); 254 | 255 | m_interval = ms; 256 | } 257 | 258 | void TimerPool::Timer::setRepeated(bool repeated) 259 | { 260 | std::lock_guard lock(m_mutex); 261 | 262 | m_repeated = repeated; 263 | } 264 | 265 | void TimerPool::Timer::start(StartMode mode) 266 | { 267 | { 268 | std::lock_guard lock(m_mutex); 269 | 270 | switch (mode) 271 | { 272 | case StartMode::StartOnly: 273 | { 274 | // Abort if timer already running, we aren't allowing restarts 275 | if (m_nextExpiry != Clock::time_point::max()) 276 | return; 277 | 278 | break; 279 | } 280 | 281 | case StartMode::RestartIfRunning: 282 | { 283 | // No preconditons, always (re)start 284 | break; 285 | } 286 | 287 | case StartMode::RestartOnly: 288 | { 289 | // Abort if timer not already running, we are only allowing restarts 290 | if (m_nextExpiry == Clock::time_point::max()) 291 | return; 292 | 293 | break; 294 | } 295 | } 296 | 297 | m_nextExpiry = Clock::now() + m_interval; 298 | } 299 | 300 | if (const auto& pool = m_pool.lock()) 301 | pool->wake(); 302 | } 303 | 304 | void TimerPool::Timer::stop() 305 | { 306 | { 307 | std::lock_guard lock(m_mutex); 308 | 309 | m_nextExpiry = Clock::time_point::max(); 310 | } 311 | 312 | if (const auto& pool = m_pool.lock()) 313 | pool->wake(); 314 | } 315 | 316 | void TimerPool::Timer::fire(Clock::time_point now) 317 | { 318 | const auto selfHandle = shared_from_this(); 319 | unsigned int callbacksRequired = 0; 320 | Callback callback; 321 | 322 | { 323 | std::lock_guard lock(m_mutex); 324 | 325 | callback = m_callback; 326 | 327 | if (m_repeated) 328 | { 329 | // We might have to catch up to the current time - it's more efficient 330 | // to fire as many callbacks as we can be sure we've missed right now while 331 | // we're making expensive callback object copies, then clean up any extra 332 | // missed callbacks later when the parent pool re-evaluates the pool timers. 333 | do 334 | { 335 | m_nextExpiry += m_interval; 336 | callbacksRequired++; 337 | } while (m_nextExpiry < now); 338 | } 339 | else 340 | { 341 | m_nextExpiry = Clock::time_point::max(); 342 | 343 | callbacksRequired++; 344 | } 345 | } 346 | 347 | if (callback) 348 | { 349 | while (callbacksRequired--) 350 | callback(selfHandle); 351 | } 352 | } 353 | 354 | bool TimerPool::Timer::running() const noexcept 355 | { 356 | std::lock_guard lock(m_mutex); 357 | 358 | return m_nextExpiry != Clock::time_point::max(); 359 | } 360 | 361 | TimerPool::Timer::Clock::time_point TimerPool::Timer::nextExpiry() const noexcept 362 | { 363 | std::lock_guard lock(m_mutex); 364 | 365 | return m_nextExpiry; 366 | } 367 | --------------------------------------------------------------------------------