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 <optional> 17 #include <type_traits> 18 19 #include "pw_assert/assert.h" 20 #include "pw_sync/lock_annotations.h" 21 #include "pw_sync/lock_traits.h" 22 #include "pw_sync/virtual_basic_lockable.h" 23 24 namespace pw::sync { 25 26 /// The `BorrowedPointer` is an RAII handle which wraps a pointer to a borrowed 27 /// object along with a held lock which is guarding the object. When destroyed, 28 /// the lock is released. 29 template <typename GuardedType, 30 typename LockType = 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 == G`` and ``Lock == L``, 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 G, typename L> BorrowedPointer(BorrowedPointer<G,L> && other)51 BorrowedPointer(BorrowedPointer<G, L>&& other) 52 : lock_(other.lock_), object_(other.object_) { 53 static_assert( 54 std::is_assignable_v<GuardedType*&, G*>, 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<LockType*&, L*>, 58 "Attempted to construct a BorrowedPointer from another whose " 59 "LockType* 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 G, typename L> 75 BorrowedPointer& operator=(BorrowedPointer<G, L>&& other) { 76 static_assert( 77 std::is_assignable_v<GuardedType*&, G*>, 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<LockType*&, L*>, 81 "Attempted to construct a BorrowedPointer from another whose " 82 "LockType* 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, typename> 129 friend class Borrowable; 130 BorrowedPointer(LockType & lock,GuardedType & object)131 constexpr BorrowedPointer(LockType& lock, GuardedType& object) 132 : lock_(&lock), object_(&object) {} 133 134 LockType* lock_; 135 GuardedType* object_; 136 137 /// Allow converting move constructor and assignment to access fields of 138 /// this class. 139 template <typename, typename> 140 friend class BorrowedPointer; 141 }; 142 143 /// The `Borrowable` is a helper construct that enables callers to borrow an 144 /// object which is guarded by a lock. 145 /// 146 /// Users who need access to the guarded object can ask to acquire a 147 /// `BorrowedPointer` which permits access while the lock is held. 148 /// 149 /// Thread-safety analysis is not supported for this class, as the 150 /// `BorrowedPointer`s it creates conditionally releases the lock. See also 151 /// https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#no-conditionally-held-locks 152 /// 153 /// This class is compatible with locks which comply with \em BasicLockable C++ 154 /// named requirement. A `try_acquire` method is conditionally available if the 155 /// lock also meets the \em Lockable requirement. This class is further extended 156 /// by `TimedBorrowable` for locks that meet the \em TimedLockable requirement. 157 /// 158 /// `Borrowable<T>` is covariant with respect to `T`, so that `Borrowable<U>` 159 /// can be converted to `Borrowable<T>`, if `U` is a subclass of `T`. 160 /// 161 /// `Borrowable` has pointer-like semantics and should be passed by value. 162 template <typename GuardedType, 163 typename LockType = pw::sync::VirtualBasicLockable> 164 class Borrowable { 165 public: 166 static_assert(is_basic_lockable_v<LockType>, 167 "lock type must satisfy BasicLockable"); 168 Borrowable(GuardedType & object,LockType & lock)169 constexpr Borrowable(GuardedType& object, LockType& lock) noexcept 170 : lock_(&lock), object_(&object) {} 171 172 template <typename U> Borrowable(const Borrowable<U,LockType> & other)173 constexpr Borrowable(const Borrowable<U, LockType>& 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, LockType> acquire() const 183 PW_NO_LOCK_SAFETY_ANALYSIS { 184 lock_->lock(); 185 return Borrow(); 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 = LockType, 192 typename = std::enable_if_t<is_lockable_v<T>>> try_acquire()193 std::optional<BorrowedPointer<GuardedType, LockType>> try_acquire() const 194 PW_NO_LOCK_SAFETY_ANALYSIS { 195 if (!lock_->try_lock()) { 196 return std::nullopt; 197 } 198 return Borrow(); 199 } 200 201 private: 202 // Befriend all template instantiations of this class and the Timed subtype. 203 template <typename, typename> 204 friend class Borrowable; 205 206 template <typename, typename> 207 friend class TimedBorrowable; 208 Borrow()209 BorrowedPointer<GuardedType, LockType> Borrow() const { 210 return BorrowedPointer<GuardedType, LockType>(*lock_, *object_); 211 } 212 213 LockType* lock_; 214 GuardedType* object_; 215 }; 216 217 } // namespace pw::sync 218