| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #pragma once |
| |
| #include <memory> |
| #include <mutex> |
| |
| namespace android::mediametrics { |
| |
| /** |
| * Wraps a shared-ptr for which member access through operator->() behaves |
| * as if the shared-ptr is atomically copied and then (without a lock) -> called. |
| * |
| * See related C++ 20: |
| * https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic2 |
| * |
| * EXAMPLE: |
| * |
| * SharedPtrWrap<T> t{}; |
| * |
| * thread1() { |
| * t->func(); // safely executes either the original t or the one created by thread2. |
| * } |
| * |
| * thread2() { |
| * t.set(std::make_shared<T>()); // overwrites the original t. |
| * } |
| */ |
| template <typename T> |
| class SharedPtrWrap { |
| mutable std::mutex mLock; |
| std::shared_ptr<T> mPtr; |
| |
| public: |
| template <typename... Args> |
| explicit SharedPtrWrap(Args&&... args) |
| : mPtr(std::make_shared<T>(std::forward<Args>(args)...)) |
| {} |
| |
| /** |
| * Gets the current shared pointer. This must return a value, not a reference. |
| * |
| * For compatibility with existing shared_ptr, we do not pass back a |
| * shared_ptr<const T> for the const getter. |
| */ |
| std::shared_ptr<T> get() const { |
| std::lock_guard lock(mLock); |
| return mPtr; |
| } |
| |
| /** |
| * Sets the current shared pointer, returning the previous shared pointer. |
| */ |
| std::shared_ptr<T> set(std::shared_ptr<T> ptr) { // pass by value as we use swap. |
| std::lock_guard lock(mLock); |
| std::swap(ptr, mPtr); |
| return ptr; |
| } |
| |
| /** |
| * Returns a shared pointer value representing T at the instant of time when |
| * the call executes. The lifetime of the shared pointer will |
| * be extended as we are returning an instance of the shared_ptr |
| * not a reference to it. The destructor to the returned shared_ptr |
| * will be called sometime after the expression including the member function or |
| * the member variable is evaluated. Do not change to a reference! |
| */ |
| |
| // For compatibility with existing shared_ptr, we do not pass back a |
| // shared_ptr<const T> for the const operator pointer access. |
| std::shared_ptr<T> operator->() const { |
| return get(); |
| } |
| /** |
| * We do not overload operator*() as the reference is not stable if the |
| * lock is not held. |
| */ |
| }; |
| |
| /** |
| * Wraps member access to the class T by a lock. |
| * |
| * The object T is constructed within the LockWrap to guarantee |
| * locked access at all times. When T's methods are accessed through ->, |
| * a monitor style lock is obtained to prevent multiple threads from executing |
| * methods in the object T at the same time. |
| * Suggested by Kevin R. |
| * |
| * EXAMPLE: |
| * |
| * // Accumulator class which is very slow, requires locking for multiple threads. |
| * |
| * class Accumulator { |
| * int32_t value_ = 0; |
| * public: |
| * void add(int32_t incr) { |
| * const int32_t temp = value_; |
| * sleep(0); // yield |
| * value_ = temp + incr; |
| * } |
| * int32_t get() { return value_; } |
| * }; |
| * |
| * // We use LockWrap on Accumulator to have safe multithread access. |
| * android::mediametrics::LockWrap<Accumulator> a{}; // locked accumulator succeeds |
| * |
| * // Conversely, the following line fails: |
| * // auto a = std::make_shared<Accumulator>(); // this fails, only 50% adds atomic. |
| * |
| * constexpr size_t THREADS = 100; |
| * constexpr size_t ITERATIONS = 10; |
| * constexpr int32_t INCREMENT = 1; |
| * |
| * // Test by generating multiple threads, all adding simultaneously. |
| * std::vector<std::future<void>> threads(THREADS); |
| * for (size_t i = 0; i < THREADS; ++i) { |
| * threads.push_back(std::async(std::launch::async, [&] { |
| * for (size_t j = 0; j < ITERATIONS; ++j) { |
| * a->add(INCREMENT); // add needs locked access here. |
| * } |
| * })); |
| * } |
| * threads.clear(); |
| * |
| * // If the add operations are not atomic, value will be smaller than expected. |
| * ASSERT_EQ(INCREMENT * THREADS * ITERATIONS, (size_t)a->get()); |
| * |
| */ |
| template <typename T> |
| class LockWrap { |
| /** |
| * Holding class that keeps the pointer and the lock. |
| * |
| * We return this holding class from operator->() to keep the lock until the |
| * method function or method variable access is completed. |
| */ |
| class LockedPointer { |
| friend LockWrap; |
| LockedPointer(T *t, std::recursive_mutex *lock, std::atomic<size_t> *recursionDepth) |
| : mT(t), mLock(*lock), mRecursionDepth(recursionDepth) { ++*mRecursionDepth; } |
| |
| T* const mT; |
| std::lock_guard<std::recursive_mutex> mLock; |
| std::atomic<size_t>* mRecursionDepth; |
| public: |
| ~LockedPointer() { |
| --*mRecursionDepth; // Used for testing, we do not check underflow. |
| } |
| |
| const T* operator->() const { |
| return mT; |
| } |
| T* operator->() { |
| return mT; |
| } |
| }; |
| |
| // We must use a recursive mutex because the end of the full expression may |
| // involve another reference to T->. |
| // |
| // A recursive mutex allows the same thread to recursively acquire, |
| // but different thread would block. |
| // |
| // Example which fails with a normal mutex: |
| // |
| // android::mediametrics::LockWrap<std::vector<int>> v{std::initializer_list<int>{1, 2}}; |
| // const int sum = v->operator[](0) + v->operator[](1); |
| // |
| mutable std::recursive_mutex mLock; |
| mutable T mT; |
| mutable std::atomic<size_t> mRecursionDepth{}; // Used for testing. |
| |
| public: |
| template <typename... Args> |
| explicit LockWrap(Args&&... args) : mT(std::forward<Args>(args)...) {} |
| |
| const LockedPointer operator->() const { |
| return LockedPointer(&mT, &mLock, &mRecursionDepth); |
| } |
| LockedPointer operator->() { |
| return LockedPointer(&mT, &mLock, &mRecursionDepth); |
| } |
| |
| // Returns the lock depth of the recursive mutex. |
| // @TestApi |
| size_t getRecursionDepth() const { |
| return mRecursionDepth; |
| } |
| }; |
| |
| } // namespace android::mediametrics |