Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

`timed_thread_context`: an operation stopped just before the context is destroyed never completes

クローズ
#2,334 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

関連するプルリクエストがすでにマージされています。

  • #2335 @workingBanana による — マージ済み

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
20/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
停滞
技術スタック
cpp
領域
backend

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

説明

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

主要言語
C++
スター
2.5k
フォーク
277
平均マージ
1日 16時間
マージ済み PR(30日)
65

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

NVIDIA/stdexec のほかの issue

NVIDIA/stdexec の issue をすべて見る

似ている issue

C++ の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。