• 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 <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/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, typename Lock = pw::sync::VirtualBasicLockable>
30 class BorrowedPointer {
31  public:
32   /// Release the lock on destruction.
~BorrowedPointer()33   ~BorrowedPointer() {
34     if (lock_ != nullptr) {
35       lock_->unlock();
36     }
37   }
38 
39   /// This object is moveable, but not copyable.
40   ///
41   /// @b Postcondition: The other BorrowedPointer is no longer valid and will
42   ///     assert if the GuardedType is accessed.
BorrowedPointer(BorrowedPointer && other)43   BorrowedPointer(BorrowedPointer&& other)
44       : lock_(other.lock_), object_(other.object_) {
45     other.lock_ = nullptr;
46     other.object_ = nullptr;
47   }
48   BorrowedPointer& operator=(BorrowedPointer&& other) {
49     lock_ = other.lock_;
50     object_ = other.object_;
51     other.lock_ = nullptr;
52     other.object_ = nullptr;
53     return *this;
54   }
55   BorrowedPointer(const BorrowedPointer&) = delete;
56   BorrowedPointer& operator=(const BorrowedPointer&) = delete;
57 
58   /// Provides access to the borrowed object's members.
59   GuardedType* operator->() {
60     PW_ASSERT(object_ != nullptr);  // Ensure this isn't a stale moved instance.
61     return object_;
62   }
63 
64   /// Const overload
65   const GuardedType* operator->() const {
66     PW_ASSERT(object_ != nullptr);  // Ensure this isn't a stale moved instance.
67     return object_;
68   }
69 
70   /// Provides access to the borrowed object directly.
71   ///
72   /// @rst
73   /// .. note::
74   ///    The member of pointer member access operator, ``operator->()``, is
75   ///    recommended over this API as this is prone to leaking references.
76   ///    However, this is sometimes necessary.
77   ///
78   /// .. warning:
79   ///    Be careful not to leak references to the borrowed object!
80   /// @endrst
81   GuardedType& operator*() {
82     PW_ASSERT(object_ != nullptr);  // Ensure this isn't a stale moved instance.
83     return *object_;
84   }
85 
86   /// Const overload
87   const GuardedType& operator*() const {
88     PW_ASSERT(object_ != nullptr);  // Ensure this isn't a stale moved instance.
89     return *object_;
90   }
91 
92  private:
93   /// Allow BorrowedPointer creation inside of Borrowable's acquire methods.
94   template <typename G, typename L>
95   friend class Borrowable;
96 
BorrowedPointer(Lock & lock,GuardedType & object)97   constexpr BorrowedPointer(Lock& lock, GuardedType& object)
98       : lock_(&lock), object_(&object) {}
99 
100   Lock* lock_;
101   GuardedType* object_;
102 };
103 
104 /// The `Borrowable` is a helper construct that enables callers to borrow an
105 /// object which is guarded by a lock.
106 ///
107 /// Users who need access to the guarded object can ask to acquire a
108 /// `BorrowedPointer` which permits access while the lock is held.
109 ///
110 /// This class is compatible with locks which comply with `BasicLockable`,
111 /// `Lockable`, and `TimedLockable` C++ named requirements.
112 template <typename GuardedType, typename Lock = pw::sync::VirtualBasicLockable>
113 class Borrowable {
114  public:
Borrowable(GuardedType & object,Lock & lock)115   constexpr Borrowable(GuardedType& object, Lock& lock) noexcept
116       : lock_(&lock), object_(&object) {}
117 
118   Borrowable(const Borrowable&) = default;
119   Borrowable& operator=(const Borrowable&) = default;
120   Borrowable(Borrowable&& other) = default;
121   Borrowable& operator=(Borrowable&& other) = default;
122 
123   /// Blocks indefinitely until the object can be borrowed. Failures are fatal.
acquire()124   BorrowedPointer<GuardedType, Lock> acquire() PW_NO_LOCK_SAFETY_ANALYSIS {
125     lock_->lock();
126     return BorrowedPointer<GuardedType, Lock>(*lock_, *object_);
127   }
128 
129   /// Tries to borrow the object in a non-blocking manner. Returns a
130   /// BorrowedPointer on success, otherwise `std::nullopt` (nothing).
try_acquire()131   std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire() {
132     if (!lock_->try_lock()) {
133       return std::nullopt;
134     }
135     return BorrowedPointer<GuardedType, Lock>(*lock_, *object_);
136   }
137 
138   /// Tries to borrow the object. Blocks until the specified timeout has elapsed
139   /// or the object has been borrowed, whichever comes first. Returns a
140   /// `BorrowedPointer` on success, otherwise `std::nullopt` (nothing).
141   template <class Rep, class Period>
try_acquire_for(std::chrono::duration<Rep,Period> timeout)142   std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_for(
143       std::chrono::duration<Rep, Period> timeout) {
144     if (!lock_->try_lock_for(timeout)) {
145       return std::nullopt;
146     }
147     return BorrowedPointer<GuardedType, Lock>(*lock_, *object_);
148   }
149 
150   /// Tries to borrow the object. Blocks until the specified deadline has passed
151   /// or the object has been borrowed, whichever comes first. Returns a
152   /// `BorrowedPointer` on success, otherwise `std::nullopt` (nothing).
153   template <class Clock, class Duration>
try_acquire_until(std::chrono::time_point<Clock,Duration> deadline)154   std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_until(
155       std::chrono::time_point<Clock, Duration> deadline) {
156     if (!lock_->try_lock_until(deadline)) {
157       return std::nullopt;
158     }
159     return BorrowedPointer<GuardedType, Lock>(*lock_, *object_);
160   }
161 
162  private:
163   Lock* lock_;
164   GuardedType* object_;
165 };
166 
167 }  // namespace pw::sync
168