1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 06:58:11 +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

@ -61,7 +61,7 @@ RefPtr<Timer> TimerQueue::add_timer_without_id(const timespec& deadline, Functio
auto timer = adopt(*new Timer(time_to_ticks(deadline), move(callback)));
ScopedSpinLock lock(g_timerqueue_lock);
timer->id = 0; // Don't generate a timer id
timer->m_id = 0; // Don't generate a timer id
add_timer_locked(timer);
return timer;
}
@ -70,31 +70,38 @@ TimerId TimerQueue::add_timer(NonnullRefPtr<Timer>&& timer)
{
ScopedSpinLock lock(g_timerqueue_lock);
timer->id = ++m_timer_id_count;
ASSERT(timer->id != 0); // wrapped
timer->m_id = ++m_timer_id_count;
ASSERT(timer->m_id != 0); // wrapped
add_timer_locked(move(timer));
return m_timer_id_count;
}
void TimerQueue::add_timer_locked(NonnullRefPtr<Timer> timer)
{
u64 timer_expiration = timer->expires;
u64 timer_expiration = timer->m_expires;
ASSERT(timer_expiration >= time_to_ticks(TimeManagement::the().monotonic_time()));
ASSERT(!timer->is_queued());
if (m_timer_queue.is_empty()) {
m_timer_queue.append(move(timer));
m_timer_queue.append(&timer.leak_ref());
m_next_timer_due = timer_expiration;
} else {
auto following_timer = m_timer_queue.find([&timer_expiration](auto& other) { return other->expires > timer_expiration; });
if (following_timer.is_end()) {
m_timer_queue.append(move(timer));
} else {
auto next_timer_needs_update = following_timer.is_begin();
m_timer_queue.insert_before(following_timer, move(timer));
Timer* following_timer = nullptr;
m_timer_queue.for_each([&](Timer& t) {
if (t.m_expires > timer_expiration) {
following_timer = &t;
return IterationDecision::Break;
}
return IterationDecision::Continue;
});
if (following_timer) {
bool next_timer_needs_update = m_timer_queue.head() == following_timer;
m_timer_queue.insert_before(following_timer, &timer.leak_ref());
if (next_timer_needs_update)
m_next_timer_due = timer_expiration;
} else {
m_timer_queue.append(&timer.leak_ref());
}
}
}
@ -125,51 +132,106 @@ u64 TimerQueue::time_to_ticks(const timespec& tspec) const
bool TimerQueue::cancel_timer(TimerId id)
{
ScopedSpinLock lock(g_timerqueue_lock);
auto it = m_timer_queue.find([id](auto& timer) { return timer->id == id; });
if (it.is_end())
Timer* found_timer = nullptr;
if (m_timer_queue.for_each([&](Timer& timer) {
if (timer.m_id == id) {
found_timer = &timer;
return IterationDecision::Break;
}
return IterationDecision::Continue;
})
!= IterationDecision::Break) {
// The timer may be executing right now, if it is then it should
// be in m_timers_executing. If it is then release the lock
// briefly to allow it to finish by removing itself
// NOTE: This can only happen with multiple processors!
while (m_timers_executing.for_each([&](Timer& timer) {
if (timer.m_id == id)
return IterationDecision::Break;
return IterationDecision::Continue;
}) == IterationDecision::Break) {
// NOTE: This isn't the most efficient way to wait, but
// it should only happen when multiple processors are used.
// Also, the timers should execute pretty quickly, so it
// should not loop here for very long. But we can't yield.
lock.unlock();
Processor::wait_check();
lock.lock();
}
// We were not able to cancel the timer, but at this point
// the handler should have completed if it was running!
return false;
}
auto was_next_timer = it.is_begin();
m_timer_queue.remove(it);
ASSERT(found_timer);
bool was_next_timer = (m_timer_queue.head() == found_timer);
m_timer_queue.remove(found_timer);
found_timer->set_queued(false);
if (was_next_timer)
update_next_timer_due();
lock.unlock();
found_timer->unref();
return true;
}
bool TimerQueue::cancel_timer(const NonnullRefPtr<Timer>& timer)
bool TimerQueue::cancel_timer(Timer& timer)
{
ScopedSpinLock lock(g_timerqueue_lock);
auto it = m_timer_queue.find([timer](auto& t) { return t.ptr() == timer.ptr(); });
if (it.is_end())
if (!m_timer_queue.contains_slow(&timer)) {
// The timer may be executing right now, if it is then it should
// be in m_timers_executing. If it is then release the lock
// briefly to allow it to finish by removing itself
// NOTE: This can only happen with multiple processors!
while (m_timers_executing.contains_slow(&timer)) {
// NOTE: This isn't the most efficient way to wait, but
// it should only happen when multiple processors are used.
// Also, the timers should execute pretty quickly, so it
// should not loop here for very long. But we can't yield.
lock.unlock();
Processor::wait_check();
lock.lock();
}
// We were not able to cancel the timer, but at this point
// the handler should have completed if it was running!
return false;
}
auto was_next_timer = it.is_begin();
m_timer_queue.remove(it);
bool was_next_timer = (m_timer_queue.head() == &timer);
m_timer_queue.remove(&timer);
timer.set_queued(false);
if (was_next_timer)
update_next_timer_due();
return true;
}
void TimerQueue::fire()
{
ScopedSpinLock lock(g_timerqueue_lock);
if (m_timer_queue.is_empty())
auto* timer = m_timer_queue.head();
if (!timer)
return;
ASSERT(m_next_timer_due == m_timer_queue.first()->expires);
ASSERT(m_next_timer_due == timer->m_expires);
while (!m_timer_queue.is_empty() && TimeManagement::the().monotonic_ticks() > m_timer_queue.first()->expires) {
auto timer = m_timer_queue.take_first();
while (timer && TimeManagement::the().monotonic_ticks() > timer->m_expires) {
m_timer_queue.remove(timer);
m_timers_executing.append(timer);
update_next_timer_due();
lock.unlock();
timer->callback();
timer->m_callback();
lock.lock();
m_timers_executing.remove(timer);
timer->set_queued(false);
timer->unref();
timer = m_timer_queue.head();
}
}
@ -177,10 +239,10 @@ void TimerQueue::update_next_timer_due()
{
ASSERT(g_timerqueue_lock.is_locked());
if (m_timer_queue.is_empty())
m_next_timer_due = 0;
if (auto* next_timer = m_timer_queue.head())
m_next_timer_due = next_timer->m_expires;
else
m_next_timer_due = m_timer_queue.first()->expires;
m_next_timer_due = 0;
}
}