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:
LockedAccessor(C & instance,std::mutex & mutex)69     LockedAccessor(C& instance, std::mutex& mutex)
70             : mInstance(instance), mMutex(mutex), mLock(mMutex, std::defer_lock) {}
LockedAccessor(const LockedAccessor & other)71     LockedAccessor(const LockedAccessor& other)
72             : mInstance(other.mInstance), mMutex(other.mMutex), mLock(mMutex, std::defer_lock) {}
~LockedAccessor()73     ~LockedAccessor() { if (mLock.owns_lock()) mLock.unlock(); }
lock()74     C& lock() { mLock.lock(); return mInstance; }
unlock()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.
Cleanups(const LockedAccessor<C> & accessor)89     explicit Cleanups(const LockedAccessor<C>& accessor) : mAccessor(accessor) {}
~Cleanups()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     }
add(Cleaner cleaner,int32_t id)97     void add(Cleaner cleaner, int32_t id) {
98         mCleanups.emplace_front(cleaner, id);
99     }
disarmAll()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
108