mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-26 01:20:25 +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:
parent
c6230b746d
commit
601a688b6f
Notes:
sideshowbarker
2024-07-19 01:05:53 +09:00
Author: https://github.com/tomuta Commit: https://github.com/SerenityOS/serenity/commit/601a688b6f7 Pull-request: https://github.com/SerenityOS/serenity/pull/4298
3 changed files with 143 additions and 53 deletions
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,41 +27,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/InlineLinkedList.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/SinglyLinkedList.h>
|
||||
#include <Kernel/Time/TimeManagement.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
typedef u64 TimerId;
|
||||
|
||||
struct Timer : public RefCounted<Timer> {
|
||||
TimerId id;
|
||||
u64 expires;
|
||||
Function<void()> callback;
|
||||
class Timer : public RefCounted<Timer>
|
||||
, public InlineLinkedListNode<Timer> {
|
||||
friend class TimerQueue;
|
||||
friend class InlineLinkedListNode<Timer>;
|
||||
|
||||
public:
|
||||
Timer(u64 expires, Function<void()>&& callback)
|
||||
: m_expires(expires)
|
||||
, m_callback(move(callback))
|
||||
{
|
||||
}
|
||||
~Timer()
|
||||
{
|
||||
ASSERT(!is_queued());
|
||||
}
|
||||
|
||||
private:
|
||||
TimerId m_id;
|
||||
u64 m_expires;
|
||||
Function<void()> m_callback;
|
||||
Timer* m_next { nullptr };
|
||||
Timer* m_prev { nullptr };
|
||||
Atomic<bool> m_queued { false };
|
||||
|
||||
bool operator<(const Timer& rhs) const
|
||||
{
|
||||
return expires < rhs.expires;
|
||||
return m_expires < rhs.m_expires;
|
||||
}
|
||||
bool operator>(const Timer& rhs) const
|
||||
{
|
||||
return expires > rhs.expires;
|
||||
return m_expires > rhs.m_expires;
|
||||
}
|
||||
bool operator==(const Timer& rhs) const
|
||||
{
|
||||
return id == rhs.id;
|
||||
}
|
||||
|
||||
Timer(u64 expires, Function<void()>&& callback)
|
||||
: expires(expires)
|
||||
, callback(move(callback))
|
||||
{
|
||||
return m_id == rhs.m_id;
|
||||
}
|
||||
bool is_queued() const { return m_queued.load(AK::MemoryOrder::memory_order_relaxed); }
|
||||
void set_queued(bool queued) { m_queued.store(queued, AK::MemoryOrder::memory_order_relaxed); }
|
||||
};
|
||||
|
||||
class TimerQueue {
|
||||
friend class Timer;
|
||||
|
||||
public:
|
||||
TimerQueue();
|
||||
static TimerQueue& the();
|
||||
|
@ -70,10 +88,14 @@ public:
|
|||
RefPtr<Timer> add_timer_without_id(const timespec& timeout, Function<void()>&& callback);
|
||||
TimerId add_timer(timeval& timeout, Function<void()>&& callback);
|
||||
bool cancel_timer(TimerId id);
|
||||
bool cancel_timer(const NonnullRefPtr<Timer>&);
|
||||
bool cancel_timer(NonnullRefPtr<Timer>&& timer)
|
||||
{
|
||||
return cancel_timer(timer.leak_ref());
|
||||
}
|
||||
void fire();
|
||||
|
||||
private:
|
||||
bool cancel_timer(Timer&);
|
||||
void update_next_timer_due();
|
||||
void add_timer_locked(NonnullRefPtr<Timer>);
|
||||
|
||||
|
@ -83,7 +105,8 @@ private:
|
|||
u64 m_next_timer_due { 0 };
|
||||
u64 m_timer_id_count { 0 };
|
||||
u64 m_ticks_per_second { 0 };
|
||||
SinglyLinkedList<NonnullRefPtr<Timer>> m_timer_queue;
|
||||
InlineLinkedList<Timer> m_timer_queue;
|
||||
InlineLinkedList<Timer> m_timers_executing;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue