xref: /aosp_15_r20/external/pigweed/pw_sync/public/pw_sync/borrow.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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