9#ifndef STLAB_CONCURRENCY_SYSTEM_TIMER_HPP
10#define STLAB_CONCURRENCY_SYSTEM_TIMER_HPP
31#include <stlab/config.hpp>
37#if STLAB_TASK_SYSTEM(LIBDISPATCH)
38#include <dispatch/dispatch.h>
40#elif STLAB_TASK_SYSTEM(WINDOWS)
43#elif STLAB_TASK_SYSTEM(PORTABLE)
45#include <condition_variable>
55STLAB_VERSION_NAMESPACE_BEGIN()
72#if STLAB_TASK_SYSTEM(LIBDISPATCH)
75 inline static bool _closed{
false};
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{}; }();
83 static bool enter_group_if_open() {
84 std::scoped_lock<std::mutex> lock(mutex());
88 dispatch_group_enter(detail::group()._group);
92 static void set_closed() {
93 std::scoped_lock<std::mutex> lock(mutex());
97 static bool is_closed() {
98 std::scoped_lock<std::mutex> lock(mutex());
104 (void)detail::group();
109 assert(is_closed() &&
"system_timer is not closed, pre_exit() was not called");
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");
117 using namespace std::chrono;
119 auto grouped = [f = std::move(f)]()
mutable {
123 if (!enter_group_if_open()) {
127 dispatch_group_leave(detail::group()._group);
130 using f_t =
decltype(grouped);
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_);
144#elif STLAB_TASK_SYSTEM(WINDOWS)
147 PTP_POOL _pool =
nullptr;
148 TP_CALLBACK_ENVIRON _callBackEnvironment;
149 PTP_CLEANUP_GROUP _cleanupgroup =
nullptr;
153 InitializeThreadpoolEnvironment(&_callBackEnvironment);
154 _pool = CreateThreadpool(
nullptr);
155 if (_pool ==
nullptr)
throw std::bad_alloc();
157 _cleanupgroup = CreateThreadpoolCleanupGroup();
158 if (_pool ==
nullptr)
throw std::bad_alloc();
160 SetThreadpoolCallbackPool(&_callBackEnvironment, _pool);
161 SetThreadpoolCallbackCleanupGroup(&_callBackEnvironment, _cleanupgroup,
nullptr);
165 CloseThreadpoolCleanupGroupMembers(_cleanupgroup, FALSE,
nullptr);
166 CloseThreadpoolCleanupGroup(_cleanupgroup);
167 CloseThreadpool(_pool);
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));
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);
184 if (timer ==
nullptr) {
185 throw std::bad_alloc();
188 auto file_time = duration_to_FILETIME(duration);
191#pragma warning(disable : 6553)
192 SetThreadpoolTimer(timer, &file_time, 0, 0);
197 template <
typename F>
198 static void CALLBACK timer_callback_impl(PTP_CALLBACK_INSTANCE ,
201 std::unique_ptr<F> f(
static_cast<F*
>(parameter));
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};
210 auto when = system_clock::now() + duration_cast<system_clock::duration>(duration);
211 time_t t = system_clock::to_time_t(when);
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);
221 std::chrono::duration_cast<std::chrono::milliseconds>(when.time_since_epoch())
224 SystemTimeToFileTime(&st, &ft);
232#elif STLAB_TASK_SYSTEM(PORTABLE)
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>;
239 queue_t _timed_queue;
240 std::condition_variable _condition;
242 std::mutex _timed_queue_mutex;
243 std::thread _timed_queue_thread;
245 struct greater_first {
246 using result_type = bool;
248 template <
typename T>
249 bool operator()(
const T& x,
const T& y) {
250 return x.first > y.first;
254 void timed_queue_run() {
258 lock_t lock(_timed_queue_mutex);
260 while (_timed_queue.empty() && !_stop)
261 _condition.wait(lock);
263 while (std::chrono::steady_clock::now() < _timed_queue.front().first) {
264 auto when = _timed_queue.front().first;
265 _condition.wait_until(lock, when);
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();
279 _timed_queue_thread = std::thread([
this] { this->timed_queue_run(); });
284 lock_t lock(_timed_queue_mutex);
287 _condition.notify_one();
288 _timed_queue_thread.join();
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));
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();
313struct system_timer_type {
314 using result_type = void;
318 return only_system_timer;
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));
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));
347STLAB_VERSION_NAMESPACE_END()
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>).