Add ownership checkings for FlagLock under debug (#432)

* add ownership checkings for FlagLock under debug

Signed-off-by: SchrodingerZhu <i@zhuyi.fan>

* fix owner reset ordering

Signed-off-by: SchrodingerZhu <i@zhuyi.fan>

* addresss CR

Signed-off-by: SchrodingerZhu <i@zhuyi.fan>

* fix include and constexpr problem

Signed-off-by: SchrodingerZhu <i@zhuyi.fan>

* use thread_local variable as thread identity

Signed-off-by: SchrodingerZhu <i@zhuyi.fan>

* change default form for initialization

Signed-off-by: SchrodingerZhu <i@zhuyi.fan>

* address CR

Signed-off-by: SchrodingerZhu <i@zhuyi.fan>

* fix typo and format

Signed-off-by: SchrodingerZhu <i@zhuyi.fan>

* add more assertions

Signed-off-by: SchrodingerZhu <i@zhuyi.fan>

* adjust flag lock and comments

Signed-off-by: SchrodingerZhu <i@zhuyi.fan>

* address CR

Signed-off-by: SchrodingerZhu <i@zhuyi.fan>
This commit is contained in:
Schrodinger ZHU Yifan
2021-12-02 17:18:24 +08:00
committed by GitHub
parent b1da339b3e
commit 9b548ed3a2
5 changed files with 124 additions and 8 deletions

View File

@@ -28,7 +28,7 @@ namespace snmalloc
* This is infrequently used code, a spin lock simplifies the code
* considerably, and should never be on the fast path.
*/
std::atomic_flag spin_lock = ATOMIC_FLAG_INIT;
FlagWord spin_lock{};
public:
/**

View File

@@ -46,7 +46,7 @@ namespace snmalloc
inline static std::atomic<bool> initialised{false};
SNMALLOC_REQUIRE_CONSTINIT
inline static std::atomic_flag initialisation_lock{};
inline static FlagWord initialisation_lock{};
public:
static ChunkAllocatorState&

View File

@@ -6,21 +6,137 @@
namespace snmalloc
{
/**
* @brief The DebugFlagWord struct
* Wrapper for std::atomic_flag so that we can examine
* the re-entrancy problem at debug mode.
*/
struct DebugFlagWord
{
/**
* @brief flag
* The underlying atomic field.
*/
std::atomic_flag flag = ATOMIC_FLAG_INIT;
constexpr DebugFlagWord() = default;
template<typename... Args>
constexpr DebugFlagWord(Args&&... args) : flag(std::forward<Args>(args)...)
{}
/**
* @brief set_owner
* Record the identity of the locker.
*/
void set_owner()
{
SNMALLOC_ASSERT(nullptr == owner);
owner = get_thread_identity();
}
/**
* @brief clear_owner
* Set the identity to null.
*/
void clear_owner()
{
SNMALLOC_ASSERT(get_thread_identity() == owner);
owner = nullptr;
}
/**
* @brief assert_not_owned_by_current_thread
* Assert the lock should not be held already by current thread.
*/
void assert_not_owned_by_current_thread()
{
SNMALLOC_ASSERT(get_thread_identity() != owner);
}
private:
using ThreadIdentity = int const*;
/**
* @brief owner
* We use a pointer to TLS field as the thread identity.
* std::thread::id can be another solution but it does not
* support `constexpr` initialisation on some platforms.
*/
ThreadIdentity owner = nullptr;
/**
* @brief get_thread_identity
* @return The identity of current thread.
*/
inline ThreadIdentity get_thread_identity()
{
static thread_local int SNMALLOC_THREAD_IDENTITY = 0;
return &SNMALLOC_THREAD_IDENTITY;
}
};
/**
* @brief The ReleaseFlagWord struct
* The shares the same structure with DebugFlagWord but
* all member functions associated with ownership checkings
* are empty so that they can be optimised out at Release mode.
*/
struct ReleaseFlagWord
{
std::atomic_flag flag = ATOMIC_FLAG_INIT;
constexpr ReleaseFlagWord() = default;
template<typename... Args>
constexpr ReleaseFlagWord(Args&&... args)
: flag(std::forward<Args>(args)...)
{}
void set_owner() {}
void clear_owner() {}
void assert_not_owned_by_current_thread() {}
};
#ifdef NDEBUG
using FlagWord = ReleaseFlagWord;
#else
using FlagWord = DebugFlagWord;
#endif
class FlagLock
{
private:
std::atomic_flag& lock;
FlagWord& lock;
public:
FlagLock(std::atomic_flag& lock) : lock(lock)
FlagLock(FlagWord& lock) : lock(lock)
{
while (lock.test_and_set(std::memory_order_acquire))
while (lock.flag.test_and_set(std::memory_order_acquire))
{
// assert_not_owned_by_current_thread is only called when the first
// acquiring is failed; which means the lock is already held somewhere
// else.
lock.assert_not_owned_by_current_thread();
#ifdef __cpp_lib_atomic_flag_test
// acquire ordering because we need other thread's release to be
// visible. This loop is better for spin-waiting because it won't issue
// expensive write operation (xchg for example).
while (lock.flag.test(std::memory_order_acquire))
{
Aal::pause();
}
#else
Aal::pause();
#endif
}
lock.set_owner();
}
~FlagLock()
{
lock.clear(std::memory_order_release);
lock.clear_owner();
lock.flag.clear(std::memory_order_release);
}
};
} // namespace snmalloc

View File

@@ -17,7 +17,7 @@ namespace snmalloc
template<class Object, void init(Object*) noexcept>
class Singleton
{
inline static std::atomic_flag flag;
inline static FlagWord flag;
inline static std::atomic<bool> initialised{false};
inline static Object obj;

View File

@@ -29,7 +29,7 @@ namespace snmalloc
private:
MPMCStack<T, PreZeroed> stack;
std::atomic_flag lock = ATOMIC_FLAG_INIT;
FlagWord lock{};
T* list{nullptr};
public: