// MIT License // // Copyright (c) 2023-2025 Advanced Micro Devices, Inc. All rights reserved. // // 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. #pragma once #include #include #include #include #include namespace rocprofiler { namespace common { /** * Sychronized is a wrapper that adds lock based write/read * protection around a datatype. The protected data is accessed * only by rlock/wlock. rlock(lambda) gets a reader lock of the * protected value, passing the protected value to the lambda as a * const. wlock(lambda) gets a writer lock on the protective value * and does the same. The reason for this class is to make it less * error prone to access shared data and more obvious when a lock * is being held. * * Example usage: * * Synchronized x(9); * x.rlock([](const auto& data){ * // data = 9 * }); * * x.wlock([](auto& data){ * // set data to new value * }); */ template class Synchronized { public: using value_type = LockedType; using this_type = Synchronized; Synchronized() = default; ~Synchronized() = default; explicit Synchronized(value_type&& data) : m_data{std::move(data)} {} Synchronized(Synchronized&& data) noexcept = default; Synchronized& operator=(Synchronized&& data) noexcept = default; // Do not allow this data structure to be copied, std::move only. Synchronized(const Synchronized&) = delete; // return a copy of the data value_type get() const; template decltype(auto) rlock(FuncT&& lambda, Args&&... args) const; template decltype(auto) wlock(FuncT&& lambda, Args&&... args); // This overload to wlock allows a synchronized map whose keys map to synchronized data to // use a read lock on the key data and then a write lock on the mapped data. template = 0> decltype(auto) wlock(FuncT&& lambda, Args&&... args) const; // Upgradable lock. If read returns false, write will be called with a unique_lock. // Essentially a helper function that does .rlock() followed by .wlock(). template bool ulock(ReadFuncT&& read, WriteFuncT&& write, Args&&... args); private: mutable std::shared_mutex m_mutex = {}; value_type m_data = {}; }; // // member definitions // template typename Synchronized::value_type Synchronized::get() const { auto lock = std::shared_lock{m_mutex}; return m_data; } template template decltype(auto) Synchronized::rlock(FuncT&& lambda, Args&&... args) const { static_assert(std::is_invocable::value, "function must accept const reference to locked type"); auto lock = std::shared_lock{m_mutex}; return std::forward(lambda)(m_data, std::forward(args)...); } template template decltype(auto) Synchronized::wlock(FuncT&& lambda, Args&&... args) { static_assert(std::is_invocable::value, "function must accept reference to locked type"); auto lock = std::unique_lock{m_mutex}; return std::forward(lambda)(m_data, std::forward(args)...); } // This overload to wlock allows a synchronized map whose keys map to synchronized data to // use a read lock on the key data and then a write lock on the mapped data. template template > decltype(auto) Synchronized::wlock(FuncT&& lambda, Args&&... args) const { return const_cast(this)->wlock(std::forward(lambda), std::forward(args)...); } // Upgradable lock. If read returns false, write will be called with a unique_lock. // Essentially a helper function that does .rlock() followed by .wlock(). template template bool Synchronized::ulock(ReadFuncT&& read, WriteFuncT&& write, Args&&... args) { static_assert(std::is_invocable::value, "read function must accept const reference to locked type"); static_assert(std::is_invocable::value, "write function must accept reference to locked type"); using read_return_type = std::invoke_result_t; using write_return_type = std::invoke_result_t; static_assert(std::is_same::value, "read and write functions must return same type"); static_assert(std::is_same::value, "read/write functions must return bool"); { auto lock = std::shared_lock{m_mutex}; if(read(m_data, std::forward(args)...)) return true; } auto lock = std::unique_lock{m_mutex}; return write(m_data, std::forward(args)...); } } // namespace common } // namespace rocprofiler