diff --git a/docs/TaskExecutor.md b/docs/TaskExecutor.md index f93392f..154c3bb 100644 --- a/docs/TaskExecutor.md +++ b/docs/TaskExecutor.md @@ -191,7 +191,13 @@ const auto lost = LOGIT_GET_DROPPED_TASKS(); `set_queue_policy()`. * The hot-resize barrier uses `m_resizing` and `m_resize_cv` so producers never touch a ring buffer that is being rebuilt. This eliminates the data races that - TSAN previously reported on `try_pop()` vs. buffer assignment. + TSAN previously reported on `try_pop()` vs. buffer assignment. The barrier + only drops once the worker thread fully stops and the queue drains; if a sink + blocks the worker or `QueuePolicy::Block` keeps `m_active_tasks` above the + limit for more than one second, `set_max_queue_size()` abandons the hot + resize, clears `m_resizing`, and leaves the existing ring untouched so + producers cannot wait indefinitely. Non-MPSC builds perform the resize as an + atomic update of `m_max_queue_size`, so they are not subject to this stall. * Non-MPSC builds rely solely on mutexes and had no known data races. * The Emscripten path is single-threaded and should not be used concurrently. diff --git a/include/logit_cpp/logit/detail/TaskExecutor.hpp b/include/logit_cpp/logit/detail/TaskExecutor.hpp index 3af0e6e..da813c0 100644 --- a/include/logit_cpp/logit/detail/TaskExecutor.hpp +++ b/include/logit_cpp/logit/detail/TaskExecutor.hpp @@ -12,12 +12,12 @@ #include #include #include -#else - #include - #include - #include - #include - #include + #else + #include + #include + #include + #include + #include #endif // Enable lock-free MPSC ring integration (non-Emscripten) by defining: @@ -323,8 +323,18 @@ namespace logit { namespace detail { // Tell producers to pause before any wait()/stop conditions run. m_resizing.store(true, std::memory_order_release); - // Drain the queue completely. - wait(); + // Drain the queue completely, but do not wait forever if the worker + // is stalled (e.g., blocked sink or backpressure keeping + // m_active_tasks > 0). If we fail to drain before the deadline, + // abort the resize and re-open the barrier so producers can + // continue. + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::seconds(1); + if (!wait_until_idle_(deadline)) { + m_resizing.store(false, std::memory_order_release); + m_resize_cv.notify_all(); + return; + } // Stop the worker so it cannot touch m_mpsc_queue during the resize. std::unique_lock lk(m_queue_mutex); @@ -474,6 +484,15 @@ namespace logit { namespace detail { bool queue_empty_() const noexcept { return m_mpsc_queue.empty(); } + + bool wait_until_idle_(std::chrono::steady_clock::time_point deadline) { + std::unique_lock lock(m_queue_mutex); + return m_queue_condition.wait_until(lock, deadline, [this]() { + return ((queue_empty_() && + m_active_tasks.load(std::memory_order_relaxed) == 0) || + m_stop_flag.load(std::memory_order_acquire)); + }); + } #endif TaskExecutor()