mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 19:58:11 +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
5 changed files with 42 additions and 59 deletions
|
@ -19,7 +19,7 @@ void Lock::lock()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_lock.store(false, AK::memory_order_release);
|
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;
|
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)
|
Thread::FileDescriptionBlocker::FileDescriptionBlocker(const FileDescription& description)
|
||||||
: m_blocked_description(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::Running:
|
||||||
case Thread::Dead:
|
case Thread::Dead:
|
||||||
case Thread::Stopped:
|
case Thread::Stopped:
|
||||||
|
case Thread::Queued:
|
||||||
/* don't know, don't care */
|
/* don't know, don't care */
|
||||||
return;
|
return;
|
||||||
case Thread::Blocked:
|
case Thread::Blocked:
|
||||||
|
|
|
@ -180,12 +180,14 @@ void Thread::yield_without_holding_big_lock()
|
||||||
process().big_lock().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();
|
return process().big_lock().unlock_if_locked();
|
||||||
Scheduler::donate_to(beneficiary, reason);
|
}
|
||||||
if (did_unlock)
|
|
||||||
process().big_lock().lock();
|
void Thread::relock_process()
|
||||||
|
{
|
||||||
|
process().big_lock().lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 Thread::sleep(u32 ticks)
|
u64 Thread::sleep(u32 ticks)
|
||||||
|
@ -227,6 +229,8 @@ const char* Thread::state_string() const
|
||||||
return "Skip1";
|
return "Skip1";
|
||||||
case Thread::Skip0SchedulerPasses:
|
case Thread::Skip0SchedulerPasses:
|
||||||
return "Skip0";
|
return "Skip0";
|
||||||
|
case Thread::Queued:
|
||||||
|
return "Queued";
|
||||||
case Thread::Blocked:
|
case Thread::Blocked:
|
||||||
ASSERT(m_blocker != nullptr);
|
ASSERT(m_blocker != nullptr);
|
||||||
return m_blocker->state_string();
|
return m_blocker->state_string();
|
||||||
|
@ -645,6 +649,9 @@ bool Thread::is_thread(void* ptr)
|
||||||
void Thread::set_state(State new_state)
|
void Thread::set_state(State new_state)
|
||||||
{
|
{
|
||||||
InterruptDisabler disabler;
|
InterruptDisabler disabler;
|
||||||
|
if (new_state == m_state)
|
||||||
|
return;
|
||||||
|
|
||||||
if (new_state == Blocked) {
|
if (new_state == Blocked) {
|
||||||
// we should always have a Blocker while blocked
|
// we should always have a Blocker while blocked
|
||||||
ASSERT(m_blocker != nullptr);
|
ASSERT(m_blocker != nullptr);
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <Kernel/Scheduler.h>
|
#include <Kernel/Scheduler.h>
|
||||||
#include <Kernel/UnixTypes.h>
|
#include <Kernel/UnixTypes.h>
|
||||||
#include <Kernel/VM/Region.h>
|
#include <Kernel/VM/Region.h>
|
||||||
|
#include <Kernel/WaitQueue.h>
|
||||||
#include <LibC/fd_set.h>
|
#include <LibC/fd_set.h>
|
||||||
|
|
||||||
class Alarm;
|
class Alarm;
|
||||||
|
@ -84,6 +85,7 @@ public:
|
||||||
Dead,
|
Dead,
|
||||||
Stopped,
|
Stopped,
|
||||||
Blocked,
|
Blocked,
|
||||||
|
Queued,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Blocker {
|
class Blocker {
|
||||||
|
@ -111,16 +113,6 @@ public:
|
||||||
void*& m_joinee_exit_value;
|
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 {
|
class FileDescriptionBlocker : public Blocker {
|
||||||
public:
|
public:
|
||||||
const FileDescription& blocked_description() const;
|
const FileDescription& blocked_description() const;
|
||||||
|
@ -268,30 +260,18 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T, class... Args>
|
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.
|
// We should never be blocking a blocked (or otherwise non-active) thread.
|
||||||
ASSERT(state() == Thread::Running);
|
ASSERT(state() == Thread::Running);
|
||||||
ASSERT(m_blocker == nullptr);
|
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)...);
|
T t(forward<Args>(args)...);
|
||||||
m_blocker = &t;
|
m_blocker = &t;
|
||||||
set_state(Thread::Blocked);
|
set_state(Thread::Blocked);
|
||||||
restore_interrupt_flag(saved_if);
|
|
||||||
|
|
||||||
// Yield to the scheduler, and wait for us to resume unblocked.
|
// Yield to the scheduler, and wait for us to resume unblocked.
|
||||||
if (beneficiary) {
|
yield_without_holding_big_lock();
|
||||||
donate_and_yield_without_holding_big_lock(beneficiary, reason);
|
|
||||||
} else {
|
|
||||||
yield_without_holding_big_lock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should no longer be blocked once we woke up
|
// We should no longer be blocked once we woke up
|
||||||
ASSERT(state() != Thread::Blocked);
|
ASSERT(state() != Thread::Blocked);
|
||||||
|
@ -305,26 +285,31 @@ public:
|
||||||
return BlockResult::WokeNormally;
|
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)
|
[[nodiscard]] BlockResult block_until(const char* state_string, Function<bool()>&& condition)
|
||||||
{
|
{
|
||||||
return block<ConditionBlocker>(state_string, move(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();
|
void unblock();
|
||||||
|
@ -400,6 +385,9 @@ private:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class SchedulerData;
|
friend class SchedulerData;
|
||||||
|
bool unlock_process_if_locked();
|
||||||
|
void relock_process();
|
||||||
|
|
||||||
String backtrace_impl() const;
|
String backtrace_impl() const;
|
||||||
Process& m_process;
|
Process& m_process;
|
||||||
int m_tid { -1 };
|
int m_tid { -1 };
|
||||||
|
@ -436,7 +424,6 @@ private:
|
||||||
bool m_should_die { false };
|
bool m_should_die { false };
|
||||||
|
|
||||||
void yield_without_holding_big_lock();
|
void yield_without_holding_big_lock();
|
||||||
void donate_and_yield_without_holding_big_lock(Thread* beneficiary, const char* reason);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
HashTable<Thread*>& thread_table();
|
HashTable<Thread*>& thread_table();
|
||||||
|
|
|
@ -21,7 +21,7 @@ void WaitQueue::wake_one()
|
||||||
if (m_threads.is_empty())
|
if (m_threads.is_empty())
|
||||||
return;
|
return;
|
||||||
if (auto* thread = m_threads.take_first())
|
if (auto* thread = m_threads.take_first())
|
||||||
thread->unblock();
|
thread->wake_from_queue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaitQueue::wake_all()
|
void WaitQueue::wake_all()
|
||||||
|
@ -30,5 +30,5 @@ void WaitQueue::wake_all()
|
||||||
if (m_threads.is_empty())
|
if (m_threads.is_empty())
|
||||||
return;
|
return;
|
||||||
while (!m_threads.is_empty())
|
while (!m_threads.is_empty())
|
||||||
m_threads.take_first()->unblock();
|
m_threads.take_first()->wake_from_queue();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue