execution 1.0.1
Portable task execution, timers, and process lifecycle
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#ifndef STLAB_CONCURRENCY_SYSTEM_TIMER_HPP
8#define STLAB_CONCURRENCY_SYSTEM_TIMER_HPP
9
32
34#include <stlab/execution/config.hpp>
35#include <stlab/pre_exit.hpp> // IWYU pragma: export
36
37#include <cassert>
38#include <chrono>
39#include <cmath>
40#include <cstddef>
41#include <cstdint>
42#include <exception>
43#include <limits>
44#include <new>
45#include <stdexcept>
46#include <system_error>
47#include <type_traits>
48
49namespace stlab {
50inline namespace v2 {
51
55 std::int32_t code;
56 std::int32_t native_error;
57};
58
59static_assert(std::is_standard_layout_v<stlab_v2_timer_status>);
60static_assert(sizeof(stlab_v2_timer_status) == 2 * sizeof(std::int32_t));
61static_assert(offsetof(stlab_v2_timer_status, native_error) == sizeof(std::int32_t));
62
64using stlab_v2_task_proc = void (*)(void*) noexcept;
65
74extern "C" stlab_v2_timer_status stlab_v2_system_timer_submit(const unsigned char* task_abi_guard,
75 const stlab_v2_task_concept* vtable,
76 stlab_v2_task_proc invoke,
77 void* source,
78 std::int64_t delay_ns) noexcept;
79
80} // namespace v2
81
82STLAB_EXECUTION_VERSION_NAMESPACE_BEGIN()
83
84
89
90namespace execution_detail {
91
95inline auto timer_integral_nanoseconds(std::uintmax_t count,
96 std::uintmax_t numerator,
97 std::uintmax_t denominator,
98 std::uintmax_t multiplier = 1) -> std::int64_t {
99 constexpr auto limit = static_cast<std::uintmax_t>(std::numeric_limits<std::int64_t>::max());
100 std::uintmax_t quotient = 0;
101 std::uintmax_t remainder = 0;
102 if (count <= std::numeric_limits<std::uintmax_t>::max() / numerator) {
103 const auto product = count * numerator;
104 quotient = product / denominator;
105 remainder = product % denominator;
106 } else {
107 // Long division avoids overflowing the intermediate product for fractional periods.
108 const auto whole = numerator / denominator;
109 const auto fraction = numerator % denominator;
110 for (int bit = std::numeric_limits<std::uintmax_t>::digits - 1; bit >= 0; --bit) {
111 if (quotient > limit / 2)
112 throw std::overflow_error("system_timer delay exceeds signed nanoseconds");
113 quotient *= 2;
114 remainder *= 2; // The ratio denominator is at most INTMAX_MAX.
115 auto carry = remainder / denominator;
116 remainder %= denominator;
117 if ((count >> bit) & 1) {
118 if (whole > limit - carry)
119 throw std::overflow_error("system_timer delay exceeds signed nanoseconds");
120 carry += whole;
121 remainder += fraction;
122 carry += remainder / denominator;
123 remainder %= denominator;
124 }
125 if (carry > limit - quotient)
126 throw std::overflow_error("system_timer delay exceeds signed nanoseconds");
127 quotient += carry;
128 }
129 }
130 if (quotient > limit / multiplier)
131 throw std::overflow_error("system_timer delay exceeds signed nanoseconds");
132 quotient *= multiplier;
133 const auto rounded_fraction =
134 multiplier == 1 ? static_cast<std::uintmax_t>(remainder != 0) :
135 static_cast<std::uintmax_t>(
136 timer_integral_nanoseconds(remainder, multiplier, denominator));
137 if (rounded_fraction > limit - quotient)
138 throw std::overflow_error("system_timer delay exceeds signed nanoseconds");
139 return static_cast<std::int64_t>(quotient + rounded_fraction);
140}
141
143template <typename Rep, typename Period>
144auto timer_nanoseconds(std::chrono::duration<Rep, Period> duration) -> std::int64_t {
145 if constexpr (std::is_floating_point_v<Rep>) {
146 if (!std::isfinite(duration.count()))
147 throw std::invalid_argument("system_timer delay must be finite");
148 }
149 if (duration.count() <= 0) return 0;
150 if constexpr (std::is_integral_v<Rep>) {
151 return timer_integral_nanoseconds(static_cast<std::uintmax_t>(duration.count()),
152 Period::num, Period::den, 1000000000);
153 } else {
154 const auto value =
155 std::ceil(static_cast<long double>(duration.count()) *
156 static_cast<long double>(Period::num) / Period::den * 1000000000);
157 // 2^63 is exactly representable even when long double has only double precision.
158 const auto bound = -static_cast<long double>(std::numeric_limits<std::int64_t>::min());
159 if (!std::isfinite(value) || value >= bound)
160 throw std::overflow_error("system_timer delay exceeds signed nanoseconds");
161 return value < 1 ? 1 : static_cast<std::int64_t>(value);
162 }
163}
164
166inline void submit_system_timer(std::int64_t delay_ns, task<void() noexcept>& f) {
167 const auto result =
168 stlab_v2_system_timer_submit(&current_task_storage_abi_guard::value, f.relocation_concept(),
169 f.relocation_invoke(), f.relocation_source(), delay_ns);
170 switch (result.code) {
171 case 0:
172 return;
173 case 1:
174 throw std::bad_alloc();
175 case 2:
176 throw std::system_error(result.native_error, std::generic_category());
177 case 3:
178 throw std::system_error(result.native_error, std::system_category());
179 default:
180 assert(false && "invalid timer ABI resource status");
181 std::terminate();
182 }
183}
184
186struct system_timer_type {
187 using result_type = void;
188
196 void operator()(std::chrono::steady_clock::time_point when, task<void() noexcept>&& f) const {
197 const auto now = std::chrono::steady_clock::now();
198 if (when <= now) {
199 submit_system_timer(0, f);
200 return;
201 }
202 using duration = std::chrono::steady_clock::duration;
203 using unsigned_rep = std::make_unsigned_t<typename duration::rep>;
204 const auto remaining = static_cast<unsigned_rep>(when.time_since_epoch().count()) -
205 static_cast<unsigned_rep>(now.time_since_epoch().count());
206 submit_system_timer(
207 timer_nanoseconds(
208 std::chrono::duration<unsigned_rep, typename duration::period>(remaining)),
209 f);
210 }
211
221 template <typename Rep, typename Period>
222 void operator()(std::chrono::duration<Rep, Period> duration, task<void() noexcept>&& f) const {
223 submit_system_timer(timer_nanoseconds(duration), f);
224 }
225};
226
227} // namespace execution_detail
228
233inline constexpr auto system_timer = execution_detail::system_timer_type{};
234
236
237STLAB_EXECUTION_VERSION_NAMESPACE_END()
238} // namespace stlab
239
240#endif
void(*)(void *) noexcept stlab_v2_task_proc
Function pointer type submitted through the shared executor ABI.
Definition default_executor.hpp:64
constexpr auto system_timer
Schedules move-only void() noexcept tasks through the process-shared timer service.
Definition system_timer.hpp:233
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:453
Definition default_executor.hpp:50
Definition default_executor.hpp:49
stlab_v2_timer_status stlab_v2_system_timer_submit(const unsigned char *task_abi_guard, const stlab_v2_task_concept *vtable, stlab_v2_task_proc invoke, void *source, std::int64_t delay_ns) noexcept
Accepts a task into the core timer service without propagating exceptions.
Register and run operations that must execute before program exit.
Versioned operation table used to relocate a task target across the shared executor ABI.
Definition task.hpp:64
Resource result: code 0 is success, 1 allocation failure, 2 generic-category error,...
Definition system_timer.hpp:54
Move-only callable wrapper for executor scheduling (task<Signature>).