1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 13:37:45 +00:00

Kernel: TimerQueue::cancel_timer needs to wait if timer is executing

We need to be able to guarantee that a timer won't be executing after
TimerQueue::cancel_timer returns. In the case of multiple processors
this means that we may need to wait while the timer handler finishes
execution on another core.

This also fixes a problem in Thread::block and Thread::wait_on where
theoretically the timer could execute after the function returned
and the Thread disappeared.
This commit is contained in:
Tom 2020-12-01 13:02:54 -07:00 committed by Andreas Kling
parent c6230b746d
commit 601a688b6f
3 changed files with 143 additions and 53 deletions

View file

@ -710,6 +710,7 @@ public:
template<typename T, class... Args>
[[nodiscard]] BlockResult block(const BlockTimeout& timeout, Args&&... args)
{
ScopedSpinLock scheduler_lock(g_scheduler_lock);
ScopedSpinLock lock(m_lock);
// We need to hold m_lock so that nobody can unblock a blocker as soon
// as it is constructed and registered elsewhere
@ -718,7 +719,6 @@ public:
bool did_timeout = false;
RefPtr<Timer> timer;
{
ScopedSpinLock scheduler_lock(g_scheduler_lock);
// We should never be blocking a blocked (or otherwise non-active) thread.
ASSERT(state() == Thread::Running);
ASSERT(m_blocker == nullptr);
@ -762,15 +762,16 @@ public:
}
lock.unlock();
scheduler_lock.unlock();
// Yield to the scheduler, and wait for us to resume unblocked.
yield_without_holding_big_lock();
scheduler_lock.lock();
lock.lock();
bool is_stopped = false;
{
ScopedSpinLock scheduler_lock(g_scheduler_lock);
if (t.was_interrupted_by_signal())
dispatch_one_pending_signal();
@ -787,17 +788,21 @@ public:
did_timeout = true;
}
// Notify the blocker that we are no longer blocking. It may need
// to clean up now while we're still holding m_lock
auto result = t.end_blocking({}, did_timeout); // calls was_unblocked internally
if (timer && !did_timeout) {
// Cancel the timer while not holding any locks. This allows
// the timer function to complete before we remove it
// (e.g. if it's on another processor)
lock.unlock();
scheduler_lock.unlock();
TimerQueue::the().cancel_timer(timer.release_nonnull());
} else {
scheduler_lock.unlock();
}
// Notify the blocker that we are no longer blocking. It may need
// to clean up now while we're still holding m_lock
auto result = t.end_blocking({}, did_timeout); // calls was_unblocked internally
if (is_stopped) {
// If we're stopped we need to yield
yield_without_holding_big_lock();