1 // Copyright 2020 The Tint Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef TOOLS_SRC_CMD_REMOTE_COMPILE_RWMUTEX_H_ 16 #define TOOLS_SRC_CMD_REMOTE_COMPILE_RWMUTEX_H_ 17 18 #include <condition_variable> // NOLINT 19 #include <mutex> // NOLINT 20 21 //////////////////////////////////////////////////////////////////////////////// 22 // RWMutex 23 //////////////////////////////////////////////////////////////////////////////// 24 25 /// A RWMutex is a reader/writer mutual exclusion lock. 26 /// The lock can be held by an arbitrary number of readers or a single writer. 27 /// Also known as a shared mutex. 28 class RWMutex { 29 public: 30 inline RWMutex() = default; 31 32 /// lockReader() locks the mutex for reading. 33 /// Multiple read locks can be held while there are no writer locks. 34 inline void lockReader(); 35 36 /// unlockReader() unlocks the mutex for reading. 37 inline void unlockReader(); 38 39 /// lockWriter() locks the mutex for writing. 40 /// If the lock is already locked for reading or writing, lockWriter blocks 41 /// until the lock is available. 42 inline void lockWriter(); 43 44 /// unlockWriter() unlocks the mutex for writing. 45 inline void unlockWriter(); 46 47 private: 48 RWMutex(const RWMutex&) = delete; 49 RWMutex& operator=(const RWMutex&) = delete; 50 51 int readLocks = 0; 52 int pendingWriteLocks = 0; 53 std::mutex mutex; 54 std::condition_variable cv; 55 }; 56 lockReader()57void RWMutex::lockReader() { 58 std::unique_lock<std::mutex> lock(mutex); 59 readLocks++; 60 } 61 unlockReader()62void RWMutex::unlockReader() { 63 std::unique_lock<std::mutex> lock(mutex); 64 readLocks--; 65 if (readLocks == 0 && pendingWriteLocks > 0) { 66 cv.notify_one(); 67 } 68 } 69 lockWriter()70void RWMutex::lockWriter() { 71 std::unique_lock<std::mutex> lock(mutex); 72 if (readLocks > 0) { 73 pendingWriteLocks++; 74 cv.wait(lock, [&] { return readLocks == 0; }); 75 pendingWriteLocks--; 76 } 77 lock.release(); // Keep lock held 78 } 79 unlockWriter()80void RWMutex::unlockWriter() { 81 if (pendingWriteLocks > 0) { 82 cv.notify_one(); 83 } 84 mutex.unlock(); 85 } 86 87 //////////////////////////////////////////////////////////////////////////////// 88 // RLock 89 //////////////////////////////////////////////////////////////////////////////// 90 91 /// RLock is a RAII read lock helper for a RWMutex. 92 class RLock { 93 public: 94 /// Constructor. 95 /// Locks `mutex` with a read-lock for the lifetime of the WLock. 96 /// @param mutex the mutex 97 explicit inline RLock(RWMutex& mutex); 98 /// Destructor. 99 /// Unlocks the RWMutex. 100 inline ~RLock(); 101 102 /// Move constructor 103 /// @param other the other RLock to move into this RLock. 104 inline RLock(RLock&& other); 105 /// Move assignment operator 106 /// @param other the other RLock to move into this RLock. 107 /// @returns this RLock so calls can be chained 108 inline RLock& operator=(RLock&& other); 109 110 private: 111 RLock(const RLock&) = delete; 112 RLock& operator=(const RLock&) = delete; 113 114 RWMutex* m; 115 }; 116 RLock(RWMutex & mutex)117RLock::RLock(RWMutex& mutex) : m(&mutex) { 118 m->lockReader(); 119 } 120 ~RLock()121RLock::~RLock() { 122 if (m != nullptr) { 123 m->unlockReader(); 124 } 125 } 126 RLock(RLock && other)127RLock::RLock(RLock&& other) { 128 m = other.m; 129 other.m = nullptr; 130 } 131 132 RLock& RLock::operator=(RLock&& other) { 133 m = other.m; 134 other.m = nullptr; 135 return *this; 136 } 137 138 //////////////////////////////////////////////////////////////////////////////// 139 // WLock 140 //////////////////////////////////////////////////////////////////////////////// 141 142 /// WLock is a RAII write lock helper for a RWMutex. 143 class WLock { 144 public: 145 /// Constructor. 146 /// Locks `mutex` with a write-lock for the lifetime of the WLock. 147 /// @param mutex the mutex 148 explicit inline WLock(RWMutex& mutex); 149 150 /// Destructor. 151 /// Unlocks the RWMutex. 152 inline ~WLock(); 153 154 /// Move constructor 155 /// @param other the other WLock to move into this WLock. 156 inline WLock(WLock&& other); 157 /// Move assignment operator 158 /// @param other the other WLock to move into this WLock. 159 /// @returns this WLock so calls can be chained 160 inline WLock& operator=(WLock&& other); 161 162 private: 163 WLock(const WLock&) = delete; 164 WLock& operator=(const WLock&) = delete; 165 166 RWMutex* m; 167 }; 168 WLock(RWMutex & mutex)169WLock::WLock(RWMutex& mutex) : m(&mutex) { 170 m->lockWriter(); 171 } 172 ~WLock()173WLock::~WLock() { 174 if (m != nullptr) { 175 m->unlockWriter(); 176 } 177 } 178 WLock(WLock && other)179WLock::WLock(WLock&& other) { 180 m = other.m; 181 other.m = nullptr; 182 } 183 184 WLock& WLock::operator=(WLock&& other) { 185 m = other.m; 186 other.m = nullptr; 187 return *this; 188 } 189 190 #endif // TOOLS_SRC_CMD_REMOTE_COMPILE_RWMUTEX_H_ 191