vulkan-wsi-layer/util/custom_mutex.hpp
Maged Elnaggar 78b21da4ef Adding exception-safe custom mutex to the WSI layer
Introduce util::mutex, util::recursive_mutex
and util::unique_lock; switch WSI layer call sites to it.
All locks are acquired via try_lock(), no system_error leaks.

Signed-off-by: Maged Elnaggar <maged.elnaggar@arm.com>
Change-Id: Ide9ef4318be7cc47e9577059695cc298f8b8e579
2025-09-05 16:06:38 +01:00

229 lines
6.1 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2025 Arm Limited.
*
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file custom_mutex.hpp
* @brief Exceptionneutral mutex wrappers and RAII guards for the Vulkan WSI layer.
*
* Provides the following helpers. All locking is exceptionneutral: the
* wrappers catch any `std::system_error` from the STL and expose boolean
* success/failure. Immediate guards use blocking `lock()` by default;
* `try_lock()` remains available for deferred cases.
* • util::mutex — wraps `std::mutex`
* • util::recursive_mutex — wraps `std::recursive_mutex`
* • util::unique_lock — flexible RAII guard (defer/adopt/retry)
*/
#pragma once
#include <mutex>
#include <system_error>
#include <utility>
#include <cassert>
#include "helpers.hpp"
namespace util
{
/**
* @class util::mutex
* @brief Exceptionneutral wrapper around @c std::mutex.
*
* Provides only a nonthrowing @c try_lock() plus @c unlock(). Any
* @c std::system_error raised by the STL is caught internally and reported as
* a simple boolean failure.
*/
class mutex : private noncopyable
{
public:
mutex() = default;
~mutex() = default;
/**
* @brief Block until the mutex is acquired.
* @retval true Lock acquired successfully.
* @retval false Underlying OS error prevented locking.
* Never throws; any @c std::system_error is caught internally.
*/
bool lock() noexcept;
/**
* @brief Attempt to acquire the mutex without blocking.
* @retval true Lock acquired successfully.
* @retval false The mutex was already owned or an OS error occurred.
*
* Never throws; any @c std::system_error is caught internally.
*/
bool try_lock() noexcept;
/**
* @brief Release the mutex.
*
* Behaviour is undefined if the current thread does not own the lock.
* This function is @c noexcept because the guard classes guarantee they
* call it only when ownership is held.
*/
void unlock() noexcept;
/**
* @brief Access the wrapped STL mutex.
* @warning Bypassing the wrapper forfeits the exceptionneutral contract.
*/
std::mutex &native() noexcept;
private:
std::mutex m_mtx;
};
/**
* @class util::recursive_mutex
* @brief Reentrant variant of @ref util::mutex.
*
* Retains the same exceptionneutral contract while permitting the same
* thread to acquire the lock multiple times.
*/
class recursive_mutex : private noncopyable
{
public:
recursive_mutex() = default;
~recursive_mutex() = default;
/**
* @brief Block until the mutex is acquired.
* @retval true Lock acquired successfully.
* @retval false Underlying OS error prevented locking.
* Never throws; any @c std::system_error is caught internally.
*/
bool lock() noexcept;
/**
* @brief Nonblocking attempt to acquire the mutex.
* @return See util::mutex::try_lock().
*/
bool try_lock() noexcept;
/**
* @brief Release one ownership level of the recursive mutex.
*/
void unlock() noexcept;
/**
* @brief Access the underlying `std::recursive_mutex`.
* @warning Direct use bypasses the exceptionneutral guarantee.
*/
std::recursive_mutex &native() noexcept;
private:
std::recursive_mutex m_mtx;
};
/**
* @class util::unique_lock
* @tparam Mutex Mutexlike type (defaults to @ref util::mutex).
* @brief Flexible RAII guard supporting deferlock, adoptlock, unlock, and retry.
*
* All lock attempts are nonblocking and exceptionneutral; failure is
* indicated by @c false, never by throwing.
*/
template <typename Mutex = util::mutex>
class unique_lock : private noncopyable
{
public:
explicit unique_lock(Mutex &m) noexcept
: m_mtx(&m)
{
m_owns = m_mtx->lock();
}
unique_lock(Mutex &m, std::defer_lock_t) noexcept
: m_mtx(&m)
{
}
unique_lock(Mutex &m, std::adopt_lock_t) noexcept
: m_mtx(&m)
, m_owns(true)
{
}
~unique_lock() noexcept
{
if (m_owns)
{
m_mtx->unlock();
}
}
/** Block until the mutex is acquired (for deferlock cases). */
bool lock() noexcept
{
assert(m_mtx && !m_owns && "unique_lock::lock: already owns or no mutex");
m_owns = m_mtx->lock();
return m_owns;
}
/* Retry after deferlock */
bool try_lock() noexcept
{
assert(m_mtx && !m_owns && "unique_lock::try_lock: already owns or no mutex");
m_owns = m_mtx->try_lock();
return m_owns;
}
void unlock() noexcept
{
if (m_owns)
{
m_mtx->unlock();
m_owns = false;
}
}
bool owns_lock() const noexcept
{
return m_owns;
}
explicit operator bool() const noexcept
{
return owns_lock();
}
/** Disown without unlocking caller becomes responsible. */
Mutex *release() noexcept
{
m_owns = false;
return std::exchange(m_mtx, nullptr);
}
/** Return a reference to the wrapped std::mutex (requires Mutex::native()). */
auto &native_mutex() noexcept
{
return m_mtx->native();
}
private:
Mutex *m_mtx{ nullptr };
bool m_owns{ false };
};
} /* namespace util */