diff --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv --- a/libcxx/docs/Status/Cxx20Papers.csv +++ b/libcxx/docs/Status/Cxx20Papers.csv @@ -104,7 +104,7 @@ "`P0553R4 `__","LWG","Bit operations","Cologne","|Complete|","9.0" "`P0631R8 `__","LWG","Math Constants","Cologne","|Complete|","11.0" "`P0645R10 `__","LWG","Text Formatting","Cologne","|Complete| [#note-P0645]_","14.0" -"`P0660R10 `__","LWG","Stop Token and Joining Thread, Rev 10","Cologne","","" +"`P0660R10 `__","LWG","Stop Token and Joining Thread, Rev 10. (Section 32.3 done)","Cologne","In Progress","" "`P0784R7 `__","CWG","More constexpr containers","Cologne","|Complete|","12.0" "`P0980R1 `__","LWG","Making std::string constexpr","Cologne","|Complete|","15.0" "`P1004R2 `__","LWG","Making std::vector constexpr","Cologne","|Complete|","15.0" diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -542,6 +542,10 @@ __ranges/zip_view.h __split_buffer __std_stream + __stop_token/stop_callback.h + __stop_token/stop_source.h + __stop_token/stop_state.h + __stop_token/stop_token.h __string/char_traits.h __string/extern_template_lists.h __support/android/locale_bionic.h @@ -850,6 +854,7 @@ stdint.h stdio.h stdlib.h + stop_token streambuf string string.h diff --git a/libcxx/include/__stop_token/stop_callback.h b/libcxx/include/__stop_token/stop_callback.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__stop_token/stop_callback.h @@ -0,0 +1,99 @@ +// -*- 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___STOP_TOKEN_STOP_CALLBACK_H +#define _LIBCPP___STOP_TOKEN_STOP_CALLBACK_H + +#include <__concepts/constructible.h> +#include <__concepts/destructible.h> +#include <__concepts/invocable.h> +#include <__config> +#include <__stop_token/stop_state.h> +#include <__stop_token/stop_token.h> +#include <__type_traits/is_nothrow_constructible.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER >= 20 + +template +class stop_callback : private __stop_callback_base { + static_assert(invocable<_Callback>, + "Mandates: stop_callback is instantiated with an argument for the template parameter Callback that " + "satisfies both invocable and destructible."); + static_assert(destructible<_Callback>, + "Mandates: stop_callback is instantiated with an argument for the template parameter Callback that " + "satisfies both invocable and destructible."); + +public: + using callback_type = _Callback; + + template + requires constructible_from<_Callback, _Cb> + explicit stop_callback(const stop_token& __st, _Cb&& __cb) noexcept(is_nothrow_constructible_v<_Callback, _Cb>) + : __stop_callback_base(&stop_callback::__callback_fn_impl), + __callback_(std::forward<_Cb>(__cb)), + __ref_counted_state_() { + if (__st.__ref_counted_state_.__has_state()) { + if (__st.__ref_counted_state_.get()->__add_callback(this)) { + // st.stop_requested() was false and this is successfully added to the linked list + __ref_counted_state_ = __st.__ref_counted_state_; + } + } + } + + template + requires constructible_from<_Callback, _Cb> + explicit stop_callback(stop_token&& __st, _Cb&& __cb) noexcept(is_nothrow_constructible_v<_Callback, _Cb>) + : __stop_callback_base(&stop_callback::__callback_fn_impl), + __callback_(std::forward<_Cb>(__cb)), + __ref_counted_state_() { + if (__st.__ref_counted_state_.__has_state()) { + if (__st.__ref_counted_state_.get()->__add_callback(this)) { + // st.stop_requested() was false and this is successfully added to the linked list + __st.__ref_counted_state_.__swap(__ref_counted_state_); + } + } + } + + ~stop_callback() { + if (__ref_counted_state_.__has_state()) { + __ref_counted_state_.get()->__remove_callback(this); + } + } + + stop_callback(const stop_callback&) = delete; + stop_callback(stop_callback&&) = delete; + stop_callback& operator=(const stop_callback&) = delete; + stop_callback& operator=(stop_callback&&) = delete; + +private: + _LIBCPP_NO_UNIQUE_ADDRESS _Callback __callback_; + __ref_counted_stop_state __ref_counted_state_; + +private: + friend __stop_callback_base; + + static void __callback_fn_impl(__stop_callback_base* __cb_base) noexcept { + std::forward<_Callback>(static_cast(__cb_base)->__callback_)(); + } +}; + +template +stop_callback(stop_token, Callback) -> stop_callback; + +#endif // _LIBCPP_STD_VER >= 20 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___STOP_TOKEN_STOP_TOKEN_H diff --git a/libcxx/include/__stop_token/stop_source.h b/libcxx/include/__stop_token/stop_source.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__stop_token/stop_source.h @@ -0,0 +1,98 @@ +// -*- 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___STOP_TOKEN_STOP_SOURCE_H +#define _LIBCPP___STOP_TOKEN_STOP_SOURCE_H + +#include <__config> +#include <__stop_token/stop_state.h> +#include <__stop_token/stop_token.h> +#include <__utility/move.h> + + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER >= 20 + +struct nostopstate_t { + explicit nostopstate_t() = default; +}; + +inline constexpr nostopstate_t nostopstate{}; + +class stop_source { +public: + stop_source() : __ref_counted_state_(__ref_counted_stop_state::__new_state_from_stop_source_tag{}) {} + + explicit stop_source(nostopstate_t) noexcept {} + + stop_source(const stop_source& __other) noexcept : __ref_counted_state_(__other.__ref_counted_state_) { + if (__ref_counted_state_.__has_state()) { + __ref_counted_state_.get()->__increment_stop_source_counter(); + } + } + + stop_source(stop_source&& __other) noexcept = default; + + stop_source& operator=(const stop_source& __other) noexcept { + if (__ref_counted_state_ != __other.__ref_counted_state_) { + auto __tmp = std::move(__ref_counted_state_); + __ref_counted_state_ = __other.__ref_counted_state_; + if (__ref_counted_state_.__has_state()) { + __ref_counted_state_.get()->__increment_stop_source_counter(); + } + if (__tmp.__has_state()) { + __tmp.get()->__decrement_stop_source_counter(); + } + } + return *this; + } + + stop_source& operator=(stop_source&&) noexcept = default; + + ~stop_source() { + if (__ref_counted_state_.__has_state()) { + __ref_counted_state_.get()->__decrement_stop_source_counter(); + } + } + + void swap(stop_source& __other) noexcept { __ref_counted_state_.__swap(__other.__ref_counted_state_); } + + [[nodiscard]] stop_token get_token() const noexcept { return stop_token(__ref_counted_state_); } + + [[nodiscard]] bool stop_possible() const noexcept { return __ref_counted_state_.__has_state(); } + + [[nodiscard]] bool stop_requested() const noexcept { + return __ref_counted_state_.__has_state() && __ref_counted_state_.get()->__stop_requested(); + } + + bool request_stop() noexcept { + if (!__ref_counted_state_.__has_state()) { + return false; + } + return __ref_counted_state_.get()->__request_stop(); + } + + [[nodiscard]] friend bool operator==(const stop_source&, const stop_source&) noexcept = default; + + friend void swap(stop_source& __lhs, stop_source& __rhs) noexcept { __lhs.swap(__rhs); } + +private: + __ref_counted_stop_state __ref_counted_state_; +}; + +#endif // _LIBCPP_STD_VER >= 20 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___STOP_TOKEN_STOP_SOURCE_H diff --git a/libcxx/include/__stop_token/stop_state.h b/libcxx/include/__stop_token/stop_state.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__stop_token/stop_state.h @@ -0,0 +1,289 @@ +// -*- 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___STOP_TOKEN_STOP_STATE_H +#define _LIBCPP___STOP_TOKEN_STOP_STATE_H + +#include <__config> +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER >= 20 + +struct __stop_callback_base { + using __callback_fn_t = void(__stop_callback_base*) noexcept; + explicit __stop_callback_base(__callback_fn_t* __callback_fn) : __callback_fn_(__callback_fn) {} + + void __invoke() noexcept { __callback_fn_(this); } + + __callback_fn_t* __callback_fn_; + __stop_callback_base* __next_ = nullptr; + __stop_callback_base* __prev_ = nullptr; + std::atomic __completed_ = false; +}; + +struct __stop_state { + __stop_callback_base* __cb_head_ = nullptr; + + static constexpr uint32_t __stop_requested_bit = 1; + static constexpr uint32_t __locked_bit = 1 << 1; + static constexpr uint32_t __stop_source_counter_shift = 2; + // 31 - 2 | 1 | 0 | + // stop_source counter | locked | stop_requested | + std::atomic __state_ = 0; + + // reference count for stop_token + stop_callback + stop_source + // used by __ref_counted_stop_state, put it here for better layout + std::atomic __ref_count_ = 0; + + std::thread::id __requesting_thread_; + + __stop_state(uint32_t __state, uint32_t __ref_count) noexcept : __state_(__state), __ref_count_(__ref_count) {} + + void __increment_stop_source_counter() noexcept { + __state_.fetch_add(1 << __stop_source_counter_shift, std::memory_order_relaxed); + } + + void __decrement_stop_source_counter() noexcept { + __state_.fetch_sub(1 << __stop_source_counter_shift, std::memory_order_relaxed); + } + + bool __stop_requested() const noexcept { + // acquire because [thread.stoptoken.intro] A call to request_stop that returns true + // synchronizes with a call to stop_requested on an associated stop_token or stop_source + // object that returns true. + // request_stop's compare_exchange_week has release which syncs with this acquire + return (__state_.load(std::memory_order_acquire) & __stop_requested_bit) != 0; + } + + bool __stop_possible_for_stop_token() const noexcept { + // [stoptoken.mem] false if "a stop request was not made and there are no associated stop_source objects" + auto __curent_state = __state_.load(std::memory_order_acquire); + return ((__curent_state & __stop_requested_bit) != 0) || ((__curent_state >> __stop_source_counter_shift) != 0); + } + + bool __request_stop() noexcept { + if (!__try_lock_for_request_stop()) { + return false; + } + __requesting_thread_ = std::this_thread::get_id(); + + while (__cb_head_) { + auto __cb = __cb_head_; + __cb_head_ = __cb_head_->__next_; + if (__cb_head_) { + __cb_head_->__prev_ = nullptr; + } + + __unlock(); + + __cb->__invoke(); + + __cb->__completed_.store(true, std::memory_order_release); + __cb->__completed_.notify_all(); + + __lock(); + } + + __unlock(); + return true; + } + + bool __try_lock_for_request_stop() noexcept { + auto __current_state = __state_.load(std::memory_order_relaxed); + do { + while (true) { + if ((__current_state & __stop_requested_bit) != 0) { + // already stop requested + return false; + } else if ((__current_state & __locked_bit) != 0) { + // another thread has locked the state to add/remove callbacks, we need to wait + // We don't need the acq/rel ordering for adding callbacks at this stage + // as we are stuck in this loop. + // We only need it when we passe the compare_exchange_weak to run the callbacks + __state_.wait(__current_state, std::memory_order_relaxed); + __current_state = __state_.load(std::memory_order_relaxed); + } else { + // for now, __stop_requested_bit == 0 && __locked_bit == 0 + // let's try compare_exchange_weak + break; + } + } + } while (!__state_.compare_exchange_weak( + __current_state, + __current_state | __locked_bit | __stop_requested_bit, // set locked and requested bit at the same time + // acq because [thread.stoptoken.intro] Registration of a callback synchronizes with the invocation of that callback. + // acquire so that we can see other threads writes to callbacks + // rel because [thread.stoptoken.intro] A call to request_stop that returns true synchronizes with a call + // to stop_requested on an associated stop_token or stop_source object that returns true. + std::memory_order_acq_rel, + std::memory_order_relaxed)); // on failure we are stuck in the loop and no need for memory order + + // successfully locked and set the requested bit + return true; + } + + void __unlock() noexcept { + __state_.fetch_and(~__locked_bit, std::memory_order_release); + __state_.notify_all(); + } + + void __lock() noexcept { + auto __current_state = __state_.load(std::memory_order_relaxed); + do { + while ((__current_state & __locked_bit) != 0) { + __state_.wait(__current_state, std::memory_order_relaxed); + __current_state = __state_.load(std::memory_order_relaxed); + } + } while (!__state_.compare_exchange_weak( + __current_state, __current_state | __locked_bit, std::memory_order_acquire, std::memory_order_relaxed)); + } + + bool __add_callback(__stop_callback_base* __cb) noexcept { + auto __current_state = __state_.load(std::memory_order_relaxed); + do { + while (true) { + if ((__current_state & __stop_requested_bit) != 0) { + // already stop requested, synchronously run the callback + __cb->__invoke(); + return false; + } else if ((__current_state >> __stop_source_counter_shift) == 0) { + // no stop source. no need to add callback + return false; + } else if ((__current_state & __locked_bit) != 0) { + // another thread has locked the state to add/remove callbacks, or request_stop, wait + __state_.wait(__current_state, std::memory_order_relaxed); + __current_state = __state_.load(std::memory_order_relaxed); + } else { + break; + } + } + } while (!__state_.compare_exchange_weak( + __current_state, __current_state | __locked_bit, std::memory_order_acquire, std::memory_order_relaxed)); + + // locked now + + __cb->__next_ = __cb_head_; + if (__cb_head_) { + __cb_head_->__prev_ = __cb; + } + __cb_head_ = __cb; + + __unlock(); + return true; + // [thread.stoptoken.intro] Registration of a callback synchronizes with the invocation of that callback. + } + + void __remove_callback(__stop_callback_base* __cb) noexcept { + __lock(); + + if (__cb->__prev_) { + // previous exists, set its next to our next to skip __cb + __cb->__prev_->__next_ = __cb->__next_; + if (__cb->__next_) { + __cb->__next_->__prev_ = __cb->__prev_; + } + __unlock(); + return; + } else if (__cb == __cb_head_) { + // remove head + __cb_head_ = __cb_head_->__next_; + if (__cb_head_) { + __cb_head_->__prev_ = nullptr; + } + __unlock(); + return; + } else { + // __cb is not in the list. There is a stop request that attempts to call the callback (and removed it from the list) + + auto __requested_thread = __requesting_thread_; + __unlock(); + + if (std::this_thread::get_id() != __requested_thread) { + // [stopcallback.cons] If callback is concurrently executing on another thread, then the return + // from the invocation of callback strongly happens before ([intro.races]) callback is destroyed. + __cb->__completed_.wait(false, std::memory_order_acquire); + } + } + } +}; + +struct __ref_counted_stop_state { + struct __new_state_from_stop_source_tag {}; + + __ref_counted_stop_state() = default; + + __ref_counted_stop_state(__new_state_from_stop_source_tag) + : __stop_state_(new __stop_state(/*state=*/1 << __stop_state::__stop_source_counter_shift, /*__ref_count=*/1)) {} + + __ref_counted_stop_state(const __ref_counted_stop_state& __other) noexcept : __stop_state_(__other.__stop_state_) { + if (__stop_state_) { + __increment_ref_count(__stop_state_); + } + } + + __ref_counted_stop_state(__ref_counted_stop_state&& __other) noexcept : __stop_state_(__other.__stop_state_) { + __other.__stop_state_ = nullptr; + } + + __ref_counted_stop_state& operator=(const __ref_counted_stop_state& __other) noexcept { + if (__other.__stop_state_ != __stop_state_) { + if (__other.__stop_state_) { + __increment_ref_count(__other.__stop_state_); + } + if (__stop_state_) { + __decrement_ref_count(__stop_state_); + } + __stop_state_ = __other.__stop_state_; + } + return *this; + } + + __ref_counted_stop_state& operator=(__ref_counted_stop_state&& __other) noexcept { + __ref_counted_stop_state(std::move(__other)).__swap(*this); + return *this; + } + + ~__ref_counted_stop_state() { + if (__stop_state_) { + __decrement_ref_count(__stop_state_); + } + } + + __stop_state* get() const noexcept { return __stop_state_; } + + bool __has_state() const noexcept { return __stop_state_ != nullptr; } + + void __swap(__ref_counted_stop_state& __other) { std::swap(__stop_state_, __other.__stop_state_); } + + friend bool constexpr operator==(const __ref_counted_stop_state&, const __ref_counted_stop_state&) = default; + +private: + __stop_state* __stop_state_ = nullptr; + + void __increment_ref_count(__stop_state* __state) { __state->__ref_count_.fetch_add(1, std::memory_order_relaxed); } + + void __decrement_ref_count(__stop_state* __state) { + if (__state->__ref_count_.fetch_sub(1, std::memory_order_acq_rel) == 1) { + delete __state; + } + } +}; + +#endif // _LIBCPP_STD_VER >= 20 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___STOP_TOKEN_STOP_STATE_H diff --git a/libcxx/include/__stop_token/stop_token.h b/libcxx/include/__stop_token/stop_token.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__stop_token/stop_token.h @@ -0,0 +1,63 @@ +// -*- 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___STOP_TOKEN_STOP_TOKEN_H +#define _LIBCPP___STOP_TOKEN_STOP_TOKEN_H + +#include <__config> +#include <__stop_token/stop_state.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER >= 20 + +class stop_token { +public: + stop_token() noexcept = default; + + stop_token(const stop_token&) noexcept = default; + stop_token(stop_token&&) noexcept = default; + stop_token& operator=(const stop_token&) noexcept = default; + stop_token& operator=(stop_token&&) noexcept = default; + ~stop_token() = default; + + void swap(stop_token& __other) noexcept { __ref_counted_state_.__swap(__other.__ref_counted_state_); } + + [[nodiscard]] bool stop_requested() const noexcept { + return __ref_counted_state_.__has_state() && __ref_counted_state_.get()->__stop_requested(); + } + + [[nodiscard]] bool stop_possible() const noexcept { + return __ref_counted_state_.__has_state() && __ref_counted_state_.get()->__stop_possible_for_stop_token(); + } + + [[nodiscard]] friend bool operator==(const stop_token&, const stop_token&) noexcept = default; + + friend void swap(stop_token& __lhs, stop_token& __rhs) noexcept { __lhs.swap(__rhs); } + +private: + __ref_counted_stop_state __ref_counted_state_; + +private: + friend class stop_source; + template + friend class stop_callback; + + explicit stop_token(const __ref_counted_stop_state& __state) : __ref_counted_state_(__state) {} +}; + +#endif // _LIBCPP_STD_VER >= 20 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___STOP_TOKEN_STOP_TOKEN_H diff --git a/libcxx/include/libcxx.imp b/libcxx/include/libcxx.imp --- a/libcxx/include/libcxx.imp +++ b/libcxx/include/libcxx.imp @@ -35,6 +35,7 @@ { include: [ "@<__numeric/.*>", "private", "", "public" ] }, { include: [ "@<__random/.*>", "private", "", "public" ] }, { include: [ "@<__ranges/.*>", "private", "", "public" ] }, + { include: [ "@<__stop_token/.*>", "private", "", "public" ] }, { include: [ "@<__string/.*>", "private", "", "public" ] }, { include: [ "@<__support/.*>", "private", "", "public" ] }, { include: [ "@<__thread/.*>", "private", "", "public" ] }, 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 @@ -1352,6 +1352,16 @@ header "stdexcept" export * } + module stop_token { + header "stop_token" + export * + + module __stop_token { + module stop_callback { private header "__stop_token/stop_callback.h" } + module stop_source { private header "__stop_token/stop_source.h" } + module stop_token { private header "__stop_token/stop_token.h" } + } + } module streambuf { @requires_LIBCXX_ENABLE_LOCALIZATION@ header "streambuf" diff --git a/libcxx/include/stop_token b/libcxx/include/stop_token new file mode 100644 --- /dev/null +++ b/libcxx/include/stop_token @@ -0,0 +1,44 @@ +// -*- 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_STOP_TOKEN +#define _LIBCPP_STOP_TOKEN + +/* + +namespace std { + // [stoptoken], class stop_token + class stop_token; + + // [stopsource], class stop_source + class stop_source; + + // no-shared-stop-state indicator + struct nostopstate_t { + explicit nostopstate_t() = default; + }; + inline constexpr nostopstate_t nostopstate{}; + + // [stopcallback], class template stop_callback + template + class stop_callback; + +*/ + +#include <__config> +#include <__stop_token/stop_callback.h> +#include <__stop_token/stop_source.h> +#include <__stop_token/stop_token.h> + +#ifdef _LIBCPP_HAS_NO_THREADS +# error " is not supported since libc++ has been configured without support for threads." +#endif + +#endif // _LIBCPP_STOP_TOKEN + diff --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp --- a/libcxx/test/libcxx/private_headers.verify.cpp +++ b/libcxx/test/libcxx/private_headers.verify.cpp @@ -573,6 +573,10 @@ #include <__ranges/zip_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/zip_view.h'}} #include <__split_buffer> // expected-error@*:* {{use of private header from outside its module: '__split_buffer'}} #include <__std_stream> // expected-error@*:* {{use of private header from outside its module: '__std_stream'}} +#include <__stop_token/stop_callback.h> // expected-error@*:* {{use of private header from outside its module: '__stop_token/stop_callback.h'}} +#include <__stop_token/stop_source.h> // expected-error@*:* {{use of private header from outside its module: '__stop_token/stop_source.h'}} +#include <__stop_token/stop_state.h> // expected-error@*:* {{use of private header from outside its module: '__stop_token/stop_state.h'}} +#include <__stop_token/stop_token.h> // expected-error@*:* {{use of private header from outside its module: '__stop_token/stop_token.h'}} #include <__string/char_traits.h> // expected-error@*:* {{use of private header from outside its module: '__string/char_traits.h'}} #include <__string/extern_template_lists.h> // expected-error@*:* {{use of private header from outside its module: '__string/extern_template_lists.h'}} #include <__thread/poll_with_backoff.h> // expected-error@*:* {{use of private header from outside its module: '__thread/poll_with_backoff.h'}} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/assign.copy.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/assign.copy.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/assign.copy.pass.cpp @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// stop_source& operator=(const stop_source& rhs) noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_copy_assignable_v); + +int main(int, char**) { + // have two different states + { + std::stop_source ss1; + std::stop_source ss2; + + assert(ss1 != ss2); + + ss2.request_stop(); + + assert(!ss1.stop_requested()); + assert(ss2.stop_requested()); + + std::same_as decltype(auto) ref = ss1 = ss2; + assert(&ref == &ss1); + + assert(ss1 == ss2); + assert(ss1.stop_requested()); + assert(ss2.stop_requested()); + } + + // this has no state + { + std::stop_source ss1{std::nostopstate}; + std::stop_source ss2; + + assert(ss1 != ss2); + + ss2.request_stop(); + + assert(!ss1.stop_requested()); + assert(!ss1.stop_possible()); + assert(ss2.stop_requested()); + assert(ss2.stop_possible()); + + std::same_as decltype(auto) ref = ss1 = ss2; + assert(&ref == &ss1); + + assert(ss1 == ss2); + assert(ss1.stop_requested()); + assert(ss1.stop_possible()); + assert(ss2.stop_requested()); + assert(ss2.stop_possible()); + } + + // other has no state + { + std::stop_source ss1; + std::stop_source ss2{std::nostopstate}; + + assert(ss1 != ss2); + + ss1.request_stop(); + + assert(ss1.stop_requested()); + assert(ss1.stop_possible()); + assert(!ss2.stop_requested()); + assert(!ss2.stop_possible()); + + std::same_as decltype(auto) ref = ss1 = ss2; + assert(&ref == &ss1); + + assert(ss1 == ss2); + assert(!ss1.stop_requested()); + assert(!ss1.stop_possible()); + assert(!ss2.stop_requested()); + assert(!ss2.stop_possible()); + } + + // both no state + { + std::stop_source ss1{std::nostopstate}; + std::stop_source ss2{std::nostopstate}; + + assert(ss1 == ss2); + + assert(!ss1.stop_requested()); + assert(!ss1.stop_possible()); + assert(!ss2.stop_requested()); + assert(!ss2.stop_possible()); + + std::same_as decltype(auto) ref = ss1 = ss2; + assert(&ref == &ss1); + + assert(ss1 == ss2); + assert(!ss1.stop_requested()); + assert(!ss1.stop_possible()); + assert(!ss2.stop_requested()); + assert(!ss2.stop_possible()); + } + + // self assignment + { + std::stop_source ss; + auto& self = ss; + + assert(!ss.stop_requested()); + + std::same_as decltype(auto) ref = ss = self; + assert(&ref == &ss); + + assert(!ss.stop_requested()); + + ss.request_stop(); + assert(ss.stop_requested()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/cons.copy.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/cons.copy.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/cons.copy.pass.cpp @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// stop_source(const stop_source&) noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_copy_constructible_v); + +int main(int, char**) { + { + std::stop_source source; + std::stop_source copy{source}; + + assert(source == copy); + + assert(source.stop_possible()); + assert(!source.stop_requested()); + + assert(copy.stop_possible()); + assert(!copy.stop_requested()); + + source.request_stop(); + assert(source.stop_possible()); + assert(source.stop_requested()); + + assert(copy.stop_possible()); + assert(copy.stop_requested()); + } + + // source counter incremented + { + std::optional source(std::in_place); + auto st = source->get_token(); + assert(st.stop_possible()); + + std::optional copy{source}; + source.reset(); + + assert(st.stop_possible()); + + copy.reset(); + assert(!st.stop_possible()); + } + + // copy from empty + { + std::stop_source ss1{std::nostopstate}; + std::stop_source copy{ss1}; + assert(!copy.stop_possible()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/cons.default.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/cons.default.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/cons.default.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: c++03, c++11, c++14, c++17 + +// stop_source(); + +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_default_constructible_v); + +int main(int, char**) { + { + std::stop_source ss = {}; // implicit + assert(ss.stop_possible()); + assert(!ss.stop_requested()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/cons.move.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/cons.move.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/cons.move.pass.cpp @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// stop_source(stop_source&&) noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_move_constructible_v); + +int main(int, char**) { + { + std::stop_source source; + + assert(source.stop_possible()); + assert(!source.stop_requested()); + + std::stop_source source2{std::move(source)}; + + assert(!source.stop_possible()); + assert(!source.stop_requested()); + + assert(source2.stop_possible()); + assert(!source2.stop_requested()); + + source2.request_stop(); + + assert(!source.stop_possible()); + assert(!source.stop_requested()); + + assert(source2.stop_possible()); + assert(source2.stop_requested()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/cons.nostopstate.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/cons.nostopstate.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/cons.nostopstate.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 + +// explicit stop_source(nostopstate_t) noexcept; + +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_constructible_v); +// explicit +static_assert(!std::is_convertible_v); + +int main(int, char**) { + { + std::stop_source ss(std::nostopstate); + assert(!ss.stop_possible()); + assert(!ss.stop_requested()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/equals.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/equals.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/equals.pass.cpp @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// [[nodiscard]] bool operator==(const stop_source& lhs, const stop_source& rhs) noexcept; +// Returns: true if lhs and rhs have ownership of the same stop state or if both lhs and rhs do not have ownership of a stop state; otherwise false. + +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsNoThrowEqualityComparable = requires(const T& t1, const T& t2) { + { t1 == t2 } noexcept; +}; + +static_assert(IsNoThrowEqualityComparable); + +int main(int, char**) { + // both no state + { + const std::stop_source ss1(std::nostopstate); + const std::stop_source ss2(std::nostopstate); + assert(ss1 == ss2); + assert(!(ss1 != ss2)); + } + + // only one has no state + { + const std::stop_source ss1(std::nostopstate); + const std::stop_source ss2; + assert(!(ss1 == ss2)); + assert(ss1 != ss2); + } + + // both has states. same state + { + const std::stop_source ss1; + const std::stop_source ss2(ss1); + assert(ss1 == ss2); + assert(!(ss1 != ss2)); + } + + // both has states. different states + { + const std::stop_source ss1; + const std::stop_source ss2; + assert(!(ss1 == ss2)); + assert(ss1 != ss2); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/get_token.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/get_token.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/get_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: c++03, c++11, c++14, c++17 + +// [[nodiscard]] stop_token get_token() const noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsGetTokenNoexcept = requires(const T& t) { + { t.get_token() } noexcept; +}; + +static_assert(IsGetTokenNoexcept); + +int main(int, char**) { + // no state + { + std::stop_source ss{std::nostopstate}; + std::same_as decltype(auto) st = ss.get_token(); + assert(!st.stop_possible()); + assert(!st.stop_requested()); + } + + // with state + { + std::stop_source ss; + std::same_as decltype(auto) st = ss.get_token(); + assert(st.stop_possible()); + assert(!st.stop_requested()); + + ss.request_stop(); + assert(st.stop_requested()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/move.copy.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/move.copy.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/move.copy.pass.cpp @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// stop_source& operator=(stop_source&& rhs) noexcept; + +#include +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_move_assignable_v); + +int main(int, char**) { + // have two different states + { + std::stop_source ss1; + std::stop_source ss2; + + assert(ss1 != ss2); + + ss2.request_stop(); + + assert(!ss1.stop_requested()); + assert(ss2.stop_requested()); + + std::same_as decltype(auto) ref = ss1 = std::move(ss2); + assert(&ref == &ss1); + + assert(ss1.stop_requested()); + assert(!ss2.stop_possible()); + assert(!ss2.stop_requested()); + } + + // this has no state + { + std::stop_source ss1{std::nostopstate}; + std::stop_source ss2; + + assert(ss1 != ss2); + + ss2.request_stop(); + + assert(!ss1.stop_requested()); + assert(!ss1.stop_possible()); + assert(ss2.stop_requested()); + assert(ss2.stop_possible()); + + std::same_as decltype(auto) ref = ss1 = std::move(ss2); + assert(&ref == &ss1); + + assert(ss1.stop_requested()); + assert(ss1.stop_possible()); + assert(!ss2.stop_requested()); + assert(!ss2.stop_possible()); + } + + // other has no state + { + std::stop_source ss1; + std::stop_source ss2{std::nostopstate}; + + assert(ss1 != ss2); + + ss1.request_stop(); + + assert(ss1.stop_requested()); + assert(ss1.stop_possible()); + assert(!ss2.stop_requested()); + assert(!ss2.stop_possible()); + + std::same_as decltype(auto) ref = ss1 = std::move(ss2); + assert(&ref == &ss1); + + assert(ss1 == ss2); + assert(!ss1.stop_requested()); + assert(!ss1.stop_possible()); + assert(!ss2.stop_requested()); + assert(!ss2.stop_possible()); + } + + // both no state + { + std::stop_source ss1{std::nostopstate}; + std::stop_source ss2{std::nostopstate}; + + assert(ss1 == ss2); + + assert(!ss1.stop_requested()); + assert(!ss1.stop_possible()); + assert(!ss2.stop_requested()); + assert(!ss2.stop_possible()); + + std::same_as decltype(auto) ref = ss1 = std::move(ss2); + assert(&ref == &ss1); + + assert(ss1 == ss2); + assert(!ss1.stop_requested()); + assert(!ss1.stop_possible()); + assert(!ss2.stop_requested()); + assert(!ss2.stop_possible()); + } + + // self assignment + { + std::stop_source ss; + auto& self = ss; + + assert(!ss.stop_requested()); + + std::same_as decltype(auto) ref = ss = std::move(self); + assert(&ref == &ss); + + assert(!ss.stop_requested()); + + ss.request_stop(); + assert(ss.stop_requested()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/nodiscard.verify.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/nodiscard.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/nodiscard.verify.cpp @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// [[nodiscard]] stop_token get_token() const noexcept; +// [[nodiscard]] bool stop_possible() const noexcept; +// [[nodiscard]] bool stop_requested() const noexcept; +// [[nodiscard]] friend bool operator==(const stop_source& lhs, const stop_source& rhs) noexcept; + +#include + +void test() { + std::stop_source ss; + ss.get_token(); // expected-warning {{ignoring return value of function}} + ss.stop_requested(); // expected-warning {{ignoring return value of function}} + ss.stop_possible(); // expected-warning {{ignoring return value of function}} + operator==(ss, ss); // expected-warning {{ignoring return value of function}} +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/request_stop.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/request_stop.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/request_stop.pass.cpp @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// bool request_stop() noexcept; + +#include +#include +#include +#include +#include +#include + +#include "make_test_thread.h" +#include "test_macros.h" + +template +concept IsRequestStopNoexcept = requires(T& t) { + { t.request_stop() } noexcept; +}; + +static_assert(IsRequestStopNoexcept); + +int main(int, char**) { + // If *this does not have ownership of a stop state, returns false + { + std::stop_source ss{std::nostopstate}; + auto ret = ss.request_stop(); + assert(!ret); + assert(!ss.stop_requested()); + } + + // Otherwise, atomically determines whether the owned stop state has received + // a stop request, and if not, makes a stop request + { + std::stop_source ss; + + auto ret = ss.request_stop(); + assert(ret); + assert(ss.stop_requested()); + } + + // already requested + { + std::stop_source ss; + ss.request_stop(); + assert(ss.stop_requested()); + + auto ret = ss.request_stop(); + assert(!ret); + assert(ss.stop_requested()); + } + + // If the request was made, the callbacks registered by + // associated stop_callback objects are synchronously called. + { + std::stop_source ss; + auto st = ss.get_token(); + + bool cb1Called = false; + bool cb2Called = false; + std::stop_callback sc1(st, [&] { cb1Called = true; }); + std::stop_callback sc2(st, [&] { cb2Called = true; }); + + ss.request_stop(); + assert(cb1Called); + assert(cb2Called); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/stop_possible.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/stop_possible.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/stop_possible.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: c++03, c++11, c++14, c++17 + +// [[nodiscard]] bool stop_possible() const noexcept; +// Returns: true if *this has ownership of a stop state; otherwise, false. + +#include +#include +#include + +#include "test_macros.h" + +template +concept IsStopPossibleNoexcept = requires(const T& t) { + { t.stop_possible() } noexcept; +}; + +static_assert(IsStopPossibleNoexcept); + +int main(int, char**) { + // no state + { + const std::stop_source st{std::nostopstate}; + assert(!st.stop_possible()); + } + + // with state + { + const std::stop_source st; + assert(st.stop_possible()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/stop_requested.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/stop_requested.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/stop_requested.pass.cpp @@ -0,0 +1,104 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// [[nodiscard]] bool stop_requested() const noexcept; +// true if *this has ownership of a stop state that has received a stop request; otherwise, false. + +#include +#include +#include +#include +#include +#include + +#include "make_test_thread.h" +#include "test_macros.h" + +template +concept IsStopRequestedNoexcept = requires(const T& t) { + { t.stop_requested() } noexcept; +}; + +static_assert(IsStopRequestedNoexcept); + +int main(int, char**) { + // no state + { + const std::stop_source ss{std::nostopstate}; + assert(!ss.stop_requested()); + } + + // has state + { + std::stop_source ss; + assert(!ss.stop_requested()); + + ss.request_stop(); + assert(ss.stop_requested()); + } + + // request from another instance with same state + { + std::stop_source ss1; + auto ss2 = ss1; + ss2.request_stop(); + assert(ss1.stop_requested()); + } + + // request from another instance with different state + { + std::stop_source ss1; + std::stop_source ss2; + + ss2.request_stop(); + assert(!ss1.stop_requested()); + } + + // multiple threads + { + std::stop_source ss; + + std::thread t = support::make_test_thread([&]() { ss.request_stop(); }); + + t.join(); + assert(ss.stop_requested()); + } + + // [thread.stopsource.intro] A call to request_stop that returns true + // synchronizes with a call to stop_requested on an associated stop_source + // or stop_source object that returns true. + { + std::stop_source ss; + + bool flag = false; + + std::thread t = support::make_test_thread([&]() { + using namespace std::chrono_literals; + std::this_thread::sleep_for(1ms); + + // happens-before request_stop + flag = true; + auto b = ss.request_stop(); + assert(b); + }); + + while (!ss.stop_requested()) { + std::this_thread::yield(); + } + + // write should be visible to the current thread + assert(flag == true); + + t.join(); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/swap.free.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/swap.free.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/swap.free.pass.cpp @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// void swap(stop_source& rhs) noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsNoThrowFreeSwappable = requires(T& t) { + { swap(t, t) } noexcept; +}; + +static_assert(IsNoThrowFreeSwappable); + +int main(int, char**) { + { + std::stop_source ss1; + std::stop_source ss2; + + assert(ss1 != ss2); + + ss2.request_stop(); + + assert(!ss1.stop_requested()); + assert(ss2.stop_requested()); + + swap(ss1, ss2); + + assert(ss1 != ss2); + assert(ss1.stop_requested()); + assert(!ss2.stop_requested()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stopsource/swap.member.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stopsource/swap.member.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stopsource/swap.member.pass.cpp @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// void swap(stop_source& rhs) noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsNoThrowMemberSwappable = requires(T& t) { + { t.swap(t) } noexcept; +}; + +static_assert(IsNoThrowMemberSwappable); + +int main(int, char**) { + { + std::stop_source ss1; + std::stop_source ss2; + + assert(ss1 != ss2); + + ss2.request_stop(); + + assert(!ss1.stop_requested()); + assert(ss2.stop_requested()); + + ss1.swap(ss2); + + assert(ss1 != ss2); + assert(ss1.stop_requested()); + assert(!ss2.stop_requested()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stoptoken/assign.copy.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stoptoken/assign.copy.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stoptoken/assign.copy.pass.cpp @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// stop_token& operator=(const stop_token& rhs) noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_copy_assignable_v); + +int main(int, char**) { + { + std::stop_token st1; + + std::stop_source source; + auto st2 = source.get_token(); + + assert(st1 != st2); + + source.request_stop(); + + assert(!st1.stop_requested()); + assert(st2.stop_requested()); + + std::same_as decltype(auto) ref = st1 = st2; + assert(&ref == &st1); + + assert(st1 == st2); + assert(st1.stop_requested()); + assert(st2.stop_requested()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stoptoken/assign.move.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stoptoken/assign.move.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stoptoken/assign.move.pass.cpp @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// stop_token& operator=(stop_token&& rhs) noexcept; + +#include +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_move_assignable_v); + +int main(int, char**) { + { + std::stop_token st1; + + std::stop_source source; + auto st2 = source.get_token(); + + assert(st1 != st2); + + source.request_stop(); + + assert(!st1.stop_requested()); + assert(st2.stop_requested()); + + std::same_as decltype(auto) ref = st1 = std::move(st2); + assert(&ref == &st1); + + assert(st1 != st2); + assert(st1.stop_requested()); + assert(!st2.stop_requested()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stoptoken/cons.copy.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stoptoken/cons.copy.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stoptoken/cons.copy.pass.cpp @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// stop_token(const stop_token&) noexcept; + +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_copy_constructible_v); + +int main(int, char**) { + { + std::stop_source source; + auto st = source.get_token(); + std::stop_token copy{st}; + + assert(st == copy); + + assert(st.stop_possible()); + assert(!st.stop_requested()); + + assert(copy.stop_possible()); + assert(!copy.stop_requested()); + + source.request_stop(); + assert(st.stop_possible()); + assert(st.stop_requested()); + + assert(copy.stop_possible()); + assert(copy.stop_requested()); + + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stoptoken/cons.default.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stoptoken/cons.default.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stoptoken/cons.default.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: c++03, c++11, c++14, c++17 + +// stop_token() noexcept; + +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_default_constructible_v); + +int main(int, char**) { + { + std::stop_token st = {}; // implicit + assert(!st.stop_possible()); + assert(!st.stop_requested()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stoptoken/cons.move.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stoptoken/cons.move.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stoptoken/cons.move.pass.cpp @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// stop_token(stop_token&&) noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_move_constructible_v); + +int main(int, char**) { + { + std::stop_source source; + auto st = source.get_token(); + + assert(st.stop_possible()); + assert(!st.stop_requested()); + + std::stop_token st2{std::move(st)}; + + assert(!st.stop_possible()); + assert(!st.stop_requested()); + + assert(st2.stop_possible()); + assert(!st2.stop_requested()); + + source.request_stop(); + + assert(!st.stop_possible()); + assert(!st.stop_requested()); + + assert(st2.stop_possible()); + assert(st2.stop_requested()); + + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stoptoken/equals.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stoptoken/equals.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stoptoken/equals.pass.cpp @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// [[nodiscard]] bool operator==(const stop_token& lhs, const stop_token& rhs) noexcept; +// Returns: true if lhs and rhs have ownership of the same stop state or if both lhs and rhs do not have ownership of a stop state; otherwise false. + +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsNoThrowEqualityComparable = requires(const T& t1, const T& t2) { + { t1 == t2 } noexcept; +}; + +static_assert(IsNoThrowEqualityComparable); + +int main(int, char**) { + // both no state + { + const std::stop_token st1; + const std::stop_token st2; + assert(st1 == st2); + assert(!(st1 != st2)); + } + + // only one has no state + { + std::stop_source ss; + const std::stop_token st1; + const auto st2 = ss.get_token(); + assert(!(st1 == st2)); + assert(st1 != st2); + } + + // both has states. same source + { + std::stop_source ss; + const auto st1 = ss.get_token(); + const auto st2 = ss.get_token(); + assert(st1 == st2); + assert(!(st1 != st2)); + } + + // both has states. different sources with same states + { + std::stop_source ss1; + auto ss2 = ss1; + const auto st1 = ss1.get_token(); + const auto st2 = ss2.get_token(); + assert(st1 == st2); + assert(!(st1 != st2)); + } + + // both has states. different sources with different states + { + std::stop_source ss1; + std::stop_source ss2; + const auto st1 = ss1.get_token(); + const auto st2 = ss2.get_token(); + assert(!(st1 == st2)); + assert(st1 != st2); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stoptoken/nodiscard.verify.cpp b/libcxx/test/std/thread/thread.stoptoken/stoptoken/nodiscard.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stoptoken/nodiscard.verify.cpp @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// [[nodiscard]] bool stop_requested() const noexcept; +// [[nodiscard]] bool stop_possible() const noexcept; +// [[nodiscard]] friend bool operator==(const stop_token& lhs, const stop_token& rhs) noexcept; + +#include + +void test() { + std::stop_token st; + st.stop_requested(); // expected-warning {{ignoring return value of function}} + st.stop_possible(); // expected-warning {{ignoring return value of function}} + operator==(st, st); // expected-warning {{ignoring return value of function}} +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stoptoken/stop_possible.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stoptoken/stop_possible.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stoptoken/stop_possible.pass.cpp @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// [[nodiscard]] bool stop_possible() const noexcept; +// Returns: false if: +// - *this does not have ownership of a stop state, or +// - a stop request was not made and there are no associated stop_source objects; +// otherwise, true. + +#include +#include +#include +#include +#include + +#include "make_test_thread.h" +#include "test_macros.h" + +template +concept IsStopPossibleNoexcept = requires(const T& t) { + { t.stop_possible() } noexcept; +}; + +static_assert(IsStopPossibleNoexcept); + +int main(int, char**) { + // no state + { + const std::stop_token st; + assert(!st.stop_possible()); + } + + // a stop request was not made and there are no associated stop_source objects + { + std::optional ss{std::in_place}; + const auto st = ss->get_token(); + ss.reset(); + + assert(!st.stop_possible()); + } + + // a stop request was not made, but there is an associated stop_source objects + { + std::stop_source ss; + const auto st = ss.get_token(); + assert(st.stop_possible()); + } + + // a stop request was made and there are no associated stop_source objects + { + std::optional ss{std::in_place}; + const auto st = ss->get_token(); + ss->request_stop(); + ss.reset(); + + assert(st.stop_possible()); + } + + // a stop request was made and there is an associated stop_source objects + { + std::stop_source ss; + const auto st = ss.get_token(); + ss.request_stop(); + assert(st.stop_possible()); + } + + // a stop request was made on a different thread and + // there are no associated stop_source objects + { + std::optional ss{std::in_place}; + const auto st = ss->get_token(); + + std::thread t = support::make_test_thread([&]() { + ss->request_stop(); + ss.reset(); + }); + + assert(st.stop_possible()); + t.join(); + assert(st.stop_possible()); + + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stoptoken/stop_requested.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stoptoken/stop_requested.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stoptoken/stop_requested.pass.cpp @@ -0,0 +1,154 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// [[nodiscard]] bool stop_requested() const noexcept; +// Returns: true if *this has ownership of a stop state that has received a stop request; otherwise, false. + +#include +#include +#include +#include +#include +#include + +#include "make_test_thread.h" +#include "test_macros.h" + +template +concept IsStopRequestedNoexcept = requires(const T& t) { + { t.stop_requested() } noexcept; +}; + +static_assert(IsStopRequestedNoexcept); + +int main(int, char**) { + // no state + { + const std::stop_token st; + assert(!st.stop_requested()); + } + + // has state + { + std::stop_source ss; + const auto st = ss.get_token(); + assert(!st.stop_requested()); + + ss.request_stop(); + assert(st.stop_requested()); + } + + // already requested before constructor + { + std::stop_source ss; + ss.request_stop(); + const auto st = ss.get_token(); + assert(st.stop_requested()); + } + + // stop_token should share the state + { + std::optional ss{std::in_place}; + ss->request_stop(); + const auto st = ss->get_token(); + + ss.reset(); + assert(st.stop_requested()); + } + + // single stop_source, multiple stop_token + { + std::stop_source ss; + const auto st1 = ss.get_token(); + const auto st2 = ss.get_token(); + assert(!st1.stop_requested()); + assert(!st2.stop_requested()); + + ss.request_stop(); + assert(st1.stop_requested()); + assert(st2.stop_requested()); + } + + // multiple stop_source, multiple stop_token + { + std::stop_source ss1; + std::stop_source ss2; + + const auto st1 = ss1.get_token(); + const auto st2 = ss2.get_token(); + assert(!st1.stop_requested()); + assert(!st2.stop_requested()); + + ss1.request_stop(); + assert(st1.stop_requested()); + assert(!st2.stop_requested()); + } + + // multiple threads + { + std::stop_source ss; + const auto st = ss.get_token(); + assert(!st.stop_requested()); + + std::thread t = support::make_test_thread([&]() { ss.request_stop(); }); + + t.join(); + assert(st.stop_requested()); + } + + // maybe concurrent calls + { + std::stop_source ss; + const auto st = ss.get_token(); + assert(!st.stop_requested()); + + std::thread t = support::make_test_thread([&]() { ss.request_stop(); }); + + while (!st.stop_requested()) { + // should eventually exit the loop + std::this_thread::yield(); + } + + t.join(); + } + + // [thread.stoptoken.intro] A call to request_stop that returns true + // synchronizes with a call to stop_requested on an associated stop_token + // or stop_source object that returns true. + { + std::stop_source ss; + const auto st = ss.get_token(); + assert(!st.stop_requested()); + + bool flag = false; + + std::thread t = support::make_test_thread([&]() { + using namespace std::chrono_literals; + std::this_thread::sleep_for(1ms); + + // happens-before request_stop + flag = true; + auto b = ss.request_stop(); + assert(b); + }); + + while (!st.stop_requested()) { + std::this_thread::yield(); + } + + // write should be visible to the current thread + assert(flag == true); + + t.join(); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stoptoken/swap.free.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stoptoken/swap.free.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stoptoken/swap.free.pass.cpp @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// friend void swap(stop_token& x, stop_token& y) noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsNoThrowFreeSwappable = requires(T& t) { + { swap(t, t) } noexcept; +}; + +static_assert(IsNoThrowFreeSwappable); + +int main(int, char**) { + { + std::stop_token st1; + + std::stop_source source; + auto st2 = source.get_token(); + + assert(st1 != st2); + + source.request_stop(); + + assert(!st1.stop_requested()); + assert(st2.stop_requested()); + + swap(st1, st2); + + assert(st1 != st2); + assert(st1.stop_requested()); + assert(!st2.stop_requested()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.stoptoken/stoptoken/swap.member.pass.cpp b/libcxx/test/std/thread/thread.stoptoken/stoptoken/swap.member.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.stoptoken/stoptoken/swap.member.pass.cpp @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// void swap(stop_token& rhs) noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsNoThrowMemberSwappable = requires(T& t) { + { t.swap(t) } noexcept; +}; + +static_assert(IsNoThrowMemberSwappable); + +int main(int, char**) { + { + std::stop_token st1; + + std::stop_source source; + auto st2 = source.get_token(); + + assert(st1 != st2); + + source.request_stop(); + + assert(!st1.stop_requested()); + assert(st2.stop_requested()); + + st1.swap(st2); + + assert(st1 != st2); + assert(st1.stop_requested()); + assert(!st2.stop_requested()); + } + + return 0; +} diff --git a/libcxx/utils/generate_header_inclusion_tests.py b/libcxx/utils/generate_header_inclusion_tests.py --- a/libcxx/utils/generate_header_inclusion_tests.py +++ b/libcxx/utils/generate_header_inclusion_tests.py @@ -73,6 +73,7 @@ "initializer_list": "11", "optional": "17", "ranges": "20", + "stop_token": "20", "string_view": "17", "syncstream": "20", "system_error": "11",