1 // Copyright 2021 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // 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, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 #pragma once 15 16 #include <chrono> 17 #include <optional> 18 #include <type_traits> 19 20 #include "pw_assert/assert.h" 21 #include "pw_sync/lock_annotations.h" 22 #include "pw_sync/lock_traits.h" 23 #include "pw_sync/virtual_basic_lockable.h" 24 25 namespace pw::sync { 26 27 /// The `BorrowedPointer` is an RAII handle which wraps a pointer to a borrowed 28 /// object along with a held lock which is guarding the object. When destroyed, 29 /// the lock is released. 30 template <typename GuardedType, typename Lock = pw::sync::VirtualBasicLockable> 31 class BorrowedPointer { 32 public: 33 /// Release the lock on destruction. ~BorrowedPointer()34 ~BorrowedPointer() { 35 if (lock_ != nullptr) { 36 lock_->unlock(); 37 } 38 } 39 40 /// Move-constructs a ``BorrowedPointer<T>`` from a ``BorrowedPointer<U>``. 41 /// 42 /// This allows not only pure move construction where 43 /// ``GuardedType == OtherType`` and ``Lock == OtherLock``, but also 44 /// converting construction where ``GuardedType`` is a base class of 45 /// ``OtherType`` and ``Lock`` is a base class of ``OtherLock``, like 46 /// ``BorrowedPointer<Base> base_ptr(derived_borrowable.acquire());` 47 /// 48 /// @b Postcondition: The other BorrowedPointer is no longer valid and will 49 /// assert if the GuardedType is accessed. 50 template <typename OtherType, typename OtherLock> BorrowedPointer(BorrowedPointer<OtherType,OtherLock> && other)51 BorrowedPointer(BorrowedPointer<OtherType, OtherLock>&& other) 52 : lock_(other.lock_), object_(other.object_) { 53 static_assert( 54 std::is_assignable_v<GuardedType*&, OtherType*>, 55 "Attempted to construct a BorrowedPointer from another whose " 56 "GuardedType* is not assignable to this object's GuardedType*."); 57 static_assert(std::is_assignable_v<Lock*&, OtherLock*>, 58 "Attempted to construct a BorrowedPointer from another whose " 59 "Lock* is not assignable to this object's Lock*."); 60 other.lock_ = nullptr; 61 other.object_ = nullptr; 62 } 63 64 /// Move-assigns a ``BorrowedPointer<T>`` from a ``BorrowedPointer<U>``. 65 /// 66 /// This allows not only pure move construction where 67 /// ``GuardedType == OtherType`` and ``Lock == OtherLock``, but also 68 /// converting construction where ``GuardedType`` is a base class of 69 /// ``OtherType`` and ``Lock`` is a base class of ``OtherLock``, like 70 /// ``BorrowedPointer<Base> base_ptr = derived_borrowable.acquire();` 71 /// 72 /// @b Postcondition: The other BorrowedPointer is no longer valid and will 73 /// assert if the GuardedType is accessed. 74 template <typename OtherType, typename OtherLock> 75 BorrowedPointer& operator=(BorrowedPointer<OtherType, OtherLock>&& other) { 76 static_assert( 77 std::is_assignable_v<GuardedType*&, OtherType*>, 78 "Attempted to construct a BorrowedPointer from another whose " 79 "GuardedType* is not assignable to this object's GuardedType*."); 80 static_assert(std::is_assignable_v<Lock*&, OtherLock*>, 81 "Attempted to construct a BorrowedPointer from another whose " 82 "Lock* is not assignable to this object's Lock*."); 83 lock_ = other.lock_; 84 object_ = other.object_; 85 other.lock_ = nullptr; 86 other.object_ = nullptr; 87 return *this; 88 } 89 BorrowedPointer(const BorrowedPointer&) = delete; 90 BorrowedPointer& operator=(const BorrowedPointer&) = delete; 91 92 /// Provides access to the borrowed object's members. 93 GuardedType* operator->() { 94 PW_ASSERT(object_ != nullptr); // Ensure this isn't a stale moved instance. 95 return object_; 96 } 97 98 /// Const overload 99 const GuardedType* operator->() const { 100 PW_ASSERT(object_ != nullptr); // Ensure this isn't a stale moved instance. 101 return object_; 102 } 103 104 /// Provides access to the borrowed object directly. 105 /// 106 /// @rst 107 /// .. note:: 108 /// The member of pointer member access operator, ``operator->()``, is 109 /// recommended over this API as this is prone to leaking references. 110 /// However, this is sometimes necessary. 111 /// 112 /// .. warning: 113 /// Be careful not to leak references to the borrowed object! 114 /// @endrst 115 GuardedType& operator*() { 116 PW_ASSERT(object_ != nullptr); // Ensure this isn't a stale moved instance. 117 return *object_; 118 } 119 120 /// Const overload 121 const GuardedType& operator*() const { 122 PW_ASSERT(object_ != nullptr); // Ensure this isn't a stale moved instance. 123 return *object_; 124 } 125 126 private: 127 /// Allow BorrowedPointer creation inside of Borrowable's acquire methods. 128 template <typename G, typename L> 129 friend class Borrowable; 130 BorrowedPointer(Lock & lock,GuardedType & object)131 constexpr BorrowedPointer(Lock& lock, GuardedType& object) 132 : lock_(&lock), object_(&object) {} 133 134 Lock* lock_; 135 GuardedType* object_; 136 137 /// Allow converting move constructor and assignment to access fields of 138 /// this class. 139 /// 140 /// Without this, ``BorrowedPointer<OtherType, OtherLock>`` would not be able 141 /// to access fields of ``BorrowedPointer<GuardedType, Lock>``. 142 template <typename OtherType, typename OtherLock> 143 friend class BorrowedPointer; 144 }; 145 146 /// The `Borrowable` is a helper construct that enables callers to borrow an 147 /// object which is guarded by a lock. 148 /// 149 /// Users who need access to the guarded object can ask to acquire a 150 /// `BorrowedPointer` which permits access while the lock is held. 151 /// 152 /// Thread-safety analysis is not supported for this class, as the 153 /// `BorrowedPointer`s it creates conditionally releases the lock. See also 154 /// https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#no-conditionally-held-locks 155 /// 156 /// This class is compatible with locks which comply with `BasicLockable`, 157 /// `Lockable`, and `TimedLockable` C++ named requirements. 158 /// 159 /// `Borrowable<T>` is covariant with respect to `T`, so that `Borrowable<U>` 160 /// can be converted to `Borrowable<T>`, if `U` is a subclass of `T`. 161 /// 162 /// `Borrowable` has pointer-like semantics and should be passed by value. 163 template <typename GuardedType, typename Lock = pw::sync::VirtualBasicLockable> 164 class Borrowable { 165 public: 166 static_assert(is_basic_lockable_v<Lock>, 167 "lock type must satisfy BasicLockable"); 168 Borrowable(GuardedType & object,Lock & lock)169 constexpr Borrowable(GuardedType& object, Lock& lock) noexcept 170 : lock_(&lock), object_(&object) {} 171 172 template <typename U> Borrowable(const Borrowable<U,Lock> & other)173 constexpr Borrowable(const Borrowable<U, Lock>& other) 174 : lock_(other.lock_), object_(other.object_) {} 175 176 Borrowable(const Borrowable&) = default; 177 Borrowable& operator=(const Borrowable&) = default; 178 Borrowable(Borrowable&& other) = default; 179 Borrowable& operator=(Borrowable&& other) = default; 180 181 /// Blocks indefinitely until the object can be borrowed. Failures are fatal. acquire()182 BorrowedPointer<GuardedType, Lock> acquire() const 183 PW_NO_LOCK_SAFETY_ANALYSIS { 184 lock_->lock(); 185 return BorrowedPointer<GuardedType, Lock>(*lock_, *object_); 186 } 187 188 /// Tries to borrow the object in a non-blocking manner. Returns a 189 /// BorrowedPointer on success, otherwise `std::nullopt` (nothing). 190 template <int&... ExplicitArgumentBarrier, 191 typename T = Lock, 192 typename = std::enable_if_t<is_lockable_v<T>>> try_acquire()193 std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire() const 194 PW_NO_LOCK_SAFETY_ANALYSIS { 195 if (!lock_->try_lock()) { 196 return std::nullopt; 197 } 198 return BorrowedPointer<GuardedType, Lock>(*lock_, *object_); 199 } 200 201 /// Tries to borrow the object. Blocks until the specified timeout has elapsed 202 /// or the object has been borrowed, whichever comes first. Returns a 203 /// `BorrowedPointer` on success, otherwise `std::nullopt` (nothing). 204 template <class Rep, 205 class Period, 206 int&... ExplicitArgumentBarrier, 207 typename T = Lock, 208 typename = std::enable_if_t< 209 is_lockable_for_v<T, std::chrono::duration<Rep, Period>>>> try_acquire_for(std::chrono::duration<Rep,Period> timeout)210 std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_for( 211 std::chrono::duration<Rep, Period> timeout) const 212 PW_NO_LOCK_SAFETY_ANALYSIS { 213 if (!lock_->try_lock_for(timeout)) { 214 return std::nullopt; 215 } 216 return BorrowedPointer<GuardedType, Lock>(*lock_, *object_); 217 } 218 219 /// Tries to borrow the object. Blocks until the specified deadline has passed 220 /// or the object has been borrowed, whichever comes first. Returns a 221 /// `BorrowedPointer` on success, otherwise `std::nullopt` (nothing). 222 template < 223 class Clock, 224 class Duration, 225 int&... ExplicitArgumentBarrier, 226 typename T = Lock, 227 typename = std::enable_if_t< 228 is_lockable_until_v<T, std::chrono::time_point<Clock, Duration>>>> try_acquire_until(std::chrono::time_point<Clock,Duration> deadline)229 std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_until( 230 std::chrono::time_point<Clock, Duration> deadline) const 231 PW_NO_LOCK_SAFETY_ANALYSIS { 232 if (!lock_->try_lock_until(deadline)) { 233 return std::nullopt; 234 } 235 return BorrowedPointer<GuardedType, Lock>(*lock_, *object_); 236 } 237 238 private: 239 Lock* lock_; 240 GuardedType* object_; 241 242 // Befriend all template instantiations of this class. 243 template <typename, typename> 244 friend class Borrowable; 245 }; 246 247 } // namespace pw::sync 248