TimingWheel::Tick() use-after-free crash when Timer::Stop() called concurrently
Describe the bug When stopping a timer while TimingWheel::Tick() is executing, the application crashes with SIGSEGV due to use-after-free. The crash typically occurs in the coroutine scheduler thread (.CRT thread).
Stack trace: #0 0x... in std::function<void()>::operator()() #1 in TimingWheel::Tick() #2 in std::call_once
questions In timing_wheel.cc, the Tick() function captures a raw pointer to the callback function instead of capturing the shared_ptr:
// Bug in timing_wheel.cc:54-75 void TimingWheel::Tick() { auto task = ite->lock(); if (task) { auto* callback = reinterpret_cast<std::function<void()>*>(&(task->callback)); gaea::Async([this, callback] { // BUG: captures raw pointer! if (this->running_) { (*callback)(); // crashes if TimerTask already freed } }); } }
Race condition timeline:
- Thread A: Tick() gets callback* pointer
- Thread B: Timer::Stop() calls task_.reset(), freeing TimerTask
- Thread A: Async task executes (*callback)() on freed memory → CRASH
To Reproduce Long time start and stop application
- Create a timer with 20ms period
- In a separate thread, repeatedly call Stop() + reset() on the timer
- When Tick() and Stop() race, crash occurs
Expected behavior Not crash
Additional context This bug is similar to the classic use-after-free pattern in concurrent systems where a raw pointer is used after the underlying object has been freed. The fix ensures proper lifetime management through shared_ptr reference counting.
Source: ApolloAuto/apollo