`timed_thread_context`: an operation stopped just before the context is destroyed never completes
Les mainteneurs répondent en général sous 1 jour
Une pull request liée a déjà été fusionnée.
- #2335 par @workingBanana — fusionnée
Évaluation
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Accessibilité débutants
- 20/100
Piste de recherche
The bug is in timed_thread_context::run() in the timed thread scheduler header that the reproducer includes (exec/timed_thread_scheduler.hpp). Read the loop that drains command_queue_ and the stop branch that closes submissions and calls set_stopped_() on the heap. Build the reproducer, which should report 1000 of 1000 lost completions. Done means every operation completes exactly once after a stop request races with destruction. Linked PR #2335 already proposes a fix, so check it before starting.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Summary
When timed_thread_context::run() sees stop_requested_, it closes submissions, stops the operations in the heap and exits, without draining command_queue_ again:
while (true) {
while (command_type* op = command_queue_.pop_front()) { ... } // the only drain
...
cv_.wait_until(lock, deadline, [this] { return ready_ || stop_requested_; });
if (stop_requested) {
// close submissions (CAS n_submissions_in_flight_ to context_closed)
// set_stopped_() on every operation in heap_
break; // commands pushed since the drain above are never processed
}
}
A stop request from another thread raises the operation's ref_count_ from 1 to 2 and queues a stop command. If the context is destroyed before its thread drains that command, the heap pass only drops the count to 1. The stop command, which would drop it to 0 and call set_stopped, is never processed. So the receiver gets neither set_value nor set_stopped, and anything waiting on it (sync_wait, async_scope::on_empty(), ...) hangs.
A schedule command queued after the last drain is lost the same way: it is never inserted into the heap, so the heap pass doesn't stop it.
This breaks the context's own shutdown guarantee: the destructor stops the operations in the heap, and submissions made after close complete inline with set_stopped.
It seems that the current behavior violates https://eel.is/c++draft/exec.async.ops#8: “After its start operation has begun executing, exactly one completion operation shall execute.”
Reproducer
#include <exec/timed_thread_scheduler.hpp>
#include <stdexec/execution.hpp>
#include <atomic>
#include <chrono>
#include <cstdio>
#include <optional>
#include <thread>
namespace ex = STDEXEC;
struct env {
ex::inplace_stop_token token;
auto query(ex::get_stop_token_t) const noexcept { return token; }
};
struct receiver {
using receiver_concept = ex::receiver_tag;
std::atomic<int>* completed;
ex::inplace_stop_token token;
void set_value() noexcept { completed->store(1); }
void set_stopped() noexcept { completed->store(2); }
env get_env() const noexcept { return {token}; }
};
int main() {
int lost = 0;
const int runs = 1000;
for (int i = 0; i < runs; ++i) {
std::atomic<int> completed{0};
ex::inplace_stop_source source;
std::optional<exec::timed_thread_context> ctx{std::in_place};
auto op = ex::connect(exec::schedule_after(ctx->get_scheduler(), std::chrono::hours(1)),
receiver{&completed, source.get_token()});
ex::start(op);
std::this_thread::sleep_for(std::chrono::microseconds(200)); // the operation reaches the heap
source.request_stop(); // queues a stop command
ctx.reset(); // request_stop() + join, usually before the thread drains the command
if (completed.load() == 0) { // the thread has exited, so nothing will ever complete it
++lost;
}
}
std::printf("receiver never completed in %d of %d runs\n", lost, runs);
return lost == 0 ? 0 : 1;
}
The output will be: receiver never completed in 1000 of 1000 runs. It should be 0 of 1000.
Suggested fix: https://github.com/NVIDIA/stdexec/pull/2335
- Langage dominant
- C++
- Étoiles
- 2.5k
- Forks
- 277
- Merge moyen
- 1 j 16 h
- PR mergées (30 j)
- 65
Préparer son environnement
Ce projet ne fournit ni conteneur de développement, ni Dockerfile, ni guide de contribution : l'installation est à votre charge. Commencez par son README, et consultez notre guide de la première contribution pour les étapes générales.
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de NVIDIA/stdexec
-
Difficulté 2/5 1-3 heures Accessibilité débutants 68/100
NVIDIA/stdexec#1751 · 3 commentaires ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 4/5 3-5 jours Accessibilité débutants 15/100
NVIDIA/stdexec#2337 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 3/5 1-2 jours Accessibilité débutants 54/100
Les mainteneurs répondent en général sous 1 jour
-
continues_on: hop opstate not connected with secondary environmentPeut-être pris @ericniebler l’a pris il y a 26 jours. Ouverte
Difficulté 4/5 3-5 jours Accessibilité débutants 45/100
NVIDIA/stdexec#2268 · 4 commentaires ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 4/5 3-5 jours Accessibilité débutants 48/100
NVIDIA/stdexec#2240 · 5 commentaires ·
Les mainteneurs répondent en général sous 1 jour
Toutes les issues de NVIDIA/stdexec
Issues similaires
-
os:Linux
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 76/100
-
acceptable_buffer_backends: unknown backend names are silently ignoredPeut-être pris @mini-1235 l’a pris aujourd’hui. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 70/100
ros2/rmw_fastrtps#945 ·
Les mainteneurs répondent en général sous 1 jour
-
bot-triaged module: correctness (silent) module: mps module: NaNs and Infs triage review
Difficulté 2/5 Une demi-journée Accessibilité débutants 62/100
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 62/100
Les mainteneurs répondent en général sous 1 jour