• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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