From 5e55a80a866b6dfdd3b3d43d534ba5cac67fd8a4 Mon Sep 17 00:00:00 2001 From: Markus Werle Date: Fri, 23 Oct 2026 19:49:56 +0200 Subject: [PATCH] schedulers: keep the waited-for top alive so its address cannot be reused Co-Authored-By: Claude Opus 5.5 --- src/rpp/rpp/schedulers/new_thread.hpp | 6 ++++-- src/rpp/rpp/schedulers/run_loop.hpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/rpp/rpp/schedulers/new_thread.hpp b/src/rpp/rpp/schedulers/new_thread.hpp index 30253a4bd..3c8a5f5a4 100644 --- a/src/rpp/rpp/schedulers/new_thread.hpp +++ b/src/rpp/rpp/schedulers/new_thread.hpp @@ -89,8 +89,10 @@ namespace rpp::schedulers { // Sleep until the current top is due, and wake as soon as the top changes // (an earlier schedulable was queued), so the loop recomputes the wait. - const auto* const top = state->queue.top().get(); - state->cv.wait_until(lock, top->get_timepoint(), [&] { return state->queue.is_empty() || state->queue.top().get() != top || state->queue.top()->is_disposed() || worker_strategy::now() >= state->queue.top()->get_timepoint(); }); + // The copy keeps the waited-for top alive, so its address cannot be reused + // by a newly queued schedulable while we wait. + const auto top = state->queue.top(); + state->cv.wait_until(lock, top->get_timepoint(), [&] { return state->queue.is_empty() || state->queue.top().get() != top.get() || state->queue.top()->is_disposed() || worker_strategy::now() >= state->queue.top()->get_timepoint(); }); continue; } } diff --git a/src/rpp/rpp/schedulers/run_loop.hpp b/src/rpp/rpp/schedulers/run_loop.hpp index 602020ad5..8af67a20e 100644 --- a/src/rpp/rpp/schedulers/run_loop.hpp +++ b/src/rpp/rpp/schedulers/run_loop.hpp @@ -68,8 +68,10 @@ namespace rpp::schedulers // Sleep until the current top is due, and wake as soon as the top changes // (an earlier schedulable was queued), so the loop recomputes the wait. - const auto* const top = m_queue.top().get(); - m_cv.wait_until(lock, top->get_timepoint(), [&]() { return is_disposed() || m_queue.is_empty() || m_queue.top().get() != top || m_queue.top()->is_disposed() || m_queue.top()->get_timepoint() <= worker_strategy::now(); }); + // The copy keeps the waited-for top alive, so its address cannot be reused + // by a newly queued schedulable while we wait. + const auto top = m_queue.top(); + m_cv.wait_until(lock, top->get_timepoint(), [&]() { return is_disposed() || m_queue.is_empty() || m_queue.top().get() != top.get() || m_queue.top()->is_disposed() || m_queue.top()->get_timepoint() <= worker_strategy::now(); }); } return {}; }