diff --git a/Kernel/Scheduler.cpp b/Kernel/Scheduler.cpp index 804e597b0c..e361ec6eff 100644 --- a/Kernel/Scheduler.cpp +++ b/Kernel/Scheduler.cpp @@ -221,8 +221,8 @@ void Thread::consider_unblock(time_t now_sec, long now_usec) /* don't know, don't care */ return; case Thread::Blocked: - ASSERT(m_blocker); - if (m_blocker->should_unblock(*this, now_sec, now_usec)) + ASSERT(!m_blockers.is_empty()); + if (m_blockers.first()->should_unblock(*this, now_sec, now_usec)) unblock(); return; case Thread::Skip1SchedulerPass: @@ -307,8 +307,8 @@ bool Scheduler::pick_next() return IterationDecision::Continue; if (was_blocked) { dbgprintf("Unblock %s(%u) due to signal\n", thread.process().name().characters(), thread.pid()); - ASSERT(thread.m_blocker); - thread.m_blocker->set_interrupted_by_signal(); + ASSERT(!thread.m_blockers.is_empty()); + thread.m_blockers.first()->set_interrupted_by_signal(); thread.unblock(); } return IterationDecision::Continue; diff --git a/Kernel/Thread.cpp b/Kernel/Thread.cpp index 48f70f2797..d271172704 100644 --- a/Kernel/Thread.cpp +++ b/Kernel/Thread.cpp @@ -148,8 +148,8 @@ const char* Thread::state_string() const case Thread::Skip0SchedulerPasses: return "Skip0"; case Thread::Blocked: - ASSERT(m_blocker); - return m_blocker->state_string(); + ASSERT(!m_blockers.is_empty()); + return m_blockers.first()->state_string(); } kprintf("to_string(Thread::State): Invalid state: %u\n", state()); ASSERT_NOT_REACHED(); @@ -539,8 +539,8 @@ void Thread::set_state(State new_state) { InterruptDisabler disabler; if (new_state == Blocked) { - // we should always have an m_blocker while blocked - ASSERT(m_blocker != nullptr); + // we should always have a Blocker while blocked + ASSERT(!m_blockers.is_empty()); } m_state = new_state; diff --git a/Kernel/Thread.h b/Kernel/Thread.h index 4af873cd87..0e531812bd 100644 --- a/Kernel/Thread.h +++ b/Kernel/Thread.h @@ -72,6 +72,8 @@ public: bool was_interrupted_by_signal() const { return m_was_interrupted_while_blocked; } private: bool m_was_interrupted_while_blocked { false }; + friend class Thread; + IntrusiveListNode m_blocker_list_node; }; class FileDescriptionBlocker : public Blocker { @@ -217,21 +219,39 @@ public: template [[nodiscard]] BlockResult block(Args&& ... args) { - // If this is triggered, state has gotten messed: we should never have a - // blocker already set. That means we're re-entering somehow, which is - // bad. - ASSERT(m_blocker == nullptr); - // We should never be blocking a blocked (or otherwise non-active) thread. ASSERT(state() == Thread::Running); + T t(AK::forward(args)...); - m_blocker = &t; + m_blockers.prepend(t); + + // Enter blocked state. set_state(Thread::Blocked); - block_helper(); // this will unlock, yield, and eventually unblock us to return here. + + // Yield to the scheduler, and wait for us to resume unblocked. + block_helper(); + + // We should no longer be blocked once we woke up ASSERT(state() != Thread::Blocked); - m_blocker = nullptr; + + // We should be the first blocker, otherwise we're waking up in a + // different order than we are waiting: scheduler bug? + ASSERT(m_blockers.first() == &t); + + // Remove ourselves... + m_blockers.remove(t); + + // If there are still pending blockers that need to be waited for, then + // set state back to Blocked, for the next one to be handled. + // Otherwise, we're good now, and done with blocking state. + if (!m_blockers.is_empty()) { + if (!m_blockers.first()->was_interrupted_by_signal()) + set_state(Thread::Blocked); + } + if (t.was_interrupted_by_signal()) return BlockResult::InterruptedBySignal; + return BlockResult::WokeNormally; }; @@ -321,7 +341,7 @@ private: RefPtr m_kernel_stack_for_signal_handler_region; SignalActionData m_signal_action_data[32]; Region* m_signal_stack_user_region { nullptr }; - Blocker* m_blocker { nullptr }; + IntrusiveList m_blockers; FPUState* m_fpu_state { nullptr }; State m_state { Invalid }; bool m_has_used_fpu { false };