mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-03 21:10:30 +00:00
Kernel: Use a dedicated thread state for wait-queued threads
Instead of using the generic block mechanism, wait-queued threads now go into the special Queued state. This fixes an issue where signal dispatch would unblock a wait-queued thread (because signal dispatch unblocks blocked threads) and cause confusion since the thread only expected to be awoken by the queue.
This commit is contained in:
parent
7126a42d4d
commit
5859e16e53
Notes:
sideshowbarker
2024-07-19 11:00:31 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/5859e16e53e
5 changed files with 42 additions and 59 deletions
|
@ -19,7 +19,7 @@ void Lock::lock()
|
|||
return;
|
||||
}
|
||||
m_lock.store(false, AK::memory_order_release);
|
||||
(void)current->donate_remaining_timeslice_and_block<Thread::WaitQueueBlocker>(m_holder, m_name, m_queue);
|
||||
current->wait_on(m_queue, m_holder, m_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,18 +82,6 @@ bool Thread::JoinBlocker::should_unblock(Thread& joiner, time_t, long)
|
|||
return !joiner.m_joinee;
|
||||
}
|
||||
|
||||
Thread::WaitQueueBlocker::WaitQueueBlocker(WaitQueue& queue)
|
||||
: m_queue(queue)
|
||||
{
|
||||
m_queue.enqueue(*current);
|
||||
}
|
||||
|
||||
bool Thread::WaitQueueBlocker::should_unblock(Thread&, time_t, long)
|
||||
{
|
||||
// Someone else will have to unblock us by calling wake_one() or wake_all() on the queue.
|
||||
return false;
|
||||
}
|
||||
|
||||
Thread::FileDescriptionBlocker::FileDescriptionBlocker(const FileDescription& description)
|
||||
: m_blocked_description(description)
|
||||
{
|
||||
|
@ -268,6 +256,7 @@ void Thread::consider_unblock(time_t now_sec, long now_usec)
|
|||
case Thread::Running:
|
||||
case Thread::Dead:
|
||||
case Thread::Stopped:
|
||||
case Thread::Queued:
|
||||
/* don't know, don't care */
|
||||
return;
|
||||
case Thread::Blocked:
|
||||
|
|
|
@ -180,12 +180,14 @@ void Thread::yield_without_holding_big_lock()
|
|||
process().big_lock().lock();
|
||||
}
|
||||
|
||||
void Thread::donate_and_yield_without_holding_big_lock(Thread* beneficiary, const char* reason)
|
||||
bool Thread::unlock_process_if_locked()
|
||||
{
|
||||
bool did_unlock = process().big_lock().unlock_if_locked();
|
||||
Scheduler::donate_to(beneficiary, reason);
|
||||
if (did_unlock)
|
||||
process().big_lock().lock();
|
||||
return process().big_lock().unlock_if_locked();
|
||||
}
|
||||
|
||||
void Thread::relock_process()
|
||||
{
|
||||
process().big_lock().lock();
|
||||
}
|
||||
|
||||
u64 Thread::sleep(u32 ticks)
|
||||
|
@ -227,6 +229,8 @@ const char* Thread::state_string() const
|
|||
return "Skip1";
|
||||
case Thread::Skip0SchedulerPasses:
|
||||
return "Skip0";
|
||||
case Thread::Queued:
|
||||
return "Queued";
|
||||
case Thread::Blocked:
|
||||
ASSERT(m_blocker != nullptr);
|
||||
return m_blocker->state_string();
|
||||
|
@ -645,6 +649,9 @@ bool Thread::is_thread(void* ptr)
|
|||
void Thread::set_state(State new_state)
|
||||
{
|
||||
InterruptDisabler disabler;
|
||||
if (new_state == m_state)
|
||||
return;
|
||||
|
||||
if (new_state == Blocked) {
|
||||
// we should always have a Blocker while blocked
|
||||
ASSERT(m_blocker != nullptr);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <Kernel/Scheduler.h>
|
||||
#include <Kernel/UnixTypes.h>
|
||||
#include <Kernel/VM/Region.h>
|
||||
#include <Kernel/WaitQueue.h>
|
||||
#include <LibC/fd_set.h>
|
||||
|
||||
class Alarm;
|
||||
|
@ -84,6 +85,7 @@ public:
|
|||
Dead,
|
||||
Stopped,
|
||||
Blocked,
|
||||
Queued,
|
||||
};
|
||||
|
||||
class Blocker {
|
||||
|
@ -111,16 +113,6 @@ public:
|
|||
void*& m_joinee_exit_value;
|
||||
};
|
||||
|
||||
class WaitQueueBlocker final : public Blocker {
|
||||
public:
|
||||
explicit WaitQueueBlocker(WaitQueue&);
|
||||
virtual bool should_unblock(Thread&, time_t now_s, long us) override;
|
||||
virtual const char* state_string() const override { return "WaitQueued"; }
|
||||
|
||||
private:
|
||||
WaitQueue& m_queue;
|
||||
};
|
||||
|
||||
class FileDescriptionBlocker : public Blocker {
|
||||
public:
|
||||
const FileDescription& blocked_description() const;
|
||||
|
@ -268,30 +260,18 @@ public:
|
|||
};
|
||||
|
||||
template<typename T, class... Args>
|
||||
[[nodiscard]] BlockResult block_impl(Thread* beneficiary, const char* reason, Args&&... args)
|
||||
[[nodiscard]] BlockResult block(Args&&... args)
|
||||
{
|
||||
// We should never be blocking a blocked (or otherwise non-active) thread.
|
||||
ASSERT(state() == Thread::Running);
|
||||
ASSERT(m_blocker == nullptr);
|
||||
|
||||
// NOTE: We disable interrupts here to avoid the situation where a WaitQueueBlocker
|
||||
// adds the current thread to a WaitQueue, and then someone wakes up before
|
||||
// we set the state to Blocked decides to wake the queue. They would find
|
||||
// unblocked threads in a wait queue, which would not be good. We can't go
|
||||
// into Blocked state earlier, since that would prevent this thread from
|
||||
// getting scheduled.
|
||||
auto saved_if = cli_and_save_interrupt_flag();
|
||||
T t(forward<Args>(args)...);
|
||||
m_blocker = &t;
|
||||
set_state(Thread::Blocked);
|
||||
restore_interrupt_flag(saved_if);
|
||||
|
||||
// Yield to the scheduler, and wait for us to resume unblocked.
|
||||
if (beneficiary) {
|
||||
donate_and_yield_without_holding_big_lock(beneficiary, reason);
|
||||
} else {
|
||||
yield_without_holding_big_lock();
|
||||
}
|
||||
yield_without_holding_big_lock();
|
||||
|
||||
// We should no longer be blocked once we woke up
|
||||
ASSERT(state() != Thread::Blocked);
|
||||
|
@ -305,26 +285,31 @@ public:
|
|||
return BlockResult::WokeNormally;
|
||||
}
|
||||
|
||||
template<typename T, class... Args>
|
||||
[[nodiscard]] BlockResult block(Args&&... args)
|
||||
{
|
||||
return block_impl<T>(nullptr, nullptr, forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename T, class... Args>
|
||||
[[nodiscard]] BlockResult donate_remaining_timeslice_and_block(Thread* beneficiary, const char* reason, Args&&... args)
|
||||
{
|
||||
return block_impl<T>(beneficiary, reason, forward<Args>(args)...);
|
||||
}
|
||||
|
||||
[[nodiscard]] BlockResult block_until(const char* state_string, Function<bool()>&& condition)
|
||||
{
|
||||
return block<ConditionBlocker>(state_string, move(condition));
|
||||
}
|
||||
|
||||
void wait_on(WaitQueue& queue)
|
||||
void wait_on(WaitQueue& queue, Thread* beneficiary = nullptr, const char* reason = nullptr)
|
||||
{
|
||||
(void)block<WaitQueueBlocker>(queue);
|
||||
bool did_unlock = unlock_process_if_locked();
|
||||
cli();
|
||||
set_state(State::Queued);
|
||||
queue.enqueue(*current);
|
||||
// Yield and wait for the queue to wake us up again.
|
||||
if (beneficiary)
|
||||
Scheduler::donate_to(beneficiary, reason);
|
||||
else
|
||||
Scheduler::yield();
|
||||
// We've unblocked, relock the process if needed and carry on.
|
||||
if (did_unlock)
|
||||
relock_process();
|
||||
}
|
||||
|
||||
void wake_from_queue()
|
||||
{
|
||||
ASSERT(state() == State::Queued);
|
||||
set_state(State::Runnable);
|
||||
}
|
||||
|
||||
void unblock();
|
||||
|
@ -400,6 +385,9 @@ private:
|
|||
|
||||
private:
|
||||
friend class SchedulerData;
|
||||
bool unlock_process_if_locked();
|
||||
void relock_process();
|
||||
|
||||
String backtrace_impl() const;
|
||||
Process& m_process;
|
||||
int m_tid { -1 };
|
||||
|
@ -436,7 +424,6 @@ private:
|
|||
bool m_should_die { false };
|
||||
|
||||
void yield_without_holding_big_lock();
|
||||
void donate_and_yield_without_holding_big_lock(Thread* beneficiary, const char* reason);
|
||||
};
|
||||
|
||||
HashTable<Thread*>& thread_table();
|
||||
|
|
|
@ -21,7 +21,7 @@ void WaitQueue::wake_one()
|
|||
if (m_threads.is_empty())
|
||||
return;
|
||||
if (auto* thread = m_threads.take_first())
|
||||
thread->unblock();
|
||||
thread->wake_from_queue();
|
||||
}
|
||||
|
||||
void WaitQueue::wake_all()
|
||||
|
@ -30,5 +30,5 @@ void WaitQueue::wake_all()
|
|||
if (m_threads.is_empty())
|
||||
return;
|
||||
while (!m_threads.is_empty())
|
||||
m_threads.take_first()->unblock();
|
||||
m_threads.take_first()->wake_from_queue();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue