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.
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:
To Reproduce
Long time start and stop application
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.