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 // Postcondition: The other BorrowedPointer is no longer valid and will assert 42 // 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 // Provides access to the borrowed object directly. 65 // 66 // NOTE: The member of pointer member access operator, operator->(), is 67 // recommended over this API as this is prone to leaking references. However, 68 // this is sometimes necessary. 69 // 70 // WARNING: Be careful not to leak references to the borrowed object! 71 GuardedType& operator*() { 72 PW_ASSERT(object_ != nullptr); // Ensure this isn't a stale moved instance. 73 return *object_; 74 } 75 76 private: 77 // Allow BorrowedPointer creation inside of Borrowable's acquire methods. 78 template <typename G, typename L> 79 friend class Borrowable; 80 BorrowedPointer(Lock & lock,GuardedType & object)81 constexpr BorrowedPointer(Lock& lock, GuardedType& object) 82 : lock_(&lock), object_(&object) {} 83 84 Lock* lock_; 85 GuardedType* object_; 86 }; 87 88 // The Borrowable is a helper construct that enables callers to borrow an object 89 // which is guarded by a lock. 90 // 91 // Users who need access to the guarded object can ask to acquire a 92 // BorrowedPointer which permits access while the lock is held. 93 // 94 // This class is compatible with locks which comply with BasicLockable, 95 // Lockable, and TimedLockable C++ named requirements. 96 template <typename GuardedType, typename Lock = pw::sync::VirtualBasicLockable> 97 class Borrowable { 98 public: Borrowable(GuardedType & object,Lock & lock)99 constexpr Borrowable(GuardedType& object, Lock& lock) noexcept 100 : lock_(&lock), object_(&object) {} 101 102 Borrowable(const Borrowable&) = default; 103 Borrowable& operator=(const Borrowable&) = default; 104 Borrowable(Borrowable&& other) = default; 105 Borrowable& operator=(Borrowable&& other) = default; 106 107 // Blocks indefinitely until the object can be borrowed. Failures are fatal. acquire()108 BorrowedPointer<GuardedType, Lock> acquire() PW_NO_LOCK_SAFETY_ANALYSIS { 109 lock_->lock(); 110 return BorrowedPointer<GuardedType, Lock>(*lock_, *object_); 111 } 112 113 // Tries to borrow the object in a non-blocking manner. Returns a 114 // BorrowedPointer on success, otherwise std::nullopt (nothing). try_acquire()115 std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire() { 116 if (!lock_->try_lock()) { 117 return std::nullopt; 118 } 119 return BorrowedPointer<GuardedType, Lock>(*lock_, *object_); 120 } 121 122 // Tries to borrow the object. Blocks until the specified timeout has elapsed 123 // or the object has been borrowed, whichever comes first. Returns a 124 // BorrowedPointer on success, otherwise std::nullopt (nothing). 125 template <class Rep, class Period> try_acquire_for(std::chrono::duration<Rep,Period> timeout)126 std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_for( 127 std::chrono::duration<Rep, Period> timeout) { 128 if (!lock_->try_lock_for(timeout)) { 129 return std::nullopt; 130 } 131 return BorrowedPointer<GuardedType, Lock>(*lock_, *object_); 132 } 133 134 // Tries to borrow the object. Blocks until the specified deadline has passed 135 // or the object has been borrowed, whichever comes first. Returns a 136 // BorrowedPointer on success, otherwise std::nullopt (nothing). 137 template <class Clock, class Duration> try_acquire_until(std::chrono::time_point<Clock,Duration> deadline)138 std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_until( 139 std::chrono::time_point<Clock, Duration> deadline) { 140 if (!lock_->try_lock_until(deadline)) { 141 return std::nullopt; 142 } 143 return BorrowedPointer<GuardedType, Lock>(*lock_, *object_); 144 } 145 146 private: 147 Lock* lock_; 148 GuardedType* object_; 149 }; 150 151 } // namespace pw::sync 152