stlab 2.3.0
Modern, modular C++ algorithms, data structures, and concurrency primitives
Loading...
Searching...
No Matches
system_timer.hpp
Go to the documentation of this file.
1/*
2 Copyright 2015 Adobe
3 Distributed under the Boost Software License, Version 1.0.
4 (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5*/
6
7/**************************************************************************************************/
8
9#ifndef STLAB_CONCURRENCY_SYSTEM_TIMER_HPP
10#define STLAB_CONCURRENCY_SYSTEM_TIMER_HPP
11
27
28/**************************************************************************************************/
29
30#include <cassert>
31#include <stlab/config.hpp>
32#include <stlab/pre_exit.hpp>
33
34#include <chrono>
35#include <type_traits>
36
37#if STLAB_TASK_SYSTEM(LIBDISPATCH)
38#include <dispatch/dispatch.h>
40#elif STLAB_TASK_SYSTEM(WINDOWS)
41#include <Windows.h>
42#include <memory>
43#elif STLAB_TASK_SYSTEM(PORTABLE)
44#include <algorithm>
45#include <condition_variable>
46#include <thread>
47#include <vector>
48#endif
49
51
52/**************************************************************************************************/
53
54namespace stlab {
55STLAB_VERSION_NAMESPACE_BEGIN()
56
57
65
66/**************************************************************************************************/
67
68namespace detail {
69
70/**************************************************************************************************/
71
72#if STLAB_TASK_SYSTEM(LIBDISPATCH)
73
74struct system_timer {
75 inline static bool _closed{false};
76
77 static inline auto mutex() -> std::mutex& {
78 alignas(std::mutex) static std::array<unsigned char, sizeof(std::mutex)> _storage = {0};
79 static std::mutex* _mutex = [&] { return new (&_storage[0]) std::mutex{}; }();
80 return *_mutex;
81 }
82
83 static bool enter_group_if_open() {
84 std::scoped_lock<std::mutex> lock(mutex());
85 if (_closed) {
86 return false;
87 }
88 dispatch_group_enter(detail::group()._group);
89 return true;
90 }
91
92 static void set_closed() {
93 std::scoped_lock<std::mutex> lock(mutex());
94 _closed = true;
95 }
96
97 static bool is_closed() {
98 std::scoped_lock<std::mutex> lock(mutex());
99 return _closed;
100 }
101
102 system_timer() {
103 // ensure the group is created and registered with pre_exit first
104 (void)detail::group();
105 at_pre_exit([]() noexcept { set_closed(); });
106 }
107
108 ~system_timer() {
109 assert(is_closed() && "system_timer is not closed, pre_exit() was not called");
110 }
111
112 template <typename F, typename Rep, typename Per = std::ratio<1>>
113 auto operator()(std::chrono::duration<Rep, Per> duration, F f) const
114 -> std::enable_if_t<std::is_nothrow_invocable_v<F>> {
115 assert(!is_closed() && "scheduling a task after pre_exit() was called");
116
117 using namespace std::chrono;
118
119 auto grouped = [f = std::move(f)]() mutable {
120 // pre_exit tasks are executed in the reverse order of registration.
121 // By entering the group before we check if the timer is closed we ensure that the
122 // task is waited on if it starts.
123 if (!enter_group_if_open()) {
124 return;
125 }
126 std::move(f)();
127 dispatch_group_leave(detail::group()._group);
128 };
129
130 using f_t = decltype(grouped);
131
132 dispatch_after_f(dispatch_time(0, duration_cast<nanoseconds>(duration).count()),
133 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
134 new f_t(std::move(grouped)), [](void* f_) {
135 auto f = static_cast<f_t*>(f_);
136 (*f)();
137 delete f;
138 });
139 }
140};
141
142/**************************************************************************************************/
143
144#elif STLAB_TASK_SYSTEM(WINDOWS)
145
146class system_timer {
147 PTP_POOL _pool = nullptr;
148 TP_CALLBACK_ENVIRON _callBackEnvironment;
149 PTP_CLEANUP_GROUP _cleanupgroup = nullptr;
150
151public:
152 system_timer() {
153 InitializeThreadpoolEnvironment(&_callBackEnvironment);
154 _pool = CreateThreadpool(nullptr);
155 if (_pool == nullptr) throw std::bad_alloc();
156
157 _cleanupgroup = CreateThreadpoolCleanupGroup();
158 if (_pool == nullptr) throw std::bad_alloc();
159
160 SetThreadpoolCallbackPool(&_callBackEnvironment, _pool);
161 SetThreadpoolCallbackCleanupGroup(&_callBackEnvironment, _cleanupgroup, nullptr);
162 }
163
164 ~system_timer() {
165 CloseThreadpoolCleanupGroupMembers(_cleanupgroup, FALSE, nullptr);
166 CloseThreadpoolCleanupGroup(_cleanupgroup);
167 CloseThreadpool(_pool);
168 }
169
170 template <typename F>
171 [[deprecated("Use chrono::duration as parameter instead")]] void operator()(
172 std::chrono::steady_clock::time_point when, F&& f) {
173 using namespace std::chrono;
174 operator()(when - steady_clock::now(), std::forward<F>(f));
175 }
176
177 template <typename F, typename Rep, typename Per = std::ratio<1>>
178 auto operator()(std::chrono::duration<Rep, Per> duration, F&& f)
179 -> std::enable_if_t<std::is_nothrow_invocable_v<F>> {
180 using namespace std::chrono;
181 auto timer = CreateThreadpoolTimer(&timer_callback_impl<F>, new F(std::forward<F>(f)),
182 &_callBackEnvironment);
183
184 if (timer == nullptr) {
185 throw std::bad_alloc();
186 }
187
188 auto file_time = duration_to_FILETIME(duration);
189
190#pragma warning(push)
191#pragma warning(disable : 6553) // bad annotation on SetThreadpoolTimer
192 SetThreadpoolTimer(timer, &file_time, 0, 0);
193#pragma warning(pop)
194 }
195
196private:
197 template <typename F>
198 static void CALLBACK timer_callback_impl(PTP_CALLBACK_INSTANCE /*Instance*/,
199 PVOID parameter,
200 PTP_TIMER /*timer*/) {
201 std::unique_ptr<F> f(static_cast<F*>(parameter));
202 (*f)();
203 }
204
205 template <typename Rep, typename Per = std::ratio<1>>
206 FILETIME duration_to_FILETIME(std::chrono::duration<Rep, Per> duration) const {
207 using namespace std::chrono;
208 FILETIME ft = {0, 0};
209 SYSTEMTIME st = {0};
210 auto when = system_clock::now() + duration_cast<system_clock::duration>(duration);
211 time_t t = system_clock::to_time_t(when);
212 tm utc_tm;
213 if (!gmtime_s(&utc_tm, &t)) {
214 st.wSecond = static_cast<WORD>(utc_tm.tm_sec);
215 st.wMinute = static_cast<WORD>(utc_tm.tm_min);
216 st.wHour = static_cast<WORD>(utc_tm.tm_hour);
217 st.wDay = static_cast<WORD>(utc_tm.tm_mday);
218 st.wMonth = static_cast<WORD>(utc_tm.tm_mon + 1);
219 st.wYear = static_cast<WORD>(utc_tm.tm_year + 1900);
220 st.wMilliseconds =
221 std::chrono::duration_cast<std::chrono::milliseconds>(when.time_since_epoch())
222 .count() %
223 1000;
224 SystemTimeToFileTime(&st, &ft);
225 }
226 return ft;
227 }
228};
229
230/**************************************************************************************************/
231
232#elif STLAB_TASK_SYSTEM(PORTABLE)
233
234class system_timer {
235 using element_t = std::pair<std::chrono::steady_clock::time_point, task<void() noexcept>>;
236 using queue_t = std::vector<element_t>;
237 using lock_t = std::unique_lock<std::mutex>;
238
239 queue_t _timed_queue;
240 std::condition_variable _condition;
241 bool _stop = false;
242 std::mutex _timed_queue_mutex;
243 std::thread _timed_queue_thread;
244
245 struct greater_first {
246 using result_type = bool;
247
248 template <typename T>
249 bool operator()(const T& x, const T& y) {
250 return x.first > y.first;
251 }
252 };
253
254 void timed_queue_run() {
255 while (true) {
256 task<void() noexcept> task;
257 {
258 lock_t lock(_timed_queue_mutex);
259
260 while (_timed_queue.empty() && !_stop)
261 _condition.wait(lock);
262 if (_stop) return;
263 while (std::chrono::steady_clock::now() < _timed_queue.front().first) {
264 auto when = _timed_queue.front().first;
265 _condition.wait_until(lock, when);
266 if (_stop) return;
267 }
268 std::pop_heap(begin(_timed_queue), end(_timed_queue), greater_first());
269 task = std::move(_timed_queue.back().second);
270 _timed_queue.pop_back();
271 }
272
273 task();
274 }
275 }
276
277public:
278 system_timer() {
279 _timed_queue_thread = std::thread([this] { this->timed_queue_run(); });
280 }
281
282 ~system_timer() {
283 {
284 lock_t lock(_timed_queue_mutex);
285 _stop = true;
286 }
287 _condition.notify_one();
288 _timed_queue_thread.join();
289 }
290
291 template <typename F>
292 [[deprecated("Use chrono::duration as parameter instead")]] void operator()(
293 std::chrono::steady_clock::time_point when, F&& f) {
294 using namespace std::chrono;
295 operator()(when - steady_clock::now(), std::forward<decltype(f)>(f));
296 }
297
298 template <typename F, typename Rep, typename Per = std::ratio<1>>
299 auto operator()(std::chrono::duration<Rep, Per> duration, F&& f)
300 -> std::enable_if_t<std::is_nothrow_invocable_v<F>> {
301 lock_t lock(_timed_queue_mutex);
302 _timed_queue.emplace_back(std::chrono::steady_clock::now() + duration, std::forward<F>(f));
303 std::push_heap(std::begin(_timed_queue), std::end(_timed_queue), greater_first());
304 _condition.notify_one();
305 }
306};
307
308#endif
309
310/**************************************************************************************************/
311
313struct system_timer_type {
314 using result_type = void;
315
316 static auto get_system_timer() -> system_timer& {
317 static system_timer only_system_timer;
318 return only_system_timer;
319 }
320
322 [[deprecated("Use chrono::duration as parameter instead")]] void operator()(
323 std::chrono::steady_clock::time_point when, task<void() noexcept>&& f) const {
324 operator()(when - std::chrono::steady_clock().now(), std::move(f));
325 }
326
328 template <typename Rep, typename Per = std::ratio<1>>
329 void operator()(std::chrono::duration<Rep, Per> duration, task<void() noexcept>&& f) const {
330 get_system_timer()(duration, std::move(f));
331 }
332};
333
334/**************************************************************************************************/
335
336} // namespace detail
337
338/**************************************************************************************************/
339
341inline constexpr auto system_timer = detail::system_timer_type{};
342
343/**************************************************************************************************/
344
346
347STLAB_VERSION_NAMESPACE_END()
348} // namespace stlab
349
350/**************************************************************************************************/
351
352#endif
353
354/**************************************************************************************************/
Thread-pool executors mapping to the OS scheduler (libdispatch, Windows pool, portable).
constexpr auto system_timer
Schedules void() noexcept tasks on a system timer / run loop (platform-dependent).
Definition system_timer.hpp:341
typename noexcept_deducer< task_, F >::type task
task_ with noexcept deduced from the function type F (e.g. void() vs void() noexcept).
Definition task.hpp:324
void at_pre_exit(pre_exit_handler f)
Register a pre-exit handler. The pre-exit-handler may not throw. With C++17 or later it is required t...
Definition pre_exit.hpp:53
Definition reverse.hpp:28
Register and run operations that must execute before program exit.
Move-only callable wrapper for executor scheduling (task<Signature>).