1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-15 04:24:59 +00:00

Thread: Cleanup m_blocker handling

The only two places we set m_blocker now are Thread::set_state(), and
Thread::block(). set_state is mostly just an issue of clarity: we don't
want to end up with state() != Blocked with an m_blocker, because that's
weird. It's also possible: if we yield, someone else may set_state() us.

We also now set_state() and set m_blocker under lock in block(), rather
than unlocking which might allow someone else to mess with our internals
while we're in the process of trying to block.

This seems to fix sending STOP & CONT causing a panic.

My guess as to what was happening is this:

    thread A blocks in select(): Blocking & m_blocker != nullptr
    thread B sends SIGSTOP: Stopped & m_blocker != nullptr
    thread B sends SIGCONT: we continue execution. Runnable & m_blocker != nullptr
    thread A tries to block in select() again:
        * sets m_blocker
        * unlocks (in block_helper)
        * someone else tries to unblock us? maybe from the old m_blocker? unclear -- clears m_blocker
        * sets Blocked (while unlocked!)

So, thread A is left with state Blocked & m_blocker == nullptr, leading
to the scheduler assert (m_blocker != nullptr) failing.

Long story short, let's do all our data management with the lock _held_.
This commit is contained in:
Robin Burchell 2019-07-19 17:58:45 +02:00 committed by Andreas Kling
parent 046f00f77e
commit d48c73b10a
3 changed files with 21 additions and 10 deletions

View file

@ -97,7 +97,6 @@ Thread::~Thread()
void Thread::unblock()
{
m_blocker = nullptr;
if (current == this) {
set_state(Thread::Running);
return;
@ -108,9 +107,11 @@ void Thread::unblock()
void Thread::block_helper()
{
// This function mostly exists to avoid circular header dependencies. If
// anything needs adding, think carefully about whether it belongs in
// block() instead. Remember that we're unlocking here, so be very careful
// about altering any state once we're unlocked!
bool did_unlock = process().big_lock().unlock_if_locked();
ASSERT(state() == Thread::Running);
set_state(Thread::Blocked);
Scheduler::yield();
if (did_unlock)
process().big_lock().lock();
@ -160,8 +161,6 @@ void Thread::finalize()
dbgprintf("Finalizing Thread %u in %s(%u)\n", tid(), m_process.name().characters(), pid());
set_state(Thread::State::Dead);
m_blocker = nullptr;
if (this == &m_process.main_thread())
m_process.finalize();
}
@ -539,6 +538,11 @@ bool Thread::is_thread(void* ptr)
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);
}
m_state = new_state;
if (m_process.pid() != 0) {
SchedulerThreadList* list = nullptr;