[libcxx-commits] [libcxx] 695138c - [libc++] implement std::`jthread`

via libcxx-commits libcxx-commits at lists.llvm.org
Sat Sep 16 11:55:38 PDT 2023


Author: Hui
Date: 2023-09-16T19:54:19+01:00
New Revision: 695138ca8405779c2b7756cc31d887aa54f56bb8

URL: https://github.com/llvm/llvm-project/commit/695138ca8405779c2b7756cc31d887aa54f56bb8
DIFF: https://github.com/llvm/llvm-project/commit/695138ca8405779c2b7756cc31d887aa54f56bb8.diff

LOG: [libc++] implement std::`jthread`

Added: 
    libcxx/include/__thread/jthread.h
    libcxx/test/std/thread/thread.jthread/assign.move.pass.cpp
    libcxx/test/std/thread/thread.jthread/cons.default.pass.cpp
    libcxx/test/std/thread/thread.jthread/cons.func.token.pass.cpp
    libcxx/test/std/thread/thread.jthread/cons.move.pass.cpp
    libcxx/test/std/thread/thread.jthread/copy.delete.compile.pass.cpp
    libcxx/test/std/thread/thread.jthread/detach.pass.cpp
    libcxx/test/std/thread/thread.jthread/dtor.pass.cpp
    libcxx/test/std/thread/thread.jthread/get_id.pass.cpp
    libcxx/test/std/thread/thread.jthread/get_stop_source.pass.cpp
    libcxx/test/std/thread/thread.jthread/get_stop_token.pass.cpp
    libcxx/test/std/thread/thread.jthread/hardware_concurrency.pass.cpp
    libcxx/test/std/thread/thread.jthread/join.deadlock.pass.cpp
    libcxx/test/std/thread/thread.jthread/join.pass.cpp
    libcxx/test/std/thread/thread.jthread/joinable.pass.cpp
    libcxx/test/std/thread/thread.jthread/nodiscard.verify.cpp
    libcxx/test/std/thread/thread.jthread/request_stop.pass.cpp
    libcxx/test/std/thread/thread.jthread/swap.free.pass.cpp
    libcxx/test/std/thread/thread.jthread/swap.member.pass.cpp
    libcxx/test/std/thread/thread.jthread/type.compile.pass.cpp

Modified: 
    libcxx/docs/UsingLibcxx.rst
    libcxx/include/CMakeLists.txt
    libcxx/include/__stop_token/atomic_unique_lock.h
    libcxx/include/__stop_token/stop_callback.h
    libcxx/include/__stop_token/stop_state.h
    libcxx/include/module.modulemap.in
    libcxx/include/thread
    libcxx/modules/std/thread.inc
    libcxx/test/libcxx/transitive_includes/cxx03.csv
    libcxx/test/libcxx/transitive_includes/cxx11.csv
    libcxx/test/libcxx/transitive_includes/cxx14.csv
    libcxx/test/libcxx/transitive_includes/cxx17.csv
    libcxx/test/libcxx/transitive_includes/cxx20.csv
    libcxx/test/libcxx/transitive_includes/cxx23.csv
    libcxx/test/libcxx/transitive_includes/cxx26.csv

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/UsingLibcxx.rst b/libcxx/docs/UsingLibcxx.rst
index 9db25f12a6a7898..0b517c0f8f7860c 100644
--- a/libcxx/docs/UsingLibcxx.rst
+++ b/libcxx/docs/UsingLibcxx.rst
@@ -48,6 +48,7 @@ when ``-fexperimental-library`` is passed:
 
 * The parallel algorithms library (``<execution>`` and the associated algorithms)
 * ``std::stop_token``, ``std::stop_source`` and ``std::stop_callback``
+* ``std::jthread``
 * ``std::chrono::tzdb`` and related time zone functionality
 * ``std::ranges::join_view``
 

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 7af9b85b974211f..70db7c721a6158e 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -677,6 +677,7 @@ set(files
   __system_error/system_error.h
   __thread/formatter.h
   __thread/id.h
+  __thread/jthread.h
   __thread/poll_with_backoff.h
   __thread/this_thread.h
   __thread/thread.h

diff  --git a/libcxx/include/__stop_token/atomic_unique_lock.h b/libcxx/include/__stop_token/atomic_unique_lock.h
index 6c63a254eab9d9a..13e59f9f0dce005 100644
--- a/libcxx/include/__stop_token/atomic_unique_lock.h
+++ b/libcxx/include/__stop_token/atomic_unique_lock.h
@@ -28,7 +28,8 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 // and LockedBit is the value of State when the lock bit is set, e.g  1 << 2
 template <class _State, _State _LockedBit>
 class _LIBCPP_AVAILABILITY_SYNC __atomic_unique_lock {
-  static_assert(std::popcount(_LockedBit) == 1, "LockedBit must be an integer where only one bit is set");
+  static_assert(std::__libcpp_popcount(static_cast<unsigned long long>(_LockedBit)) == 1,
+                "LockedBit must be an integer where only one bit is set");
 
   std::atomic<_State>& __state_;
   bool __is_locked_;

diff  --git a/libcxx/include/__stop_token/stop_callback.h b/libcxx/include/__stop_token/stop_callback.h
index c9dcad3deb911ea..e52f0350dacc5bb 100644
--- a/libcxx/include/__stop_token/stop_callback.h
+++ b/libcxx/include/__stop_token/stop_callback.h
@@ -26,6 +26,9 @@
 #  pragma GCC system_header
 #endif
 
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
 _LIBCPP_BEGIN_NAMESPACE_STD
 
 #if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN)
@@ -96,4 +99,6 @@ _LIBCPP_AVAILABILITY_SYNC stop_callback(stop_token, _Callback) -> stop_callback<
 
 _LIBCPP_END_NAMESPACE_STD
 
+_LIBCPP_POP_MACROS
+
 #endif // _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN)

diff  --git a/libcxx/include/__stop_token/stop_state.h b/libcxx/include/__stop_token/stop_state.h
index 9103bb1961599fe..0e43292faae8819 100644
--- a/libcxx/include/__stop_token/stop_state.h
+++ b/libcxx/include/__stop_token/stop_state.h
@@ -14,9 +14,9 @@
 #include <__config>
 #include <__stop_token/atomic_unique_lock.h>
 #include <__stop_token/intrusive_list_view.h>
+#include <__thread/id.h>
 #include <atomic>
 #include <cstdint>
-#include <thread>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -63,7 +63,7 @@ class __stop_state {
   using __callback_list      = __intrusive_list_view<__stop_callback_base>;
 
   __callback_list __callback_list_;
-  thread::id __requesting_thread_;
+  __thread_id __requesting_thread_;
 
 public:
   _LIBCPP_HIDE_FROM_ABI __stop_state() noexcept = default;

diff  --git a/libcxx/include/__thread/jthread.h b/libcxx/include/__thread/jthread.h
new file mode 100644
index 000000000000000..fc86b13afb13431
--- /dev/null
+++ b/libcxx/include/__thread/jthread.h
@@ -0,0 +1,130 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___THREAD_JTHREAD_H
+#define _LIBCPP___THREAD_JTHREAD_H
+
+#include <__availability>
+#include <__config>
+#include <__functional/invoke.h>
+#include <__stop_token/stop_source.h>
+#include <__stop_token/stop_token.h>
+#include <__thread/thread.h>
+#include <__threading_support>
+#include <__type_traits/decay.h>
+#include <__type_traits/is_constructible.h>
+#include <__type_traits/is_same.h>
+#include <__type_traits/remove_cvref.h>
+#include <__utility/forward.h>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN)
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+class _LIBCPP_AVAILABILITY_SYNC jthread {
+public:
+  // types
+  using id                 = thread::id;
+  using native_handle_type = thread::native_handle_type;
+
+  // [thread.jthread.cons], constructors, move, and assignment
+  _LIBCPP_HIDE_FROM_ABI jthread() noexcept : __stop_source_(std::nostopstate) {}
+
+  template <class _Fun, class... _Args>
+  _LIBCPP_HIDE_FROM_ABI explicit jthread(_Fun&& __fun, _Args&&... __args)
+    requires(!std::is_same_v<remove_cvref_t<_Fun>, jthread>)
+      : __stop_source_(),
+        __thread_(__init_thread(__stop_source_, std::forward<_Fun>(__fun), std::forward<_Args>(__args)...)) {
+    static_assert(is_constructible_v<decay_t<_Fun>, _Fun>);
+    static_assert((is_constructible_v<decay_t<_Args>, _Args> && ...));
+    static_assert(is_invocable_v<decay_t<_Fun>, decay_t<_Args>...> ||
+                  is_invocable_v<decay_t<_Fun>, stop_token, decay_t<_Args>...>);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI ~jthread() {
+    if (joinable()) {
+      request_stop();
+      join();
+    }
+  }
+
+  jthread(const jthread&) = delete;
+
+  _LIBCPP_HIDE_FROM_ABI jthread(jthread&&) noexcept = default;
+
+  jthread& operator=(const jthread&) = delete;
+
+  _LIBCPP_HIDE_FROM_ABI jthread& operator=(jthread&& __other) noexcept {
+    if (this != &__other) {
+      if (joinable()) {
+        request_stop();
+        join();
+      }
+      __stop_source_ = std::move(__other.__stop_source_);
+      __thread_      = std::move(__other.__thread_);
+    }
+
+    return *this;
+  }
+
+  // [thread.jthread.mem], members
+  _LIBCPP_HIDE_FROM_ABI void swap(jthread& __other) noexcept {
+    std::swap(__stop_source_, __other.__stop_source_);
+    std::swap(__thread_, __other.__thread_);
+  }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool joinable() const noexcept { return get_id() != id(); }
+
+  _LIBCPP_HIDE_FROM_ABI void join() { __thread_.join(); }
+
+  _LIBCPP_HIDE_FROM_ABI void detach() { __thread_.detach(); }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI id get_id() const noexcept { return __thread_.get_id(); }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI native_handle_type native_handle() { return __thread_.native_handle(); }
+
+  // [thread.jthread.stop], stop token handling
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI stop_source get_stop_source() noexcept { return __stop_source_; }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI stop_token get_stop_token() const noexcept { return __stop_source_.get_token(); }
+
+  _LIBCPP_HIDE_FROM_ABI bool request_stop() noexcept { return __stop_source_.request_stop(); }
+
+  // [thread.jthread.special], specialized algorithms
+  _LIBCPP_HIDE_FROM_ABI friend void swap(jthread& __lhs, jthread& __rhs) noexcept { __lhs.swap(__rhs); }
+
+  // [thread.jthread.static], static members
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static unsigned int hardware_concurrency() noexcept {
+    return thread::hardware_concurrency();
+  }
+
+private:
+  template <class _Fun, class... _Args>
+  _LIBCPP_HIDE_FROM_ABI static thread __init_thread(const stop_source& __ss, _Fun&& __fun, _Args&&... __args) {
+    if constexpr (is_invocable_v<decay_t<_Fun>, stop_token, decay_t<_Args>...>) {
+      return thread(std::forward<_Fun>(__fun), __ss.get_token(), std::forward<_Args>(__args)...);
+    } else {
+      return thread(std::forward<_Fun>(__fun), std::forward<_Args>(__args)...);
+    }
+  }
+
+  stop_source __stop_source_;
+  thread __thread_;
+};
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN)
+
+#endif // _LIBCPP___THREAD_JTHREAD_H

diff  --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index e349386e0fba133..6d9bb8653fcb5e9 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1789,6 +1789,10 @@ module std_private_system_error_system_error    [system] { header "__system_erro
 
 module std_private_thread_formatter            [system] { header "__thread/formatter.h" }
 module std_private_thread_id                   [system] { header "__thread/id.h" }
+module std_private_thread_jthread              [system] {
+  header "__thread/jthread.h"
+  export *
+}
 module std_private_thread_poll_with_backoff    [system] { header "__thread/poll_with_backoff.h" }
 module std_private_thread_this_thread          [system] { header "__thread/this_thread.h" }
 module std_private_thread_thread               [system] {

diff  --git a/libcxx/include/thread b/libcxx/include/thread
index 4ddcf3ec79cb0e6..943085b7af03529 100644
--- a/libcxx/include/thread
+++ b/libcxx/include/thread
@@ -90,6 +90,7 @@ void sleep_for(const chrono::duration<Rep, Period>& rel_time);
 #include <__availability>
 #include <__config>
 #include <__thread/formatter.h>
+#include <__thread/jthread.h>
 #include <__thread/this_thread.h>
 #include <__thread/thread.h>
 #include <__threading_support>

diff  --git a/libcxx/modules/std/thread.inc b/libcxx/modules/std/thread.inc
index 43594d1a29cf305..6504a39a7aeaee1 100644
--- a/libcxx/modules/std/thread.inc
+++ b/libcxx/modules/std/thread.inc
@@ -15,7 +15,9 @@ export namespace std {
   using std::swap;
 
   // [thread.jthread.class], class jthread
-  // using std::jthread;
+#  if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN)
+  using std::jthread;
+#  endif
 
   // [thread.thread.this], namespace this_thread
   namespace this_thread {

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv
index f78345656175f56..57420b9a3795d41 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -805,9 +805,9 @@ stop_token cstddef
 stop_token cstdint
 stop_token cstring
 stop_token ctime
+stop_token iosfwd
 stop_token limits
 stop_token ratio
-stop_token thread
 stop_token type_traits
 stop_token version
 streambuf cstdint
@@ -867,6 +867,7 @@ system_error string
 system_error type_traits
 system_error version
 thread array
+thread atomic
 thread cerrno
 thread chrono
 thread compare

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv
index 2518c2bcafc99de..464c0ef892c6195 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -811,9 +811,9 @@ stop_token cstddef
 stop_token cstdint
 stop_token cstring
 stop_token ctime
+stop_token iosfwd
 stop_token limits
 stop_token ratio
-stop_token thread
 stop_token type_traits
 stop_token version
 streambuf cstdint
@@ -873,6 +873,7 @@ system_error string
 system_error type_traits
 system_error version
 thread array
+thread atomic
 thread cerrno
 thread chrono
 thread compare

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv
index 4191094251f0633..eb698e29d922ddc 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv
@@ -813,9 +813,9 @@ stop_token cstddef
 stop_token cstdint
 stop_token cstring
 stop_token ctime
+stop_token iosfwd
 stop_token limits
 stop_token ratio
-stop_token thread
 stop_token type_traits
 stop_token version
 streambuf cstdint
@@ -875,6 +875,7 @@ system_error string
 system_error type_traits
 system_error version
 thread array
+thread atomic
 thread cerrno
 thread chrono
 thread compare

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
index 4191094251f0633..eb698e29d922ddc 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -813,9 +813,9 @@ stop_token cstddef
 stop_token cstdint
 stop_token cstring
 stop_token ctime
+stop_token iosfwd
 stop_token limits
 stop_token ratio
-stop_token thread
 stop_token type_traits
 stop_token version
 streambuf cstdint
@@ -875,6 +875,7 @@ system_error string
 system_error type_traits
 system_error version
 thread array
+thread atomic
 thread cerrno
 thread chrono
 thread compare

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
index 2cfb6e9e3861cc6..9ba59c10734b3ec 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -818,9 +818,9 @@ stop_token cstddef
 stop_token cstdint
 stop_token cstring
 stop_token ctime
+stop_token iosfwd
 stop_token limits
 stop_token ratio
-stop_token thread
 stop_token type_traits
 stop_token version
 streambuf cstdint
@@ -880,6 +880,7 @@ system_error string
 system_error type_traits
 system_error version
 thread array
+thread atomic
 thread cerrno
 thread compare
 thread cstddef

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv
index 37095662bc30bc7..baaf19a002b0eb1 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -582,9 +582,9 @@ stop_token cstddef
 stop_token cstdint
 stop_token cstring
 stop_token ctime
+stop_token iosfwd
 stop_token limits
 stop_token ratio
-stop_token thread
 stop_token version
 streambuf cstdint
 streambuf ios
@@ -627,6 +627,7 @@ system_error stdexcept
 system_error string
 system_error version
 thread array
+thread atomic
 thread cerrno
 thread compare
 thread cstddef

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index 37095662bc30bc7..baaf19a002b0eb1 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -582,9 +582,9 @@ stop_token cstddef
 stop_token cstdint
 stop_token cstring
 stop_token ctime
+stop_token iosfwd
 stop_token limits
 stop_token ratio
-stop_token thread
 stop_token version
 streambuf cstdint
 streambuf ios
@@ -627,6 +627,7 @@ system_error stdexcept
 system_error string
 system_error version
 thread array
+thread atomic
 thread cerrno
 thread compare
 thread cstddef

diff  --git a/libcxx/test/std/thread/thread.jthread/assign.move.pass.cpp b/libcxx/test/std/thread/thread.jthread/assign.move.pass.cpp
new file mode 100644
index 000000000000000..b932ac39d2f3773
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/assign.move.pass.cpp
@@ -0,0 +1,116 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+// ADDITIONAL_COMPILE_FLAGS: -Wno-self-move
+
+// jthread& operator=(jthread&&) noexcept;
+
+#include <atomic>
+#include <cassert>
+#include <concepts>
+#include <stop_token>
+#include <thread>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "test_macros.h"
+
+static_assert(std::is_nothrow_move_assignable_v<std::jthread>);
+
+int main(int, char**) {
+  // If &x == this is true, there are no effects.
+  {
+    std::jthread j([] {});
+    auto id      = j.get_id();
+    auto ssource = j.get_stop_source();
+    j            = std::move(j);
+    assert(j.get_id() == id);
+    assert(j.get_stop_source() == ssource);
+  }
+
+  // if joinable() is true, calls request_stop() and then join()
+  // request_stop is called
+  {
+    std::jthread j1([] {});
+    bool called = false;
+    std::stop_callback cb(j1.get_stop_token(), [&called] { called = true; });
+
+    std::jthread j2([] {});
+    j1 = std::move(j2);
+    assert(called);
+  }
+
+  // if joinable() is true, calls request_stop() and then join()
+  // join is called
+  {
+    std::atomic_int calledTimes = 0;
+    std::vector<std::jthread> jts;
+    constexpr auto numberOfThreads = 10u;
+    jts.reserve(numberOfThreads);
+    for (auto i = 0u; i < numberOfThreads; ++i) {
+      jts.emplace_back([&] {
+        std::this_thread::sleep_for(std::chrono::milliseconds(2));
+        calledTimes.fetch_add(1, std::memory_order_relaxed);
+      });
+    }
+
+    for (auto i = 0u; i < numberOfThreads; ++i) {
+      jts[i] = std::jthread{};
+    }
+
+    // If join was called as expected, calledTimes must equal to numberOfThreads
+    // If join was not called, there is a chance that the check below happened
+    // before test threads incrementing the counter, thus calledTimed would
+    // be less than numberOfThreads.
+    // This is not going to catch issues 100%. Creating more threads to increase
+    // the probability of catching the issue
+    assert(calledTimes.load(std::memory_order_relaxed) == numberOfThreads);
+  }
+
+  // then assigns the state of x to *this
+  {
+    std::jthread j1([] {});
+    std::jthread j2([] {});
+    auto id2      = j2.get_id();
+    auto ssource2 = j2.get_stop_source();
+
+    j1 = std::move(j2);
+
+    assert(j1.get_id() == id2);
+    assert(j1.get_stop_source() == ssource2);
+  }
+
+  // sets x to a default constructed state
+  {
+    std::jthread j1([] {});
+    std::jthread j2([] {});
+    j1 = std::move(j2);
+
+    assert(j2.get_id() == std::jthread::id());
+    assert(!j2.get_stop_source().stop_possible());
+  }
+
+  // joinable is false
+  {
+    std::jthread j1;
+    std::jthread j2([] {});
+
+    auto j2Id = j2.get_id();
+
+    j1 = std::move(j2);
+
+    assert(j1.get_id() == j2Id);
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/cons.default.pass.cpp b/libcxx/test/std/thread/thread.jthread/cons.default.pass.cpp
new file mode 100644
index 000000000000000..88585eaef599e52
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/cons.default.pass.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// jthread() noexcept;
+
+#include <cassert>
+#include <stop_token>
+#include <thread>
+#include <type_traits>
+
+#include "test_macros.h"
+
+static_assert(std::is_nothrow_default_constructible_v<std::jthread>);
+
+int main(int, char**) {
+  {
+    std::jthread jt = {}; // implicit
+    assert(!jt.get_stop_source().stop_possible());
+    assert(jt.get_id() == std::jthread::id());
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/cons.func.token.pass.cpp b/libcxx/test/std/thread/thread.jthread/cons.func.token.pass.cpp
new file mode 100644
index 000000000000000..2d029271d75ce84
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/cons.func.token.pass.cpp
@@ -0,0 +1,160 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// template<class F, class... Args>
+// explicit jthread(F&& f, Args&&... args);
+
+#include <cassert>
+#include <stop_token>
+#include <thread>
+#include <type_traits>
+
+#include "test_macros.h"
+
+template <class... Args>
+struct Func {
+  void operator()(Args...) const;
+};
+
+// Constraints: remove_cvref_t<F> is not the same type as jthread.
+static_assert(std::is_constructible_v<std::jthread, Func<>>);
+static_assert(std::is_constructible_v<std::jthread, Func<int>, int>);
+static_assert(!std::is_constructible_v<std::jthread, std::jthread const&>);
+
+// explicit
+template <class T>
+void conversion_test(T);
+
+template <class T, class... Args>
+concept ImplicitlyConstructible = requires(Args&&... args) { conversion_test<T>({std::forward<Args>(args)...}); };
+
+static_assert(!ImplicitlyConstructible<std::jthread, Func<>>);
+static_assert(!ImplicitlyConstructible<std::jthread, Func<int>, int>);
+
+int main(int, char**) {
+  // Effects: Initializes ssource
+  // Postconditions: get_id() != id() is true and ssource.stop_possible() is true
+  // and *this represents the newly started thread.
+  {
+    std::jthread jt{[] {}};
+    assert(jt.get_stop_source().stop_possible());
+    assert(jt.get_id() != std::jthread::id());
+  }
+
+  // The new thread of execution executes
+  // invoke(auto(std::forward<F>(f)), get_stop_token(), auto(std::forward<Args>(args))...)
+  // if that expression is well-formed,
+  {
+    int result = 0;
+    std::jthread jt{[&result](std::stop_token st, int i) {
+                      assert(st.stop_possible());
+                      assert(!st.stop_requested());
+                      result += i;
+                    },
+                    5};
+    jt.join();
+    assert(result == 5);
+  }
+
+  // otherwise
+  // invoke(auto(std::forward<F>(f)), auto(std::forward<Args>(args))...)
+  {
+    int result = 0;
+    std::jthread jt{[&result](int i) { result += i; }, 5};
+    jt.join();
+    assert(result == 5);
+  }
+
+  // with the values produced by auto being materialized ([conv.rval]) in the constructing thread.
+  {
+    struct TrackThread {
+      std::jthread::id threadId;
+      bool copyConstructed = false;
+      bool moveConstructed = false;
+
+      TrackThread() : threadId(std::this_thread::get_id()) {}
+      TrackThread(const TrackThread&) : threadId(std::this_thread::get_id()), copyConstructed(true) {}
+      TrackThread(TrackThread&&) : threadId(std::this_thread::get_id()), moveConstructed(true) {}
+    };
+
+    auto mainThread = std::this_thread::get_id();
+
+    TrackThread arg1;
+    std::jthread jt1{[mainThread](const TrackThread& arg) {
+                       assert(arg.threadId == mainThread);
+                       assert(arg.threadId != std::this_thread::get_id());
+                       assert(arg.copyConstructed);
+                     },
+                     arg1};
+
+    TrackThread arg2;
+    std::jthread jt2{[mainThread](const TrackThread& arg) {
+                       assert(arg.threadId == mainThread);
+                       assert(arg.threadId != std::this_thread::get_id());
+                       assert(arg.moveConstructed);
+                     },
+                     std::move(arg2)};
+  }
+
+#if !defined(TEST_HAS_NO_EXCEPTIONS)
+  // [Note 1: This implies that any exceptions not thrown from the invocation of the copy
+  // of f will be thrown in the constructing thread, not the new thread. - end note]
+  {
+    struct Exception {
+      std::jthread::id threadId;
+    };
+    struct ThrowOnCopyFunc {
+      ThrowOnCopyFunc() = default;
+      ThrowOnCopyFunc(const ThrowOnCopyFunc&) { throw Exception{std::this_thread::get_id()}; }
+      void operator()() const {}
+    };
+    ThrowOnCopyFunc f1;
+    try {
+      std::jthread jt{f1};
+      assert(false);
+    } catch (const Exception& e) {
+      assert(e.threadId == std::this_thread::get_id());
+    }
+  }
+#endif // !defined(TEST_HAS_NO_EXCEPTIONS)
+
+  // Synchronization: The completion of the invocation of the constructor
+  // synchronizes with the beginning of the invocation of the copy of f.
+  {
+    int flag = 0;
+    struct Arg {
+      int& flag_;
+      Arg(int& f) : flag_(f) {}
+
+      Arg(const Arg& other) : flag_(other.flag_) { flag_ = 5; }
+    };
+
+    Arg arg(flag);
+    std::jthread jt(
+        [&flag](const auto&) {
+          assert(flag == 5); // happens-after the copy-construction of arg
+        },
+        arg);
+  }
+
+  // Per https://eel.is/c++draft/thread.jthread.class#thread.jthread.cons-8:
+  //
+  // Throws: system_error if unable to start the new thread.
+  // Error conditions:
+  // resource_unavailable_try_again - the system lacked the necessary resources to create another thread,
+  // or the system-imposed limit on the number of threads in a process would be exceeded.
+  //
+  // Unfortunately, this is extremely hard to test portably so we don't have a test for this error condition right now.
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/cons.move.pass.cpp b/libcxx/test/std/thread/thread.jthread/cons.move.pass.cpp
new file mode 100644
index 000000000000000..9eacf8971c2a58b
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/cons.move.pass.cpp
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// jthread(jthread&& x) noexcept;
+
+#include <cassert>
+#include <stop_token>
+#include <thread>
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+
+static_assert(std::is_nothrow_move_constructible_v<std::jthread>);
+
+int main(int, char**) {
+  {
+    // x.get_id() == id() and get_id() returns the value of x.get_id() prior
+    // to the start of construction.
+    std::jthread j1{[] {}};
+    auto id1 = j1.get_id();
+
+    std::jthread j2(std::move(j1));
+    assert(j1.get_id() == std::jthread::id());
+    assert(j2.get_id() == id1);
+  }
+
+  {
+    // ssource has the value of x.ssource prior to the start of construction
+    // and x.ssource.stop_possible() is false.
+    std::jthread j1{[] {}};
+    auto ss1 = j1.get_stop_source();
+
+    std::jthread j2(std::move(j1));
+    assert(ss1 == j2.get_stop_source());
+    assert(!j1.get_stop_source().stop_possible());
+    assert(j2.get_stop_source().stop_possible());
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/copy.delete.compile.pass.cpp b/libcxx/test/std/thread/thread.jthread/copy.delete.compile.pass.cpp
new file mode 100644
index 000000000000000..e1c34afca9d4791
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/copy.delete.compile.pass.cpp
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// jthread(const jthread&) = delete;
+// jthread& operator=(const jthread&) = delete;
+
+#include <thread>
+#include <type_traits>
+
+static_assert(!std::is_copy_constructible_v<std::jthread>);
+static_assert(!std::is_copy_assignable_v<std::jthread>);

diff  --git a/libcxx/test/std/thread/thread.jthread/detach.pass.cpp b/libcxx/test/std/thread/thread.jthread/detach.pass.cpp
new file mode 100644
index 000000000000000..ee48d2691e68439
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/detach.pass.cpp
@@ -0,0 +1,74 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// void detach();
+
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <concepts>
+#include <functional>
+#include <optional>
+#include <system_error>
+#include <thread>
+#include <type_traits>
+
+#include "test_macros.h"
+
+int main(int, char**) {
+  // Effects: The thread represented by *this continues execution without the calling thread blocking.
+  {
+    std::atomic_bool start{false};
+    std::atomic_bool done{false};
+    std::optional<std::jthread> jt{[&start, &done] {
+      start.wait(false);
+      done = true;
+    }};
+
+    // If it blocks, it will deadlock here
+    jt->detach();
+
+    jt.reset();
+
+    // The other thread continues execution
+    start = true;
+    start.notify_all();
+    while (!done) {
+    }
+  }
+
+  // Postconditions: get_id() == id().
+  {
+    std::jthread jt{[] {}};
+    assert(jt.get_id() != std::jthread::id());
+    jt.detach();
+    assert(jt.get_id() == std::jthread::id());
+  }
+
+#if !defined(TEST_HAS_NO_EXCEPTIONS)
+  // Throws: system_error when an exception is required ([thread.req.exception]).
+  // invalid_argument - if the thread is not joinable.
+  {
+    std::jthread jt;
+    try {
+      jt.detach();
+      assert(false);
+    } catch (const std::system_error& err) {
+      assert(err.code() == std::errc::invalid_argument);
+    }
+  }
+#endif
+
+  std::this_thread::sleep_for(std::chrono::milliseconds{2});
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/dtor.pass.cpp b/libcxx/test/std/thread/thread.jthread/dtor.pass.cpp
new file mode 100644
index 000000000000000..47ee62023f62d94
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/dtor.pass.cpp
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// ~jthread();
+
+#include <atomic>
+#include <cassert>
+#include <optional>
+#include <stop_token>
+#include <thread>
+#include <type_traits>
+#include <vector>
+#include "test_macros.h"
+
+int main(int, char**) {
+  // !joinable()
+  {
+    std::jthread jt;
+    assert(!jt.joinable());
+  }
+
+  // If joinable() is true, calls request_stop() and then join().
+  // request_stop is called
+  {
+    std::optional<std::jthread> jt([] {});
+    bool called = false;
+    std::stop_callback cb(jt->get_stop_token(), [&called] { called = true; });
+    jt.reset();
+    assert(called);
+  }
+
+  // If joinable() is true, calls request_stop() and then join().
+  // join is called
+  {
+    std::atomic_int calledTimes = 0;
+    std::vector<std::jthread> jts;
+
+    constexpr auto numberOfThreads = 10u;
+    jts.reserve(numberOfThreads);
+    for (auto i = 0u; i < numberOfThreads; ++i) {
+      jts.emplace_back([&calledTimes] {
+        std::this_thread::sleep_for(std::chrono::milliseconds{2});
+        calledTimes.fetch_add(1, std::memory_order_relaxed);
+      });
+    }
+    jts.clear();
+
+    // If join was called as expected, calledTimes must equal to numberOfThreads
+    // If join was not called, there is a chance that the check below happened
+    // before test threads incrementing the counter, thus calledTimed would
+    // be less than numberOfThreads.
+    // This is not going to catch issues 100%. Creating more threads would increase
+    // the probability of catching the issue
+    assert(calledTimes.load(std::memory_order_relaxed) == numberOfThreads);
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/get_id.pass.cpp b/libcxx/test/std/thread/thread.jthread/get_id.pass.cpp
new file mode 100644
index 000000000000000..f92472d3d8dc649
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/get_id.pass.cpp
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// [[nodiscard]] id get_id() const noexcept;
+
+#include <cassert>
+#include <concepts>
+#include <thread>
+#include <type_traits>
+
+#include "test_macros.h"
+
+static_assert(noexcept(std::declval<const std::jthread&>().get_id()));
+
+int main(int, char**) {
+  // Does not represent a thread
+  {
+    const std::jthread jt;
+    std::same_as<std::jthread::id> decltype(auto) result = jt.get_id();
+    assert(result == std::jthread::id());
+  }
+
+  // Represents a thread
+  {
+    const std::jthread jt{[] {}};
+    std::same_as<std::jthread::id> decltype(auto) result = jt.get_id();
+    assert(result != std::jthread::id());
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/get_stop_source.pass.cpp b/libcxx/test/std/thread/thread.jthread/get_stop_source.pass.cpp
new file mode 100644
index 000000000000000..41df2d894f45df0
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/get_stop_source.pass.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// [[nodiscard]] stop_source get_stop_source() noexcept;
+
+#include <cassert>
+#include <concepts>
+#include <stop_token>
+#include <thread>
+#include <type_traits>
+
+#include "test_macros.h"
+
+static_assert(noexcept(std::declval<std::jthread&>().get_stop_source()));
+
+int main(int, char**) {
+  // Represents a thread
+  {
+    std::jthread jt{[] {}};
+    std::same_as<std::stop_source> decltype(auto) result = jt.get_stop_source();
+    assert(result.stop_possible());
+  }
+
+  // Does not represents a thread
+  {
+    std::jthread jt{};
+    std::same_as<std::stop_source> decltype(auto) result = jt.get_stop_source();
+    assert(!result.stop_possible());
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/get_stop_token.pass.cpp b/libcxx/test/std/thread/thread.jthread/get_stop_token.pass.cpp
new file mode 100644
index 000000000000000..c65d39b3cdf4a77
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/get_stop_token.pass.cpp
@@ -0,0 +1,49 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// [[nodiscard]] stop_token get_stop_token() const noexcept;
+
+#include <cassert>
+#include <concepts>
+#include <stop_token>
+#include <thread>
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+
+static_assert(noexcept(std::declval<const std::jthread&>().get_stop_token()));
+
+int main(int, char**) {
+  // Represents a thread
+  {
+    std::jthread jt{[] {}};
+    auto ss                                         = jt.get_stop_source();
+    std::same_as<std::stop_token> decltype(auto) st = std::as_const(jt).get_stop_token();
+
+    assert(st.stop_possible());
+    assert(!st.stop_requested());
+    ss.request_stop();
+    assert(st.stop_requested());
+  }
+
+  // Does not represent a thread
+  {
+    const std::jthread jt{};
+    std::same_as<std::stop_token> decltype(auto) st = jt.get_stop_token();
+
+    assert(!st.stop_possible());
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/hardware_concurrency.pass.cpp b/libcxx/test/std/thread/thread.jthread/hardware_concurrency.pass.cpp
new file mode 100644
index 000000000000000..669d72f1fd5f5fa
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/hardware_concurrency.pass.cpp
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// [[nodiscard]] static unsigned int hardware_concurrency() noexcept;
+
+#include <cassert>
+#include <concepts>
+#include <thread>
+#include <type_traits>
+
+#include "test_macros.h"
+
+static_assert(noexcept(std::jthread::hardware_concurrency()));
+
+int main(int, char**) {
+  std::same_as<unsigned int> decltype(auto) result = std::jthread::hardware_concurrency();
+  assert(result == std::thread::hardware_concurrency());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/join.deadlock.pass.cpp b/libcxx/test/std/thread/thread.jthread/join.deadlock.pass.cpp
new file mode 100644
index 000000000000000..aa5cdf2783dba04
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/join.deadlock.pass.cpp
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Windows cannot detect the deadlock. Instead of throwing system_error,
+// it would dead lock the test
+// UNSUPPORTED: windows
+
+// TSAN bug: https://github.com/llvm/llvm-project/issues/66537
+// UNSUPPORTED: tsan
+
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: no-exceptions
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// void join();
+
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <concepts>
+#include <functional>
+#include <system_error>
+#include <thread>
+#include <type_traits>
+#include <vector>
+
+#include "test_macros.h"
+
+int main(int, char**) {
+  // resource_deadlock_would_occur - if deadlock is detected or get_id() == this_thread::get_id().
+  {
+    std::function<void()> f;
+    std::atomic_bool start = false;
+    std::atomic_bool done  = false;
+
+    std::jthread jt{[&] {
+      start.wait(false);
+      f();
+      done = true;
+      done.notify_all();
+    }};
+
+    f = [&] {
+      try {
+        jt.join();
+        assert(false);
+      } catch (const std::system_error& err) {
+        assert(err.code() == std::errc::resource_deadlock_would_occur);
+      }
+    };
+    start = true;
+    start.notify_all();
+    done.wait(false);
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/join.pass.cpp b/libcxx/test/std/thread/thread.jthread/join.pass.cpp
new file mode 100644
index 000000000000000..38986bdfed8d745
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/join.pass.cpp
@@ -0,0 +1,88 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// void join();
+
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <concepts>
+#include <functional>
+#include <system_error>
+#include <thread>
+#include <type_traits>
+#include <vector>
+
+#include "test_macros.h"
+
+int main(int, char**) {
+  // Effects: Blocks until the thread represented by *this has completed.
+  {
+    std::atomic_int calledTimes = 0;
+    std::vector<std::jthread> jts;
+    constexpr auto numberOfThreads = 10u;
+    jts.reserve(numberOfThreads);
+    for (auto i = 0u; i < numberOfThreads; ++i) {
+      jts.emplace_back([&] {
+        std::this_thread::sleep_for(std::chrono::milliseconds(2));
+        calledTimes.fetch_add(1, std::memory_order_relaxed);
+      });
+    }
+
+    for (auto i = 0u; i < numberOfThreads; ++i) {
+      jts[i].join();
+    }
+
+    // If join did block, calledTimes must equal to numberOfThreads
+    // If join did not block, there is a chance that the check below happened
+    // before test threads incrementing the counter, thus calledTimed would
+    // be less than numberOfThreads.
+    // This is not going to catch issues 100%. Creating more threads to increase
+    // the probability of catching the issue
+    assert(calledTimes.load(std::memory_order_relaxed) == numberOfThreads);
+  }
+
+  // Synchronization: The completion of the thread represented by *this synchronizes with
+  // ([intro.multithread]) the corresponding successful join() return.
+  {
+    bool flag = false;
+    std::jthread jt{[&] { flag = true; }};
+    jt.join();
+    assert(flag); // non atomic write is visible to the current thread
+  }
+
+  // Postconditions: The thread represented by *this has completed. get_id() == id().
+  {
+    std::jthread jt{[] {}};
+    assert(jt.get_id() != std::jthread::id());
+    jt.join();
+    assert(jt.get_id() == std::jthread::id());
+  }
+
+#if !defined(TEST_HAS_NO_EXCEPTIONS)
+  // Throws: system_error when an exception is required ([thread.req.exception]).
+  // invalid_argument - if the thread is not joinable.
+  {
+    std::jthread jt;
+    try {
+      jt.join();
+      assert(false);
+    } catch (const std::system_error& err) {
+      assert(err.code() == std::errc::invalid_argument);
+    }
+  }
+
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/joinable.pass.cpp b/libcxx/test/std/thread/thread.jthread/joinable.pass.cpp
new file mode 100644
index 000000000000000..8d9619bdefd8cef
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/joinable.pass.cpp
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// [[nodiscard]] bool joinable() const noexcept;
+
+#include <atomic>
+#include <cassert>
+#include <concepts>
+#include <thread>
+#include <type_traits>
+
+#include "test_macros.h"
+
+static_assert(noexcept(std::declval<const std::jthread&>().joinable()));
+
+int main(int, char**) {
+  // Default constructed
+  {
+    const std::jthread jt;
+    std::same_as<bool> decltype(auto) result = jt.joinable();
+    assert(!result);
+  }
+
+  // Non-default constructed
+  {
+    const std::jthread jt{[] {}};
+    std::same_as<bool> decltype(auto) result = jt.joinable();
+    assert(result);
+  }
+
+  // Non-default constructed
+  // the thread of execution has not finished
+  {
+    std::atomic_bool done = false;
+    const std::jthread jt{[&done] { done.wait(false); }};
+    std::same_as<bool> decltype(auto) result = jt.joinable();
+    done                                     = true;
+    assert(result);
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/nodiscard.verify.cpp b/libcxx/test/std/thread/thread.jthread/nodiscard.verify.cpp
new file mode 100644
index 000000000000000..d0fbef733860cd2
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/nodiscard.verify.cpp
@@ -0,0 +1,31 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// [[nodiscard]] bool joinable() const noexcept;
+// [[nodiscard]] id get_id() const noexcept;
+// [[nodiscard]] native_handle_type native_handle();
+// [[nodiscard]] stop_source get_stop_source() noexcept;
+// [[nodiscard]] stop_token get_stop_token() const noexcept;
+// [[nodiscard]] static unsigned int hardware_concurrency() noexcept;
+
+#include <thread>
+
+void test() {
+  std::jthread jt;
+  jt.joinable();             // expected-warning {{ignoring return value of function}}
+  jt.get_id();               // expected-warning {{ignoring return value of function}}
+  jt.native_handle();        // expected-warning {{ignoring return value of function}}
+  jt.get_stop_source();      // expected-warning {{ignoring return value of function}}
+  jt.get_stop_token();       // expected-warning {{ignoring return value of function}}
+  jt.hardware_concurrency(); // expected-warning {{ignoring return value of function}}
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/request_stop.pass.cpp b/libcxx/test/std/thread/thread.jthread/request_stop.pass.cpp
new file mode 100644
index 000000000000000..f1109561cf9f299
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/request_stop.pass.cpp
@@ -0,0 +1,45 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// [[nodiscard]] bool request_stop() noexcept;
+
+#include <cassert>
+#include <concepts>
+#include <stop_token>
+#include <thread>
+#include <type_traits>
+
+#include "test_macros.h"
+
+static_assert(noexcept(std::declval<std::jthread&>().request_stop()));
+
+int main(int, char**) {
+  // Represents a thread
+  {
+    std::jthread jt{[] {}};
+    auto st = jt.get_stop_token();
+    assert(!st.stop_requested());
+    std::same_as<bool> decltype(auto) result = jt.request_stop();
+    assert(result);
+    assert(st.stop_requested());
+  }
+
+  // Does not represent a thread
+  {
+    std::jthread jt{};
+    std::same_as<bool> decltype(auto) result = jt.request_stop();
+    assert(!result);
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/swap.free.pass.cpp b/libcxx/test/std/thread/thread.jthread/swap.free.pass.cpp
new file mode 100644
index 000000000000000..776537cdff48358
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/swap.free.pass.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// friend void swap(jthread& x, jthread& y) noexcept;
+
+#include <cassert>
+#include <thread>
+#include <type_traits>
+
+#include "test_macros.h"
+
+template <class T>
+concept IsFreeSwapNoexcept = requires(T& a, T& b) {
+  { swap(a, b) } noexcept;
+};
+
+static_assert(IsFreeSwapNoexcept<std::jthread>);
+
+int main(int, char**) {
+  // x is default constructed
+  {
+    std::jthread t1;
+    std::jthread t2{[] {}};
+    const auto originalId2 = t2.get_id();
+    swap(t1, t2);
+
+    assert(t1.get_id() == originalId2);
+    assert(t2.get_id() == std::jthread::id());
+  }
+
+  // y is default constructed
+  {
+    std::jthread t1([] {});
+    std::jthread t2{};
+    const auto originalId1 = t1.get_id();
+    swap(t1, t2);
+
+    assert(t1.get_id() == std::jthread::id());
+    assert(t2.get_id() == originalId1);
+  }
+
+  // both not default constructed
+  {
+    std::jthread t1([] {});
+    std::jthread t2{[] {}};
+    const auto originalId1 = t1.get_id();
+    const auto originalId2 = t2.get_id();
+    swap(t1, t2);
+
+    assert(t1.get_id() == originalId2);
+    assert(t2.get_id() == originalId1);
+  }
+
+  // both default constructed
+  {
+    std::jthread t1;
+    std::jthread t2;
+    swap(t1, t2);
+
+    assert(t1.get_id() == std::jthread::id());
+    assert(t2.get_id() == std::jthread::id());
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/swap.member.pass.cpp b/libcxx/test/std/thread/thread.jthread/swap.member.pass.cpp
new file mode 100644
index 000000000000000..614e3ac8312dab7
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/swap.member.pass.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+// void swap(jthread& x) noexcept;
+
+#include <cassert>
+#include <thread>
+#include <type_traits>
+
+#include "test_macros.h"
+
+template <class T>
+concept IsMemberSwapNoexcept = requires(T& a, T& b) {
+  { a.swap(b) } noexcept;
+};
+
+static_assert(IsMemberSwapNoexcept<std::jthread>);
+
+int main(int, char**) {
+  // this is default constructed
+  {
+    std::jthread t1;
+    std::jthread t2{[] {}};
+    const auto originalId2 = t2.get_id();
+    t1.swap(t2);
+
+    assert(t1.get_id() == originalId2);
+    assert(t2.get_id() == std::jthread::id());
+  }
+
+  // that is default constructed
+  {
+    std::jthread t1([] {});
+    std::jthread t2{};
+    const auto originalId1 = t1.get_id();
+    t1.swap(t2);
+
+    assert(t1.get_id() == std::jthread::id());
+    assert(t2.get_id() == originalId1);
+  }
+
+  // both not default constructed
+  {
+    std::jthread t1([] {});
+    std::jthread t2{[] {}};
+    const auto originalId1 = t1.get_id();
+    const auto originalId2 = t2.get_id();
+    t1.swap(t2);
+
+    assert(t1.get_id() == originalId2);
+    assert(t2.get_id() == originalId1);
+  }
+
+  // both default constructed
+  {
+    std::jthread t1;
+    std::jthread t2;
+    t1.swap(t2);
+
+    assert(t1.get_id() == std::jthread::id());
+    assert(t2.get_id() == std::jthread::id());
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/thread/thread.jthread/type.compile.pass.cpp b/libcxx/test/std/thread/thread.jthread/type.compile.pass.cpp
new file mode 100644
index 000000000000000..496f793896757d6
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/type.compile.pass.cpp
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-stop_token
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// XFAIL: availability-synchronization_library-missing
+
+//  using id = thread::id;
+//  using native_handle_type = thread::native_handle_type;
+
+#include <thread>
+#include <type_traits>
+
+static_assert(std::is_same_v<std::jthread::id, std::thread::id>);
+static_assert(std::is_same_v<std::jthread::native_handle_type, std::thread::native_handle_type>);


        


More information about the libcxx-commits mailing list