vulkan-wsi-layer/util/custom_mutex.hpp

230 lines
6.1 KiB
C++
Raw Normal View History

/*
* 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 */