std::future::wait_until(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

🌸 NAME 🌸

std::future::wait_until β€” bestie, it's literally std::future::wait_until, that's the whole name, no notes πŸ’…

ok so real talk, this is from the C++ Standard Libary (yes that's how the header spells it, we're not fixing it, we're respecting it 🫢). Sit down, get a snack, we're learning how to wait until a specific moment for a future to be ready. It's giving "are we there yet" but with a time_point. πŸ•βœ¨


✨ Synopsis ✨

template< class Clock, class Duration >
std::future_status wait_until( const
std::chrono::time_point<Clock,Duration>& timeout_time ) const;

(since C++11)

So what does it do? wait_until waits for a result to become available. It blocks until the specified timeout_time has been reached or the result becomes available β€” whichever comes first. Whichever!! First!! 🏁 And the return value tells you why wait_until came back, which is so helpful of it honestly.

🚨 Special case incoming: if the future is the result of a call to async that used lazy evaluation, this function returns immediately without waiting. No waiting. Zero. Zip. It said "not my problem bestie" πŸ’€

⚠️ The scary part (read this, fr fr)

The behavior is undefined if valid() is false before the call to this function, or if Clock does not meet the Clock requirements. The program is ill-formed if std::chrono::is_clock_v<Clock> is false. (since C++20)

Undefined behavior is not a vibe. Do not do this. πŸ™ˆ


πŸŽ€ Parameters πŸŽ€

  • timeout_time β€” the maximum time point to block until. That's it. That's the whole parameter. The absolute latest you're willing to sit there being patient. ⏳

πŸ’– Return value πŸ’–

Here's the little lookup table of why-it-came-back, in bullet form because tables are so not it:

  • future_status::deferred β€” the shared state contains a deferred function using lazy evaluation, so the result will be computed only when explicitly requested. (i.e. it's not doing the work yet, it's waiting to be asked, main character energy tbh πŸ‘‘)
  • future_status::ready β€” the result is ready. πŸŽ‰ slay.
  • future_status::timeout β€” the timeout has expired. 😭 the clock said "time's up".

πŸ’€ Exceptions πŸ’€

Any exception thrown by clock, time_point, or duration during the execution propagates.

🫢 Good news though: clocks, time points, and durations provided by the standard library never throw. So that's one less thing to worry about.

πŸ¦‹ Notes πŸ¦‹

Implementations are encouraged (encouraged!! not required, so don't come for me) to detect the case when valid() == false before the call and throw a std::future_error with an error condition of future_errc::no_state.

The standard recommends that the clock tied to timeout_time be used to measure time; that clock is not required to be a monotonic clock. There are no guarantees regarding the behavior of this function if the clock is adjusted discontinuously β€” 🚩 so like, don't be weird with your clocks β€” but the existing implementations convert timeout_time from Clock to std::chrono::system_clock and delegate to POSIX pthread_cond_timedwait so that the wait honors adjustments to the system clock, but not to the user-provided Clock. In any case, the function also may wait for longer than until after timeout_time has been reached due to scheduling or resource contention delays. πŸ™ƒ Yes. Longer. It's not a promise, it's a vibe.


🌈 Example 🌈

ok this example ATE, it literally demonstrates both outcomes in one program, obsessed 😭✨

// Run this code

#include <chrono>
#include <future>
#include <iostream>
#include <thread>

int main()
{
    std::chrono::system_clock::time_point two_seconds_passed
        = std::chrono::system_clock::now() + std::chrono::seconds(2);

    // Make a future that takes 1 second to complete
    std::promise<int> p1;
    std::future<int> f_completes = p1.get_future();
    std::thread([](std::promise<int> p1)
                {
                    std::this_thread::sleep_for(std::chrono::seconds(1));
                    p1.set_value_at_thread_exit(9);
                },
                std::move(p1)
    ).detach();

    // Make a future that takes 5 seconds to complete
    std::promise<int> p2;
    std::future<int> f_times_out = p2.get_future();
    std::thread([](std::promise<int> p2)
                {
                    std::this_thread::sleep_for(std::chrono::seconds(5));
                    p2.set_value_at_thread_exit(8);
                },
                std::move(p2)
    ).detach();

    std::cout << "Waiting for 2 seconds..." << std::endl;

    if (std::future_status::ready ==
       f_completes.wait_until(two_seconds_passed))
        std::cout << "f_completes: " << f_completes.get() << "\n";
    else
        std::cout << "f_completes did not complete!\n";

    if (std::future_status::ready ==
       f_times_out.wait_until(two_seconds_passed))
        std::cout << "f_times_out: " << f_times_out.get() << "\n";
    else
        std::cout << "f_times_out did not complete!\n";

    std::cout << "Done!\n";
}

⭐ Possible output

Waiting for 2 seconds...
f_completes: 9
f_times_out did not complete!
Done!

see how f_completes finished in 1 second (under the 2 second limit, so ready βœ…) and f_times_out needed 5 seconds (over the limit, so timeout 😭)? chef's kiss. std::move(p1) / std::move(p2) because promises are move-only, iykyk 🧚


πŸ”₯ See also πŸ”₯

  • wait β€” waits for the result to become available (public member function)
  • wait_for β€” waits for the result, returns if it is not available for the specified timeout duration (public member function)

k that's the whole page bestie, go forth and block responsibly πŸ’œ

mwah πŸ’‹ β€” from the std::future::wait_until(3) page over at http://cppreference.com, dated 2024.06.10. πŸŽ€

πŸ“¦ where this page comes from