├── .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 |
--------------------------------------------------------------------------------