|
| 1 | +// Copyright (c) Darrell Wright |
| 2 | +// |
| 3 | +// Distributed under the Boost Software License, Version 1.0. (See accompanying |
| 4 | +// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) |
| 5 | +// |
| 6 | +// Official repository: https://github.com/beached/header_libraries |
| 7 | +// |
| 8 | + |
| 9 | +#pragma once |
| 10 | + |
| 11 | +#include "daw_attributes.h" |
| 12 | +#include "daw_concepts.h" |
| 13 | + |
| 14 | +#include <atomic> |
| 15 | +#include <chrono> |
| 16 | +#include <thread> |
| 17 | +#include <utility> |
| 18 | + |
| 19 | +namespace daw { |
| 20 | + enum class wait_status { found, timeout }; |
| 21 | +} |
| 22 | + |
| 23 | +namespace daw::atomic_impl { |
| 24 | + inline constexpr struct { |
| 25 | + DAW_ATTRIB_INLINE DAW_CPP23_STATIC_CALL_OP bool operator( )( |
| 26 | + std::chrono::nanoseconds elapsed ) DAW_CPP23_STATIC_CALL_OP_CONST { |
| 27 | + if( elapsed > std::chrono::milliseconds( 128 ) ) { |
| 28 | + std::this_thread::sleep_for( std::chrono::milliseconds( 8 ) ); |
| 29 | + } else if( elapsed > std::chrono::microseconds( 64 ) ) { |
| 30 | + std::this_thread::sleep_for( elapsed / 2 ); |
| 31 | + } else if( elapsed > std::chrono::microseconds( 4 ) ) { |
| 32 | + std::this_thread::yield( ); |
| 33 | + } else { |
| 34 | + // poll |
| 35 | + } |
| 36 | + return false; |
| 37 | + } |
| 38 | + } timed_backoff_policy{ }; |
| 39 | + |
| 40 | + [[nodiscard]] bool |
| 41 | + poll_with_backoff( Fn<bool( std::chrono::nanoseconds )> auto &&backoff_policy, |
| 42 | + Fn<bool( )> auto &&func, |
| 43 | + std::chrono::nanoseconds max_elapsed ) { |
| 44 | + |
| 45 | + auto const start_time = std::chrono::high_resolution_clock::now( ); |
| 46 | + int count = 0; |
| 47 | + while( true ) { |
| 48 | + if( func( ) ) { |
| 49 | + return true; |
| 50 | + } |
| 51 | + if( count < 64 ) { |
| 52 | + ++count; |
| 53 | + continue; |
| 54 | + } |
| 55 | + auto const elapsed = |
| 56 | + std::chrono::high_resolution_clock::now( ) - start_time; |
| 57 | + if( elapsed >= max_elapsed ) { |
| 58 | + return false; |
| 59 | + } |
| 60 | + if( backoff_policy( elapsed ) ) { |
| 61 | + return false; |
| 62 | + } |
| 63 | + } |
| 64 | + } |
| 65 | +} // namespace daw::atomic_impl |
| 66 | + |
| 67 | +namespace daw { |
| 68 | + |
| 69 | + template<typename T, typename Rep, typename Period> |
| 70 | + /** |
| 71 | + * Waits for the atomic object to change from the old value or until the |
| 72 | + * timeout expires. |
| 73 | + * |
| 74 | + * This function blocks the calling thread, periodically polling the atomic |
| 75 | + * object, and returns as soon as the atomic value differs from the specified |
| 76 | + * old value or the relative timeout period elapses. |
| 77 | + * |
| 78 | + * @tparam T The type of the atomic object. |
| 79 | + * @tparam Rep The representation type of the relative time duration. |
| 80 | + * @tparam Period The tick period type of the relative time duration. |
| 81 | + * |
| 82 | + * @param object Pointer to the atomic object to wait on. |
| 83 | + * @param old The value that the atomic object is expected to differ from. |
| 84 | + * @param rel_time The maximum duration to wait before timing out. |
| 85 | + * @param order The memory order to use for atomic load operations. Defaults |
| 86 | + * to `std::memory_order_acquire`. |
| 87 | + * |
| 88 | + * @return A wait_status indicating whether the atomic value changed or the |
| 89 | + * timeout expired. |
| 90 | + */ |
| 91 | + [[nodiscard]] wait_status |
| 92 | + atomic_wait_for( std::atomic<T> const *object, T const &old, |
| 93 | + std::chrono::duration<Rep, Period> const &rel_time, |
| 94 | + std::memory_order order = std::memory_order_acquire ) { |
| 95 | + auto const final_time = |
| 96 | + std::chrono::high_resolution_clock::now( ) + rel_time; |
| 97 | + auto current = |
| 98 | + std::atomic_load_explicit( object, std::memory_order_acquire ); |
| 99 | + |
| 100 | + for( int tries = 0; current == old and tries < 16; ++tries ) { |
| 101 | + std::this_thread::yield( ); |
| 102 | + current = std::atomic_load_explicit( object, order ); |
| 103 | + } |
| 104 | + auto const poll_fn = [&] { |
| 105 | + current = std::atomic_load_explicit( object, order ); |
| 106 | + if( current != old ) { |
| 107 | + return true; |
| 108 | + } |
| 109 | + auto const current_time = std::chrono::high_resolution_clock::now( ); |
| 110 | + auto const is_timed_out = current_time >= final_time; |
| 111 | + return is_timed_out; |
| 112 | + }; |
| 113 | + (void)atomic_impl::poll_with_backoff( atomic_impl::timed_backoff_policy, |
| 114 | + poll_fn, rel_time ); |
| 115 | + if( current == old ) { |
| 116 | + return wait_status::timeout; |
| 117 | + } |
| 118 | + return wait_status::found; |
| 119 | + } |
| 120 | + |
| 121 | + /** |
| 122 | + * Waits until the atomic object changes from the specified value or the |
| 123 | + * timeout time is reached. |
| 124 | + * |
| 125 | + * This function blocks the calling thread until the atomic value |
| 126 | + * pointed to by `object` is not equal to `old` or until the specified |
| 127 | + * `timeout_time` is reached. |
| 128 | + * |
| 129 | + * @tparam T The type of the atomic object. |
| 130 | + * @tparam Clock The clock type used to measure the timeout. |
| 131 | + * @tparam Duration The duration type used to represent the timeout. |
| 132 | + * |
| 133 | + * @param object Pointer to the atomic object to wait on. |
| 134 | + * @param old The value that the atomic object is expected to change from |
| 135 | + * @param timeout_time The time point at which the wait is aborted if the |
| 136 | + * value has not been reached. |
| 137 | + * @param order The memory order to use. Defaults to |
| 138 | + * `std::memory_order_acquire`. |
| 139 | + * |
| 140 | + * @return A `wait_status` indicating whether the wait was successful or if it |
| 141 | + * timed out. |
| 142 | + */ |
| 143 | + template<typename T, typename Clock, typename Duration> |
| 144 | + [[nodiscard]] wait_status atomic_wait_until( |
| 145 | + std::atomic<T> const *object, T const &old, |
| 146 | + std::chrono::time_point<Clock, Duration> const &timeout_time, |
| 147 | + std::memory_order order = std::memory_order_acquire ) { |
| 148 | + |
| 149 | + return daw::atomic_wait_for( object, std::move( old ), |
| 150 | + timeout_time - Clock::now( ), order ); |
| 151 | + } |
| 152 | + |
| 153 | + /** |
| 154 | + * Waits until the atomic object satisfies the given predicate. |
| 155 | + * |
| 156 | + * This function blocks the calling thread until the value of the atomic |
| 157 | + * object pointed to by `object` satisfies the provided `predicate`. |
| 158 | + * |
| 159 | + * @tparam T The type of the atomic object. |
| 160 | + * @tparam Fn A templated function type that takes a value of type T and |
| 161 | + * returns bool. |
| 162 | + * |
| 163 | + * @param object Pointer to the atomic object to wait on. |
| 164 | + * @param predicate A callable object (such as a lambda or function object) |
| 165 | + * that takes a value of type T and returns a boolean indicating whether the |
| 166 | + * condition is met. |
| 167 | + * @param order The memory order to use. Defaults to |
| 168 | + * `std::memory_order_acquire`. |
| 169 | + */ |
| 170 | + template<typename T> |
| 171 | + void atomic_wait_if( std::atomic<T> const *object, |
| 172 | + Fn<bool( T )> auto &&predicate, |
| 173 | + std::memory_order order = std::memory_order_acquire ) { |
| 174 | + auto current = |
| 175 | + std::atomic_load_explicit( object, std::memory_order_acquire ); |
| 176 | + auto const &const_current = current; |
| 177 | + while( not predicate( const_current ) ) { |
| 178 | + std::atomic_wait_explicit( object, current, order ); |
| 179 | + current = std::atomic_load_explicit( object, std::memory_order_relaxed ); |
| 180 | + } |
| 181 | + } |
| 182 | + |
| 183 | + /** |
| 184 | + * Waits for an atomic object to satisfy a predicate within a specified time |
| 185 | + * duration. |
| 186 | + * |
| 187 | + * This function blocks the calling thread until the predicate applied |
| 188 | + * to the atomic object evaluates to true or the specified timeout period |
| 189 | + * elapses. |
| 190 | + * |
| 191 | + * @tparam T The type of the atomic object. |
| 192 | + * @tparam Fn A callable type that returns a boolean value when applied to an |
| 193 | + * object of type T. |
| 194 | + * @tparam Rep A type representing the number of ticks of the time duration. |
| 195 | + * @tparam Period A std::ratio type representing the tick period of the time |
| 196 | + * duration. |
| 197 | + * |
| 198 | + * @param object Pointer to the atomic object to wait on. |
| 199 | + * @param predicate Predicate function to apply to the atomic object's value. |
| 200 | + * @param rel_time The relative time duration to wait for the predicate to |
| 201 | + * become true. |
| 202 | + * @param order The memory order to use for atomic operations. Defaults to |
| 203 | + * `std::memory_order_acquire`. |
| 204 | + * |
| 205 | + * @return Returns wait_status::found if the predicate evaluates to true |
| 206 | + * before the timeout, and wait_status::timeout otherwise. |
| 207 | + */ |
| 208 | + template<typename T, typename Rep, typename Period> |
| 209 | + [[nodiscard]] wait_status |
| 210 | + atomic_wait_if_for( std::atomic<T> const *object, |
| 211 | + Fn<bool( T )> auto &&predicate, |
| 212 | + std::chrono::duration<Rep, Period> const &rel_time, |
| 213 | + std::memory_order order = std::memory_order_acquire ) { |
| 214 | + auto const final_time = |
| 215 | + std::chrono::high_resolution_clock::now( ) + rel_time; |
| 216 | + auto current_value = |
| 217 | + std::atomic_load_explicit( object, std::memory_order_acquire ); |
| 218 | + |
| 219 | + for( int tries = 0; not predicate( current_value ) and tries < 16; |
| 220 | + ++tries ) { |
| 221 | + std::this_thread::yield( ); |
| 222 | + current_value = std::atomic_load_explicit( object, order ); |
| 223 | + } |
| 224 | + auto const poll_fn = [&] { |
| 225 | + current_value = std::atomic_load_explicit( object, order ); |
| 226 | + auto const pred_result = predicate( current_value ); |
| 227 | + if( pred_result ) { |
| 228 | + return true; |
| 229 | + } |
| 230 | + auto const current_time = std::chrono::high_resolution_clock::now( ); |
| 231 | + auto const is_timed_out = current_time >= final_time; |
| 232 | + return is_timed_out; |
| 233 | + }; |
| 234 | + (void)atomic_impl::poll_with_backoff( atomic_impl::timed_backoff_policy, |
| 235 | + poll_fn, rel_time ); |
| 236 | + if( predicate( current_value ) ) { |
| 237 | + return wait_status::found; |
| 238 | + } |
| 239 | + return wait_status::timeout; |
| 240 | + } |
| 241 | + |
| 242 | + /** |
| 243 | + * Waits until the atomic object satisfies the given predicate or the absolute |
| 244 | + * timeout time is reached. |
| 245 | + * |
| 246 | + * This function blocks the calling thread until the atomic value pointed to |
| 247 | + * by `object` satisfies the `predicate` function or the specified timeout |
| 248 | + * time `timeout_time` is reached. |
| 249 | + * |
| 250 | + * @tparam T The type of the atomic object. |
| 251 | + * @tparam Clock The clock used for the timeout. |
| 252 | + * @tparam Duration The duration type used by the clock. |
| 253 | + * @tparam Fn A callable type that takes a value of type T and returns a bool. |
| 254 | + * |
| 255 | + * @param object Pointer to the atomic object to wait on. |
| 256 | + * @param predicate A callable that takes the atomic object's value and |
| 257 | + * returns whether it satisfies the condition. |
| 258 | + * @param timeout_time The absolute time point by which the wait operation |
| 259 | + * should be aborted if the predicate is not satisfied. |
| 260 | + * @param order The memory order to use. Defaults to |
| 261 | + * std::memory_order_acquire. |
| 262 | + * |
| 263 | + * @return A `wait_status` enum indicating whether the predicate was satisfied |
| 264 | + * or a timeout occurred. |
| 265 | + */ |
| 266 | + template<typename T, typename Clock, typename Duration> |
| 267 | + [[nodiscard]] wait_status atomic_wait_if_until( |
| 268 | + std::atomic<T> const *object, Fn<bool( T )> auto &&predicate, |
| 269 | + std::chrono::time_point<Clock, Duration> const &timeout_time, |
| 270 | + std::memory_order order = std::memory_order_acquire ) { |
| 271 | + return daw::atomic_wait_if_for( object, std::move( predicate ), |
| 272 | + timeout_time - Clock::now( ), order ); |
| 273 | + } |
| 274 | + |
| 275 | + template<typename T> |
| 276 | + /** |
| 277 | + * Waits until the atomic object reaches the desired value. |
| 278 | + * |
| 279 | + * This function blocks the calling thread until the atomic value |
| 280 | + * pointed to by `object` equals `desired_value`. |
| 281 | + * |
| 282 | + * @tparam T The type of the atomic object. |
| 283 | + * |
| 284 | + * @param object Pointer to the atomic object to wait on. |
| 285 | + * @param desired_value The value that the atomic object is expected to reach. |
| 286 | + * @param order The memory order to use. Defaults to |
| 287 | + * `std::memory_order_acquire`. |
| 288 | + */ |
| 289 | + void atomic_wait_value_equal( |
| 290 | + std::atomic<T> const *object, T const &desired_value, |
| 291 | + std::memory_order order = std::memory_order_acquire ) { |
| 292 | + atomic_wait_if( |
| 293 | + object, |
| 294 | + [&desired_value]( T const ¤t_value ) { |
| 295 | + return current_value == desired_value; |
| 296 | + }, |
| 297 | + order ); |
| 298 | + } |
| 299 | + |
| 300 | + /** |
| 301 | + * Waits until the atomic object reaches the desired value or the relative |
| 302 | + * time duration has elapsed. |
| 303 | + * |
| 304 | + * This function blocks the calling thread until the atomic value pointed to |
| 305 | + * by `object` equals `desired_value` or the specified relative time duration |
| 306 | + * `rel_time` has elapsed. |
| 307 | + * |
| 308 | + * @tparam T The type of the atomic object. |
| 309 | + * @tparam Rep The type representing the number of ticks. |
| 310 | + * @tparam Period The type representing the tick period. |
| 311 | + * |
| 312 | + * @param object Pointer to the atomic object to wait on. |
| 313 | + * @param desired_value The value that the atomic object is expected to reach. |
| 314 | + * @param rel_time The maximum duration to wait for the atomic object to reach |
| 315 | + * the desired value. |
| 316 | + * @param order The memory order to use. Defaults to |
| 317 | + * `std::memory_order_acquire`. |
| 318 | + * |
| 319 | + * @return A `wait_status` enum indicating whether the desired value was found |
| 320 | + * or a timeout occurred. |
| 321 | + */ |
| 322 | + template<typename T, typename Rep, typename Period> |
| 323 | + [[nodiscard]] wait_status atomic_wait_value_equal_for( |
| 324 | + std::atomic<T> const *object, T const &desired_value, |
| 325 | + std::chrono::duration<Rep, Period> const &rel_time, |
| 326 | + std::memory_order order = std::memory_order_acquire ) { |
| 327 | + return atomic_wait_if_for( |
| 328 | + object, |
| 329 | + [&desired_value]( T const ¤t_value ) { |
| 330 | + return current_value == desired_value; |
| 331 | + }, |
| 332 | + rel_time, order ); |
| 333 | + } |
| 334 | + |
| 335 | + /** |
| 336 | + * Waits until the specified object reaches the desired value or the timeout |
| 337 | + * is reached. |
| 338 | + * |
| 339 | + * This function blocks the calling thread until the atomic value pointed to |
| 340 | + * by `object` equals `desired_value` or the specified timeout time |
| 341 | + * `timeout_time` is reached. |
| 342 | + * |
| 343 | + * @tparam T The type of the atomic object. |
| 344 | + * @tparam Clock The clock used for the timeout. |
| 345 | + * @tparam Duration The duration type used by the clock. |
| 346 | + * |
| 347 | + * @param object Pointer to the atomic object to wait on. |
| 348 | + * @param desired_value The value that the atomic object is expected to reach. |
| 349 | + * @param timeout_time The absolute time point by which the wait operation |
| 350 | + * should be aborted if the desired value is not reached. |
| 351 | + * @param order The memory order to use. Defaults to |
| 352 | + * `std::memory_order_acquire`. |
| 353 | + * |
| 354 | + * @return A `wait_status` enum indicating whether the desired value was found |
| 355 | + * or a timeout occurred. |
| 356 | + */ |
| 357 | + template<typename T, typename Clock, typename Duration> |
| 358 | + [[nodiscard]] wait_status atomic_wait_value_equal_until( |
| 359 | + std::atomic<T> const *object, T const &desired_value, |
| 360 | + std::chrono::time_point<Clock, Duration> const &timeout_time, |
| 361 | + std::memory_order order = std::memory_order_acquire ) { |
| 362 | + return daw::atomic_wait_if_for( |
| 363 | + object, |
| 364 | + [&desired_value]( T const ¤t_value ) { |
| 365 | + return current_value == desired_value; |
| 366 | + }, |
| 367 | + timeout_time - Clock::now( ), order ); |
| 368 | + } |
| 369 | + |
| 370 | +} // namespace daw |
0 commit comments