diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -667,6 +667,7 @@ __system_error/error_condition.h __system_error/system_error.h __thread/formatter.h + __thread/jthread.h __thread/poll_with_backoff.h __thread/this_thread.h __thread/thread.h diff --git a/libcxx/include/__stop_token/stop_state.h b/libcxx/include/__stop_token/stop_state.h --- a/libcxx/include/__stop_token/stop_state.h +++ b/libcxx/include/__stop_token/stop_state.h @@ -14,8 +14,8 @@ #include <__config> #include <__stop_token/atomic_unique_lock.h> #include <__stop_token/intrusive_list_view.h> +#include <__threading_support> #include -#include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header @@ -62,7 +62,7 @@ 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 --- /dev/null +++ b/libcxx/include/__thread/jthread.h @@ -0,0 +1,132 @@ +// -*- 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 <__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 + +#ifdef _LIBCPP_HAS_NO_THREADS +# error " is not supported since libc++ has been configured without support for threads." +#endif + +#if _LIBCPP_STD_VER >= 20 + +_LIBCPP_BEGIN_NAMESPACE_STD + +class 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 + _LIBCPP_HIDE_FROM_ABI explicit jthread(_Fun&& __fun, _Args&&... __args) + requires(!std::is_same_v, jthread>) + : __stop_source_(), __thread_(__init_thread(std::forward<_Fun>(__fun), std::forward<_Args>(__args)...)) { + static_assert(is_constructible_v, _Fun>); + static_assert((is_constructible_v, _Args> && ...)); + static_assert(is_invocable_v, decay_t<_Args>...> || + is_invocable_v, 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&& __other) 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 + _LIBCPP_HIDE_FROM_ABI thread __init_thread(_Fun&& __fun, _Args&&... __args) { + if constexpr (is_invocable_v, stop_token, decay_t<_Args>...>) { + return thread(std::forward<_Fun>(__fun), __stop_source_.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 + +#endif // _LIBCPP___THREAD_JTHREAD_H diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -1530,6 +1530,7 @@ module __thread { module formatter { private header "__thread/formatter.h" } + module jthread { private header "__thread/jthread.h" } module poll_with_backoff { private header "__thread/poll_with_backoff.h" } module this_thread { private header "__thread/this_thread.h" } module thread { private header "__thread/thread.h" } diff --git a/libcxx/include/thread b/libcxx/include/thread --- a/libcxx/include/thread +++ b/libcxx/include/thread @@ -90,6 +90,7 @@ #include <__availability> #include <__config> #include <__thread/formatter.h> +#include <__thread/jthread.h> #include <__thread/poll_with_backoff.h> #include <__thread/this_thread.h> #include <__thread/thread.h> 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 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/assign.move.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: c++03, c++11, c++14, c++17 +// XFAIL: availability-synchronization_library-missing +// ADDITIONAL_COMPILE_FLAGS: -Wno-self-move + +// jthread& operator=(jthread&&) noexcept; + +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_move_assignable_v); + +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 + { + bool called = false; + std::jthread j1([&called] { + std::this_thread::sleep_for(std::chrono::milliseconds{2}); + called = true; + }); + + std::jthread j2; + j1 = std::move(j2); + assert(called); + } + + // 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()); + } + + 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 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/cons.default.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// 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: c++03, c++11, c++14, c++17 +// XFAIL: availability-synchronization_library-missing + +// jthread() noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_default_constructible_v); + +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 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/cons.func.token.pass.cpp @@ -0,0 +1,155 @@ +//===----------------------------------------------------------------------===// +// +// 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: c++03, c++11, c++14, c++17 +// XFAIL: availability-synchronization_library-missing + +// template +// explicit jthread(F&& f, Args&&... args); + +#include +#include +#include +#include + +#include "test_macros.h" + +template +struct Func { + void operator()(Args...) const; +}; + +// Constraints: remove_cvref_t is not the same type as jthread. +static_assert(std::is_constructible_v>); +static_assert(std::is_constructible_v, int>); +static_assert(!std::is_constructible_v); + +// explicit +template +void conversion_test(T); + +template +concept ImplicitlyConstructible = requires(Args&&... args) { conversion_test({std::forward(args)...}); }; + +static_assert(!ImplicitlyConstructible>); +static_assert(!ImplicitlyConstructible, 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)), get_stop_token(), auto(std::forward(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)), auto(std::forward(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)}; + } + + // [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()); + } + } + + // 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); + } + + // TODO: test the following: + // + // 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. + + 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 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/cons.move.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: c++03, c++11, c++14, c++17 +// XFAIL: availability-synchronization_library-missing + +// jthread(jthread&& x) noexcept; + +#include +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_move_constructible_v); + +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(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 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/copy.delete.compile.pass.cpp @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// 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: c++03, c++11, c++14, c++17 +// XFAIL: availability-synchronization_library-missing + +// jthread(const jthread&) = delete; +// jthread& operator=(const jthread&) = delete; + +#include +#include + +static_assert(!std::is_copy_constructible_v); +static_assert(!std::is_copy_assignable_v); 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 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/dtor.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: c++03, c++11, c++14, c++17 +// XFAIL: availability-synchronization_library-missing + +// ~jthread(); + +#include +#include +#include +#include +#include +#include "test_macros.h" + +int main(int, char**) { + // !joinable() + { std::jthread jt; } + + // If joinable() is true, calls request_stop() and then join(). + // request_stop is called + { + std::optional 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 + { + bool called = false; + std::optional jt([&called] { + std::this_thread::sleep_for(std::chrono::milliseconds{2}); + called = true; + }); + jt.reset(); + assert(called); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/typedef.compile.pass.cpp b/libcxx/test/std/thread/thread.jthread/typedef.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/typedef.compile.pass.cpp @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// 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: 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 +#include + +static_assert(std::is_same_v); +static_assert(std::is_same_v);