Mikhail Naganov | ac9d4e7 | 2023-10-23 12:00:09 -0700 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright (C) 2023 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #pragma once |
| 18 | |
| 19 | #include <forward_list> |
| 20 | #include <mutex> |
| 21 | #include <utility> |
| 22 | |
| 23 | namespace android { |
| 24 | |
| 25 | // This class implements the "monitor" idiom for providing locked access to a class instance. |
| 26 | // This is how it is intended to be used. Let's assume there is a "Main" class which owns |
| 27 | // an instance of a "Resource" class, which is protected by a mutex. We add an instance of |
| 28 | // "LockedAccessor<Resource>" as a member of "Main": |
| 29 | // |
| 30 | // class Resource; |
| 31 | // |
| 32 | // class Main { |
| 33 | // Main() : mAccessor(mResource, mLock) {} |
| 34 | // private: |
| 35 | // std::mutex mLock; |
| 36 | // Resource mResource GUARDED_BY(mLock); // owns the resource |
| 37 | // LockedAccessor<Resource> mAccessor; |
| 38 | // }; |
| 39 | // |
| 40 | // The accessor is initialized in the constructor when no locking is needed. The accessor |
| 41 | // defers locking until the resource is accessed. |
| 42 | // |
| 43 | // Although "mAccessor" can be used by the methods of "Main" for scoped access to the resource, |
| 44 | // its main role is for granting access to the resource to other classes. This is achieved by |
| 45 | // making a copy of "mAccessor" and giving it away to another class. This obviously does not |
| 46 | // transfer ownership of the resource. The intent is to allow another class to use the resource |
| 47 | // with proper locking in a "lazy" fashion: |
| 48 | // |
| 49 | // class Another { |
| 50 | // public: |
| 51 | // Another(const LockedAccessor<Resource>& accessor) : mAccessor(accessor) {} |
| 52 | // void doItLater() { // Use explicit 'lock' / 'unlock' |
| 53 | // auto resource = mAccessor.lock(); |
| 54 | // resource.use(); |
| 55 | // mAccessor.unlock(); |
| 56 | // } |
| 57 | // void doItLaterScoped() { // Rely on the scoped accessor do perform unlocking. |
| 58 | // LockedAccessor<Resource> scopedAccessor(mAccessor); |
| 59 | // auto resource = scopedAccessor.lock(); |
| 60 | // resource.use(); |
| 61 | // } |
| 62 | // private: |
| 63 | // LockedAccessor<Resource> mAccessor; |
| 64 | // }; |
| 65 | // |
| 66 | template<class C> |
| 67 | class LockedAccessor { |
| 68 | public: |
| 69 | LockedAccessor(C& instance, std::mutex& mutex) |
| 70 | : mInstance(instance), mMutex(mutex), mLock(mMutex, std::defer_lock) {} |
| 71 | LockedAccessor(const LockedAccessor& other) |
| 72 | : mInstance(other.mInstance), mMutex(other.mMutex), mLock(mMutex, std::defer_lock) {} |
| 73 | ~LockedAccessor() { if (mLock.owns_lock()) mLock.unlock(); } |
| 74 | C& lock() { mLock.lock(); return mInstance; } |
| 75 | void unlock() { mLock.unlock(); } |
| 76 | private: |
| 77 | C& mInstance; |
| 78 | std::mutex& mMutex; |
| 79 | std::unique_lock<std::mutex> mLock; |
| 80 | }; |
| 81 | |
| 82 | // This class implements scoped cleanups. A "cleanup" is a call to a method of class "C" which |
| 83 | // takes an integer parameter. Cleanups are executed in the reverse order to how they were added. |
| 84 | // For executing cleanups, the instance of "C" is retrieved via the provided "LockedAccessor". |
| 85 | template<class C> |
| 86 | class Cleanups { |
| 87 | public: |
| 88 | typedef void (C::*Cleaner)(int32_t); // A member function of "C" performing a cleanup action. |
| 89 | explicit Cleanups(const LockedAccessor<C>& accessor) : mAccessor(accessor) {} |
| 90 | ~Cleanups() { |
| 91 | if (!mCleanups.empty()) { |
| 92 | C& c = mAccessor.lock(); |
| 93 | for (auto& cleanup : mCleanups) (c.*cleanup.first)(cleanup.second); |
| 94 | mAccessor.unlock(); |
| 95 | } |
| 96 | } |
| 97 | void add(Cleaner cleaner, int32_t id) { |
| 98 | mCleanups.emplace_front(cleaner, id); |
| 99 | } |
| 100 | void disarmAll() { mCleanups.clear(); } |
| 101 | private: |
| 102 | using Cleanup = std::pair<Cleaner, int32_t>; |
| 103 | LockedAccessor<C> mAccessor; |
| 104 | std::forward_list<Cleanup> mCleanups; |
| 105 | }; |
| 106 | |
| 107 | } // namespace android |