std::jthread stop_token with timeout-aware interruptible wait
0 reputation · 30 Oct 2021, 15:33 UTC
0 reputation · 30 Oct 2021, 15:33 UTC
C++20 provides std::jthread with cooperative cancellation via std::stop_token and std::stop_source. The stop_token interface offers stop_requested and stop_possible queries and stop_callback for notification, but the standard does not define a timeout-aware stop_token. Timeouts must be layered externally around blocking waits.
The design goal is to combine cooperative cancellation with a bounded wait so a worker can exit promptly on request_stop or after a deadline, without relying on non-standard interruption of system calls. Cancellation remains cooperative, and stop_callback lifetime is tied to the stop_source. stop_token checks are not atomic with work, so a race between check and a long operation can delay cancellation.
Where should timeout logic be placed relative to stop_token checks in a loop that performs interruptible work? Is a polling interval with condition_variable_any::wait_for and a stop_requested predicate preferable to a stop_callback-driven wake-up for blocking waits? What ordering guarantees exist for stop_callback invocation relative to an ongoing wait?
33275 reputation · 30 Oct 2021, 15:53 UTC
Place the timeout logic inside the condition variable wait, not as a separate polling loop. Use std::condition_variable_any::wait_for with the stop token as the predicate so the wait wakes on timeout, stop request, or predicate satisfaction.
std::unique_lock lk(mtx);
while (!pred()) {
// wait_for returns false on timeout, true on stop or pred()
bool ok = cv.wait_for(lk, std::chrono::milliseconds(200), stoken, [&]{ return pred() || stoken.stop_requested(); });
if (!ok) { /* timeout handling */ }
// loop re‑checks pred() and stop state after each wake‑up
}
// work proceeds here
This approach avoids a separate polling interval and eliminates the need for a manual stop_callback just to interrupt the wait.
wait_for with predicate is preferablestop_callback associated with the token is invoked (see ordering below) and the condition variable is notified, causing wait_for to return true.stop_callbackstop_source::request_stop() is called, the library invokes all registered stop_callback objects before returning from request_stop.request_stop (not necessarily the worker thread).condition_variable_any::wait_for, the callback’s internal notification causes the wait to wake up. After the callback finishes, the lock is reacquired and wait_for returns true (indicating stop or predicate).wait_for returns false and no stop callback is invoked for that wake‑up.Confirm that stoken.stop_possible() is true; if it is false, the token cannot be interrupted and the wait behaves as a plain timed wait.
Use comments to ask for clarification. Post a solution as an answer.
33,275 reputation · 31 Oct 2021, 00:28 UTC
In a std::jthread that blocks with std::condition_variable_any::wait_for and a std::stop_token, the library guarantees that a stop_callback is invoked on the thread that calls request_stop() before request_stop() returns. The callback performs an internal notify_one() on the condition variable the worker is waiting on.
Because the callback runs concurrently with the worker’s wait_for, the wait may still be in progress when the notification arrives. The wait will therefore return after the callback has finished, but the stop_requested() flag is already set. This means that a worker that wakes solely because of a timeout will still need to re‑check stop_requested() before starting the next iteration.
To guarantee prompt cancellation, structure the loop like this:
while (!stoken.stop_requested()) {
std::unique_lock lk(mtx);
cv.wait_for(lk, timeout, stoken, [&]{ return pred() || stoken.stop_requested(); });
if (stoken.stop_requested()) break;
// process work
}
Using the wait_for overload that accepts a stop token ensures the thread wakes immediately when the stop is requested, eliminating the need for a separate polling loop.