pw_sync: Add ConditionVariable facade

ConditionVariable mimics std::condition_variable in terms of API and
semantics. There is one provided backend in pw_sync_stl.

Change-Id: Iae1ddbf17bf2084dbfe57399cce1298ef16c5903
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/95001
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Taylor Cramer <cramertj@google.com>
diff --git a/pw_sync/BUILD.bazel b/pw_sync/BUILD.bazel
index cd0c326..079d8ca 100644
--- a/pw_sync/BUILD.bazel
+++ b/pw_sync/BUILD.bazel
@@ -388,6 +388,42 @@
     includes = ["public"],
 )
 
+pw_cc_facade(
+    name = "condition_variable_facade",
+    hdrs = [
+        "public/pw_sync/condition_variable.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_chrono:system_clock",
+        "//pw_sync:mutex",
+    ],
+)
+
+# TODO(b/228998350): This needs to be instantiated for each platform that
+# provides an implementation of $dir_pw_thread:test_threads and
+# $dir_pw_sync:condition_variable.
+# pw_cc_library(
+#     name = "condition_variable_test",
+#     srcs = ["condition_variable_test.cc"],
+#     deps = [
+#         ":condition_variable_facade",
+#         "//pw_containers:vector",
+#         "//pw_sync:mutex",
+#         "//pw_sync:timed_thread_notification",
+#         "//pw_thread:sleep",
+#         "//pw_thread:test_threads_header",
+#         "//pw_thread:thread",
+#         "//pw_unit_test",
+#     ],
+# )
+#
+# Filegroup to mark `condition_variable_test.cc` as used for the linter:
+filegroup(
+    name = "condition_variable_test_filegroup",
+    srcs = ["condition_variable_test.cc"],
+)
+
 pw_cc_test(
     name = "borrow_test",
     srcs = [
diff --git a/pw_sync/BUILD.gn b/pw_sync/BUILD.gn
index 1f4dbf6..2da99c0 100644
--- a/pw_sync/BUILD.gn
+++ b/pw_sync/BUILD.gn
@@ -185,6 +185,16 @@
   public_configs = [ ":public_include_path" ]
 }
 
+pw_facade("condition_variable") {
+  backend = pw_sync_CONDITION_VARIABLE_BACKEND
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_sync/condition_variable.h" ]
+  public_deps = [
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_sync:mutex",
+  ]
+}
+
 pw_test_group("tests") {
   tests = [
     ":borrow_test",
@@ -304,6 +314,23 @@
   ]
 }
 
+# This needs to be instantiated per platform that provides
+# an implementation of $dir_pw_thread:test_threads and
+# $dir_pw_sync:condition_variable.
+pw_source_set("condition_variable_test") {
+  sources = [ "condition_variable_test.cc" ]
+  deps = [
+    ":condition_variable",
+    "$dir_pw_containers:vector",
+    "$dir_pw_sync:mutex",
+    "$dir_pw_sync:timed_thread_notification",
+    "$dir_pw_thread:sleep",
+    "$dir_pw_thread:test_threads",
+    "$dir_pw_thread:thread",
+    "$dir_pw_unit_test",
+  ]
+}
+
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_sync/CMakeLists.txt b/pw_sync/CMakeLists.txt
index 596a7f6..192a0b1 100644
--- a/pw_sync/CMakeLists.txt
+++ b/pw_sync/CMakeLists.txt
@@ -173,6 +173,16 @@
     public
 )
 
+pw_add_facade(pw_sync.condition_variable
+  HEADERS
+    public/pw_sync/condition_variable.h
+  PUBLIC_INCLUDES
+    public
+  PUBLIC_DEPS
+    pw_chrono.system_clock
+    pw_sync.mutex
+)
+
 pw_add_test(pw_sync.borrow_test
   SOURCES
     borrow_test.cc
@@ -284,3 +294,17 @@
       pw_sync
   )
 endif()
+
+pw_add_module_library(pw_sync.condition_variable_test
+  PUBLIC_DEPS
+    pw_sync.condition_variable
+    pw_containers.vector
+    pw_sync.mutex
+    pw_sync.timed_thread_notification
+    pw_thread.sleep
+    pw_thread.test_threads
+    pw_thread.thread
+    pw_unit_test
+  SOURCES
+    condition_variable_test.cc
+)
diff --git a/pw_sync/backend.gni b/pw_sync/backend.gni
index 6663972..2327640 100644
--- a/pw_sync/backend.gni
+++ b/pw_sync/backend.gni
@@ -16,6 +16,9 @@
   # Backend for the pw_sync module's binary semaphore.
   pw_sync_BINARY_SEMAPHORE_BACKEND = ""
 
+  # Backend for the pw_sync module's condition variable.
+  pw_sync_CONDITION_VARIABLE_BACKEND = ""
+
   # Backend for the pw_sync module's counting semaphore.
   pw_sync_COUNTING_SEMAPHORE_BACKEND = ""
 
diff --git a/pw_sync/condition_variable_test.cc b/pw_sync/condition_variable_test.cc
new file mode 100644
index 0000000..c0ddc4d
--- /dev/null
+++ b/pw_sync/condition_variable_test.cc
@@ -0,0 +1,356 @@
+// Copyright 2022 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/condition_variable.h"
+
+#include <chrono>
+#include <functional>
+
+#include "gtest/gtest.h"
+#include "pw_containers/vector.h"
+#include "pw_sync/mutex.h"
+#include "pw_sync/timed_thread_notification.h"
+#include "pw_thread/sleep.h"
+#include "pw_thread/test_threads.h"
+#include "pw_thread/thread.h"
+
+namespace pw::sync {
+namespace {
+
+using namespace std::chrono_literals;
+
+// A timeout for tests where successful behaviour involves waiting.
+constexpr auto kRequiredTimeout = 100ms;
+
+// Maximum extra wait time allowed for test that ensure something waits for
+// `kRequiredTimeout`.
+const auto kAllowedSlack = kRequiredTimeout * 1.5;
+
+// A timeout that should only be hit if something goes wrong.
+constexpr auto kFailureTimeout = 5s;
+
+using StateLock = std::unique_lock<Mutex>;
+
+struct ThreadInfo {
+  explicit ThreadInfo(int id) : thread_id(id) {}
+
+  // waiting_notifier is signalled in predicates to indicate that the predicate
+  // has been evaluated. This guarantees (via insider information) that the
+  // thread will acquire the internal ThreadNotification.
+  TimedThreadNotification waiting_notifier;
+
+  // Signals when the worker thread is done.
+  TimedThreadNotification done_notifier;
+
+  // The result of the predicate the worker thread uses with wait*(). Set from
+  // the main test thread and read by the worker thread.
+  bool predicate_result = false;
+
+  // Stores the result of ConditionVariable::wait_for() or ::wait_until() for
+  // use in test asserts.
+  bool wait_result = false;
+
+  // For use in recording the order in which threads block on a condition.
+  const int thread_id;
+
+  // Returns a function which will return the current value of
+  //`predicate_result` and release `waiting_notifier`.
+  std::function<bool()> Predicate() {
+    return [this]() {
+      bool result = this->predicate_result;
+      this->waiting_notifier.release();
+      return result;
+    };
+  }
+};
+
+// A `ThreadCore` implementation that delegates to an `std::function`.
+class LambdaThreadCore : public pw::thread::ThreadCore {
+ public:
+  explicit LambdaThreadCore(std::function<void()> work)
+      : work_(std::move(work)) {}
+
+ private:
+  void Run() override { work_(); }
+
+  std::function<void()> work_;
+};
+
+class LambdaThread {
+ public:
+  // Starts a new thread which runs `work`, joining the thread on destruction.
+  explicit LambdaThread(
+      std::function<void()> work,
+      pw::thread::Options options = pw::thread::test::TestOptionsThread0())
+      : thread_core_(std::move(work)), thread_(options, thread_core_) {}
+  ~LambdaThread() { thread_.join(); }
+  LambdaThread(const LambdaThread&) = delete;
+  LambdaThread(LambdaThread&&) = delete;
+  LambdaThread& operator=(const LambdaThread&) = delete;
+  LambdaThread&& operator=(LambdaThread&&) = delete;
+
+ private:
+  LambdaThreadCore thread_core_;
+  pw::thread::Thread thread_;
+};
+
+TEST(Wait, PredicateTrueNoWait) {
+  Mutex mutex;
+  ConditionVariable condvar;
+  ThreadInfo thread_info(0);
+
+  LambdaThread thread([&mutex, &condvar, &info = thread_info] {
+    StateLock l{mutex};
+    condvar.wait(l, [] { return true; });
+
+    info.done_notifier.release();
+  });
+  EXPECT_TRUE(thread_info.done_notifier.try_acquire_for(kFailureTimeout));
+}
+
+TEST(NotifyOne, BlocksUntilSignaled) {
+  Mutex mutex;
+  ConditionVariable condvar;
+  ThreadInfo thread_info(0);
+
+  LambdaThread thread([&mutex, &condvar, &info = thread_info] {
+    StateLock l{mutex};
+    condvar.wait(l, info.Predicate());
+    info.done_notifier.release();
+  });
+  ASSERT_TRUE(thread_info.waiting_notifier.try_acquire_for(kFailureTimeout));
+  {
+    StateLock l{mutex};
+    thread_info.predicate_result = true;
+  }
+  condvar.notify_one();
+  ASSERT_TRUE(thread_info.done_notifier.try_acquire_for(kFailureTimeout));
+}
+
+TEST(NotifyOne, UnblocksOne) {
+  Mutex mutex;
+  ConditionVariable condvar;
+  std::array<ThreadInfo, 2> thread_info = {ThreadInfo(0), ThreadInfo(1)};
+  pw::Vector<int, 2> wait_order;
+
+  LambdaThread thread_1(
+      [&mutex, &condvar, &info = thread_info[0], &wait_order] {
+        StateLock l{mutex};
+        auto predicate = [&info, &wait_order] {
+          wait_order.push_back(info.thread_id);
+          auto result = info.predicate_result;
+          info.waiting_notifier.release();
+          return result;
+        };
+        condvar.wait(l, predicate);
+        info.done_notifier.release();
+      },
+      pw::thread::test::TestOptionsThread0());
+  LambdaThread thread_2(
+      [&mutex, &condvar, &info = thread_info[1], &wait_order] {
+        StateLock l{mutex};
+        auto predicate = [&info, &wait_order] {
+          wait_order.push_back(info.thread_id);
+          auto result = info.predicate_result;
+          info.waiting_notifier.release();
+          return result;
+        };
+        condvar.wait(l, predicate);
+        info.done_notifier.release();
+      },
+      pw::thread::test::TestOptionsThread1());
+
+  ASSERT_TRUE(thread_info[0].waiting_notifier.try_acquire_for(kFailureTimeout));
+  ASSERT_TRUE(thread_info[1].waiting_notifier.try_acquire_for(kFailureTimeout));
+
+  {
+    StateLock l{mutex};
+    thread_info[1].predicate_result = true;
+    thread_info[0].predicate_result = true;
+  }
+  condvar.notify_one();
+  ASSERT_TRUE(thread_info[wait_order[0]].done_notifier.try_acquire_for(
+      kFailureTimeout));
+  ASSERT_FALSE(thread_info[wait_order[0]].done_notifier.try_acquire());
+  condvar.notify_one();
+  ASSERT_TRUE(thread_info[wait_order[1]].done_notifier.try_acquire_for(
+      kFailureTimeout));
+}
+
+TEST(NotifyAll, UnblocksMultiple) {
+  Mutex mutex;
+  ConditionVariable condvar;
+  std::array<ThreadInfo, 2> thread_info = {ThreadInfo(0), ThreadInfo(1)};
+
+  LambdaThread thread_1(
+      [&mutex, &condvar, &info = thread_info[0]] {
+        StateLock l{mutex};
+        condvar.wait(l, info.Predicate());
+        info.done_notifier.release();
+      },
+      pw::thread::test::TestOptionsThread0());
+  LambdaThread thread_2(
+      [&mutex, &condvar, &info = thread_info[1]] {
+        StateLock l{mutex};
+        condvar.wait(l, info.Predicate());
+        info.done_notifier.release();
+      },
+      pw::thread::test::TestOptionsThread1());
+
+  ASSERT_TRUE(thread_info[0].waiting_notifier.try_acquire_for(kFailureTimeout));
+  ASSERT_TRUE(thread_info[1].waiting_notifier.try_acquire_for(kFailureTimeout));
+  {
+    StateLock l{mutex};
+    thread_info[0].predicate_result = true;
+    thread_info[1].predicate_result = true;
+  }
+  condvar.notify_all();
+  ASSERT_TRUE(thread_info[0].done_notifier.try_acquire_for(kFailureTimeout));
+  ASSERT_TRUE(thread_info[1].done_notifier.try_acquire_for(kFailureTimeout));
+}
+
+TEST(WaitFor, ReturnsTrueIfSignalled) {
+  Mutex mutex;
+  ConditionVariable condvar;
+  ThreadInfo thread_info(0);
+
+  LambdaThread thread([&mutex, &condvar, &info = thread_info] {
+    StateLock l{mutex};
+    info.wait_result = condvar.wait_for(l, kFailureTimeout, info.Predicate());
+    info.done_notifier.release();
+  });
+
+  ASSERT_TRUE(thread_info.waiting_notifier.try_acquire_for(kFailureTimeout));
+  {
+    StateLock l{mutex};
+    thread_info.predicate_result = true;
+  }
+  condvar.notify_one();
+  ASSERT_TRUE(thread_info.done_notifier.try_acquire_for(kFailureTimeout));
+  ASSERT_TRUE(thread_info.wait_result);
+}
+
+TEST(WaitFor, ReturnsFalseIfTimesOut) {
+  Mutex mutex;
+  ConditionVariable condvar;
+  ThreadInfo thread_info(0);
+
+  LambdaThread thread([&mutex, &condvar, &info = thread_info] {
+    StateLock l{mutex};
+    info.wait_result = condvar.wait_for(l, 0ms, info.Predicate());
+    info.done_notifier.release();
+  });
+
+  ASSERT_TRUE(thread_info.waiting_notifier.try_acquire_for(kFailureTimeout));
+  ASSERT_TRUE(thread_info.done_notifier.try_acquire_for(kFailureTimeout));
+  ASSERT_FALSE(thread_info.wait_result);
+}
+
+// NOTE: This test waits even in successful circumstances.
+TEST(WaitFor, TimeoutApproximatelyCorrect) {
+  Mutex mutex;
+  ConditionVariable condvar;
+  ThreadInfo thread_info(0);
+  pw::chrono::SystemClock::duration wait_duration{};
+
+  LambdaThread thread([&mutex, &condvar, &info = thread_info, &wait_duration] {
+    StateLock l{mutex};
+    auto start = pw::chrono::SystemClock::now();
+    info.wait_result = condvar.wait_for(l, kRequiredTimeout, info.Predicate());
+    wait_duration = pw::chrono::SystemClock::now() - start;
+    info.done_notifier.release();
+  });
+
+  ASSERT_TRUE(thread_info.waiting_notifier.try_acquire_for(kFailureTimeout));
+  // Wake up thread multiple times. Make sure the timeout is observed.
+  for (int i = 0; i < 5; ++i) {
+    condvar.notify_one();
+    pw::this_thread::sleep_for(kRequiredTimeout / 6);
+  }
+  ASSERT_TRUE(thread_info.done_notifier.try_acquire_for(kFailureTimeout));
+  EXPECT_FALSE(thread_info.wait_result);
+  EXPECT_GE(wait_duration, kRequiredTimeout);
+  EXPECT_LT(wait_duration, (kRequiredTimeout + kAllowedSlack));
+}
+
+TEST(WaitUntil, ReturnsTrueIfSignalled) {
+  Mutex mutex;
+  ConditionVariable condvar;
+  ThreadInfo thread_info(0);
+
+  LambdaThread thread([&mutex, &condvar, &info = thread_info] {
+    StateLock l{mutex};
+    info.wait_result = condvar.wait_until(
+        l, pw::chrono::SystemClock::now() + kRequiredTimeout, info.Predicate());
+    info.done_notifier.release();
+  });
+
+  ASSERT_TRUE(thread_info.waiting_notifier.try_acquire_for(kFailureTimeout));
+  {
+    StateLock l{mutex};
+    thread_info.predicate_result = true;
+  }
+  condvar.notify_one();
+  ASSERT_TRUE(thread_info.done_notifier.try_acquire_for(kFailureTimeout));
+  ASSERT_TRUE(thread_info.wait_result);
+}
+
+// NOTE: This test waits even in successful circumstances.
+TEST(WaitUntil, ReturnsFalseIfTimesOut) {
+  Mutex mutex;
+  ConditionVariable condvar;
+  ThreadInfo thread_info(0);
+
+  LambdaThread thread([&mutex, &condvar, &info = thread_info] {
+    StateLock l{mutex};
+    info.wait_result = condvar.wait_until(
+        l, pw::chrono::SystemClock::now() + kRequiredTimeout, info.Predicate());
+    info.done_notifier.release();
+  });
+
+  ASSERT_TRUE(thread_info.waiting_notifier.try_acquire_for(kFailureTimeout));
+  ASSERT_TRUE(thread_info.done_notifier.try_acquire_for(kFailureTimeout));
+  ASSERT_FALSE(thread_info.wait_result);
+}
+
+// NOTE: This test waits even in successful circumstances.
+TEST(WaitUntil, TimeoutApproximatelyCorrect) {
+  Mutex mutex;
+  ConditionVariable condvar;
+  ThreadInfo thread_info(0);
+  pw::chrono::SystemClock::duration wait_duration{};
+
+  LambdaThread thread([&mutex, &condvar, &info = thread_info, &wait_duration] {
+    StateLock l{mutex};
+    auto start = pw::chrono::SystemClock::now();
+    info.wait_result = condvar.wait_until(
+        l, pw::chrono::SystemClock::now() + kRequiredTimeout, info.Predicate());
+    wait_duration = pw::chrono::SystemClock::now() - start;
+    info.done_notifier.release();
+  });
+
+  ASSERT_TRUE(thread_info.waiting_notifier.try_acquire_for(kFailureTimeout));
+  // Wake up thread multiple times. Make sure the timeout is observed.
+  for (int i = 0; i < 5; ++i) {
+    condvar.notify_one();
+    pw::this_thread::sleep_for(kRequiredTimeout / 6);
+  }
+  ASSERT_TRUE(thread_info.done_notifier.try_acquire_for(kFailureTimeout));
+  ASSERT_FALSE(thread_info.wait_result);
+  ASSERT_GE(wait_duration, kRequiredTimeout);
+  ASSERT_LE(wait_duration, kRequiredTimeout + kAllowedSlack);
+}
+
+}  // namespace
+}  // namespace pw::sync
diff --git a/pw_sync/docs.rst b/pw_sync/docs.rst
index 8673d13..a4fc780 100644
--- a/pw_sync/docs.rst
+++ b/pw_sync/docs.rst
@@ -1856,11 +1856,7 @@
 
 Conditional Variables
 =====================
-We've decided for now to skip on conditional variables. These are constructs,
-which are typically not natively available on RTOSes. CVs would have to be
-backed by a multiple hidden semaphore(s) in addition to the explicit public
-mutex. In other words a CV typically ends up as a a composition of
-synchronization primitives on RTOSes. That being said, one could implement them
-using our semaphore and mutex layers and we may consider providing this in the
-future. However for most of our resource constrained customers they will mostly
-likely be using semaphores more often than CVs.
+``pw::sync::ConditionVariable`` provides a condition variable implementation
+that provides semantics and an API very similar to `std::condition_variable
+<https://en.cppreference.com/w/cpp/thread/condition_variable>`_ in the C++
+Standard Library.
diff --git a/pw_sync/public/pw_sync/condition_variable.h b/pw_sync/public/pw_sync/condition_variable.h
new file mode 100644
index 0000000..7aa16ae
--- /dev/null
+++ b/pw_sync/public/pw_sync/condition_variable.h
@@ -0,0 +1,91 @@
+// Copyright 2022 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 <mutex>
+
+#include "pw_chrono/system_clock.h"
+#include "pw_sync/mutex.h"
+#include "pw_sync_backend/condition_variable_native.h"
+
+namespace pw::sync {
+
+// ConditionVariable represents a condition variable using an API very similar
+// to std::condition_variable. Implementations of this class should share the
+// same semantics as std::condition_variable.
+class ConditionVariable {
+ public:
+  using native_handle_type = backend::NativeConditionVariableHandle;
+
+  ConditionVariable() = default;
+
+  ConditionVariable(const ConditionVariable&) = delete;
+
+  ~ConditionVariable() = default;
+
+  ConditionVariable& operator=(const ConditionVariable&) = delete;
+
+  // Wake up one thread waiting on a condition.
+  //
+  // The thread will re-evaluate the condition via its predicate. Threads where
+  // the predicate evaluates false will go back to waiting. The new order of
+  // waiting threads is undefined.
+  void notify_one();
+
+  // Wake up all threads waiting on the condition variable.
+  //
+  // Woken threads will re-evaluate the condition via their predicate. Threads
+  // where the predicate evaluates false will go back to waiting. The new order
+  // of waiting threads is undefined.
+  void notify_all();
+
+  // Block the current thread until predicate() == true.
+  //
+  // Precondition: the provided lock must be locked.
+  template <typename Predicate>
+  void wait(std::unique_lock<Mutex>& lock, Predicate predicate);
+
+  // Block the current thread for a duration up to the given timeout or
+  // until predicate() == true whichever comes first.
+  //
+  // Returns: true if predicate() == true.
+  //          false if timeout expired.
+  //
+  // Precondition: the provided lock must be locked.
+  template <typename Predicate>
+  bool wait_for(std::unique_lock<Mutex>& lock,
+                pw::chrono::SystemClock::duration timeout,
+                Predicate predicate);
+
+  // Block the current thread until given point in time or until predicate() ==
+  // true whichever comes first.
+  //
+  // Returns: true if predicate() == true.
+  //          false if the deadline was reached.
+  //
+  // Precondition: the provided lock must be locked.
+  template <typename Predicate>
+  bool wait_until(std::unique_lock<Mutex>& lock,
+                  pw::chrono::SystemClock::time_point deadline,
+                  Predicate predicate);
+
+  native_handle_type native_handle();
+
+ private:
+  backend::NativeConditionVariable native_type_;
+};
+
+}  // namespace pw::sync
+
+#include "pw_sync_backend/condition_variable_inline.h"
diff --git a/pw_sync_stl/BUILD.bazel b/pw_sync_stl/BUILD.bazel
index 19eca46..d0e62b8 100644
--- a/pw_sync_stl/BUILD.bazel
+++ b/pw_sync_stl/BUILD.bazel
@@ -193,3 +193,36 @@
         "//pw_sync:yield_core",
     ],
 )
+
+pw_cc_library(
+    name = "condition_variable_headers",
+    hdrs = [
+        "public/pw_sync_stl/condition_variable_inline.h",
+        "public/pw_sync_stl/condition_variable_native.h",
+        "public_overrides/pw_sync_backend/condition_variable_inline.h",
+        "public_overrides/pw_sync_backend/condition_variable_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
+)
+
+pw_cc_library(
+    name = "condition_variable",
+    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
+    deps = [
+        ":condition_variable_headers",
+        "//pw_sync:condition_variable_facade",
+    ],
+)
+
+# TODO(b/228998350): Figure out how to conditionally enable this test like GN
+# pw_cc_test(
+#     name = "condition_variable_test",
+#     deps = [
+#         "//pw_sync:condition_variable_test",
+#         "//pw_thread_stl:test_threads",
+#     ]
+# )
diff --git a/pw_sync_stl/BUILD.gn b/pw_sync_stl/BUILD.gn
index 984f734..e5b2d5d 100644
--- a/pw_sync_stl/BUILD.gn
+++ b/pw_sync_stl/BUILD.gn
@@ -18,6 +18,8 @@
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_sync/backend.gni")
+import("$dir_pw_thread/backend.gni")
 import("$dir_pw_unit_test/test.gni")
 
 config("public_include_path") {
@@ -150,9 +152,36 @@
   ]
 }
 
+# This target provides the backend for pw::sync::ConditionVariable.
+pw_source_set("condition_variable_backend") {
+  allow_circular_includes_from = [ "$dir_pw_sync:condition_variable.facade" ]
+  public_configs = [
+    ":public_include_path",
+    ":backend_config",
+  ]
+  public = [
+    "public/pw_sync_stl/condition_variable_inline.h",
+    "public/pw_sync_stl/condition_variable_native.h",
+    "public_overrides/pw_sync_backend/condition_variable_inline.h",
+    "public_overrides/pw_sync_backend/condition_variable_native.h",
+  ]
+  public_deps = [ "$dir_pw_sync:condition_variable.facade" ]
+}
+
+pw_test("condition_variable_test") {
+  enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_stl:thread" &&
+              pw_sync_CONDITION_VARIABLE_BACKEND ==
+              "$dir_pw_sync_stl:condition_variable"
+  deps = [
+    "$dir_pw_sync:condition_variable_test",
+    "$dir_pw_thread_stl:test_threads",
+  ]
+}
+
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
 
 pw_test_group("tests") {
+  tests = [ ":condition_variable_test" ]
 }
diff --git a/pw_sync_stl/CMakeLists.txt b/pw_sync_stl/CMakeLists.txt
index e2d4433..8e4fbbc 100644
--- a/pw_sync_stl/CMakeLists.txt
+++ b/pw_sync_stl/CMakeLists.txt
@@ -33,6 +33,19 @@
     pw_chrono.system_clock
 )
 
+pw_add_module_library(pw_sync_stl.condition_variable_backend
+  IMPLEMENTS_FACADES
+    pw_sync.condition_variable
+  HEADERS
+    public/pw_sync_stl/condition_variable_inline.h
+    public/pw_sync_stl/condition_variable_native.h
+    public_overrides/pw_sync_backend/condition_variable_inline.h
+    public_overrides/pw_sync_backend/condition_variable_native.h
+  PUBLIC_INCLUDES
+    public
+    public_overrides
+)
+
 # This target provides the backend for pw::sync::CountingSemaphore.
 pw_add_module_library(pw_sync_stl.counting_semaphore_backend
   IMPLEMENTS_FACADES
diff --git a/pw_sync_stl/public/pw_sync_stl/condition_variable_inline.h b/pw_sync_stl/public/pw_sync_stl/condition_variable_inline.h
new file mode 100644
index 0000000..2c84902
--- /dev/null
+++ b/pw_sync_stl/public/pw_sync_stl/condition_variable_inline.h
@@ -0,0 +1,50 @@
+// Copyright 2022 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 <mutex>
+
+#include "pw_sync/condition_variable.h"
+
+namespace pw::sync {
+
+inline void ConditionVariable::notify_one() { native_type_.notify_one(); }
+
+inline void ConditionVariable::notify_all() { native_type_.notify_all(); }
+
+template <typename Predicate>
+void ConditionVariable::wait(std::unique_lock<Mutex>& lock,
+                             Predicate predicate) {
+  native_type_.wait(lock, std::move(predicate));
+}
+
+template <typename Predicate>
+bool ConditionVariable::wait_for(std::unique_lock<Mutex>& lock,
+                                 pw::chrono::SystemClock::duration timeout,
+                                 Predicate predicate) {
+  return native_type_.wait_for(lock, timeout, std::move(predicate));
+}
+
+template <typename Predicate>
+bool ConditionVariable::wait_until(std::unique_lock<Mutex>& lock,
+                                   pw::chrono::SystemClock::time_point deadline,
+                                   Predicate predicate) {
+  return native_type_.wait_until(lock, deadline, std::move(predicate));
+}
+
+inline auto ConditionVariable::native_handle() -> native_handle_type {
+  return native_type_;
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync_stl/public/pw_sync_stl/condition_variable_native.h b/pw_sync_stl/public/pw_sync_stl/condition_variable_native.h
new file mode 100644
index 0000000..e3032a5
--- /dev/null
+++ b/pw_sync_stl/public/pw_sync_stl/condition_variable_native.h
@@ -0,0 +1,25 @@
+// Copyright 2022 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 <condition_variable>
+
+#include "pw_sync/condition_variable.h"
+
+namespace pw::sync::backend {
+
+using NativeConditionVariable = std::condition_variable_any;
+using NativeConditionVariableHandle = std::condition_variable_any&;
+
+}  // namespace pw::sync::backend
diff --git a/pw_sync_stl/public_overrides/pw_sync_backend/condition_variable_inline.h b/pw_sync_stl/public_overrides/pw_sync_backend/condition_variable_inline.h
new file mode 100644
index 0000000..c6579c6
--- /dev/null
+++ b/pw_sync_stl/public_overrides/pw_sync_backend/condition_variable_inline.h
@@ -0,0 +1,16 @@
+// Copyright 2022 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 "pw_sync_stl/condition_variable_inline.h"
diff --git a/pw_sync_stl/public_overrides/pw_sync_backend/condition_variable_native.h b/pw_sync_stl/public_overrides/pw_sync_backend/condition_variable_native.h
new file mode 100644
index 0000000..bb31927
--- /dev/null
+++ b/pw_sync_stl/public_overrides/pw_sync_backend/condition_variable_native.h
@@ -0,0 +1,16 @@
+// Copyright 2022 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 "pw_sync_stl/condition_variable_native.h"
diff --git a/pw_system/stl_backends.gni b/pw_system/stl_backends.gni
index 7956f6a..de2f0c2 100644
--- a/pw_system/stl_backends.gni
+++ b/pw_system/stl_backends.gni
@@ -17,10 +17,12 @@
 PW_SYSTEM_STL_BACKENDS = {
   pw_chrono_SYSTEM_CLOCK_BACKEND = "$dir_pw_chrono_stl:system_clock"
   pw_chrono_SYSTEM_TIMER_BACKEND = "$dir_pw_chrono_stl:system_timer"
-  pw_sync_INTERRUPT_SPIN_LOCK_BACKEND = "$dir_pw_sync_stl:interrupt_spin_lock"
   pw_sync_BINARY_SEMAPHORE_BACKEND = "$dir_pw_sync_stl:binary_semaphore_backend"
+  pw_sync_CONDITION_VARIABLE_BACKEND =
+      "$dir_pw_sync_stl:condition_variable_backend"
   pw_sync_COUNTING_SEMAPHORE_BACKEND =
       "$dir_pw_sync_stl:counting_semaphore_backend"
+  pw_sync_INTERRUPT_SPIN_LOCK_BACKEND = "$dir_pw_sync_stl:interrupt_spin_lock"
   pw_sync_MUTEX_BACKEND = "$dir_pw_sync_stl:mutex_backend"
   pw_sync_TIMED_MUTEX_BACKEND = "$dir_pw_sync_stl:timed_mutex_backend"
   pw_sync_THREAD_NOTIFICATION_BACKEND =
diff --git a/targets/host/target_toolchains.gni b/targets/host/target_toolchains.gni
index df99ece..0bccc4c 100644
--- a/targets/host/target_toolchains.gni
+++ b/targets/host/target_toolchains.gni
@@ -43,10 +43,12 @@
   pw_string_CONFIG = "$dir_pw_string:enable_decimal_float_expansion"
 
   # Configure backends for pw_sync's facades.
-  pw_sync_INTERRUPT_SPIN_LOCK_BACKEND = "$dir_pw_sync_stl:interrupt_spin_lock"
   pw_sync_BINARY_SEMAPHORE_BACKEND = "$dir_pw_sync_stl:binary_semaphore_backend"
+  pw_sync_CONDITION_VARIABLE_BACKEND =
+      "$dir_pw_sync_stl:condition_variable_backend"
   pw_sync_COUNTING_SEMAPHORE_BACKEND =
       "$dir_pw_sync_stl:counting_semaphore_backend"
+  pw_sync_INTERRUPT_SPIN_LOCK_BACKEND = "$dir_pw_sync_stl:interrupt_spin_lock"
   pw_sync_MUTEX_BACKEND = "$dir_pw_sync_stl:mutex_backend"
   pw_sync_RECURSIVE_MUTEX_BACKEND = "$dir_pw_sync_stl:recursive_mutex_backend"
   pw_sync_TIMED_MUTEX_BACKEND = "$dir_pw_sync_stl:timed_mutex_backend"