blob: f6f00cfb03b479f520f01fb4b47df952a8962204 [file] [log] [blame]
// Copyright 2021 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once
#include <chrono>
#include <optional>
#include <type_traits>
#include "pw_assert/assert.h"
#include "pw_sync/lock_annotations.h"
#include "pw_sync/lock_traits.h"
#include "pw_sync/virtual_basic_lockable.h"
namespace pw::sync {
/// The `BorrowedPointer` is an RAII handle which wraps a pointer to a borrowed
/// object along with a held lock which is guarding the object. When destroyed,
/// the lock is released.
template <typename GuardedType, typename Lock = pw::sync::VirtualBasicLockable>
class BorrowedPointer {
public:
/// Release the lock on destruction.
~BorrowedPointer() {
if (lock_ != nullptr) {
lock_->unlock();
}
}
/// Move-constructs a ``BorrowedPointer<T>`` from a ``BorrowedPointer<U>``.
///
/// This allows not only pure move construction where
/// ``GuardedType == OtherType`` and ``Lock == OtherLock``, but also
/// converting construction where ``GuardedType`` is a base class of
/// ``OtherType`` and ``Lock`` is a base class of ``OtherLock``, like
/// ``BorrowedPointer<Base> base_ptr(derived_borrowable.acquire());`
///
/// @b Postcondition: The other BorrowedPointer is no longer valid and will
/// assert if the GuardedType is accessed.
template <typename OtherType, typename OtherLock>
BorrowedPointer(BorrowedPointer<OtherType, OtherLock>&& other)
: lock_(other.lock_), object_(other.object_) {
static_assert(
std::is_assignable_v<GuardedType*&, OtherType*>,
"Attempted to construct a BorrowedPointer from another whose "
"GuardedType* is not assignable to this object's GuardedType*.");
static_assert(std::is_assignable_v<Lock*&, OtherLock*>,
"Attempted to construct a BorrowedPointer from another whose "
"Lock* is not assignable to this object's Lock*.");
other.lock_ = nullptr;
other.object_ = nullptr;
}
/// Move-assigns a ``BorrowedPointer<T>`` from a ``BorrowedPointer<U>``.
///
/// This allows not only pure move construction where
/// ``GuardedType == OtherType`` and ``Lock == OtherLock``, but also
/// converting construction where ``GuardedType`` is a base class of
/// ``OtherType`` and ``Lock`` is a base class of ``OtherLock``, like
/// ``BorrowedPointer<Base> base_ptr = derived_borrowable.acquire();`
///
/// @b Postcondition: The other BorrowedPointer is no longer valid and will
/// assert if the GuardedType is accessed.
template <typename OtherType, typename OtherLock>
BorrowedPointer& operator=(BorrowedPointer<OtherType, OtherLock>&& other) {
static_assert(
std::is_assignable_v<GuardedType*&, OtherType*>,
"Attempted to construct a BorrowedPointer from another whose "
"GuardedType* is not assignable to this object's GuardedType*.");
static_assert(std::is_assignable_v<Lock*&, OtherLock*>,
"Attempted to construct a BorrowedPointer from another whose "
"Lock* is not assignable to this object's Lock*.");
lock_ = other.lock_;
object_ = other.object_;
other.lock_ = nullptr;
other.object_ = nullptr;
return *this;
}
BorrowedPointer(const BorrowedPointer&) = delete;
BorrowedPointer& operator=(const BorrowedPointer&) = delete;
/// Provides access to the borrowed object's members.
GuardedType* operator->() {
PW_ASSERT(object_ != nullptr); // Ensure this isn't a stale moved instance.
return object_;
}
/// Const overload
const GuardedType* operator->() const {
PW_ASSERT(object_ != nullptr); // Ensure this isn't a stale moved instance.
return object_;
}
/// Provides access to the borrowed object directly.
///
/// @rst
/// .. note::
/// The member of pointer member access operator, ``operator->()``, is
/// recommended over this API as this is prone to leaking references.
/// However, this is sometimes necessary.
///
/// .. warning:
/// Be careful not to leak references to the borrowed object!
/// @endrst
GuardedType& operator*() {
PW_ASSERT(object_ != nullptr); // Ensure this isn't a stale moved instance.
return *object_;
}
/// Const overload
const GuardedType& operator*() const {
PW_ASSERT(object_ != nullptr); // Ensure this isn't a stale moved instance.
return *object_;
}
private:
/// Allow BorrowedPointer creation inside of Borrowable's acquire methods.
template <typename G, typename L>
friend class Borrowable;
constexpr BorrowedPointer(Lock& lock, GuardedType& object)
: lock_(&lock), object_(&object) {}
Lock* lock_;
GuardedType* object_;
/// Allow converting move constructor and assignment to access fields of
/// this class.
///
/// Without this, ``BorrowedPointer<OtherType, OtherLock>`` would not be able
/// to access fields of ``BorrowedPointer<GuardedType, Lock>``.
template <typename OtherType, typename OtherLock>
friend class BorrowedPointer;
};
/// The `Borrowable` is a helper construct that enables callers to borrow an
/// object which is guarded by a lock.
///
/// Users who need access to the guarded object can ask to acquire a
/// `BorrowedPointer` which permits access while the lock is held.
///
/// Thread-safety analysis is not supported for this class, as the
/// `BorrowedPointer`s it creates conditionally releases the lock. See also
/// https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#no-conditionally-held-locks
///
/// This class is compatible with locks which comply with `BasicLockable`,
/// `Lockable`, and `TimedLockable` C++ named requirements.
///
/// `Borrowable<T>` is covariant with respect to `T`, so that `Borrowable<U>`
/// can be converted to `Borrowable<T>`, if `U` is a subclass of `T`.
///
/// `Borrowable` has pointer-like semantics and should be passed by value.
template <typename GuardedType, typename Lock = pw::sync::VirtualBasicLockable>
class Borrowable {
public:
static_assert(is_basic_lockable_v<Lock>,
"lock type must satisfy BasicLockable");
constexpr Borrowable(GuardedType& object, Lock& lock) noexcept
: lock_(&lock), object_(&object) {}
template <typename U>
constexpr Borrowable(const Borrowable<U, Lock>& other)
: lock_(other.lock_), object_(other.object_) {}
Borrowable(const Borrowable&) = default;
Borrowable& operator=(const Borrowable&) = default;
Borrowable(Borrowable&& other) = default;
Borrowable& operator=(Borrowable&& other) = default;
/// Blocks indefinitely until the object can be borrowed. Failures are fatal.
BorrowedPointer<GuardedType, Lock> acquire() const
PW_NO_LOCK_SAFETY_ANALYSIS {
lock_->lock();
return BorrowedPointer<GuardedType, Lock>(*lock_, *object_);
}
/// Tries to borrow the object in a non-blocking manner. Returns a
/// BorrowedPointer on success, otherwise `std::nullopt` (nothing).
template <int&... ExplicitArgumentBarrier,
typename T = Lock,
typename = std::enable_if_t<is_lockable_v<T>>>
std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire() const
PW_NO_LOCK_SAFETY_ANALYSIS {
if (!lock_->try_lock()) {
return std::nullopt;
}
return BorrowedPointer<GuardedType, Lock>(*lock_, *object_);
}
/// Tries to borrow the object. Blocks until the specified timeout has elapsed
/// or the object has been borrowed, whichever comes first. Returns a
/// `BorrowedPointer` on success, otherwise `std::nullopt` (nothing).
template <class Rep,
class Period,
int&... ExplicitArgumentBarrier,
typename T = Lock,
typename = std::enable_if_t<
is_lockable_for_v<T, std::chrono::duration<Rep, Period>>>>
std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_for(
std::chrono::duration<Rep, Period> timeout) const
PW_NO_LOCK_SAFETY_ANALYSIS {
if (!lock_->try_lock_for(timeout)) {
return std::nullopt;
}
return BorrowedPointer<GuardedType, Lock>(*lock_, *object_);
}
/// Tries to borrow the object. Blocks until the specified deadline has passed
/// or the object has been borrowed, whichever comes first. Returns a
/// `BorrowedPointer` on success, otherwise `std::nullopt` (nothing).
template <
class Clock,
class Duration,
int&... ExplicitArgumentBarrier,
typename T = Lock,
typename = std::enable_if_t<
is_lockable_until_v<T, std::chrono::time_point<Clock, Duration>>>>
std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_until(
std::chrono::time_point<Clock, Duration> deadline) const
PW_NO_LOCK_SAFETY_ANALYSIS {
if (!lock_->try_lock_until(deadline)) {
return std::nullopt;
}
return BorrowedPointer<GuardedType, Lock>(*lock_, *object_);
}
private:
Lock* lock_;
GuardedType* object_;
// Befriend all template instantiations of this class.
template <typename, typename>
friend class Borrowable;
};
} // namespace pw::sync