Skip to content

TimingWheel::Tick() use-after-free crash when Timer::Stop() called concurrently #15810

Description

@1613302984-cell

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:

  1. Thread A: Tick() gets callback* pointer
  2. Thread B: Timer::Stop() calls task_.reset(), freeing TimerTask
  3. Thread A: Async task executes (*callback)() on freed memory → CRASH

To Reproduce
Long time start and stop application

  1. Create a timer with 20ms period
  2. In a separate thread, repeatedly call Stop() + reset() on the timer
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions