Skip to content

Commit

Permalink
Implement new "Global Mutex" functionality.
Browse files Browse the repository at this point in the history
This CL improves performance compared to the existing code as well as
allows using non-std mutex implementations. Also acts as a base for
future changes.

CL adds new build option:
    angle_enable_global_mutex_recursion = is_android && angle_enable_vulkan

"mutex_recursion" work same way as `std::recursive_mutex` before. It
will help in situations when Vulkan API may return back to the ANGLE.
For example: RenderDoc layer EGL deadlock.
Automatic loading of "libVkLayer_GLES_RenderDoc.so" layer causes
deadlock in EGL.
Recursion stack:
    #1 pc 000000000029ea80  /vendor/lib64/egl/libGLESv2_angle.so (egl::GlobalMutexHelper::lock(int)+596)
    #2 pc 000000000029c59c  /vendor/lib64/egl/libGLESv2_angle.so (EGL_GetError+32)

    #4 pc 0000000000062368  /system/lib64/libEGL.so (eglQueryString+20)
    #5 pc 0000000000508fec  /data/local/debug/vulkan/libVkLayer_GLES_RenderDoc.so

    google#20 pc 0000000000016690  /system/lib64/libvulkan.so (vulkan::api::EnumerateInstanceLayerProperties(unsigned int*, VkLayerProperties*)+40)
    google#21 pc 00000000005aa030  /vendor/lib64/egl/libGLESv2_angle.so (rx::RendererVk::initialize(rx::DisplayVk*, egl::Display*, char const*, char const*)+292)
    google#26 pc 000000000029c7e8  /vendor/lib64/egl/libGLESv2_angle.so (EGL_Initialize+192)

Additionally, recursive mutex will partially solve Android
SurfaceTexture deadlock (angleproject:4354).

Some performance numbers for 1000'000 `eglGetError()` calls.

    Mutex                                       Time (ms.)

        Android S906B

    egl::GetGlobalMutex()(std::recursive_mutex)    41.4

    (Default)   GlobalMutex (std::recursive_mutex) 39.1

    (Recursive) GlobalMutex (std::mutex)           34.9
    (Debug)     GlobalMutex (std::mutex)           34.7
    (Default)   GlobalMutex (std::mutex)           34.4

        Windows

    egl::GetGlobalMutex()(std::recursive_mutex)    20.5

    (Default)   GlobalMutex (std::recursive_mutex) 20.0

    (Recursive) GlobalMutex (std::mutex)           21.9
    (Debug)     GlobalMutex (std::mutex)           20.5
    (Default)   GlobalMutex (std::mutex)           19.9

Note: Recursive GlobalMutex enabled only for Android Vulkan by default.
Original fix:
    https://chromium-review.googlesource.com/c/angle/angle/+/2029218

Bug: angleproject:8101
Bug: angleproject:4354
Test: angle_unittests --gtest_filter=GlobalMutexTest.*
Change-Id: I9e9d9b5c598ad1177ffa147ea690bd955946a712
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4401940
Commit-Queue: Igor Nazarov <[email protected]>
Reviewed-by: Charlie Lao <[email protected]>
Reviewed-by: Shahbaz Youssefi <[email protected]>
  • Loading branch information
i-nazarov-samsung authored and Angle LUCI CQ committed Apr 24, 2023
1 parent 22b5159 commit b22124b
Show file tree
Hide file tree
Showing 8 changed files with 361 additions and 64 deletions.
6 changes: 6 additions & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ declare_args() {
# Use Android TLS slot to store current context.
angle_use_android_tls_slot = !build_with_chromium
}

angle_enable_global_mutex_recursion = is_android && angle_enable_vulkan
}

if (angle_build_all) {
Expand Down Expand Up @@ -161,6 +163,10 @@ config("internal_config") {
}
}

if (angle_enable_global_mutex_recursion) {
defines += [ "ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION=1" ]
}

# Enables debug/trace-related functionality, including logging every GLES/EGL API command to the
# "angle_debug.txt" file on desktop. Enables debug markers for AGI, but must also set
# angle_enable_annotator_run_time_checks to improve performance.
Expand Down
193 changes: 193 additions & 0 deletions src/libANGLE/GlobalMutex.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
//
// Copyright 2023 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// GlobalMutex.cpp: Defines Global Mutex and utilities.

#include "libANGLE/GlobalMutex.h"

#include <atomic>

#include "common/debug.h"
#include "common/system_utils.h"

namespace egl
{
namespace priv
{
using GlobalMutexType = std::mutex;

#if !defined(ANGLE_ENABLE_ASSERTS) && !defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION)
// Default version.
class GlobalMutex final : angle::NonCopyable
{
public:
ANGLE_INLINE void lock() { mMutex.lock(); }
ANGLE_INLINE void unlock() { mMutex.unlock(); }

protected:
GlobalMutexType mMutex;
};
#endif

#if defined(ANGLE_ENABLE_ASSERTS) && !defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION)
// Debug version.
class GlobalMutex final : angle::NonCopyable
{
public:
ANGLE_INLINE void lock()
{
const angle::ThreadId threadId = angle::GetCurrentThreadId();
ASSERT(getOwnerThreadId() != threadId);
mMutex.lock();
ASSERT(getOwnerThreadId() == angle::InvalidThreadId());
mOwnerThreadId.store(threadId, std::memory_order_relaxed);
}

ANGLE_INLINE void unlock()
{
ASSERT(getOwnerThreadId() == angle::GetCurrentThreadId());
mOwnerThreadId.store(angle::InvalidThreadId(), std::memory_order_relaxed);
mMutex.unlock();
}

private:
ANGLE_INLINE angle::ThreadId getOwnerThreadId() const
{
return mOwnerThreadId.load(std::memory_order_relaxed);
}

GlobalMutexType mMutex;
std::atomic<angle::ThreadId> mOwnerThreadId{angle::InvalidThreadId()};
};
#endif // defined(ANGLE_ENABLE_ASSERTS) && !defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION)

#if defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION)
// Recursive version.
class GlobalMutex final : angle::NonCopyable
{
public:
ANGLE_INLINE void lock()
{
const angle::ThreadId threadId = angle::GetCurrentThreadId();
if (ANGLE_UNLIKELY(!mMutex.try_lock()))
{
if (ANGLE_UNLIKELY(getOwnerThreadId() == threadId))
{
ASSERT(mLockLevel > 0);
++mLockLevel;
return;
}
mMutex.lock();
}
ASSERT(getOwnerThreadId() == angle::InvalidThreadId());
ASSERT(mLockLevel == 0);
mOwnerThreadId.store(threadId, std::memory_order_relaxed);
mLockLevel = 1;
}

ANGLE_INLINE void unlock()
{
ASSERT(getOwnerThreadId() == angle::GetCurrentThreadId());
ASSERT(mLockLevel > 0);
if (ANGLE_LIKELY(--mLockLevel == 0))
{
mOwnerThreadId.store(angle::InvalidThreadId(), std::memory_order_relaxed);
mMutex.unlock();
}
}

private:
ANGLE_INLINE angle::ThreadId getOwnerThreadId() const
{
return mOwnerThreadId.load(std::memory_order_relaxed);
}

GlobalMutexType mMutex;
std::atomic<angle::ThreadId> mOwnerThreadId{angle::InvalidThreadId()};
uint32_t mLockLevel = 0;
};
#endif // defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION)
} // namespace priv

namespace
{
ANGLE_REQUIRE_CONSTANT_INIT std::atomic<priv::GlobalMutex *> g_Mutex(nullptr);
static_assert(std::is_trivially_destructible<decltype(g_Mutex)>::value,
"global mutex is not trivially destructible");

priv::GlobalMutex *AllocateGlobalMutexImpl()
{
priv::GlobalMutex *currentMutex = nullptr;
std::unique_ptr<priv::GlobalMutex> newMutex(new priv::GlobalMutex());
if (g_Mutex.compare_exchange_strong(currentMutex, newMutex.get(), std::memory_order_release,
std::memory_order_acquire))
{
return newMutex.release();
}
return currentMutex;
}

priv::GlobalMutex *GetGlobalMutex()
{
priv::GlobalMutex *mutex = g_Mutex.load(std::memory_order_acquire);
return mutex != nullptr ? mutex : AllocateGlobalMutexImpl();
}
} // anonymous namespace

// ScopedGlobalMutexLock implementation.
ScopedGlobalMutexLock::ScopedGlobalMutexLock() : mMutex(*GetGlobalMutex())
{
mMutex.lock();
}

ScopedGlobalMutexLock::~ScopedGlobalMutexLock()
{
mMutex.unlock();
}

// ScopedOptionalGlobalMutexLock implementation.
ScopedOptionalGlobalMutexLock::ScopedOptionalGlobalMutexLock(bool enabled)
{
if (enabled)
{
mMutex = GetGlobalMutex();
mMutex->lock();
}
else
{
mMutex = nullptr;
}
}

ScopedOptionalGlobalMutexLock::~ScopedOptionalGlobalMutexLock()
{
if (mMutex != nullptr)
{
mMutex->unlock();
}
}

// Global functions.
#if defined(ANGLE_PLATFORM_WINDOWS) && !defined(ANGLE_STATIC)
void AllocateGlobalMutex()
{
(void)AllocateGlobalMutexImpl();
}

void DeallocateGlobalMutex()
{
priv::GlobalMutex *mutex = g_Mutex.exchange(nullptr);
if (mutex != nullptr)
{
{
// Wait for the mutex to become released by other threads before deleting.
std::lock_guard<priv::GlobalMutex> lock(*mutex);
}
delete mutex;
}
}
#endif

} // namespace egl
48 changes: 48 additions & 0 deletions src/libANGLE/GlobalMutex.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// Copyright 2023 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// GlobalMutex.h: Defines Global Mutex and utilities.

#ifndef LIBANGLE_GLOBAL_MUTEX_H_
#define LIBANGLE_GLOBAL_MUTEX_H_

#include "common/angleutils.h"

namespace egl
{
namespace priv
{
class GlobalMutex;
} // namespace priv

class [[nodiscard]] ScopedGlobalMutexLock final : angle::NonCopyable
{
public:
ScopedGlobalMutexLock();
~ScopedGlobalMutexLock();

private:
priv::GlobalMutex &mMutex;
};

// For Context protection where lock is optional. Works slower than ScopedGlobalMutexLock.
class [[nodiscard]] ScopedOptionalGlobalMutexLock final : angle::NonCopyable
{
public:
explicit ScopedOptionalGlobalMutexLock(bool enabled);
~ScopedOptionalGlobalMutexLock();

private:
priv::GlobalMutex *mMutex;
};

#if defined(ANGLE_PLATFORM_WINDOWS) && !defined(ANGLE_STATIC)
void AllocateGlobalMutex();
void DeallocateGlobalMutex();
#endif

} // namespace egl

#endif // LIBANGLE_GLOBAL_MUTEX_H_
104 changes: 104 additions & 0 deletions src/libANGLE/GlobalMutex_unittest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//
// Copyright 2023 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// GlobalMutex_unittest:
// Tests of the Scoped<*>GlobalMutexLock classes
//

#include <gtest/gtest.h>

#include "libANGLE/GlobalMutex.h"

namespace
{
template <class ScopedGlobalLockT, class... Args>
void runBasicGlobalMutexTest(bool expectToPass, Args &&...args)
{
constexpr size_t kThreadCount = 16;
constexpr size_t kIterationCount = 50'000;

std::array<std::thread, kThreadCount> threads;

std::mutex mutex;
std::condition_variable condVar;
size_t readyCount = 0;

std::atomic<size_t> testVar;

for (size_t i = 0; i < kThreadCount; ++i)
{
threads[i] = std::thread([&]() {
{
std::unique_lock<std::mutex> lock(mutex);
++readyCount;
if (readyCount < kThreadCount)
{
condVar.wait(lock, [&]() { return readyCount == kThreadCount; });
}
else
{
condVar.notify_all();
}
}
for (size_t j = 0; j < kIterationCount; ++j)
{
ScopedGlobalLockT lock(std::forward<Args>(args)...);
const int local = testVar.load(std::memory_order_relaxed);
const int newValue = local + 1;
testVar.store(newValue, std::memory_order_relaxed);
}
});
}

for (size_t i = 0; i < kThreadCount; ++i)
{
threads[i].join();
}

if (expectToPass)
{
EXPECT_EQ(testVar.load(), kThreadCount * kIterationCount);
}
else
{
EXPECT_NE(testVar.load(), kThreadCount * kIterationCount);
}
}

// Tests basic usage of ScopedGlobalMutexLock.
TEST(GlobalMutexTest, ScopedGlobalMutexLock)
{
runBasicGlobalMutexTest<egl::ScopedGlobalMutexLock>(true);
}

// Tests basic usage of ScopedOptionalGlobalMutexLock (Enabled).
TEST(GlobalMutexTest, ScopedOptionalGlobalMutexLockEnabled)
{
runBasicGlobalMutexTest<egl::ScopedOptionalGlobalMutexLock>(true, true);
}

// Tests basic usage of ScopedOptionalGlobalMutexLock (Disabled).
TEST(GlobalMutexTest, ScopedOptionalGlobalMutexLockDisabled)
{
runBasicGlobalMutexTest<egl::ScopedOptionalGlobalMutexLock>(false, false);
}

#if defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION)
// Tests that ScopedGlobalMutexLock can be recursively locked.
TEST(GlobalMutexTest, RecursiveScopedGlobalMutexLock)
{
egl::ScopedGlobalMutexLock lock;
egl::ScopedGlobalMutexLock lock2;
}

// Tests that ScopedOptionalGlobalMutexLock can be recursively locked.
TEST(GlobalMutexTest, RecursiveScopedOptionalGlobalMutexLock)
{
egl::ScopedOptionalGlobalMutexLock lock(true);
egl::ScopedOptionalGlobalMutexLock lock2(true);
}
#endif

} // anonymous namespace
2 changes: 2 additions & 0 deletions src/libGLESv2.gni
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ libangle_headers = [
"src/libANGLE/GLES1Renderer.h",
"src/libANGLE/GLES1Shaders.inc",
"src/libANGLE/GLES1State.h",
"src/libANGLE/GlobalMutex.h",
"src/libANGLE/HandleAllocator.h",
"src/libANGLE/Image.h",
"src/libANGLE/ImageIndex.h",
Expand Down Expand Up @@ -403,6 +404,7 @@ libangle_sources = [
"src/libANGLE/FramebufferAttachment.cpp",
"src/libANGLE/GLES1Renderer.cpp",
"src/libANGLE/GLES1State.cpp",
"src/libANGLE/GlobalMutex.cpp",
"src/libANGLE/HandleAllocator.cpp",
"src/libANGLE/Image.cpp",
"src/libANGLE/ImageIndex.cpp",
Expand Down
Loading

0 comments on commit b22124b

Please sign in to comment.