diff --git a/.github/workflows/ci.cpu.yml b/.github/workflows/ci.cpu.yml index b4b17d312..10b9488d4 100644 --- a/.github/workflows/ci.cpu.yml +++ b/.github/workflows/ci.cpu.yml @@ -142,11 +142,46 @@ jobs: path: /tmp/sccache*.log compression-level: 9 + valgrind-run-loop: + runs-on: ubuntu-latest + name: CPU (gcc 14, Valgrind run_loop) + container: + options: -u root + image: rapidsai/devcontainers:26.08-cpp-gcc14-cuda12.9 + steps: + - name: Checkout stdexec + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install Valgrind + run: | + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends valgrind + + - name: Build run_loop reproducer + run: | + cmake -S . -B build -GNinja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_CXX_STANDARD=20 \ + -DCMAKE_CXX_EXTENSIONS=OFF \ + -DSTDEXEC_BUILD_TESTS=ON + cmake --build build --target test.run_loop_finish_repro -v + + - name: Run run_loop reproducer under Valgrind + run: | + timeout 60s valgrind \ + --fair-sched=no \ + --error-exitcode=1 \ + --quiet \ + ./build/test/test.run_loop_finish_repro + ci-cpu: runs-on: ubuntu-latest name: CI (CPU) needs: - build-cpu + - valgrind-run-loop steps: - run: echo "CI (CPU) success" diff --git a/include/stdexec/__detail/__run_loop.hpp b/include/stdexec/__detail/__run_loop.hpp index cf806ab10..d51e8bd41 100644 --- a/include/stdexec/__detail/__run_loop.hpp +++ b/include/stdexec/__detail/__run_loop.hpp @@ -69,8 +69,21 @@ namespace STDEXEC } // drain the queue, taking care to execute any tasks that get added while // executing the remaining tasks (also wait for other tasks that might still be in flight): - while (__execute_all() || __task_count_.load(__std::memory_order_acquire) > 0) - ; + while (true) + { + if (__execute_all()) + { + continue; + } + + if (__task_count_.load(__std::memory_order_acquire) == 0) + { + break; + } + + // Another thread still has work in flight. Let it make progress. + std::this_thread::yield(); + } } STDEXEC_ATTRIBUTE(host, device) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2725a8fba..78909f15f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -120,6 +120,15 @@ target_compile_options( $<$:-Wno-gnu-line-marker>) target_link_libraries(common_test_settings INTERFACE $) + +add_executable(test.run_loop_finish_repro EXCLUDE_FROM_ALL + run_loop_finish_repro.cpp) +add_compile_diagnostics(test.run_loop_finish_repro) +target_link_libraries( + test.run_loop_finish_repro + PUBLIC STDEXEC::stdexec stdexec_executable_flags + PRIVATE common_test_settings) + # target_compile_definitions( common_test_settings INTERFACE # $<$,$>>:STDEXEC_ENABLE_EXTRA_TYPE_CHECKING>) diff --git a/test/run_loop_finish_repro.cpp b/test/run_loop_finish_repro.cpp new file mode 100644 index 000000000..51bc7cf26 --- /dev/null +++ b/test/run_loop_finish_repro.cpp @@ -0,0 +1,31 @@ +#include + +#include + +namespace ex = STDEXEC_NAMESPACE; + +void run_once() +{ + ex::run_loop loop; + + std::thread worker([&loop] { loop.run(); }); + + // Ensure run() is actively servicing the loop before initiating + // shutdown from this thread. + if (!ex::sync_wait(ex::schedule(loop.get_scheduler()) | ex::then([]() {}))) + { + loop.finish(); + worker.join(); + } + + loop.finish(); + worker.join(); +} + +int main() +{ + for (int i = 0; i < 1000; ++i) + { + run_once(); + } +}