diff --git a/include/kpn/scheduler.hpp b/include/kpn/scheduler.hpp index 54bbb5b..d11d775 100644 --- a/include/kpn/scheduler.hpp +++ b/include/kpn/scheduler.hpp @@ -53,6 +53,19 @@ public: } void start() override { + // Under the lifecycle lock for the same reason stop() is: submit() + // reads queues_ and this rebuilds it. A network starts its nodes one at + // a time, and a node already started fires into the next one's channel, + // whose push callback submits — so a submission can genuinely land + // while another pool is still inside start(). ThreadSanitizer reports + // it as a read at submit() against this write, and the consequence is + // worse than a torn read: push_back can reallocate the vector under a + // reader that has already indexed it. + // + // Queues are all constructed before any worker is spawned, which is + // what keeps worker_loop's own queues_[id] out of this — it never takes + // the lock, so holding it across the spawn cannot deadlock. + std::unique_lock lk(lifecycle_mx_); stopped_.store(false, std::memory_order_relaxed); queues_.clear(); for (std::size_t i = 0; i < thread_count_; ++i)