mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
7826729ab2
When we lock a mutex, eventually `Thread::block` is invoked which could in turn invoke `Process::big_lock().restore_exclusive_lock()`. This would then try to add the current thread to a different blocked thread list then the one in use for the original mutex being locked, and because it's an intrusive list, the thread is removed from its original list during the `.append()`. When the original mutex eventually unblocks, we no longer have the thread in the intrusive blocked threads list and we panic. Solve this by making the big lock mutex special and giving it its own blocked thread list. Because the process big lock is temporary and is being actively removed from e.g. syscalls, it's a matter of time before we can also remove the fix introduced by this commit. Fixes issue #9401.
181 lines
4.9 KiB
C++
181 lines
4.9 KiB
C++
/*
|
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
|
* Copyright (c) 2022, Idan Horowitz <idan.horowitz@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/Assertions.h>
|
|
#include <AK/Atomic.h>
|
|
#include <AK/HashMap.h>
|
|
#include <AK/Types.h>
|
|
#include <Kernel/Forward.h>
|
|
#include <Kernel/Locking/LockLocation.h>
|
|
#include <Kernel/Locking/LockMode.h>
|
|
#include <Kernel/WaitQueue.h>
|
|
|
|
namespace Kernel {
|
|
|
|
class Mutex {
|
|
friend class Thread;
|
|
|
|
AK_MAKE_NONCOPYABLE(Mutex);
|
|
AK_MAKE_NONMOVABLE(Mutex);
|
|
|
|
public:
|
|
using Mode = LockMode;
|
|
|
|
// FIXME: remove this after annihilating Process::m_big_lock
|
|
enum class MutexBehavior {
|
|
Regular,
|
|
BigLock,
|
|
};
|
|
|
|
Mutex(StringView name = {}, MutexBehavior behavior = MutexBehavior::Regular)
|
|
: m_name(name)
|
|
, m_behavior(behavior)
|
|
{
|
|
}
|
|
~Mutex() = default;
|
|
|
|
void lock(Mode mode = Mode::Exclusive, LockLocation const& location = LockLocation::current());
|
|
void restore_exclusive_lock(u32, LockLocation const& location = LockLocation::current());
|
|
|
|
void unlock();
|
|
[[nodiscard]] Mode force_unlock_exclusive_if_locked(u32&);
|
|
[[nodiscard]] bool is_locked() const
|
|
{
|
|
SpinlockLocker lock(m_lock);
|
|
return m_mode != Mode::Unlocked;
|
|
}
|
|
|
|
[[nodiscard]] bool is_exclusively_locked_by_current_thread() const
|
|
{
|
|
SpinlockLocker lock(m_lock);
|
|
VERIFY(m_mode != Mode::Shared); // This method should only be used on exclusively-held locks
|
|
if (m_mode == Mode::Unlocked)
|
|
return false;
|
|
return m_holder == Thread::current();
|
|
}
|
|
|
|
[[nodiscard]] StringView name() const { return m_name; }
|
|
|
|
static StringView mode_to_string(Mode mode)
|
|
{
|
|
switch (mode) {
|
|
case Mode::Unlocked:
|
|
return "unlocked"sv;
|
|
case Mode::Exclusive:
|
|
return "exclusive"sv;
|
|
case Mode::Shared:
|
|
return "shared"sv;
|
|
default:
|
|
return "invalid"sv;
|
|
}
|
|
}
|
|
|
|
private:
|
|
using BlockedThreadList = IntrusiveList<&Thread::m_blocked_threads_list_node>;
|
|
|
|
// FIXME: remove this after annihilating Process::m_big_lock
|
|
using BigLockBlockedThreadList = IntrusiveList<&Thread::m_big_lock_blocked_threads_list_node>;
|
|
|
|
void block(Thread&, Mode, SpinlockLocker<Spinlock>&, u32);
|
|
void unblock_waiters(Mode);
|
|
|
|
StringView m_name;
|
|
Mode m_mode { Mode::Unlocked };
|
|
|
|
// FIXME: remove this after annihilating Process::m_big_lock
|
|
MutexBehavior m_behavior;
|
|
|
|
// When locked exclusively, only the thread already holding the lock can
|
|
// lock it again. When locked in shared mode, any thread can do that.
|
|
u32 m_times_locked { 0 };
|
|
|
|
// One of the threads that hold this lock, or nullptr. When locked in shared
|
|
// mode, this is stored on best effort basis: nullptr value does *not* mean
|
|
// the lock is unlocked, it just means we don't know which threads hold it.
|
|
// When locked exclusively, this is always the one thread that holds the
|
|
// lock.
|
|
RefPtr<Thread> m_holder;
|
|
size_t m_shared_holders { 0 };
|
|
|
|
struct BlockedThreadLists {
|
|
BlockedThreadList exclusive;
|
|
BlockedThreadList shared;
|
|
|
|
// FIXME: remove this after annihilating Process::m_big_lock
|
|
BigLockBlockedThreadList exclusive_big_lock;
|
|
|
|
ALWAYS_INLINE BlockedThreadList& list_for_mode(Mode mode)
|
|
{
|
|
VERIFY(mode == Mode::Exclusive || mode == Mode::Shared);
|
|
return mode == Mode::Exclusive ? exclusive : shared;
|
|
}
|
|
};
|
|
SpinlockProtected<BlockedThreadLists> m_blocked_thread_lists;
|
|
|
|
mutable Spinlock m_lock;
|
|
|
|
#if LOCK_SHARED_UPGRADE_DEBUG
|
|
HashMap<Thread*, u32> m_shared_holders_map;
|
|
#endif
|
|
};
|
|
|
|
class MutexLocker {
|
|
AK_MAKE_NONCOPYABLE(MutexLocker);
|
|
|
|
public:
|
|
ALWAYS_INLINE explicit MutexLocker()
|
|
: m_lock(nullptr)
|
|
, m_locked(false)
|
|
{
|
|
}
|
|
|
|
ALWAYS_INLINE explicit MutexLocker(Mutex& l, Mutex::Mode mode = Mutex::Mode::Exclusive, LockLocation const& location = LockLocation::current())
|
|
: m_lock(&l)
|
|
{
|
|
m_lock->lock(mode, location);
|
|
}
|
|
|
|
ALWAYS_INLINE ~MutexLocker()
|
|
{
|
|
if (m_locked)
|
|
unlock();
|
|
}
|
|
|
|
ALWAYS_INLINE void unlock()
|
|
{
|
|
VERIFY(m_lock);
|
|
VERIFY(m_locked);
|
|
m_locked = false;
|
|
m_lock->unlock();
|
|
}
|
|
|
|
ALWAYS_INLINE void attach_and_lock(Mutex& lock, Mutex::Mode mode = Mutex::Mode::Exclusive, LockLocation const& location = LockLocation::current())
|
|
{
|
|
VERIFY(!m_locked);
|
|
m_lock = &lock;
|
|
m_locked = true;
|
|
|
|
m_lock->lock(mode, location);
|
|
}
|
|
|
|
ALWAYS_INLINE void lock(Mutex::Mode mode = Mutex::Mode::Exclusive, LockLocation const& location = LockLocation::current())
|
|
{
|
|
VERIFY(m_lock);
|
|
VERIFY(!m_locked);
|
|
m_locked = true;
|
|
|
|
m_lock->lock(mode, location);
|
|
}
|
|
|
|
private:
|
|
Mutex* m_lock;
|
|
bool m_locked { true };
|
|
};
|
|
|
|
}
|