pw_sync: add Borrowable helper for transactional guarding

Adds a templated pw::sync::Borrowable wrapper which permits
objects or pointers/references to them to be wrapped with a lock
to enable threadsafety wrapping for data and/or objects with
transactional APIs where an internal lock may not make sense.

Change-Id: Ic59df609e933a571fd755b943f8ffcaccc4a5201
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/59120
Reviewed-by: Wyatt Hepler <hepler@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
diff --git a/pw_sync/BUILD.bazel b/pw_sync/BUILD.bazel
index 5687165..c8cbe1c 100644
--- a/pw_sync/BUILD.bazel
+++ b/pw_sync/BUILD.bazel
@@ -108,6 +108,17 @@
     ],
 )
 
+pw_cc_library(
+    name = "borrow",
+    hdrs = [
+        "public/pw_sync/borrow.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_assert",
+    ],
+)
+
 pw_cc_facade(
     name = "mutex_facade",
     hdrs = [
@@ -333,6 +344,18 @@
 )
 
 pw_cc_test(
+    name = "borrow_test",
+    srcs = [
+        "borrow_test.cc",
+    ],
+    deps = [
+        ":borrow",
+        "//pw_assert",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
     name = "binary_semaphore_facade_test",
     srcs = [
         "binary_semaphore_facade_test.cc",
diff --git a/pw_sync/BUILD.gn b/pw_sync/BUILD.gn
index 5b6fecf..67d0448 100644
--- a/pw_sync/BUILD.gn
+++ b/pw_sync/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_build/facade.gni")
 import("$dir_pw_build/target_types.gni")
+import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 import("backend.gni")
@@ -58,6 +59,12 @@
   public_deps = [ "$dir_pw_preprocessor" ]
 }
 
+pw_source_set("borrow") {
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_sync/borrow.h" ]
+  public_deps = [ dir_pw_assert ]
+}
+
 pw_facade("mutex") {
   backend = pw_sync_MUTEX_BACKEND
   public_configs = [ ":public_include_path" ]
@@ -152,6 +159,7 @@
 
 pw_test_group("tests") {
   tests = [
+    ":borrow_test",
     ":binary_semaphore_facade_test",
     ":counting_semaphore_facade_test",
     ":mutex_facade_test",
@@ -162,6 +170,14 @@
   ]
 }
 
+pw_test("borrow_test") {
+  sources = [ "borrow_test.cc" ]
+  deps = [
+    ":borrow",
+    dir_pw_assert,
+  ]
+}
+
 pw_test("binary_semaphore_facade_test") {
   enable_if = pw_sync_BINARY_SEMAPHORE_BACKEND != ""
   sources = [
diff --git a/pw_sync/borrow_test.cc b/pw_sync/borrow_test.cc
new file mode 100644
index 0000000..4782d78
--- /dev/null
+++ b/pw_sync/borrow_test.cc
@@ -0,0 +1,307 @@
+// 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.
+
+#include "pw_sync/borrow.h"
+
+#include <chrono>
+#include <ratio>
+
+#include "gtest/gtest.h"
+#include "pw_assert/check.h"
+
+namespace pw::sync {
+namespace {
+
+template <typename Lock>
+class BorrowableTest : public ::testing::Test {
+ protected:
+  static constexpr int kInitialValue = 42;
+
+  BorrowableTest()
+      : foo_{.value = kInitialValue}, borrowable_foo_(lock_, foo_) {}
+
+  void SetUp() override {
+    EXPECT_FALSE(lock_.locked());  // Ensure it's not locked on construction.
+  }
+
+  struct Foo {
+    int value;
+  };
+  Lock lock_;
+  Foo foo_;
+  Borrowable<Foo&, Lock> borrowable_foo_;
+};
+
+class BasicLockable {
+ public:
+  void lock() {
+    PW_CHECK(!locked_, "Recursive lock detected");
+    locked_ = true;
+  }
+
+  void unlock() {
+    PW_CHECK(locked_, "Unlock while unlocked detected");
+    locked_ = false;
+  }
+
+  bool locked() const { return locked_; }
+
+ protected:
+  bool locked_ = false;
+};
+
+using BorrowableBasicLockableTest = BorrowableTest<BasicLockable>;
+
+TEST_F(BorrowableBasicLockableTest, Acquire) {
+  {
+    BorrowedPointer<Foo, BasicLockable> borrowed_foo =
+        borrowable_foo_.acquire();
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(borrowed_foo->value, kInitialValue);
+    borrowed_foo->value = 13;
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+  EXPECT_EQ(foo_.value, 13);
+}
+
+TEST_F(BorrowableBasicLockableTest, RepeatedAcquire) {
+  {
+    BorrowedPointer<Foo, BasicLockable> borrowed_foo =
+        borrowable_foo_.acquire();
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(borrowed_foo->value, kInitialValue);
+    borrowed_foo->value = 13;
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+  {
+    BorrowedPointer<Foo, BasicLockable> borrowed_foo =
+        borrowable_foo_.acquire();
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(borrowed_foo->value, 13);
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+}
+
+TEST_F(BorrowableBasicLockableTest, Moveable) {
+  Borrowable<Foo&, BasicLockable> borrowable_foo = std::move(borrowable_foo_);
+  {
+    BorrowedPointer<Foo, BasicLockable> borrowed_foo = borrowable_foo.acquire();
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(borrowed_foo->value, kInitialValue);
+    borrowed_foo->value = 13;
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+}
+
+TEST_F(BorrowableBasicLockableTest, Copyable) {
+  const Borrowable<Foo&, BasicLockable>& other = borrowable_foo_;
+  Borrowable<Foo&, BasicLockable> borrowable_foo(other);
+  {
+    BorrowedPointer<Foo, BasicLockable> borrowed_foo = borrowable_foo.acquire();
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(borrowed_foo->value, kInitialValue);
+    borrowed_foo->value = 13;
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+}
+
+class Lockable : public BasicLockable {
+ public:
+  bool try_lock() {
+    if (locked()) {
+      return false;
+    }
+    locked_ = true;
+    return true;
+  }
+};
+
+using BorrowableLockableTest = BorrowableTest<Lockable>;
+
+TEST_F(BorrowableLockableTest, Acquire) {
+  {
+    BorrowedPointer<Foo, Lockable> borrowed_foo = borrowable_foo_.acquire();
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(borrowed_foo->value, kInitialValue);
+    borrowed_foo->value = 13;
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+  EXPECT_EQ(foo_.value, 13);
+}
+
+TEST_F(BorrowableLockableTest, RepeatedAcquire) {
+  {
+    BorrowedPointer<Foo, Lockable> borrowed_foo = borrowable_foo_.acquire();
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(borrowed_foo->value, kInitialValue);
+    borrowed_foo->value = 13;
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+  {
+    BorrowedPointer<Foo, Lockable> borrowed_foo = borrowable_foo_.acquire();
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(borrowed_foo->value, 13);
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+}
+
+TEST_F(BorrowableLockableTest, TryAcquireSuccess) {
+  {
+    std::optional<BorrowedPointer<Foo, Lockable>> maybe_borrowed_foo =
+        borrowable_foo_.try_acquire();
+    ASSERT_TRUE(maybe_borrowed_foo.has_value());
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(maybe_borrowed_foo.value()->value, kInitialValue);
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+}
+
+TEST_F(BorrowableLockableTest, TryAcquireFailure) {
+  lock_.lock();
+  EXPECT_TRUE(lock_.locked());
+  {
+    std::optional<BorrowedPointer<Foo, Lockable>> maybe_borrowed_foo =
+        borrowable_foo_.try_acquire();
+    EXPECT_FALSE(maybe_borrowed_foo.has_value());
+  }
+  EXPECT_TRUE(lock_.locked());
+  lock_.unlock();
+}
+
+struct Clock {
+  using rep = int64_t;
+  using period = std::micro;
+  using duration = std::chrono::duration<rep, period>;
+  using time_point = std::chrono::time_point<Clock>;
+};
+
+class TimedLockable : public Lockable {
+ public:
+  bool try_lock() {
+    if (locked()) {
+      return false;
+    }
+    locked_ = true;
+    return true;
+  }
+
+  bool try_lock_for(const Clock::duration&) { return try_lock(); }
+  bool try_lock_until(const Clock::time_point&) { return try_lock(); }
+};
+
+using BorrowableTimedLockableTest = BorrowableTest<TimedLockable>;
+
+TEST_F(BorrowableTimedLockableTest, Acquire) {
+  {
+    BorrowedPointer<Foo, TimedLockable> borrowed_foo =
+        borrowable_foo_.acquire();
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(borrowed_foo->value, kInitialValue);
+    borrowed_foo->value = 13;
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+  EXPECT_EQ(foo_.value, 13);
+}
+
+TEST_F(BorrowableTimedLockableTest, RepeatedAcquire) {
+  {
+    BorrowedPointer<Foo, TimedLockable> borrowed_foo =
+        borrowable_foo_.acquire();
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(borrowed_foo->value, kInitialValue);
+    borrowed_foo->value = 13;
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+  {
+    BorrowedPointer<Foo, TimedLockable> borrowed_foo =
+        borrowable_foo_.acquire();
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(borrowed_foo->value, 13);
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+}
+
+TEST_F(BorrowableTimedLockableTest, TryAcquireSuccess) {
+  {
+    std::optional<BorrowedPointer<Foo, TimedLockable>> maybe_borrowed_foo =
+        borrowable_foo_.try_acquire();
+    ASSERT_TRUE(maybe_borrowed_foo.has_value());
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(maybe_borrowed_foo.value()->value, kInitialValue);
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+}
+
+TEST_F(BorrowableTimedLockableTest, TryAcquireFailure) {
+  lock_.lock();
+  EXPECT_TRUE(lock_.locked());
+  {
+    std::optional<BorrowedPointer<Foo, TimedLockable>> maybe_borrowed_foo =
+        borrowable_foo_.try_acquire();
+    EXPECT_FALSE(maybe_borrowed_foo.has_value());
+  }
+  EXPECT_TRUE(lock_.locked());
+  lock_.unlock();
+}
+
+TEST_F(BorrowableTimedLockableTest, TryAcquireForSuccess) {
+  {
+    std::optional<BorrowedPointer<Foo, TimedLockable>> maybe_borrowed_foo =
+        borrowable_foo_.try_acquire_for(std::chrono::seconds(0));
+    ASSERT_TRUE(maybe_borrowed_foo.has_value());
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(maybe_borrowed_foo.value()->value, kInitialValue);
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+}
+
+TEST_F(BorrowableTimedLockableTest, TryAcquireForFailure) {
+  lock_.lock();
+  EXPECT_TRUE(lock_.locked());
+  {
+    std::optional<BorrowedPointer<Foo, TimedLockable>> maybe_borrowed_foo =
+        borrowable_foo_.try_acquire_for(std::chrono::seconds(0));
+    EXPECT_FALSE(maybe_borrowed_foo.has_value());
+  }
+  EXPECT_TRUE(lock_.locked());
+  lock_.unlock();
+}
+
+TEST_F(BorrowableTimedLockableTest, TryAcquireUntilSuccess) {
+  {
+    std::optional<BorrowedPointer<Foo, TimedLockable>> maybe_borrowed_foo =
+        borrowable_foo_.try_acquire_until(
+            Clock::time_point(std::chrono::seconds(0)));
+    ASSERT_TRUE(maybe_borrowed_foo.has_value());
+    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
+    EXPECT_EQ(maybe_borrowed_foo.value()->value, kInitialValue);
+  }
+  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
+}
+
+TEST_F(BorrowableTimedLockableTest, TryAcquireUntilFailure) {
+  lock_.lock();
+  EXPECT_TRUE(lock_.locked());
+  {
+    std::optional<BorrowedPointer<Foo, TimedLockable>> maybe_borrowed_foo =
+        borrowable_foo_.try_acquire_until(
+            Clock::time_point(std::chrono::seconds(0)));
+    EXPECT_FALSE(maybe_borrowed_foo.has_value());
+  }
+  EXPECT_TRUE(lock_.locked());
+  lock_.unlock();
+}
+
+}  // namespace
+}  // namespace pw::sync
diff --git a/pw_sync/docs.rst b/pw_sync/docs.rst
index 1308f00..7dcb97b 100644
--- a/pw_sync/docs.rst
+++ b/pw_sync/docs.rst
@@ -948,6 +948,218 @@
    Documents functions that dynamically check to see if a lock is held, and fail
    if it is not held.
 
+-----------------------------
+Critical Section Lock Helpers
+-----------------------------
+
+Borrowable
+==========
+The Borrowable is a helper construct that enables callers to borrow an object
+which is guarded by a lock, enabling a containerized style of external locking.
+
+Users who need access to the guarded object can ask to acquire a
+``BorrowedPointer`` which permits access while the lock is held.
+
+This class is compatible with locks which comply with
+`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_,
+`Lockable <https://en.cppreference.com/w/cpp/named_req/Lockable>`_, and
+`TimedLockable <https://en.cppreference.com/w/cpp/named_req/TimedLockable>`_
+C++ named requirements.
+
+External vs Internal locking
+----------------------------
+Before we explain why Borrowable is useful, it's important to understand the
+trade-offs when deciding on using internal and/or external locking.
+
+Internal locking is when the lock is hidden from the caller entirely and is used
+internally to the API. For example:
+
+.. code-block:: cpp
+
+  class BankAccount {
+   public:
+    void Deposit(int amount) {
+      std::lock_guard lock(mutex_);
+      balance_ += amount;
+    }
+
+    void Withdraw(int amount) {
+      std::lock_guard lock(mutex_);
+      balance_ -= amount;
+    }
+
+    void Balance() const {
+      std::lock_guard lock(mutex_);
+      return balance_;
+    }
+
+   private:
+    int balance_ PW_GUARDED_BY(mutex_);
+    pw::sync::Mutex mutex_;
+  };
+
+Internal locking guarantees that any concurrent calls to its public member
+functions don't corrupt an instance of that class. This is typically ensured by
+having each member function acquire a lock on the object upon entry. This way,
+for any instance, there can only be one member function call active at any
+moment, serializing the operations.
+
+One common issue that pops up is that member functions may have to call other
+member functions which also require locks. This typically results in a
+duplication of the public API into an internal mirror where the lock is already
+held. This along with having to modify every thread-safe public member function
+may results in an increased code size.
+
+However, with the per-method locking approach, it is not possible to perform a
+multi-method thread-safe transaction. For example, what if we only wanted to
+withdraw money if the balance was high enough? With the current API there would
+be a risk that money is withdrawn after we've checked the balance.
+
+This is usually why external locking is used. This is when the lock is exposed
+to the caller and may be used externally to the public API. External locking
+can take may forms which may even include mixing internal and external locking.
+In its most simplistic form it is an external lock used along side each
+instance, e.g.:
+
+.. code-block:: cpp
+
+  class BankAccount {
+   public:
+    void Deposit(int amount) {
+      balance_ += amount;
+    }
+
+    void Withdraw(int amount) {
+      balance_ -= amount;
+    }
+
+    void Balance() const {
+      return balance_;
+    }
+
+   private:
+    int balance_;
+  };
+
+  pw::sync::Mutex bobs_account_mutex;
+  BankAccount bobs_account PW_GUARDED_BY(bobs_account_mutex);
+
+The lock is acquired before the bank account is used for a transaction. In
+addition, we do not have to modify every public function and its trivial to
+call other public member functions from a public member function. However, as
+you can imagine instantiating and passing around the instances and their locks
+can become error prone.
+
+This is why ``Borrowable`` exists.
+
+Why use Borrowable?
+-------------------
+``Borrowable`` offers code-size efficient way to enable external locking that is
+easy and safe to use. It is effectively a container which holds references to a
+protected instance and its lock which provides RAII-style access.
+
+.. code-block:: cpp
+
+  pw::sync::Mutex bobs_account_mutex;
+  BankAccount bobs_account PW_GUARDED_BY(bobs_account_mutex);
+  pw::sync::Borrowable<BankAccount&, pw::sync::Mutex> bobs_acount(
+      bobs_account_mutex, bobs_account);
+
+This construct is useful when sharing objects or data which are transactional in
+nature where making individual operations threadsafe is insufficient. See the
+section on internal vs external locking tradeoffs above.
+
+It can also offer a code-size and stack-usage efficient way to separate timeout
+constraints between the acquiring of the shared object and timeouts used for the
+shared object's API. For example, imagine you have an I2c bus which is used by
+several threads and you'd like to specify an ACK timeout of 50ms. It'd be ideal
+if the duration it takes to gain exclusive access to the I2c bus does not eat
+into the ACK timeout you'd like to use for the transaction. Borrowable can help
+you do exactly this if you provide access to the I2c bus through a
+``Borrowable``.
+
+C++
+---
+.. cpp:class:: template <typename GuardedType, typename Lock> pw::sync::BorrowedPointer
+
+  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.
+
+  This object is moveable, but not copyable.
+
+  .. cpp:function:: GuardedType* operator->()
+
+     Provides access to the borrowed object's members.
+
+  .. cpp:function:: GuardedType& operator*()
+
+     Provides access to the borrowed object directly.
+
+     **Warning:** 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.
+
+.. cpp:class:: template <typename GuardedReference, typename Lock> pw::sync::Borrowable
+
+  .. cpp:function:: BorrowedPointer<GuardedType, Lock> acquire()
+
+     Blocks indefinitely until the object can be borrowed. Failures are fatal.
+
+  .. cpp:function:: std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire()
+
+     Tries to borrow the object in a non-blocking manner. Returns a
+     BorrowedPointer on success, otherwise std::nullopt (nothing).
+
+  .. cpp:function:: template <class Rep, class Period> std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_for(std::chrono::duration<Rep, Period> timeout)
+
+     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).
+
+  .. cpp:function:: template <class Rep, class Period> std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_until(std::chrono::duration<Rep, Period> deadline)
+
+     Tries to borrow the object. Blocks until the specified deadline has been
+     reached or the object has been borrowed, whichever comes first. Returns a
+     BorrowedPointer on success, otherwise std::nullopt (nothing).
+
+Example in C++
+^^^^^^^^^^^^^^
+
+.. code-block:: cpp
+
+  #include <chrono>
+
+  #include "pw_bytes/span.h"
+  #include "pw_i2c/initiator.h"
+  #include "pw_status/try.h"
+  #include "pw_status/result.h"
+  #include "pw_sync/borrow.h"
+  #include "pw_sync/mutex.h"
+
+  class ExampleI2c : public pw::i2c::Initiator;
+
+  pw::sync::Mutex i2c_mutex;
+  ExampleI2c i2c;
+  pw::sync::Borrowable<ExampleI2c&, pw::sync::Mutex> borrowable_i2c(
+      i2c_mutex, i2c);
+
+  pw::Result<ConstByteSpan> ReadI2cData(ByteSpan buffer) {
+    // Block indefinitely waiting to borrow the i2c bus.
+    pw::sync::BorrowedPointer<ExampleI2c, pw::sync::Mutex> borrowed_i2c =
+        borrowable_i2c.acquire();
+
+    // Execute a sequence of transactions to get the needed data.
+    PW_TRY(borrowed_i2c->WriteFor(kFirstWrite, std::chrono::milliseconds(50)));
+    PW_TRY(borrowed_i2c->WriteReadFor(kSecondWrite, buffer,
+                                      std::chrono::milliseconds(10)));
+
+    // Borrowed i2c pointer is returned when the scope exits.
+    return buffer;
+  }
+
 --------------------
 Signaling Primitives
 --------------------
diff --git a/pw_sync/public/pw_sync/borrow.h b/pw_sync/public/pw_sync/borrow.h
new file mode 100644
index 0000000..4ae02ed
--- /dev/null
+++ b/pw_sync/public/pw_sync/borrow.h
@@ -0,0 +1,152 @@
+// 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 <mutex>
+#include <optional>
+#include <type_traits>
+
+#include "pw_assert/assert.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>
+class BorrowedPointer {
+ public:
+  // Release the lock on destruction.
+  ~BorrowedPointer() = default;
+
+  // This object is moveable, but not copyable.
+  //
+  // Postcondition: The other BorrowedPointer is no longer valid and will assert
+  //     if the GuardedType is accessed.
+  BorrowedPointer(BorrowedPointer&& other)
+      : unique_lock_(std::move(other.unique_lock_)), object_(other.object_) {
+    other.object_ = nullptr;
+  }
+  BorrowedPointer& operator=(BorrowedPointer&& other) {
+    unique_lock_ = std::move(other.unique_lock_);
+    object_ = other.object_;
+    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_;
+  }
+
+  // Provides access to the borrowed object directly.
+  //
+  // 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!
+  GuardedType& operator*() {
+    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;
+
+  BorrowedPointer(std::unique_lock<Lock> unique_lock, GuardedType* object)
+      : unique_lock_(std::move(unique_lock)), object_(object) {}
+
+  std::unique_lock<Lock> unique_lock_;
+  GuardedType* object_;
+};
+
+// 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.
+//
+// This class is compatible with locks which comply with BasicLockable,
+// Lockable, and TimedLockable C++ named requirements.
+template <typename GuardedReference, typename Lock>
+class Borrowable {
+ public:
+  static_assert(std::is_reference<GuardedReference>::value,
+                "GuardedReference must be a reference type");
+
+  using guarded_type = typename std::remove_reference<GuardedReference>::type;
+
+  constexpr Borrowable(Lock& lock, GuardedReference object)
+      : lock_(&lock), object_(&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<guarded_type, Lock> acquire() {
+    std::unique_lock unique_lock(*lock_);
+    return BorrowedPointer<guarded_type, Lock>(std::move(unique_lock), object_);
+  }
+
+  // Tries to borrow the object in a non-blocking manner. Returns a
+  // BorrowedPointer on success, otherwise std::nullopt (nothing).
+  std::optional<BorrowedPointer<guarded_type, Lock>> try_acquire() {
+    std::unique_lock unique_lock(*lock_, std::defer_lock);
+    if (!unique_lock.try_lock()) {
+      return std::nullopt;
+    }
+    return BorrowedPointer<guarded_type, Lock>(std::move(unique_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>
+  std::optional<BorrowedPointer<guarded_type, Lock>> try_acquire_for(
+      std::chrono::duration<Rep, Period> timeout) {
+    std::unique_lock unique_lock(*lock_, std::defer_lock);
+    if (!unique_lock.try_lock_for(timeout)) {
+      return std::nullopt;
+    }
+    return BorrowedPointer<guarded_type, Lock>(std::move(unique_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>
+  std::optional<BorrowedPointer<guarded_type, Lock>> try_acquire_until(
+      std::chrono::time_point<Clock, Duration> deadline) {
+    std::unique_lock unique_lock(*lock_, std::defer_lock);
+    if (!unique_lock.try_lock_until(deadline)) {
+      return std::nullopt;
+    }
+    return BorrowedPointer<guarded_type, Lock>(std::move(unique_lock), object_);
+  }
+
+ private:
+  Lock* lock_;
+  guarded_type* object_;
+};
+
+}  // namespace pw::sync