2
votes

I have a function that takes in the number of microseconds before a timeout occurs as a long. This timeout is the timeout for the function to complete its work, even though the function may take longer than the timeout due to things like scheduling and other overhead.

The function does the following:

  1. Performs some setup and launches several threads with std::future and std::async.
  2. Keeps track of the threads using std::future::wait_for() in a loop. Basically, I time each call to wait_for() and subtract the time it took from the timeout. This new timeout is then used when checking the next thread. My goal here is to ensure that all the threads I launch complete their work before the timeout (i.e., the timeout parameter passed to the function) expires.

Pseudo-code below:

void myFunctionWithTimeout(/*some other inputs*/ const long timeout_us) {
  auto start_time = std::chrono::steady_clock::now();
  double time_remaining_us = std::chrono::microseconds(timeout_us).count();

  // Launch threads here using std::future and std::async...

  auto end_time = std::chrono::steady_clock::now();
  const auto setup_time_us =
    std::chrono::duration<double, std::micro>(end_time - start_time);
  time_remaining_us -= setup_time_us.count();

  for(auto& worker : workers) {
    auto start_time = std::chrono::steady_clock::now();
    const auto status =
      worker.wait_for(std::chrono::duration<double, std::micro>(time_remaining_us));
    auto end_time = std::chrono::steady_clock::now();

    // Check status and do the appropriate actions.
    // Note that it is OK that this time isn't part of the timeout.

    const auto wait_time_us =
      std::chrono::duration<double, std::micro>(end_time - start_time);
    time_remaining_us -= wait_time_us.count();
  }
}

My questions:

  1. Is there an easier way to do what I am proposing? My goal is to store the time remaining as a double so in the various computations I can account for fractions of a microsecond. Note that I know that wait_for() won't exactly wait for the duration I specify due to scheduling and what-not, but, at the very least, I don't want to add any round off error in my computations.
  2. Related to #1: Do I need to get the count each time or is there a clean way to update a std::chrono::duration? I'm looking to store the time remaining as a duration and then subtract the setup time or wait time from it.
  3. What happens when time_remaining_us becomes negative? How does this affect the constructor for std::chrono::duration? What happens when a negative duration is passed to std::future::wait_for()? I haven't found these details in the documentation and am wondering if the behavior here is well defined.

===================================================================== Edited to add:

Per Howard's answer, I looked into using wait_until(), but I don't think it will work for me due to the following issue I found in my research (excerpt from: https://en.cppreference.com/w/cpp/thread/future/wait_until):

The clock tied to timeout_time is used, which is not required to be a monotonic clock.There are no guarantees regarding the behavior of this function if the clock is adjusted discontinuously, 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 ajustments to the system clock, but not to the 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.

The way I read that is that even if I use steady_clock for my ending time, it will be converted to system_clock, which means that if the clock is adjusted (say rolled back an hour) I could end up with a timeout of much, much longer than I expected.

That said, I did take the concept of computing the ending time and it simplified my code. Here's some pseudo-code with where I am at currently:

void myFunctionWithTimeout(/*some other inputs*/ const long timeout_us) {
  const auto start_time = std::chrono::steady_clock::now();
  const auto end_time =
    start_time + std::chrono::duration<double, std::micro>(timeout_us);

  // Launch threads here using std::future and std::async...

  for(auto& worker : workers) {
    const auto current_timeout_us =
      std::chrono::duration<double, std::micro>(end_time - std::chrono::steady_clock::now());
    if (current_timeout_us.count() <= 0) {  // Is this needed?
      // Handle timeout...
    }
    const auto status = worker.wait_for(current_timeout_us);

    // Check status and do the appropriate actions...
  }
}

I'm still unsure whether I can pass in a negative duration to wait_for() so I manually check first. If anyone knows if wait_for() can accept a negative duration, please let me know. Also, if my understanding of the documentation for wait_until() is incorrect, please let me know as well.

2
Yes, it is possible your computer's system_clock is rolled back an hour. But that should not happen on a regular basis. Your system_clock tracks UTC, not local time. system_clock is normally adjusted a few times a day in amounts of small fractions of a second as your computer likely sets its system_clock using the NTP protocol. Even leap seconds are commonly "smeared" over a period of hours by adjusting the system_clock by small fractions of a second at a time. - Howard Hinnant
The waiting functions ending in _for should return immediately for negative durations. - Howard Hinnant

2 Answers

3
votes

Just use wait_until instead of wait_for. Compute the time_point you want to wait until just once, and keep using it. If that time_point starts falling into the past, wait_until will return immediately.

0
votes

Huge thanks to Howard for putting me on the right track. In my testing, wait_for() does indeed return immediately when passed in a negative duration.

Here is the code I ended up with:

void myFunctionWithTimeout(/*some other inputs*/ const long timeout_us) {
  const auto start_time = std::chrono::steady_clock::now();
  const auto end_time =
    start_time + std::chrono::duration<double, std::micro>(timeout_us);

  // Launch threads here using std::future and std::async...

  for(auto& worker : workers) {
    const auto current_timeout_us =
      std::chrono::duration<double, std::micro>(end_time - std::chrono::steady_clock::now());
    const auto status = worker.wait_for(current_timeout_us);
    // Check status and do the appropriate actions...
  }
}

Note that wait_until() is certainly a viable alternative, but I am just a bit too paranoid regarding system_clock changes and therefore am using a monotonic clock.