* [PATCH] Add support for C++2a stop_token @ 2019-10-23 0:59 Thomas Rodgers 2019-10-23 19:50 ` Thomas Rodgers 0 siblings, 1 reply; 10+ messages in thread From: Thomas Rodgers @ 2019-10-23 0:59 UTC (permalink / raw) To: gcc-patches, libstdc++ [-- Attachment #1: le patch --] [-- Type: x-epoc/x-sisx-app, Size: 17865 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] Add support for C++2a stop_token 2019-10-23 0:59 [PATCH] Add support for C++2a stop_token Thomas Rodgers @ 2019-10-23 19:50 ` Thomas Rodgers 2019-10-24 15:45 ` Jonathan Wakely 0 siblings, 1 reply; 10+ messages in thread From: Thomas Rodgers @ 2019-10-23 19:50 UTC (permalink / raw) To: gcc-patches, libstdc++ [-- Attachment #1: Type: text/plain, Size: 49 bytes --] Thomas Rodgers writes: Let's try this again. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: Le Patch --] [-- Type: text/x-patch, Size: 18458 bytes --] From 23e1c9402cc15666d099fd61b58a0019181a9115 Mon Sep 17 00:00:00 2001 From: Thomas Rodgers <trodgers@trodgers.remote.f30> Date: Tue, 22 Oct 2019 17:53:00 -0700 Subject: [PATCH] Add support for C++2a stop_token * include/Makefile.am: Add <stop_token> header. * include/Makefile.in: Regenerate. * include/std/stop_token: New file. * include/std/version (__cpp_lib_jthread): New value. * testsuite/30_threads/stop_token/1.cc: New test. * testsuite/30_threads/stop_token/2.cc: New test. * testsuite/30_threads/stop_token/stop_token.cc: New test. --- libstdc++-v3/ChangeLog | 10 + libstdc++-v3/include/Makefile.am | 1 + libstdc++-v3/include/Makefile.in | 1 + libstdc++-v3/include/std/stop_token | 338 ++++++++++++++++++ libstdc++-v3/include/std/version | 1 + .../testsuite/30_threads/stop_token/1.cc | 27 ++ .../testsuite/30_threads/stop_token/2.cc | 27 ++ .../30_threads/stop_token/stop_token.cc | 93 +++++ 8 files changed, 498 insertions(+) create mode 100644 libstdc++-v3/include/std/stop_token create mode 100644 libstdc++-v3/testsuite/30_threads/stop_token/1.cc create mode 100644 libstdc++-v3/testsuite/30_threads/stop_token/2.cc create mode 100644 libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index 2ea0fe4ec40..970c5c2a018 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,3 +1,13 @@ +2019-10-22 Thomas Rodgers <trodgers@redhat.com> + + * include/Makefile.am: Add <stop_token> header. + * include/Makefile.in: Regenerate. + * include/std/stop_token: New file. + * include/std/version (__cpp_lib_jthread): New value. + * testsuite/30_threads/stop_token/1.cc: New test. + * testsuite/30_threads/stop_token/2.cc: New test. + * testsuite/30_threads/stop_token/stop_token.cc: New test. + 2019-09-05 Jonathan Wakely <jwakely@redhat.com> * doc/xml/manual/status_cxx2020.xml: Update status for P0122R7 and diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am index b8b786d9260..fb6777366bd 100644 --- a/libstdc++-v3/include/Makefile.am +++ b/libstdc++-v3/include/Makefile.am @@ -72,6 +72,7 @@ std_headers = \ ${std_srcdir}/sstream \ ${std_srcdir}/stack \ ${std_srcdir}/stdexcept \ + ${std_srcdir}/stop_token \ ${std_srcdir}/streambuf \ ${std_srcdir}/string \ ${std_srcdir}/string_view \ diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in index cd1e9df5482..9b4ab670315 100644 --- a/libstdc++-v3/include/Makefile.in +++ b/libstdc++-v3/include/Makefile.in @@ -416,6 +416,7 @@ std_headers = \ ${std_srcdir}/sstream \ ${std_srcdir}/stack \ ${std_srcdir}/stdexcept \ + ${std_srcdir}/stop_token \ ${std_srcdir}/streambuf \ ${std_srcdir}/string \ ${std_srcdir}/string_view \ diff --git a/libstdc++-v3/include/std/stop_token b/libstdc++-v3/include/std/stop_token new file mode 100644 index 00000000000..b3655b85eae --- /dev/null +++ b/libstdc++-v3/include/std/stop_token @@ -0,0 +1,338 @@ +// <stop_token> -*- C++ -*- + +// Copyright (C) 2008-2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// <http://www.gnu.org/licenses/>. + +/** @file include/stop_token + * This is a Standard C++ Library header. + */ + +#ifndef _GLIBCXX_STOP_TOKEN +#define _GLIBCXX_STOP_TOKEN + +#include <type_traits> +#include <memory> +#include <mutex> +#include <atomic> + +#define __cpp_lib_jthread 201907L + +namespace std _GLIBCXX_VISIBILITY(default) +{ + _GLIBCXX_BEGIN_NAMESPACE_VERSION + + class stop_source; + template<class _Callback> + class stop_callback; + + struct nostopstate_t { explicit nostopstate_t() = default; }; + inline constexpr nostopstate_t nostopstate(); + + class stop_token { + public: + stop_token() noexcept = default; + + stop_token(const stop_token& __other) noexcept + : _M_state(__other._M_state) + { } + + stop_token(stop_token&& __other) noexcept + : _M_state(std::move(__other._M_state)) + { } + + ~stop_token() = default; + + stop_token& + operator=(const stop_token& __rhs) noexcept { + _M_state = __rhs._M_state; + return *this; + } + + stop_token& + operator=(stop_token&& __rhs) noexcept { + std::swap(_M_state, __rhs._M_state); + return *this; + } + + [[nodiscard]] + bool + stop_possible() const noexcept + { + return static_cast<bool>(_M_state); + } + + [[nodiscard]] + bool + stop_requested() const noexcept + { + return stop_possible() && _M_state->_M_stop_requested(); + } + + private: + friend stop_source; + template<typename _Callback> + friend class stop_callback; + + struct _Stop_cb { + void(*_M_callback)(_Stop_cb*); + _Stop_cb* _M_prev = nullptr; + _Stop_cb* _M_next = nullptr; + + template<typename _CB> + _Stop_cb(_CB&& __cb) + : _M_callback(std::move(__cb)) + { } + + static void + _S_execute(_Stop_cb* __cb) noexcept + { + __cb->_M_callback(__cb); + } + }; + + struct _Stop_state_t { + std::atomic<bool> _M_stopped; + std::mutex _M_mtx; + _Stop_cb* _M_head = nullptr; + + _Stop_state_t() + : _M_stopped{false} + { } + + bool + _M_stop_requested() + { + return _M_stopped; + } + + bool + _M_request_stop() + { + bool __stopped = false; + if (_M_stopped.compare_exchange_strong(__stopped, true)) + { + std::unique_lock<std::mutex> __lck{_M_mtx}; + while (auto __p = _M_head) + { + _M_head = __p->_M_next; + _Stop_cb::_S_execute(__p); + } + return true; + } + return false; + } + + bool + _M_register_callback(_Stop_cb* __cb) + { + std::unique_lock<std::mutex> __lck{_M_mtx}; + if (_M_stopped) + return false; + + __cb->_M_next = _M_head; + _M_head->_M_prev = __cb; + _M_head = __cb; + return true; + } + + void + _M_remove_callback(_Stop_cb* __cb) + { + std::unique_lock<std::mutex> __lck{_M_mtx}; + if (__cb == _M_head) + { + _M_head = _M_head->_M_next; + _M_head->_M_prev = nullptr; + } + else + { + __cb->_M_prev->_M_next = __cb->_M_next; + if (__cb->_M_next) + { + __cb->_M_next->_M_prev = __cb->_M_prev; + } + } + } + }; + + using _Stop_state = std::shared_ptr<_Stop_state_t>; + _Stop_state _M_state; + + explicit stop_token(_Stop_state __state) + : _M_state{std::move(__state)} + { } + }; + + class stop_source { + using _Stop_state_t = stop_token::_Stop_state_t; + using _Stop_state = stop_token::_Stop_state; + + public: + stop_source() + : _M_state(std::make_shared<_Stop_state_t>()) + { } + + explicit stop_source(std::nostopstate_t) noexcept + { } + + stop_source(const stop_source& __other) noexcept + : _M_state(__other._M_state) + { } + + stop_source(stop_source&& __other) noexcept + : _M_state(std::move(__other._M_state)) + { } + + stop_source& + operator=(const stop_source& __rhs) noexcept + { + if (_M_state != __rhs._M_state) + _M_state = __rhs._M_state; + return *this; + } + + stop_source& + operator=(stop_source&& __rhs) noexcept + { + std::swap(_M_state, __rhs._M_state); + return *this; + } + + [[nodiscard]] + bool + stop_possible() const noexcept + { + return static_cast<bool>(_M_state); + } + + [[nodiscard]] + bool + stop_requested() const noexcept + { + return stop_possible() && _M_state->_M_stop_requested(); + } + + bool + request_stop() const noexcept + { + if (stop_possible()) + return _M_state->_M_request_stop(); + return false; + } + + [[nodiscard]] + stop_token + get_token() const noexcept + { + return stop_token{_M_state}; + } + + void + swap(stop_source& __other) noexcept + { + std::swap(_M_state, __other._M_state); + } + + [[nodiscard]] + friend bool + operator==(const stop_source& __a, const stop_source& __b) noexcept + { + return __a._M_state == __b._M_state; + } + + [[nodiscard]] + friend bool + operator!=(const stop_source& __a, const stop_source& __b) noexcept + { + return __a._M_state != __b._M_state; + } + + private: + _Stop_state _M_state; + }; + + template<typename _Callback> + class [[nodiscard]] stop_callback + : private stop_token::_Stop_cb { + using _Stop_cb = stop_token::_Stop_cb; + using _Stop_state = stop_token::_Stop_state; + public: + using callback_type = _Callback; + + template<typename _CB, + std::enable_if_t<std::is_constructible_v<_Callback, _CB>, int> = 0> + explicit stop_callback(const stop_token& __token, _CB&& __cb) + noexcept(std::is_nothrow_constructible_v<_Callback, _CB>) + : _Stop_cb([](_Stop_cb* __that) noexcept + { + static_cast<stop_callback*>(__that)->_M_execute(); + }), + _M_cb(std::move(__cb)) + { + if (__token._M_state && __token._M_state->_M_register_callback(this)) + { + _M_state = __token._M_state; + } + } + + template<typename _CB, + std::enable_if_t<std::is_constructible_v<_Callback, _CB>, int> = 0> + explicit stop_callback(stop_token&& __token, _CB&& __cb) + noexcept(std::is_nothrow_constructible_v<_Callback, _CB>) + : _Stop_cb([](_Stop_cb* __that) noexcept + { + static_cast<stop_callback*>(__that)->_M_execute(); + }), + _M_cb(std::move(__cb)) + { + if (__token._M_state && __token._M_state->_M_register_callback(this)) + { + std::swap(_M_state, __token._M_state); + } + } + + ~stop_callback() + { + if (_M_state) + _M_state->_M_remove_callback(this); + } + + stop_callback(const stop_callback&) = delete; + stop_callback& operator=(const stop_callback&) = delete; + stop_callback(stop_callback&&) = delete; + stop_callback& operator=(stop_callback&&) = delete; + + private: + _Callback _M_cb; + _Stop_state _M_state = nullptr; + + void + _M_execute() noexcept + { + _M_cb(); + } + }; + _GLIBCXX_END_NAMESPACE_VERSION +} // namespace + +; +#endif // _GLIBCXX_STOP_TOKEN diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version index 7625d397a0d..9fb190537f8 100644 --- a/libstdc++-v3/include/std/version +++ b/libstdc++-v3/include/std/version @@ -168,6 +168,7 @@ #define __cpp_lib_math_constants 201907L #define __cpp_lib_span 201902L #define __cpp_lib_to_array 201907L +#define __cpp_lib_jthread 201907L #endif // C++2a #endif // C++17 #endif // C++14 diff --git a/libstdc++-v3/testsuite/30_threads/stop_token/1.cc b/libstdc++-v3/testsuite/30_threads/stop_token/1.cc new file mode 100644 index 00000000000..4c0e7a33d25 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/stop_token/1.cc @@ -0,0 +1,27 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <stop_token> + +#ifndef __cpp_lib_jthread +# error "Feature-test macro for jthread missing in <stop_token>" +#elif __cpp_lib_jthread != 201907L +# error "Feature-test macro for jthread has wrong value in <stop_token>" +#endif diff --git a/libstdc++-v3/testsuite/30_threads/stop_token/2.cc b/libstdc++-v3/testsuite/30_threads/stop_token/2.cc new file mode 100644 index 00000000000..621965c8910 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/stop_token/2.cc @@ -0,0 +1,27 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <version> + +#ifndef __cpp_lib_jthread +# error "Feature-test macro for jthread missing in <version>" +#elif __cpp_lib_jthread != 201907L +# error "Feature-test macro for jthread has wrong value in <version>" +#endif diff --git a/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc b/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc new file mode 100644 index 00000000000..96390918c84 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc @@ -0,0 +1,93 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <stop_token> +#include <cassert> + +int main() +{ + // create stop_source + std::stop_source ssrc; + assert(ssrc.stop_possible()); + assert(!ssrc.stop_requested()); + + // create stop_token from stop_source + std::stop_token stok{ssrc.get_token()}; + assert(ssrc.stop_possible()); + assert(!ssrc.stop_requested()); + assert(stok.stop_possible()); + assert(!stok.stop_requested()); + + // register callback + bool cb1called{false}; + auto cb1 = [&]{ + cb1called = true; + }; + { + std::stop_callback<decltype(cb1)> scb1{stok, cb1}; // copies cb1 + assert(ssrc.stop_possible()); + assert(!ssrc.stop_requested()); + assert(stok.stop_possible()); + assert(!stok.stop_requested()); + assert(!cb1called); + } // unregister callback + + // register another callback + bool cb2called{false}; + auto cb2 = [&]{ + assert(stok.stop_requested()); + cb2called = true; + }; + std::stop_callback<decltype(cb2)> scb2a{stok, cb2}; // copies cb2 + std::stop_callback<decltype(cb2)> scb2b{stok, std::move(cb2)}; + assert(ssrc.stop_possible()); + assert(!ssrc.stop_requested()); + assert(stok.stop_possible()); + assert(!stok.stop_requested()); + assert(!cb1called); + assert(!cb2called); + + // request stop + auto b = ssrc.request_stop(); + assert(b); + assert(ssrc.stop_possible()); + assert(ssrc.stop_requested()); + assert(stok.stop_possible()); + assert(stok.stop_requested()); + assert(!cb1called); + assert(cb2called); + + b = ssrc.request_stop(); + assert(!b); + + // register another callback + bool cb3called{false}; + auto cb3 = [&]{ + cb3called = true; + }; + std::stop_callback<decltype(cb3)> scb3{stok, cb3}; + assert(ssrc.stop_possible()); + assert(ssrc.stop_requested()); + assert(stok.stop_possible()); + assert(stok.stop_requested()); + assert(!cb1called); + assert(cb2called); + assert(cb3called); +} -- 2.21.0 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] Add support for C++2a stop_token 2019-10-23 19:50 ` Thomas Rodgers @ 2019-10-24 15:45 ` Jonathan Wakely 2019-11-14 2:00 ` Thomas Rodgers 0 siblings, 1 reply; 10+ messages in thread From: Jonathan Wakely @ 2019-10-24 15:45 UTC (permalink / raw) To: Thomas Rodgers; +Cc: gcc-patches, libstdc++ On 23/10/19 12:50 -0700, Thomas Rodgers wrote: > >Thomas Rodgers writes: > >Let's try this again. That's better, thanks :-) >+ * include/Makefile.am: Add <stop_token> header. >+ * include/Makefile.in: Regenerate. >+ * include/std/stop_token: New file. >+ * include/std/version (__cpp_lib_jthread): New value. >+ * testsuite/30_threads/stop_token/1.cc: New test. >+ * testsuite/30_threads/stop_token/2.cc: New test. >+ * testsuite/30_threads/stop_token/stop_token.cc: New test. ChangeLog entries should be provided separately from the patch (e.g. inline in the makefile, or as a separte attachment) because they never apply cleanly. Also it looks like you have a mixture of tabs and space there, it should be only tabs. >diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in >index cd1e9df5482..9b4ab670315 100644 >--- a/libstdc++-v3/include/Makefile.in >+++ b/libstdc++-v3/include/Makefile.in >@@ -416,6 +416,7 @@ std_headers = \ > ${std_srcdir}/sstream \ > ${std_srcdir}/stack \ > ${std_srcdir}/stdexcept \ >+ ${std_srcdir}/stop_token \ > ${std_srcdir}/streambuf \ > ${std_srcdir}/string \ > ${std_srcdir}/string_view \ Generated files like Makefile.in don't need to be in the patch. I use a git alias called 'filter' to filter them out: !f(){ git $@ | filterdiff -x '*/ChangeLog' -x '*/Makefile.in' -x '*/configure' -x '*/config.h.in' -x '*/doc/html/*' | sed -e '/^diff.*ChangeLog/{N;d}' ; }; f And then I create a patch with this alias: !f(){ git filter show --diff-algorithm=histogram ${*:-HEAD} > /dev/shm/patch.txt; }; f >diff --git a/libstdc++-v3/include/std/stop_token b/libstdc++-v3/include/std/stop_token >new file mode 100644 >index 00000000000..b3655b85eae >--- /dev/null >+++ b/libstdc++-v3/include/std/stop_token >@@ -0,0 +1,338 @@ >+// <stop_token> -*- C++ -*- >+ >+// Copyright (C) 2008-2019 Free Software Foundation, Inc. The copyright date should be just 2019. >+// >+// This file is part of the GNU ISO C++ Library. This library is free >+// software; you can redistribute it and/or modify it under the >+// terms of the GNU General Public License as published by the >+// Free Software Foundation; either version 3, or (at your option) >+// any later version. >+ >+// This library is distributed in the hope that it will be useful, >+// but WITHOUT ANY WARRANTY; without even the implied warranty of >+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+// GNU General Public License for more details. >+ >+// Under Section 7 of GPL version 3, you are granted additional >+// permissions described in the GCC Runtime Library Exception, version >+// 3.1, as published by the Free Software Foundation. >+ >+// You should have received a copy of the GNU General Public License and >+// a copy of the GCC Runtime Library Exception along with this program; >+// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see >+// <http://www.gnu.org/licenses/>. >+ >+/** @file include/stop_token >+ * This is a Standard C++ Library header. >+ */ >+ >+#ifndef _GLIBCXX_STOP_TOKEN >+#define _GLIBCXX_STOP_TOKEN >+ Please add: #if __cplusplus > 201703L here (and the corresponding #endif before the final #endif) so that this file is empty when included pre-C++20. >+#include <type_traits> >+#include <memory> >+#include <mutex> >+#include <atomic> >+ >+#define __cpp_lib_jthread 201907L >+ >+namespace std _GLIBCXX_VISIBILITY(default) >+{ >+ _GLIBCXX_BEGIN_NAMESPACE_VERSION These BEGIN/END macros should not be indented (which has to be done manually as editors always want to indent the content following the opening brace of the namespace). >+ >+ class stop_source; >+ template<class _Callback> s/class/typename/ >+ class stop_callback; >+ >+ struct nostopstate_t { explicit nostopstate_t() = default; }; >+ inline constexpr nostopstate_t nostopstate(); >+ >+ class stop_token { The opening brace should be on the next line please, in the same column as "class". (Same comment for all classes and structs in the patch). >+ public: >+ stop_token() noexcept = default; >+ >+ stop_token(const stop_token& __other) noexcept >+ : _M_state(__other._M_state) >+ { } >+ >+ stop_token(stop_token&& __other) noexcept >+ : _M_state(std::move(__other._M_state)) >+ { } >+ >+ ~stop_token() = default; >+ >+ stop_token& >+ operator=(const stop_token& __rhs) noexcept { >+ _M_state = __rhs._M_state; >+ return *this; >+ } >+ >+ stop_token& >+ operator=(stop_token&& __rhs) noexcept { >+ std::swap(_M_state, __rhs._M_state); >+ return *this; >+ } This doesn't leave the RHS empty as required. I think the copy/move constructors and copy/move assignment operators can all be defined as = default and that will do the right thing. >+ >+ [[nodiscard]] >+ bool >+ stop_possible() const noexcept >+ { >+ return static_cast<bool>(_M_state); >+ } >+ >+ [[nodiscard]] >+ bool >+ stop_requested() const noexcept >+ { >+ return stop_possible() && _M_state->_M_stop_requested(); >+ } >+ >+ private: >+ friend stop_source; >+ template<typename _Callback> >+ friend class stop_callback; >+ >+ struct _Stop_cb { >+ void(*_M_callback)(_Stop_cb*); >+ _Stop_cb* _M_prev = nullptr; >+ _Stop_cb* _M_next = nullptr; >+ >+ template<typename _CB> Please Use _Cb so it's not all uppercase (which is more likely to conflict with libc macros). >+ _Stop_cb(_CB&& __cb) >+ : _M_callback(std::move(__cb)) >+ { } >+ >+ static void >+ _S_execute(_Stop_cb* __cb) noexcept >+ { >+ __cb->_M_callback(__cb); >+ } >+ }; >+ >+ struct _Stop_state_t { >+ std::atomic<bool> _M_stopped; >+ std::mutex _M_mtx; >+ _Stop_cb* _M_head = nullptr; >+ >+ _Stop_state_t() >+ : _M_stopped{false} >+ { } >+ >+ bool >+ _M_stop_requested() >+ { >+ return _M_stopped; >+ } >+ >+ bool >+ _M_request_stop() >+ { >+ bool __stopped = false; >+ if (_M_stopped.compare_exchange_strong(__stopped, true)) >+ { >+ std::unique_lock<std::mutex> __lck{_M_mtx}; >+ while (auto __p = _M_head) >+ { >+ _M_head = __p->_M_next; >+ _Stop_cb::_S_execute(__p); >+ } >+ return true; >+ } >+ return false; >+ } >+ >+ bool >+ _M_register_callback(_Stop_cb* __cb) >+ { >+ std::unique_lock<std::mutex> __lck{_M_mtx}; >+ if (_M_stopped) >+ return false; >+ >+ __cb->_M_next = _M_head; >+ _M_head->_M_prev = __cb; >+ _M_head = __cb; >+ return true; >+ } >+ >+ void >+ _M_remove_callback(_Stop_cb* __cb) >+ { >+ std::unique_lock<std::mutex> __lck{_M_mtx}; >+ if (__cb == _M_head) >+ { >+ _M_head = _M_head->_M_next; >+ _M_head->_M_prev = nullptr; >+ } >+ else >+ { >+ __cb->_M_prev->_M_next = __cb->_M_next; >+ if (__cb->_M_next) >+ { >+ __cb->_M_next->_M_prev = __cb->_M_prev; >+ } >+ } >+ } >+ }; >+ >+ using _Stop_state = std::shared_ptr<_Stop_state_t>; >+ _Stop_state _M_state; >+ >+ explicit stop_token(_Stop_state __state) >+ : _M_state{std::move(__state)} >+ { } >+ }; >+ >+ class stop_source { >+ using _Stop_state_t = stop_token::_Stop_state_t; >+ using _Stop_state = stop_token::_Stop_state; >+ >+ public: >+ stop_source() >+ : _M_state(std::make_shared<_Stop_state_t>()) >+ { } >+ >+ explicit stop_source(std::nostopstate_t) noexcept >+ { } >+ >+ stop_source(const stop_source& __other) noexcept >+ : _M_state(__other._M_state) >+ { } >+ >+ stop_source(stop_source&& __other) noexcept >+ : _M_state(std::move(__other._M_state)) >+ { } >+ >+ stop_source& >+ operator=(const stop_source& __rhs) noexcept >+ { >+ if (_M_state != __rhs._M_state) >+ _M_state = __rhs._M_state; >+ return *this; >+ } >+ >+ stop_source& >+ operator=(stop_source&& __rhs) noexcept >+ { >+ std::swap(_M_state, __rhs._M_state); >+ return *this; >+ } These can all be defaulted too. >+ [[nodiscard]] >+ bool >+ stop_possible() const noexcept >+ { >+ return static_cast<bool>(_M_state); >+ } >+ >+ [[nodiscard]] >+ bool >+ stop_requested() const noexcept >+ { >+ return stop_possible() && _M_state->_M_stop_requested(); >+ } >+ >+ bool >+ request_stop() const noexcept >+ { >+ if (stop_possible()) >+ return _M_state->_M_request_stop(); >+ return false; >+ } >+ >+ [[nodiscard]] >+ stop_token >+ get_token() const noexcept >+ { >+ return stop_token{_M_state}; >+ } >+ >+ void >+ swap(stop_source& __other) noexcept >+ { >+ std::swap(_M_state, __other._M_state); >+ } >+ >+ [[nodiscard]] >+ friend bool >+ operator==(const stop_source& __a, const stop_source& __b) noexcept >+ { >+ return __a._M_state == __b._M_state; >+ } >+ >+ [[nodiscard]] >+ friend bool >+ operator!=(const stop_source& __a, const stop_source& __b) noexcept >+ { >+ return __a._M_state != __b._M_state; >+ } >+ >+ private: >+ _Stop_state _M_state; >+ }; >+ >+ template<typename _Callback> >+ class [[nodiscard]] stop_callback >+ : private stop_token::_Stop_cb { Opening brace on the next line, in the same column as "class". >+ using _Stop_cb = stop_token::_Stop_cb; And then everything in the class body should be indented by another two columns. >+ using _Stop_state = stop_token::_Stop_state; >+ public: >+ using callback_type = _Callback; >+ >+ template<typename _CB, Change to _Cb here too. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] Add support for C++2a stop_token 2019-10-24 15:45 ` Jonathan Wakely @ 2019-11-14 2:00 ` Thomas Rodgers 2019-11-14 16:12 ` Jonathan Wakely 2019-11-15 14:38 ` Jonathan Wakely 0 siblings, 2 replies; 10+ messages in thread From: Thomas Rodgers @ 2019-11-14 2:00 UTC (permalink / raw) To: gcc-patches, libstdc++ [-- Attachment #1: Type: text/plain, Size: 185 bytes --] The attached patch should be a complete implementation of C++20 stop_token, jthread, and changes to condition_variable_any which also addresses the comments on the previous patch. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: Le patch --] [-- Type: text/x-patch, Size: 43660 bytes --] From 2cdaa367ed919b24f3bbb84d6f7391a350dce77b Mon Sep 17 00:00:00 2001 From: Thomas Rodgers <trodgers@trodgers.remote.f30> Date: Wed, 13 Nov 2019 17:51:11 -0800 Subject: [PATCH] Support for jthread and stop_token * include/Makefile.am: Add <stop_token> header. * include/Makefile.in: Regenerate. * include/std/condition_variable: Add overloads for stop_token support to condition_variable_any. * include/std/stop_token: New file. * include/std/thread: Add jthread type. * include/std/version (__cpp_lib_jthread): New value. * testsuite/30_threads/condition_variable_any/stop_token/1.cc: New test. * testsuite/30_threads/condition_variable_any/stop_token/2.cc: New test. * testsuite/30_threads/condition_variable_any/stop_token/wait_on.cc: New test. * testsuite/30_threads/jthread/1.cc: New test. * testsuite/30_threads/jthread/2.cc: New test. * testsuite/30_threads/jthread/jthread.cc: New test. * testsuite/30_threads/stop_token/1.cc: New test. * testsuite/30_threads/stop_token/2.cc: New test. * testsuite/30_threads/stop_token/stop_token.cc: New test. --- libstdc++-v3/include/Makefile.am | 1 + libstdc++-v3/include/std/condition_variable | 84 ++++ libstdc++-v3/include/std/stop_token | 370 ++++++++++++++++++ libstdc++-v3/include/std/thread | 125 ++++++ libstdc++-v3/include/std/version | 1 + .../condition_variable_any/stop_token/1.cc | 27 ++ .../condition_variable_any/stop_token/2.cc | 27 ++ .../stop_token/wait_on.cc | 136 +++++++ .../testsuite/30_threads/jthread/1.cc | 27 ++ .../testsuite/30_threads/jthread/2.cc | 27 ++ .../testsuite/30_threads/jthread/jthread.cc | 198 ++++++++++ .../testsuite/30_threads/stop_token/1.cc | 27 ++ .../testsuite/30_threads/stop_token/2.cc | 27 ++ .../30_threads/stop_token/stop_token.cc | 100 +++++ 14 files changed, 1177 insertions(+) create mode 100644 libstdc++-v3/include/std/stop_token create mode 100644 libstdc++-v3/testsuite/30_threads/condition_variable_any/stop_token/1.cc create mode 100644 libstdc++-v3/testsuite/30_threads/condition_variable_any/stop_token/2.cc create mode 100644 libstdc++-v3/testsuite/30_threads/condition_variable_any/stop_token/wait_on.cc create mode 100644 libstdc++-v3/testsuite/30_threads/jthread/1.cc create mode 100644 libstdc++-v3/testsuite/30_threads/jthread/2.cc create mode 100644 libstdc++-v3/testsuite/30_threads/jthread/jthread.cc create mode 100644 libstdc++-v3/testsuite/30_threads/stop_token/1.cc create mode 100644 libstdc++-v3/testsuite/30_threads/stop_token/2.cc create mode 100644 libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am index 49fd41360bc..6300de9e96d 100644 --- a/libstdc++-v3/include/Makefile.am +++ b/libstdc++-v3/include/Makefile.am @@ -74,6 +74,7 @@ std_headers = \ ${std_srcdir}/sstream \ ${std_srcdir}/stack \ ${std_srcdir}/stdexcept \ + ${std_srcdir}/stop_token \ ${std_srcdir}/streambuf \ ${std_srcdir}/string \ ${std_srcdir}/string_view \ diff --git a/libstdc++-v3/include/std/condition_variable b/libstdc++-v3/include/std/condition_variable index cc96661e94c..8887cee29fa 100644 --- a/libstdc++-v3/include/std/condition_variable +++ b/libstdc++-v3/include/std/condition_variable @@ -36,6 +36,7 @@ #else #include <chrono> + #include <bits/std_mutex.h> #include <bits/unique_lock.h> #include <ext/concurrence.h> @@ -45,6 +46,11 @@ #include <bits/shared_ptr.h> #include <bits/cxxabi_forced.h> +#if __cplusplus > 201703L +#define __cpp_lib_jthread 201907L +#include <stop_token> +#endif + #if defined(_GLIBCXX_HAS_GTHREADS) namespace std _GLIBCXX_VISIBILITY(default) @@ -360,6 +366,84 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION wait_for(_Lock& __lock, const chrono::duration<_Rep, _Period>& __rtime, _Predicate __p) { return wait_until(__lock, __clock_t::now() + __rtime, std::move(__p)); } + +#ifdef __cpp_lib_jthread + template <class _Lock, class _Predicate> + bool wait_on(_Lock& __lock, + stop_token __stoken, + _Predicate __p) + { + if (__stoken.stop_requested()) + { + return __p(); + } + + std::stop_callback __cb(__stoken, [this] { notify_all(); }); + shared_ptr<mutex> __mutex = _M_mutex; + while (!__p()) + { + unique_lock<mutex> __my_lock(*__mutex); + if (__stoken.stop_requested()) + { + return false; + } + // *__mutex must be unlocked before re-locking __lock so move + // ownership of *__mutex lock to an object with shorter lifetime. + _Unlock<_Lock> __unlock(__lock); + unique_lock<mutex> __my_lock2(std::move(__my_lock)); + _M_cond.wait(__my_lock2); + } + return true; + } + + template <class _Lock, class _Clock, class _Duration, class _Predicate> + bool wait_on_until(_Lock& __lock, + stop_token __stoken, + const chrono::time_point<_Clock, _Duration>& __abs_time, + _Predicate __p) + { + if (__stoken.stop_requested()) + { + return __p(); + } + + std::stop_callback __cb(__stoken, [this] { notify_all(); }); + shared_ptr<mutex> __mutex = _M_mutex; + while (!__p()) + { + bool __stop; + { + unique_lock<mutex> __my_lock(*__mutex); + if (__stoken.stop_requested()) + { + return false; + } + _Unlock<_Lock> __u(__lock); + unique_lock<mutex> __my_lock2(std::move(__my_lock)); + const auto __status = _M_cond.wait_until(__my_lock2, __abs_time); + __stop = (__status == std::cv_status::timeout) || __stoken.stop_requested(); + } + if (__stop) + { + return __p(); + } + } + return true; + } + + template <class _Lock, class _Rep, class _Period, class _Predicate> + bool wait_on_for(_Lock& __lock, + stop_token __stoken, + const chrono::duration<_Rep, _Period>& __rel_time, + _Predicate __p) + { + auto __abst = std::chrono::steady_clock::now() + __rel_time; + return wait_on_until(__lock, + std::move(__stoken), + __abst, + std::move(__p)); + } +#endif }; } // end inline namespace diff --git a/libstdc++-v3/include/std/stop_token b/libstdc++-v3/include/std/stop_token new file mode 100644 index 00000000000..f5f6f64210e --- /dev/null +++ b/libstdc++-v3/include/std/stop_token @@ -0,0 +1,370 @@ +// <stop_token> -*- C++ -*- + +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// <http://www.gnu.org/licenses/>. + +/** @file include/stop_token + * This is a Standard C++ Library header. + */ + +#ifndef _GLIBCXX_STOP_TOKEN +#define _GLIBCXX_STOP_TOKEN + +#if __cplusplus >= 201703L + +#include <type_traits> +#include <memory> +#include <mutex> +#include <atomic> + +#define __cpp_lib_jthread 201907L + +namespace std _GLIBCXX_VISIBILITY(default) +{ +_GLIBCXX_BEGIN_NAMESPACE_VERSION + + class stop_source; + template<typename _Callback> + class stop_callback; + + struct nostopstate_t { explicit nostopstate_t() = default; }; + inline constexpr nostopstate_t nostopstate(); + + class stop_token + { + public: + stop_token() noexcept = default; + + stop_token(const stop_token& __other) noexcept = default; + stop_token(stop_token&& __other) noexcept = default; + + ~stop_token() = default; + + stop_token& + operator=(const stop_token& __rhs) noexcept = default; + + stop_token& + operator=(stop_token&& __rhs) noexcept; + + [[nodiscard]] + bool + stop_possible() const noexcept + { + return static_cast<bool>(_M_state); + } + + [[nodiscard]] + bool + stop_requested() const noexcept + { + return stop_possible() && _M_state->_M_stop_requested(); + } + + [[nodiscard]] + friend bool + operator==(const stop_token& __a, const stop_token& __b) + { + return __a._M_state == __b._M_state; + } + + [[nodiscard]] + friend bool + operator!=(const stop_token& __a, const stop_token& __b) + { + return __a._M_state == __b._M_state; + } + + private: + friend stop_source; + template<typename _Callback> + friend class stop_callback; + + struct _Stop_cb { + void(*_M_callback)(_Stop_cb*); + _Stop_cb* _M_prev = nullptr; + _Stop_cb* _M_next = nullptr; + + template<typename _Cb> + _Stop_cb(_Cb&& __cb) + : _M_callback(std::move(__cb)) + { } + + bool + _M_linked() const + { + return (_M_prev != nullptr) + || (_M_next != nullptr); + } + + static void + _S_execute(_Stop_cb* __cb) noexcept + { + __cb->_M_callback(__cb); + __cb->_M_prev = __cb->_M_next = nullptr; + } + }; + + struct _Stop_state_t { + std::atomic<bool> _M_stopped; + std::mutex _M_mtx; + _Stop_cb* _M_head = nullptr; + + _Stop_state_t() + : _M_stopped{false} + { } + + bool + _M_stop_requested() + { + return _M_stopped; + } + + bool + _M_request_stop() + { + bool __stopped = false; + if (_M_stopped.compare_exchange_strong(__stopped, true)) + { + std::unique_lock<std::mutex> __lck{_M_mtx}; + while (_M_head) + { + auto __p = _M_head; + _M_head = _M_head->_M_next; + _Stop_cb::_S_execute(__p); + } + return true; + } + return false; + } + + bool + _M_register_callback(_Stop_cb* __cb) + { + std::unique_lock<std::mutex> __lck{_M_mtx}; + if (_M_stopped) + return false; + + __cb->_M_next = _M_head; + if (_M_head) + { + _M_head->_M_prev = __cb; + } + _M_head = __cb; + return true; + } + + void + _M_remove_callback(_Stop_cb* __cb) + { + std::unique_lock<std::mutex> __lck{_M_mtx}; + if (__cb == _M_head) + { + _M_head = _M_head->_M_next; + if (_M_head) + { + _M_head->_M_prev = nullptr; + } + } + else if (!__cb->_M_linked()) + { + return; + } + else + { + __cb->_M_prev->_M_next = __cb->_M_next; + if (__cb->_M_next) + { + __cb->_M_next->_M_prev = __cb->_M_prev; + } + } + } + }; + + using _Stop_state = std::shared_ptr<_Stop_state_t>; + _Stop_state _M_state; + + explicit stop_token(_Stop_state __state) + : _M_state{std::move(__state)} + { } + }; + + class stop_source { + using _Stop_state_t = stop_token::_Stop_state_t; + using _Stop_state = stop_token::_Stop_state; + + public: + stop_source() + : _M_state(std::make_shared<_Stop_state_t>()) + { } + + explicit stop_source(std::nostopstate_t) noexcept + { } + + stop_source(const stop_source& __other) noexcept + : _M_state(__other._M_state) + { } + + stop_source(stop_source&& __other) noexcept + : _M_state(std::move(__other._M_state)) + { } + + stop_source& + operator=(const stop_source& __rhs) noexcept + { + if (_M_state != __rhs._M_state) + _M_state = __rhs._M_state; + return *this; + } + + stop_source& + operator=(stop_source&& __rhs) noexcept + { + std::swap(_M_state, __rhs._M_state); + return *this; + } + + [[nodiscard]] + bool + stop_possible() const noexcept + { + return static_cast<bool>(_M_state); + } + + [[nodiscard]] + bool + stop_requested() const noexcept + { + return stop_possible() && _M_state->_M_stop_requested(); + } + + bool + request_stop() const noexcept + { + if (stop_possible()) + return _M_state->_M_request_stop(); + return false; + } + + [[nodiscard]] + stop_token + get_token() const noexcept + { + return stop_token{_M_state}; + } + + void + swap(stop_source& __other) noexcept + { + std::swap(_M_state, __other._M_state); + } + + [[nodiscard]] + friend bool + operator==(const stop_source& __a, const stop_source& __b) noexcept + { + return __a._M_state == __b._M_state; + } + + [[nodiscard]] + friend bool + operator!=(const stop_source& __a, const stop_source& __b) noexcept + { + return __a._M_state != __b._M_state; + } + + private: + _Stop_state _M_state; + }; + + template<typename _Callback> + class [[nodiscard]] stop_callback + : private stop_token::_Stop_cb + { + using _Stop_cb = stop_token::_Stop_cb; + using _Stop_state = stop_token::_Stop_state; + public: + using callback_type = _Callback; + + template<typename _Cb, + std::enable_if_t<std::is_constructible_v<_Callback, _Cb>, int> = 0> + explicit stop_callback(const stop_token& __token, _Cb&& __cb) + noexcept(std::is_nothrow_constructible_v<_Callback, _Cb>) + : _Stop_cb([](_Stop_cb* __that) noexcept + { + static_cast<stop_callback*>(__that)->_M_execute(); + }), + _M_cb(std::move(__cb)) + { + auto res = __token._M_state->_M_register_callback(this); + if (__token._M_state && res) + { + _M_state = __token._M_state; + } + } + + template<typename _Cb, + std::enable_if_t<std::is_constructible_v<_Callback, _Cb>, int> = 0> + explicit stop_callback(stop_token&& __token, _Cb&& __cb) + noexcept(std::is_nothrow_constructible_v<_Callback, _Cb>) + : _Stop_cb([](_Stop_cb* __that) noexcept + { + static_cast<stop_callback*>(__that)->_M_execute(); + }), + _M_cb(std::move(__cb)) + { + if (__token._M_state && __token._M_state->_M_register_callback(this)) + { + std::swap(_M_state, __token._M_state); + } + } + + ~stop_callback() + { + if (_M_state) + { + _M_state->_M_remove_callback(this); + } + } + + stop_callback(const stop_callback&) = delete; + stop_callback& operator=(const stop_callback&) = delete; + stop_callback(stop_callback&&) = delete; + stop_callback& operator=(stop_callback&&) = delete; + + private: + _Callback _M_cb; + _Stop_state _M_state = nullptr; + + void + _M_execute() noexcept + { + _M_cb(); + } + }; + + template<typename _Callback> + stop_callback(stop_token, _Callback) -> stop_callback<_Callback>; + +_GLIBCXX_END_NAMESPACE_VERSION +} // namespace +#endif // __cplusplus >= 201703L +#endif // _GLIBCXX_STOP_TOKEN diff --git a/libstdc++-v3/include/std/thread b/libstdc++-v3/include/std/thread index 90b4be6cd16..93afa766d18 100644 --- a/libstdc++-v3/include/std/thread +++ b/libstdc++-v3/include/std/thread @@ -39,6 +39,13 @@ #include <memory> #include <tuple> #include <cerrno> + +#if __cplusplus > 201703L +#define __cpp_lib_jthread 201907L +#include <functional> +#include <stop_token> +#endif + #include <bits/functexcept.h> #include <bits/functional_hash.h> #include <bits/invoke.h> @@ -409,6 +416,124 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // @} group threads +#ifdef __cpp_lib_jthread + + class jthread + { + public: + using id = std::thread::id; + using native_handle_type = std::thread::native_handle_type; + + jthread() noexcept + : _M_stop_source{ nostopstate_t{ } } + { } + + template<typename _Callable, typename... _Args, + typename = std::enable_if_t<!std::is_same_v<std::decay_t<_Callable>, jthread>>> + explicit + jthread(_Callable&& __f, _Args&&... __args) + : _M_thread{[](stop_token __token, auto&& __cb, auto&&... __args) + { + if constexpr(std::is_invocable_v<_Callable, stop_token, _Args...>) + { + std::invoke(std::forward<decltype(__cb)>(__cb), + std::move(__token), + std::forward<decltype(__args)>(__args)...); + } + else + { + std::invoke(std::forward<decltype(__cb)>(__cb), + std::forward<decltype(__args)>(__args)...); + } + }, + _M_stop_source.get_token(), + std::forward<_Callable>(__f), + std::forward<_Args>(__args)...} + { } + + jthread(const jthread&) = delete; + jthread(jthread&&) noexcept = default; + + ~jthread() + { + if (joinable()) + { + request_stop(); + join(); + } + } + + jthread& + operator=(const jthread&) = delete; + + jthread& + operator=(jthread&&) noexcept = default; + + void + swap(jthread& __other) noexcept + { + std::swap(_M_stop_source, __other._M_stop_source); + std::swap(_M_thread, __other._M_thread); + } + + bool + joinable() const noexcept + { + return _M_thread.joinable(); + } + + void + join() + { + _M_thread.join(); + } + + void + detach() + { + _M_thread.detach(); + } + + id + get_id() const noexcept + { + _M_thread.get_id(); + } + + native_handle_type + native_handle() + { + return _M_thread.native_handle(); + } + + static unsigned + hardware_concurrency() noexcept + { + return std::thread::hardware_concurrency(); + } + + [[nodiscard]] stop_source + get_stop_source() noexcept + { + return _M_stop_source; + } + + [[nodiscard]] stop_token + get_stop_token() const noexcept + { + return _M_stop_source.get_token(); + } + + bool request_stop() noexcept + { + return get_stop_source().request_stop(); + } + + private: + stop_source _M_stop_source; + std::thread _M_thread; + }; +#endif // __cpp_lib_jthread _GLIBCXX_END_NAMESPACE_VERSION } // namespace diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version index fa6d27467f7..27807428903 100644 --- a/libstdc++-v3/include/std/version +++ b/libstdc++-v3/include/std/version @@ -187,6 +187,7 @@ #define __cpp_lib_list_remove_return_type 201806L #define __cpp_lib_math_constants 201907L #define __cpp_lib_span 201902L +#define __cpp_lib_jthread 201907L #if __cpp_impl_three_way_comparison >= 201907L # define __cpp_lib_three_way_comparison 201711L #endif diff --git a/libstdc++-v3/testsuite/30_threads/condition_variable_any/stop_token/1.cc b/libstdc++-v3/testsuite/30_threads/condition_variable_any/stop_token/1.cc new file mode 100644 index 00000000000..c1f4b5849ac --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/condition_variable_any/stop_token/1.cc @@ -0,0 +1,27 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <condition_variable> + +#ifndef __cpp_lib_jthread +# error "Feature-test macro for jthread missing in <condition_variable>" +#elif __cpp_lib_jthread != 201907L +# error "Feature-test macro for jthread has wrong value in <condition_variable>" +#endif diff --git a/libstdc++-v3/testsuite/30_threads/condition_variable_any/stop_token/2.cc b/libstdc++-v3/testsuite/30_threads/condition_variable_any/stop_token/2.cc new file mode 100644 index 00000000000..621965c8910 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/condition_variable_any/stop_token/2.cc @@ -0,0 +1,27 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <version> + +#ifndef __cpp_lib_jthread +# error "Feature-test macro for jthread missing in <version>" +#elif __cpp_lib_jthread != 201907L +# error "Feature-test macro for jthread has wrong value in <version>" +#endif diff --git a/libstdc++-v3/testsuite/30_threads/condition_variable_any/stop_token/wait_on.cc b/libstdc++-v3/testsuite/30_threads/condition_variable_any/stop_token/wait_on.cc new file mode 100644 index 00000000000..212fc949b3f --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/condition_variable_any/stop_token/wait_on.cc @@ -0,0 +1,136 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a -pthread" } +// { dg-do run } +// { dg-require-effective-target c++2a } +// { dg-require-effective-target pthread } + +#include <condition_variable> +#include <thread> +#include <mutex> +#include <chrono> +#include <testsuite_hooks.h> + +using namespace::std::literals; + +void test_wait_on_stop() +{ + bool ready = false; + std::mutex mtx; + std::condition_variable_any cv; + + std::stop_source src; + + auto tok = src.get_token(); + std::thread t([&ready, &mtx, &cv, tok] + { + std::unique_lock lck(mtx); + auto res = cv.wait_on(lck, tok, [&ready] { return ready; }); + if (!res) + { + VERIFY(tok.stop_requested()); + } + }); + + std::this_thread::sleep_for(0.5s); + VERIFY(!src.stop_requested()); + src.request_stop(); + t.join(); + VERIFY(src.stop_requested()); +} + +void test_wait_on_until(bool ck = true) +{ + bool ready = false; + std::mutex mtx; + std::condition_variable_any cv; + + std::stop_source src; + + auto abst = std::chrono::steady_clock::now() + 1.0s; + auto tok = src.get_token(); + std::thread t([ck, &ready, &mtx, &cv, abst, tok] + { + std::unique_lock lck(mtx); + auto res = cv.wait_on_until(lck, tok, abst, [&ready] { return ready; }); + if (!res && ck) + { + VERIFY(tok.stop_requested()); + } + }); + + if (ck) + { + std::this_thread::sleep_for(0.5s); + VERIFY(!src.stop_requested()); + src.request_stop(); + t.join(); + VERIFY(src.stop_requested()); + } + else + { + std::this_thread::sleep_for(1.5s); + t.join(); + VERIFY(!src.stop_requested()); + } +} + +void test_wait_on_for(bool ck = true) +{ + bool ready = false; + std::mutex mtx; + std::condition_variable_any cv; + + std::stop_source src; + + auto tok = src.get_token(); + std::thread t([ck, &ready, &mtx, &cv, tok] + { + std::unique_lock lck(mtx); + auto res = cv.wait_on_for(lck, tok, 1.0s, [&ready] { return ready; }); + if (!res && ck) + { + VERIFY(tok.stop_requested()); + } + }); + + if (ck) + { + std::this_thread::sleep_for(0.5s); + VERIFY(!src.stop_requested()); + src.request_stop(); + t.join(); + VERIFY(src.stop_requested()); + } + else + { + std::this_thread::sleep_for(1.5s); + t.join(); + VERIFY(!src.stop_requested()); + }; +} + +int main() +{ + test_wait_on_stop(); + test_wait_on_until(false); + test_wait_on_until(); + test_wait_on_for(); + test_wait_on_for(false); + return 0; +} diff --git a/libstdc++-v3/testsuite/30_threads/jthread/1.cc b/libstdc++-v3/testsuite/30_threads/jthread/1.cc new file mode 100644 index 00000000000..1fb5650dbc6 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/jthread/1.cc @@ -0,0 +1,27 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <thread> + +#ifndef __cpp_lib_jthread +# error "Feature-test macro for jthread missing in <thread>" +#elif __cpp_lib_jthread != 201907L +# error "Feature-test macro for jthread has wrong value in <thread>" +#endif diff --git a/libstdc++-v3/testsuite/30_threads/jthread/2.cc b/libstdc++-v3/testsuite/30_threads/jthread/2.cc new file mode 100644 index 00000000000..621965c8910 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/jthread/2.cc @@ -0,0 +1,27 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <version> + +#ifndef __cpp_lib_jthread +# error "Feature-test macro for jthread missing in <version>" +#elif __cpp_lib_jthread != 201907L +# error "Feature-test macro for jthread has wrong value in <version>" +#endif diff --git a/libstdc++-v3/testsuite/30_threads/jthread/jthread.cc b/libstdc++-v3/testsuite/30_threads/jthread/jthread.cc new file mode 100644 index 00000000000..c29db212167 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/jthread/jthread.cc @@ -0,0 +1,198 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <thread> +#include <chrono> +#include <cassert> +#include <atomic> + +using namespace::std::literals; + +//------------------------------------------------------ + +void test_no_stop_token() +{ + // test the basic jthread API (not taking stop_token arg) + + assert(std::jthread::hardware_concurrency() == std::thread::hardware_concurrency()); + std::stop_token stoken; + assert(!stoken.stop_possible()); + { + std::jthread::id t1ID{std::this_thread::get_id()}; + std::atomic<bool> t1AllSet{false}; + std::jthread t1([&t1ID, &t1AllSet] { + t1ID = std::this_thread::get_id(); + t1AllSet.store(true); + for (int c='9'; c>='0'; --c) { + std::this_thread::sleep_for(222ms); + } + }); + for (int i=0; !t1AllSet.load(); ++i) { + std::this_thread::sleep_for(10ms); + } + assert(t1.joinable()); + assert(t1ID == t1.get_id()); + stoken = t1.get_stop_token(); + assert(!stoken.stop_requested()); + } + assert(stoken.stop_requested()); +} + +//------------------------------------------------------ + +void test_stop_token() +{ + // test the basic thread API (taking stop_token arg) + + std::stop_source ssource; + std::stop_source origsource; + assert(ssource.stop_possible()); + assert(!ssource.stop_requested()); + { + std::jthread::id t1ID{std::this_thread::get_id()}; + std::atomic<bool> t1AllSet{false}; + std::atomic<bool> t1done{false}; + std::jthread t1([&t1ID, &t1AllSet, &t1done] (std::stop_token st) { + // check some values of the started thread: + t1ID = std::this_thread::get_id(); + t1AllSet.store(true); + for (int i=0; !st.stop_requested(); ++i) { + std::this_thread::sleep_for(100ms); + } + t1done.store(true); + }, + ssource.get_token()); + for (int i=0; !t1AllSet.load(); ++i) { + std::this_thread::sleep_for(10ms); + } + // and check all values: + assert(t1.joinable()); + assert(t1ID == t1.get_id()); + + std::this_thread::sleep_for(470ms); + origsource = std::move(ssource); + ssource = t1.get_stop_source(); + assert(!ssource.stop_requested()); + auto ret = ssource.request_stop(); + assert(ret); + ret = ssource.request_stop(); + assert(!ret); + assert(ssource.stop_requested()); + assert(!t1done.load()); + assert(!origsource.stop_requested()); + + std::this_thread::sleep_for(470ms); + origsource.request_stop(); + } + assert(origsource.stop_requested()); + assert(ssource.stop_requested()); +} + +//------------------------------------------------------ + +void test_join() +{ + std::stop_source ssource; + assert(ssource.stop_possible()); + { + std::jthread t1([](std::stop_token stoken) { + for (int i=0; !stoken.stop_requested(); ++i) { + std::this_thread::sleep_for(100ms); + } + }); + ssource = t1.get_stop_source(); + std::jthread t2([ssource] () mutable { + for (int i=0; i < 10; ++i) { + std::this_thread::sleep_for(70ms); + } + ssource.request_stop(); + }); + // wait for all thread to finish: + t2.join(); + assert(!t2.joinable()); + assert(t1.joinable()); + t1.join(); + assert(!t1.joinable()); + } +} + +//------------------------------------------------------ + +void test_detach() +{ + std::stop_source ssource; + assert(ssource.stop_possible()); + std::atomic<bool> t1FinallyInterrupted{false}; + { + std::jthread t0; + std::jthread::id t1ID{std::this_thread::get_id()}; + bool t1IsInterrupted; + std::stop_token t1InterruptToken; + std::atomic<bool> t1AllSet{false}; + std::jthread t1([&t1ID, &t1IsInterrupted, &t1InterruptToken, &t1AllSet, &t1FinallyInterrupted] + (std::stop_token stoken) { + // check some values of the started thread: + t1ID = std::this_thread::get_id(); + t1InterruptToken = stoken; + t1IsInterrupted = stoken.stop_requested(); + assert(stoken.stop_possible()); + assert(!stoken.stop_requested()); + t1AllSet.store(true); + for (int i=0; !stoken.stop_requested(); ++i) { + std::this_thread::sleep_for(100ms); + } + t1FinallyInterrupted.store(true); + }); + for (int i=0; !t1AllSet.load(); ++i) { + std::this_thread::sleep_for(10ms); + } + assert(!t0.joinable()); + assert(t1.joinable()); + assert(t1ID == t1.get_id()); + assert(t1IsInterrupted == false); + assert(t1InterruptToken == t1.get_stop_source().get_token()); + ssource = t1.get_stop_source(); + assert(t1InterruptToken.stop_possible()); + assert(!t1InterruptToken.stop_requested()); + t1.detach(); + assert(!t1.joinable()); + } + + assert(!t1FinallyInterrupted.load()); + ssource.request_stop(); + assert(ssource.stop_requested()); + for (int i=0; !t1FinallyInterrupted.load() && i < 100; ++i) { + std::this_thread::sleep_for(100ms); + } + assert(t1FinallyInterrupted.load()); +} + +int main() +{ + std::set_terminate([](){ + assert(false); + }); + + test_no_stop_token(); + test_stop_token(); + test_join(); + test_detach(); +} + diff --git a/libstdc++-v3/testsuite/30_threads/stop_token/1.cc b/libstdc++-v3/testsuite/30_threads/stop_token/1.cc new file mode 100644 index 00000000000..4c0e7a33d25 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/stop_token/1.cc @@ -0,0 +1,27 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <stop_token> + +#ifndef __cpp_lib_jthread +# error "Feature-test macro for jthread missing in <stop_token>" +#elif __cpp_lib_jthread != 201907L +# error "Feature-test macro for jthread has wrong value in <stop_token>" +#endif diff --git a/libstdc++-v3/testsuite/30_threads/stop_token/2.cc b/libstdc++-v3/testsuite/30_threads/stop_token/2.cc new file mode 100644 index 00000000000..621965c8910 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/stop_token/2.cc @@ -0,0 +1,27 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <version> + +#ifndef __cpp_lib_jthread +# error "Feature-test macro for jthread missing in <version>" +#elif __cpp_lib_jthread != 201907L +# error "Feature-test macro for jthread has wrong value in <version>" +#endif diff --git a/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc b/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc new file mode 100644 index 00000000000..37d79e8a64e --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc @@ -0,0 +1,100 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a -pthread" } +// { dg-do run } +// { dg-require-effective-target c++2a } +// { dg-require-effective-target pthread } + +#include <stop_token> +#include <iostream> +#include <testsuite_hooks.h> + +int main() +{ + // create stop_source + std::stop_source ssrc; + VERIFY(ssrc.stop_possible()); + VERIFY(!ssrc.stop_requested()); + + // create stop_token from stop_source + std::stop_token stok{ssrc.get_token()}; + VERIFY(ssrc.stop_possible()); + VERIFY(!ssrc.stop_requested()); + VERIFY(stok.stop_possible()); + VERIFY(!stok.stop_requested()); + + // register callback + bool cb1called{false}; + auto cb1 = [&]{ + std::cout << "cb1" << std::endl; + cb1called = true; + }; + { + std::stop_callback scb1{stok, cb1}; + VERIFY(ssrc.stop_possible()); + VERIFY(!ssrc.stop_requested()); + VERIFY(stok.stop_possible()); + VERIFY(!stok.stop_requested()); + VERIFY(!cb1called); + } // unregister callback + + // register another callback + bool cb2called{false}; + auto cb2 = [&]{ + VERIFY(stok.stop_requested()); + cb2called = true; + }; + std::stop_callback scb2a{stok, cb2}; // copies cb2 + // std::stop_callback scb2b{stok, std::move(cb2)}; + VERIFY(ssrc.stop_possible()); + VERIFY(!ssrc.stop_requested()); + VERIFY(stok.stop_possible()); + VERIFY(!stok.stop_requested()); + VERIFY(!cb1called); + VERIFY(!cb2called); + + // request stop + auto b = ssrc.request_stop(); + VERIFY(b); + VERIFY(ssrc.stop_possible()); + VERIFY(ssrc.stop_requested()); + VERIFY(stok.stop_possible()); + VERIFY(stok.stop_requested()); + VERIFY(!cb1called); + VERIFY(cb2called); + + b = ssrc.request_stop(); + VERIFY(!b); + + // TODO verify the standard requires this +#if 0 + // register another callback + bool cb3called{false}; + std::stop_callback scb3{stok, [&] + { + cb3called = true; + }}; + VERIFY(ssrc.stop_possible()); + VERIFY(ssrc.stop_requested()); + VERIFY(stok.stop_possible()); + VERIFY(stok.stop_requested()); + VERIFY(!cb1called); + VERIFY(cb2called); + VERIFY(cb3called); +#endif +} -- 2.21.0 [-- Attachment #3: Type: text/plain, Size: 10719 bytes --] Jonathan Wakely writes: > On 23/10/19 12:50 -0700, Thomas Rodgers wrote: >> >>Thomas Rodgers writes: >> >>Let's try this again. > > That's better, thanks :-) > >>+ * include/Makefile.am: Add <stop_token> header. >>+ * include/Makefile.in: Regenerate. >>+ * include/std/stop_token: New file. >>+ * include/std/version (__cpp_lib_jthread): New value. >>+ * testsuite/30_threads/stop_token/1.cc: New test. >>+ * testsuite/30_threads/stop_token/2.cc: New test. >>+ * testsuite/30_threads/stop_token/stop_token.cc: New test. > > ChangeLog entries should be provided separately from the patch (e.g. > inline in the makefile, or as a separte attachment) because they never > apply cleanly. > > Also it looks like you have a mixture of tabs and space there, it > should be only tabs. > >>diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in >>index cd1e9df5482..9b4ab670315 100644 >>--- a/libstdc++-v3/include/Makefile.in >>+++ b/libstdc++-v3/include/Makefile.in >>@@ -416,6 +416,7 @@ std_headers = \ >> ${std_srcdir}/sstream \ >> ${std_srcdir}/stack \ >> ${std_srcdir}/stdexcept \ >>+ ${std_srcdir}/stop_token \ >> ${std_srcdir}/streambuf \ >> ${std_srcdir}/string \ >> ${std_srcdir}/string_view \ > > Generated files like Makefile.in don't need to be in the patch. I use > a git alias called 'filter' to filter them out: > > !f(){ git $@ | filterdiff -x '*/ChangeLog' -x '*/Makefile.in' -x '*/configure' -x '*/config.h.in' -x '*/doc/html/*' | sed -e '/^diff.*ChangeLog/{N;d}' ; }; f > > And then I create a patch with this alias: > > !f(){ git filter show --diff-algorithm=histogram ${*:-HEAD} > /dev/shm/patch.txt; }; f > > >>diff --git a/libstdc++-v3/include/std/stop_token b/libstdc++-v3/include/std/stop_token >>new file mode 100644 >>index 00000000000..b3655b85eae >>--- /dev/null >>+++ b/libstdc++-v3/include/std/stop_token >>@@ -0,0 +1,338 @@ >>+// <stop_token> -*- C++ -*- >>+ >>+// Copyright (C) 2008-2019 Free Software Foundation, Inc. > > The copyright date should be just 2019. > >>+// >>+// This file is part of the GNU ISO C++ Library. This library is free >>+// software; you can redistribute it and/or modify it under the >>+// terms of the GNU General Public License as published by the >>+// Free Software Foundation; either version 3, or (at your option) >>+// any later version. >>+ >>+// This library is distributed in the hope that it will be useful, >>+// but WITHOUT ANY WARRANTY; without even the implied warranty of >>+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >>+// GNU General Public License for more details. >>+ >>+// Under Section 7 of GPL version 3, you are granted additional >>+// permissions described in the GCC Runtime Library Exception, version >>+// 3.1, as published by the Free Software Foundation. >>+ >>+// You should have received a copy of the GNU General Public License and >>+// a copy of the GCC Runtime Library Exception along with this program; >>+// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see >>+// <http://www.gnu.org/licenses/>. >>+ >>+/** @file include/stop_token >>+ * This is a Standard C++ Library header. >>+ */ >>+ >>+#ifndef _GLIBCXX_STOP_TOKEN >>+#define _GLIBCXX_STOP_TOKEN >>+ > > Please add: > > #if __cplusplus > 201703L > > here (and the corresponding #endif before the final #endif) so that > this file is empty when included pre-C++20. > >>+#include <type_traits> >>+#include <memory> >>+#include <mutex> >>+#include <atomic> >>+ >>+#define __cpp_lib_jthread 201907L >>+ >>+namespace std _GLIBCXX_VISIBILITY(default) >>+{ >>+ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > These BEGIN/END macros should not be indented (which has to be done > manually as editors always want to indent the content following the > opening brace of the namespace). > >>+ >>+ class stop_source; >>+ template<class _Callback> > > s/class/typename/ > >>+ class stop_callback; >>+ >>+ struct nostopstate_t { explicit nostopstate_t() = default; }; >>+ inline constexpr nostopstate_t nostopstate(); >>+ >>+ class stop_token { > > The opening brace should be on the next line please, in the same > column as "class". > (Same comment for all classes and structs in the patch). > > >>+ public: >>+ stop_token() noexcept = default; >>+ >>+ stop_token(const stop_token& __other) noexcept >>+ : _M_state(__other._M_state) >>+ { } >>+ >>+ stop_token(stop_token&& __other) noexcept >>+ : _M_state(std::move(__other._M_state)) >>+ { } >>+ >>+ ~stop_token() = default; >>+ >>+ stop_token& >>+ operator=(const stop_token& __rhs) noexcept { >>+ _M_state = __rhs._M_state; >>+ return *this; >>+ } >>+ >>+ stop_token& >>+ operator=(stop_token&& __rhs) noexcept { >>+ std::swap(_M_state, __rhs._M_state); >>+ return *this; >>+ } > > This doesn't leave the RHS empty as required. > > I think the copy/move constructors and copy/move assignment operators > can all be defined as = default and that will do the right thing. > >>+ >>+ [[nodiscard]] >>+ bool >>+ stop_possible() const noexcept >>+ { >>+ return static_cast<bool>(_M_state); >>+ } >>+ >>+ [[nodiscard]] >>+ bool >>+ stop_requested() const noexcept >>+ { >>+ return stop_possible() && _M_state->_M_stop_requested(); >>+ } >>+ >>+ private: >>+ friend stop_source; >>+ template<typename _Callback> >>+ friend class stop_callback; >>+ >>+ struct _Stop_cb { >>+ void(*_M_callback)(_Stop_cb*); >>+ _Stop_cb* _M_prev = nullptr; >>+ _Stop_cb* _M_next = nullptr; >>+ >>+ template<typename _CB> > > Please Use _Cb so it's not all uppercase (which is more likely to > conflict with libc macros). > >>+ _Stop_cb(_CB&& __cb) >>+ : _M_callback(std::move(__cb)) >>+ { } >>+ >>+ static void >>+ _S_execute(_Stop_cb* __cb) noexcept >>+ { >>+ __cb->_M_callback(__cb); >>+ } >>+ }; >>+ >>+ struct _Stop_state_t { >>+ std::atomic<bool> _M_stopped; >>+ std::mutex _M_mtx; >>+ _Stop_cb* _M_head = nullptr; >>+ >>+ _Stop_state_t() >>+ : _M_stopped{false} >>+ { } >>+ >>+ bool >>+ _M_stop_requested() >>+ { >>+ return _M_stopped; >>+ } >>+ >>+ bool >>+ _M_request_stop() >>+ { >>+ bool __stopped = false; >>+ if (_M_stopped.compare_exchange_strong(__stopped, true)) >>+ { >>+ std::unique_lock<std::mutex> __lck{_M_mtx}; >>+ while (auto __p = _M_head) >>+ { >>+ _M_head = __p->_M_next; >>+ _Stop_cb::_S_execute(__p); >>+ } >>+ return true; >>+ } >>+ return false; >>+ } >>+ >>+ bool >>+ _M_register_callback(_Stop_cb* __cb) >>+ { >>+ std::unique_lock<std::mutex> __lck{_M_mtx}; >>+ if (_M_stopped) >>+ return false; >>+ >>+ __cb->_M_next = _M_head; >>+ _M_head->_M_prev = __cb; >>+ _M_head = __cb; >>+ return true; >>+ } >>+ >>+ void >>+ _M_remove_callback(_Stop_cb* __cb) >>+ { >>+ std::unique_lock<std::mutex> __lck{_M_mtx}; >>+ if (__cb == _M_head) >>+ { >>+ _M_head = _M_head->_M_next; >>+ _M_head->_M_prev = nullptr; >>+ } >>+ else >>+ { >>+ __cb->_M_prev->_M_next = __cb->_M_next; >>+ if (__cb->_M_next) >>+ { >>+ __cb->_M_next->_M_prev = __cb->_M_prev; >>+ } >>+ } >>+ } >>+ }; >>+ >>+ using _Stop_state = std::shared_ptr<_Stop_state_t>; >>+ _Stop_state _M_state; >>+ >>+ explicit stop_token(_Stop_state __state) >>+ : _M_state{std::move(__state)} >>+ { } >>+ }; >>+ >>+ class stop_source { >>+ using _Stop_state_t = stop_token::_Stop_state_t; >>+ using _Stop_state = stop_token::_Stop_state; >>+ >>+ public: >>+ stop_source() >>+ : _M_state(std::make_shared<_Stop_state_t>()) >>+ { } >>+ >>+ explicit stop_source(std::nostopstate_t) noexcept >>+ { } >>+ >>+ stop_source(const stop_source& __other) noexcept >>+ : _M_state(__other._M_state) >>+ { } >>+ >>+ stop_source(stop_source&& __other) noexcept >>+ : _M_state(std::move(__other._M_state)) >>+ { } >>+ >>+ stop_source& >>+ operator=(const stop_source& __rhs) noexcept >>+ { >>+ if (_M_state != __rhs._M_state) >>+ _M_state = __rhs._M_state; >>+ return *this; >>+ } >>+ >>+ stop_source& >>+ operator=(stop_source&& __rhs) noexcept >>+ { >>+ std::swap(_M_state, __rhs._M_state); >>+ return *this; >>+ } > > These can all be defaulted too. > >>+ [[nodiscard]] >>+ bool >>+ stop_possible() const noexcept >>+ { >>+ return static_cast<bool>(_M_state); >>+ } >>+ >>+ [[nodiscard]] >>+ bool >>+ stop_requested() const noexcept >>+ { >>+ return stop_possible() && _M_state->_M_stop_requested(); >>+ } >>+ >>+ bool >>+ request_stop() const noexcept >>+ { >>+ if (stop_possible()) >>+ return _M_state->_M_request_stop(); >>+ return false; >>+ } >>+ >>+ [[nodiscard]] >>+ stop_token >>+ get_token() const noexcept >>+ { >>+ return stop_token{_M_state}; >>+ } >>+ >>+ void >>+ swap(stop_source& __other) noexcept >>+ { >>+ std::swap(_M_state, __other._M_state); >>+ } >>+ >>+ [[nodiscard]] >>+ friend bool >>+ operator==(const stop_source& __a, const stop_source& __b) noexcept >>+ { >>+ return __a._M_state == __b._M_state; >>+ } >>+ >>+ [[nodiscard]] >>+ friend bool >>+ operator!=(const stop_source& __a, const stop_source& __b) noexcept >>+ { >>+ return __a._M_state != __b._M_state; >>+ } >>+ >>+ private: >>+ _Stop_state _M_state; >>+ }; >>+ >>+ template<typename _Callback> >>+ class [[nodiscard]] stop_callback >>+ : private stop_token::_Stop_cb { > > Opening brace on the next line, in the same column as "class". > >>+ using _Stop_cb = stop_token::_Stop_cb; > > And then everything in the class body should be indented by another > two columns. > >>+ using _Stop_state = stop_token::_Stop_state; >>+ public: >>+ using callback_type = _Callback; >>+ >>+ template<typename _CB, > > Change to _Cb here too. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] Add support for C++2a stop_token 2019-11-14 2:00 ` Thomas Rodgers @ 2019-11-14 16:12 ` Jonathan Wakely 2019-11-15 3:13 ` Thomas Rodgers 2019-11-15 14:38 ` Jonathan Wakely 1 sibling, 1 reply; 10+ messages in thread From: Jonathan Wakely @ 2019-11-14 16:12 UTC (permalink / raw) To: Thomas Rodgers; +Cc: gcc-patches, libstdc++ On 13/11/19 17:59 -0800, Thomas Rodgers wrote: >+/** @file include/stop_token >+ * This is a Standard C++ Library header. >+ */ >+ >+#ifndef _GLIBCXX_STOP_TOKEN >+#define _GLIBCXX_STOP_TOKEN >+ >+#if __cplusplus >= 201703L This should be > not >= OK for trunk with that change. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] Add support for C++2a stop_token 2019-11-14 16:12 ` Jonathan Wakely @ 2019-11-15 3:13 ` Thomas Rodgers 0 siblings, 0 replies; 10+ messages in thread From: Thomas Rodgers @ 2019-11-15 3:13 UTC (permalink / raw) To: Jonathan Wakely; +Cc: gcc-patches, libstdc++ Tested x86_64-pc-linux-gnu, committed to trunk. Jonathan Wakely writes: > On 13/11/19 17:59 -0800, Thomas Rodgers wrote: >>+/** @file include/stop_token >>+ * This is a Standard C++ Library header. >>+ */ >>+ >>+#ifndef _GLIBCXX_STOP_TOKEN >>+#define _GLIBCXX_STOP_TOKEN >>+ >>+#if __cplusplus >= 201703L > > This should be > not >= > > OK for trunk with that change. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] Add support for C++2a stop_token 2019-11-14 2:00 ` Thomas Rodgers 2019-11-14 16:12 ` Jonathan Wakely @ 2019-11-15 14:38 ` Jonathan Wakely 2019-11-15 14:41 ` Jonathan Wakely 1 sibling, 1 reply; 10+ messages in thread From: Jonathan Wakely @ 2019-11-15 14:38 UTC (permalink / raw) To: Thomas Rodgers; +Cc: gcc-patches, libstdc++ On 13/11/19 17:59 -0800, Thomas Rodgers wrote: >+ // TODO verify the standard requires this >+#if 0 >+ // register another callback >+ bool cb3called{false}; >+ std::stop_callback scb3{stok, [&] >+ { >+ cb3called = true; >+ }}; >+ VERIFY(ssrc.stop_possible()); >+ VERIFY(ssrc.stop_requested()); >+ VERIFY(stok.stop_possible()); >+ VERIFY(stok.stop_requested()); >+ VERIFY(!cb1called); >+ VERIFY(cb2called); >+ VERIFY(cb3called); >+#endif The working draft definitely requires this: Effects: Initializes callback with std::forward<C>(cb). If st.stop_requested() is true, then std::forward<Callback>(callback)() is evaluated in the current thread before the constructor returns. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] Add support for C++2a stop_token 2019-11-15 14:38 ` Jonathan Wakely @ 2019-11-15 14:41 ` Jonathan Wakely 2019-11-15 23:44 ` Jonathan Wakely 0 siblings, 1 reply; 10+ messages in thread From: Jonathan Wakely @ 2019-11-15 14:41 UTC (permalink / raw) To: Thomas Rodgers; +Cc: gcc-patches, libstdc++ [-- Attachment #1: Type: text/plain, Size: 1197 bytes --] On 15/11/19 14:38 +0000, Jonathan Wakely wrote: >On 13/11/19 17:59 -0800, Thomas Rodgers wrote: >>+ // TODO verify the standard requires this >>+#if 0 >>+ // register another callback >>+ bool cb3called{false}; >>+ std::stop_callback scb3{stok, [&] >>+ { >>+ cb3called = true; >>+ }}; >>+ VERIFY(ssrc.stop_possible()); >>+ VERIFY(ssrc.stop_requested()); >>+ VERIFY(stok.stop_possible()); >>+ VERIFY(stok.stop_requested()); >>+ VERIFY(!cb1called); >>+ VERIFY(cb2called); >>+ VERIFY(cb3called); >>+#endif > >The working draft definitely requires this: > >Effects: Initializes callback with std::forward<C>(cb). If st.stop_requested() is true, then >std::forward<Callback>(callback)() is evaluated in the current thread before the constructor >returns. I've committed this fix to the nostopstate initializer, so it defines a variable, instead of declaring a function. I've also added a test that uses the stop_source(nostopstate_t) constructor, and a few other tweaks to include the new header where it's needed. Tested powerpc64le-linux, committed to trunk. [-- Attachment #2: patch.txt --] [-- Type: text/x-patch, Size: 8714 bytes --] commit 293fddacf1d794d92408824c95de24d285bdc273 Author: Jonathan Wakely <jwakely@redhat.com> Date: Fri Nov 15 13:25:35 2019 +0000 libstdc++: Fix definition of std::nostopstate object Also add <stop_token> header to PCH and Doxygen config. * doc/doxygen/user.cfg.in: Add <stop_token>. * include/precompiled/stdc++.h: Likewise. * include/std/stop_token: Fix definition of std::nostopstate. * testsuite/30_threads/headers/stop_token/synopsis.cc: New test. * testsuite/30_threads/headers/thread/types_std_c++20.cc: New test. * testsuite/30_threads/stop_token/stop_source.cc: New test. * testsuite/30_threads/stop_token/stop_token.cc: Remove unnecessary dg-require directives. Remove I/O and inclusion of <iostream>. diff --git a/libstdc++-v3/doc/doxygen/user.cfg.in b/libstdc++-v3/doc/doxygen/user.cfg.in index 18994703f0b..19f8ffd8230 100644 --- a/libstdc++-v3/doc/doxygen/user.cfg.in +++ b/libstdc++-v3/doc/doxygen/user.cfg.in @@ -840,6 +840,7 @@ INPUT = @srcdir@/doc/doxygen/doxygroups.cc \ include/sstream \ include/stack \ include/stdexcept \ + include/stop_token \ include/streambuf \ include/string \ include/string_view \ diff --git a/libstdc++-v3/include/precompiled/stdc++.h b/libstdc++-v3/include/precompiled/stdc++.h index 118fc8f359a..c77136ef441 100644 --- a/libstdc++-v3/include/precompiled/stdc++.h +++ b/libstdc++-v3/include/precompiled/stdc++.h @@ -140,6 +140,7 @@ #include <numbers> #include <ranges> #include <span> +#include <stop_token> // #include <syncstream> #include <version> #endif diff --git a/libstdc++-v3/include/std/stop_token b/libstdc++-v3/include/std/stop_token index af64018bb0e..f96c267f22e 100644 --- a/libstdc++-v3/include/std/stop_token +++ b/libstdc++-v3/include/std/stop_token @@ -47,7 +47,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION class stop_callback; struct nostopstate_t { explicit nostopstate_t() = default; }; - inline constexpr nostopstate_t nostopstate(); + inline constexpr nostopstate_t nostopstate{}; class stop_token { diff --git a/libstdc++-v3/testsuite/30_threads/headers/stop_token/synopsis.cc b/libstdc++-v3/testsuite/30_threads/headers/stop_token/synopsis.cc new file mode 100644 index 00000000000..b185bc7be39 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/headers/stop_token/synopsis.cc @@ -0,0 +1,35 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <stop_token> + +namespace std +{ + class stop_token; + class stop_source; + struct nostopstate_t; + template<class> + class stop_callback; +} + +namespace gnu +{ + constexpr const std::nostopstate_t* tag = &std::nostopstate; +} diff --git a/libstdc++-v3/testsuite/30_threads/headers/thread/types_std_c++20.cc b/libstdc++-v3/testsuite/30_threads/headers/thread/types_std_c++20.cc new file mode 100644 index 00000000000..86f29d8b001 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/headers/thread/types_std_c++20.cc @@ -0,0 +1,30 @@ +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } +// { dg-require-gthreads "" } + +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +#include <thread> + +void test01() +{ + using t_t = std::thread; + using j_t = std::jthread; + + using namespace std::this_thread; +} diff --git a/libstdc++-v3/testsuite/30_threads/stop_token/stop_source.cc b/libstdc++-v3/testsuite/30_threads/stop_token/stop_source.cc new file mode 100644 index 00000000000..9e477b7d7ee --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/stop_token/stop_source.cc @@ -0,0 +1,75 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do run { target c++2a } } + +#include <stop_token> +#include <testsuite_hooks.h> + +void +test01() +{ + std::stop_source ssrc; + VERIFY( ssrc.stop_possible() ); + VERIFY( !ssrc.stop_requested() ); + + std::stop_source copy(ssrc); + VERIFY( copy.stop_possible() ); + VERIFY( !copy.stop_requested() ); + VERIFY( ssrc.stop_possible() ); + VERIFY( !ssrc.stop_requested() ); + + std::stop_source move(std::move(ssrc)); + VERIFY( move.stop_possible() ); + VERIFY( !move.stop_requested() ); + VERIFY( copy.stop_possible() ); + VERIFY( !copy.stop_requested() ); + VERIFY( !ssrc.stop_possible() ); + VERIFY( !ssrc.stop_requested() ); +} + +void +test02() +{ + // stop_source(nostopstate_t) constructor is explicit: + static_assert(!std::is_convertible_v<std::nostopstate_t, std::stop_source>); + + std::stop_source ssrc(std::nostopstate); + VERIFY( !ssrc.stop_possible() ); + VERIFY( !ssrc.stop_requested() ); + + std::stop_source copy(ssrc); + VERIFY( !copy.stop_possible() ); + VERIFY( !copy.stop_requested() ); + VERIFY( !ssrc.stop_possible() ); + VERIFY( !ssrc.stop_requested() ); + + std::stop_source move(std::move(ssrc)); + VERIFY( !move.stop_possible() ); + VERIFY( !move.stop_requested() ); + VERIFY( !copy.stop_possible() ); + VERIFY( !copy.stop_requested() ); + VERIFY( !ssrc.stop_possible() ); + VERIFY( !ssrc.stop_requested() ); +} + +int main() +{ + test01(); + test02(); +} diff --git a/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc b/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc index 37d79e8a64e..f0772c1d80f 100644 --- a/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc +++ b/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc @@ -15,13 +15,10 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. -// { dg-options "-std=gnu++2a -pthread" } -// { dg-do run } -// { dg-require-effective-target c++2a } -// { dg-require-effective-target pthread } +// { dg-options "-std=gnu++2a" } +// { dg-do run { target c++2a } } #include <stop_token> -#include <iostream> #include <testsuite_hooks.h> int main() @@ -41,7 +38,6 @@ int main() // register callback bool cb1called{false}; auto cb1 = [&]{ - std::cout << "cb1" << std::endl; cb1called = true; }; { ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] Add support for C++2a stop_token 2019-11-15 14:41 ` Jonathan Wakely @ 2019-11-15 23:44 ` Jonathan Wakely 2019-11-18 12:28 ` Jonathan Wakely 0 siblings, 1 reply; 10+ messages in thread From: Jonathan Wakely @ 2019-11-15 23:44 UTC (permalink / raw) To: Thomas Rodgers; +Cc: gcc-patches, libstdc++ [-- Attachment #1: Type: text/plain, Size: 1389 bytes --] On 15/11/19 14:40 +0000, Jonathan Wakely wrote: >On 15/11/19 14:38 +0000, Jonathan Wakely wrote: >>On 13/11/19 17:59 -0800, Thomas Rodgers wrote: >>>+ // TODO verify the standard requires this >>>+#if 0 >>>+ // register another callback >>>+ bool cb3called{false}; >>>+ std::stop_callback scb3{stok, [&] >>>+ { >>>+ cb3called = true; >>>+ }}; >>>+ VERIFY(ssrc.stop_possible()); >>>+ VERIFY(ssrc.stop_requested()); >>>+ VERIFY(stok.stop_possible()); >>>+ VERIFY(stok.stop_requested()); >>>+ VERIFY(!cb1called); >>>+ VERIFY(cb2called); >>>+ VERIFY(cb3called); >>>+#endif >> >>The working draft definitely requires this: >> >>Effects: Initializes callback with std::forward<C>(cb). If st.stop_requested() is true, then >>std::forward<Callback>(callback)() is evaluated in the current thread before the constructor >>returns. > >I've committed this fix to the nostopstate initializer, so it defines >a variable, instead of declaring a function. I've also added a test >that uses the stop_source(nostopstate_t) constructor, and a few other >tweaks to include the new header where it's needed. This fixes some other issues I noticed, and should fix the failues for the single-threaded multilib on AIX. Tested powerpc64le-linux, committed to trunk. [-- Attachment #2: patch.txt --] [-- Type: text/x-patch, Size: 19565 bytes --] commit 9c9f5a4bd84b924190551b65d4b66665aaa3fbe6 Author: Jonathan Wakely <jwakely@redhat.com> Date: Fri Nov 15 21:27:26 2019 +0000 libstdc++: Fix <stop_token> and improve tests * include/std/stop_token: Reduce header dependencies by including internal headers. (stop_token::swap(stop_token&), swap(stop_token&, stop_token&)): Define. (operator!=(const stop_token&, const stop_token&)): Fix return value. (stop_token::_Stop_cb::_Stop_cb(Cb&&)): Use std::forward instead of (stop_token::_Stop_state_t) [_GLIBCXX_HAS_GTHREADS]: Use lock_guard instead of unique_lock. [!_GLIBCXX_HAS_GTHREADS]: Do not use mutex. (stop_token::stop_token(_Stop_state)): Change parameter to lvalue reference. (stop_source): Remove unnecessary using-declarations for names only used once. (swap(stop_source&, stop_source&)): Define. (stop_callback(const stop_token&, _Cb&&)) (stop_callback(stop_token&&, _Cb&&)): Replace lambdas with a named function. Use std::forward instead of std::move. Run callbacks if a stop request has already been made. (stop_source::_M_execute()): Remove. (stop_source::_S_execute(_Stop_cb*)): Define. * include/std/version (__cpp_lib_jthread): Define conditionally. * testsuite/30_threads/stop_token/stop_callback.cc: New test. * testsuite/30_threads/stop_token/stop_source.cc: New test. * testsuite/30_threads/stop_token/stop_token.cc: Enable test for immediate execution of callback. diff --git a/libstdc++-v3/include/std/stop_token b/libstdc++-v3/include/std/stop_token index f96c267f22e..bb082431d7f 100644 --- a/libstdc++-v3/include/std/stop_token +++ b/libstdc++-v3/include/std/stop_token @@ -31,24 +31,25 @@ #if __cplusplus > 201703L -#include <type_traits> -#include <memory> -#include <mutex> #include <atomic> +#include <bits/std_mutex.h> +#include <ext/concurrence.h> +#include <bits/unique_ptr.h> +#include <bits/shared_ptr.h> -#define __cpp_lib_jthread 201907L +#ifdef _GLIBCXX_HAS_GTHREADS +# define __cpp_lib_jthread 201907L +#endif namespace std _GLIBCXX_VISIBILITY(default) { _GLIBCXX_BEGIN_NAMESPACE_VERSION - class stop_source; - template<typename _Callback> - class stop_callback; - + /// Tag type indicating a stop_source should have no shared-stop-state. struct nostopstate_t { explicit nostopstate_t() = default; }; inline constexpr nostopstate_t nostopstate{}; + /// Allow testing whether a stop request has been made on a `stop_source`. class stop_token { public: @@ -63,7 +64,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION operator=(const stop_token& __rhs) noexcept = default; stop_token& - operator=(stop_token&& __rhs) noexcept; + operator=(stop_token&& __rhs) noexcept = default; [[nodiscard]] bool @@ -79,6 +80,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return stop_possible() && _M_state->_M_stop_requested(); } + void + swap(stop_token& __rhs) noexcept + { _M_state.swap(__rhs._M_state); } + [[nodiscard]] friend bool operator==(const stop_token& __a, const stop_token& __b) @@ -90,26 +95,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend bool operator!=(const stop_token& __a, const stop_token& __b) { - return __a._M_state == __b._M_state; + return __a._M_state != __b._M_state; } - private: - friend stop_source; - template<typename _Callback> - friend class stop_callback; + friend void + swap(stop_token& __lhs, stop_token& __rhs) noexcept + { __lhs.swap(__rhs); } - struct _Stop_cb { + private: + friend class stop_source; + template<typename _Callback> + friend class stop_callback; + + struct _Stop_cb + { void(*_M_callback)(_Stop_cb*); _Stop_cb* _M_prev = nullptr; _Stop_cb* _M_next = nullptr; template<typename _Cb> - _Stop_cb(_Cb&& __cb) - : _M_callback(std::move(__cb)) - { } + _Stop_cb(_Cb&& __cb) + : _M_callback(std::forward<_Cb>(__cb)) + { } bool - _M_linked() const + _M_linked() const noexcept { return (_M_prev != nullptr) || (_M_next != nullptr); @@ -123,17 +133,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } }; - struct _Stop_state_t { - std::atomic<bool> _M_stopped; - std::mutex _M_mtx; + struct _Stop_state_t + { + std::atomic<bool> _M_stopped{false}; _Stop_cb* _M_head = nullptr; +#ifdef _GLIBCXX_HAS_GTHREADS + std::mutex _M_mtx; +#endif - _Stop_state_t() - : _M_stopped{false} - { } + _Stop_state_t() = default; bool - _M_stop_requested() + _M_stop_requested() noexcept { return _M_stopped; } @@ -144,7 +155,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION bool __stopped = false; if (_M_stopped.compare_exchange_strong(__stopped, true)) { - std::unique_lock<std::mutex> __lck{_M_mtx}; +#ifdef _GLIBCXX_HAS_GTHREADS + std::lock_guard<std::mutex> __lck{_M_mtx}; +#endif while (_M_head) { auto __p = _M_head; @@ -159,7 +172,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION bool _M_register_callback(_Stop_cb* __cb) { - std::unique_lock<std::mutex> __lck{_M_mtx}; +#ifdef _GLIBCXX_HAS_GTHREADS + std::lock_guard<std::mutex> __lck{_M_mtx}; +#endif if (_M_stopped) return false; @@ -169,13 +184,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _M_head->_M_prev = __cb; } _M_head = __cb; - return true; + return true; } void _M_remove_callback(_Stop_cb* __cb) { - std::unique_lock<std::mutex> __lck{_M_mtx}; +#ifdef _GLIBCXX_HAS_GTHREADS + std::lock_guard<std::mutex> __lck{_M_mtx}; +#endif if (__cb == _M_head) { _M_head = _M_head->_M_next; @@ -202,18 +219,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION using _Stop_state = std::shared_ptr<_Stop_state_t>; _Stop_state _M_state; - explicit stop_token(_Stop_state __state) - : _M_state{std::move(__state)} + explicit + stop_token(const _Stop_state& __state) noexcept + : _M_state{__state} { } }; - class stop_source { - using _Stop_state_t = stop_token::_Stop_state_t; - using _Stop_state = stop_token::_Stop_state; - + /// A type that allows a stop request to be made. + class stop_source + { public: stop_source() - : _M_state(std::make_shared<_Stop_state_t>()) + : _M_state(std::make_shared<stop_token::_Stop_state_t>()) { } explicit stop_source(std::nostopstate_t) noexcept @@ -274,7 +291,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION void swap(stop_source& __other) noexcept { - std::swap(_M_state, __other._M_state); + _M_state.swap(__other._M_state); } [[nodiscard]] @@ -291,59 +308,63 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return __a._M_state != __b._M_state; } + friend void + swap(stop_source& __lhs, stop_source& __rhs) noexcept + { + __lhs.swap(__rhs); + } + private: - _Stop_state _M_state; + stop_token::_Stop_state _M_state; }; + /// A wrapper for callbacks to be run when a stop request is made. template<typename _Callback> class [[nodiscard]] stop_callback : private stop_token::_Stop_cb { - using _Stop_cb = stop_token::_Stop_cb; - using _Stop_state = stop_token::_Stop_state; public: using callback_type = _Callback; template<typename _Cb, - std::enable_if_t<std::is_constructible_v<_Callback, _Cb>, int> = 0> - explicit stop_callback(const stop_token& __token, _Cb&& __cb) - noexcept(std::is_nothrow_constructible_v<_Callback, _Cb>) - : _Stop_cb([](_Stop_cb* __that) noexcept - { - static_cast<stop_callback*>(__that)->_M_execute(); - }), - _M_cb(std::move(__cb)) + enable_if_t<is_constructible_v<_Callback, _Cb>, int> = 0> + explicit + stop_callback(const stop_token& __token, _Cb&& __cb) + noexcept(is_nothrow_constructible_v<_Callback, _Cb>) + : _Stop_cb(&_S_execute), _M_cb(std::forward<_Cb>(__cb)) { - auto res = __token._M_state->_M_register_callback(this); - if (__token._M_state && res) - { - _M_state = __token._M_state; - } + if (auto __state = __token._M_state) + { + if (__state->_M_stop_requested()) + _S_execute(this); // ensures std::terminate on throw + else if (__state->_M_register_callback(this)) + _M_state.swap(__state); + } } template<typename _Cb, - std::enable_if_t<std::is_constructible_v<_Callback, _Cb>, int> = 0> - explicit stop_callback(stop_token&& __token, _Cb&& __cb) - noexcept(std::is_nothrow_constructible_v<_Callback, _Cb>) - : _Stop_cb([](_Stop_cb* __that) noexcept - { - static_cast<stop_callback*>(__that)->_M_execute(); - }), - _M_cb(std::move(__cb)) - { - if (__token._M_state && __token._M_state->_M_register_callback(this)) - { - std::swap(_M_state, __token._M_state); - } - } + enable_if_t<is_constructible_v<_Callback, _Cb>, int> = 0> + explicit + stop_callback(stop_token&& __token, _Cb&& __cb) + noexcept(is_nothrow_constructible_v<_Callback, _Cb>) + : _Stop_cb(&_S_execute), _M_cb(std::forward<_Cb>(__cb)) + { + if (auto& __state = __token._M_state) + { + if (__state->_M_stop_requested()) + _S_execute(this); // ensures std::terminate on throw + else if (__state->_M_register_callback(this)) + _M_state.swap(__state); + } + } ~stop_callback() - { - if (_M_state) - { - _M_state->_M_remove_callback(this); - } - } + { + if (_M_state) + { + _M_state->_M_remove_callback(this); + } + } stop_callback(const stop_callback&) = delete; stop_callback& operator=(const stop_callback&) = delete; @@ -352,12 +373,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION private: _Callback _M_cb; - _Stop_state _M_state = nullptr; + stop_token::_Stop_state _M_state = nullptr; - void - _M_execute() noexcept + static void + _S_execute(_Stop_cb* __that) noexcept { - _M_cb(); + static_cast<stop_callback*>(__that)->_M_cb(); } }; @@ -366,5 +387,5 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _GLIBCXX_END_NAMESPACE_VERSION } // namespace -#endif // __cplusplus >= 201703L +#endif // __cplusplus > 201703L #endif // _GLIBCXX_STOP_TOKEN diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version index 27807428903..8e08dc99049 100644 --- a/libstdc++-v3/include/std/version +++ b/libstdc++-v3/include/std/version @@ -184,10 +184,12 @@ #define __cpp_lib_constexpr_invoke 201907L #define __cpp_lib_erase_if 201900L #define __cpp_lib_interpolate 201902L +#ifdef _GLIBCXX_HAS_GTHREADS +# define __cpp_lib_jthread 201907L +#endif #define __cpp_lib_list_remove_return_type 201806L #define __cpp_lib_math_constants 201907L #define __cpp_lib_span 201902L -#define __cpp_lib_jthread 201907L #if __cpp_impl_three_way_comparison >= 201907L # define __cpp_lib_three_way_comparison 201711L #endif diff --git a/libstdc++-v3/testsuite/30_threads/stop_token/stop_callback.cc b/libstdc++-v3/testsuite/30_threads/stop_token/stop_callback.cc new file mode 100644 index 00000000000..beb155d494a --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/stop_token/stop_callback.cc @@ -0,0 +1,128 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do run { target c++2a } } + +#include <stop_token> +#include <functional> +#include <testsuite_hooks.h> + +void +test01() +{ + bool called = false; + std::function<void()> f = [&called]{ called = true; }; + std::stop_source ssrc; + ssrc.request_stop(); + std::stop_token tok = ssrc.get_token(); + std::stop_callback cb1(tok, f); + VERIFY( tok.stop_possible() ); + VERIFY( f != nullptr ); + VERIFY( called == true ); + + called = false; + std::stop_callback cb2(std::move(tok), f); + // when callback is executed immediately, no change in ownership: + VERIFY( tok.stop_possible() ); + VERIFY( f != nullptr ); + VERIFY( called == true ); + + std::stop_token sink(std::move(tok)); // leave tok empty + + called = false; + std::stop_callback cb3(tok, f); + VERIFY( f != nullptr ); + VERIFY( called == false ); + + called = false; + std::stop_callback cb4(std::move(tok), f); + VERIFY( f != nullptr ); + VERIFY( called == false ); +} + +void +test02() +{ + bool called = false; + std::function<void()> f0 = [&called]{ called = true; }; + std::function<void()> f = f0; + std::stop_source ssrc; + ssrc.request_stop(); + std::stop_token tok = ssrc.get_token(); + + std::stop_callback cb1(tok, std::move(f)); + VERIFY( tok.stop_possible() ); + VERIFY( f == nullptr ); + VERIFY( called == true ); + + called = false; + f = f0; + std::stop_callback cb2(std::move(tok), std::move(f)); + // when callback is executed immediately, no change in ownership: + VERIFY( tok.stop_possible() ); + VERIFY( f == nullptr ); + VERIFY( called == true ); + + std::stop_token sink(std::move(tok)); // leave tok empty + + called = false; + f = f0; + std::stop_callback cb3(tok, std::move(f)); + VERIFY( f == nullptr ); + VERIFY( called == false ); + + called = false; + f = f0; + std::stop_callback cb4(std::move(tok), std::move(f)); + VERIFY( f == nullptr ); + VERIFY( called == false ); +} + +void +test03() +{ + bool called[4] = { }; + std::stop_source ssrc; + std::stop_token tok = ssrc.get_token(); + std::stop_callback cb1(tok, [&]{ called[0] = true; }); + VERIFY( tok.stop_possible() ); + VERIFY( called[0] == false ); + + std::stop_callback cb2(std::move(tok), [&]{ called[1] = true; }); + VERIFY( !tok.stop_possible() ); + VERIFY( called[1] == false ); + + std::stop_callback cb3(tok, [&]{ called[2] = true; }); + VERIFY( called[2] == false ); + + std::stop_callback cb4(std::move(tok), [&]{ called[3] = true; }); + VERIFY( called[3] == false ); + + ssrc.request_stop(); + VERIFY( called[0] == true ); + VERIFY( called[1] == true ); + VERIFY( called[2] == false ); + VERIFY( called[3] == false ); +} + +int main() +{ + test01(); + test02(); + test03(); +} diff --git a/libstdc++-v3/testsuite/30_threads/stop_token/stop_source.cc b/libstdc++-v3/testsuite/30_threads/stop_token/stop_source.cc index 9e477b7d7ee..3fe76143bd0 100644 --- a/libstdc++-v3/testsuite/30_threads/stop_token/stop_source.cc +++ b/libstdc++-v3/testsuite/30_threads/stop_token/stop_source.cc @@ -68,8 +68,27 @@ test02() VERIFY( !ssrc.stop_requested() ); } +void +test03() +{ + std::stop_source s1; + s1.request_stop(); + std::stop_source s2(std::nostopstate); + s1.swap(s2); + VERIFY( !s1.stop_possible() ); + VERIFY( !s1.stop_requested() ); + VERIFY( s2.stop_possible() ); + VERIFY( s2.stop_requested() ); + swap(s1, s2); + VERIFY( s1.stop_possible() ); + VERIFY( s1.stop_requested() ); + VERIFY( !s2.stop_possible() ); + VERIFY( !s2.stop_requested() ); +} + int main() { test01(); test02(); + test03(); } diff --git a/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc b/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc index f0772c1d80f..6dc850c6441 100644 --- a/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc +++ b/libstdc++-v3/testsuite/30_threads/stop_token/stop_token.cc @@ -21,7 +21,76 @@ #include <stop_token> #include <testsuite_hooks.h> -int main() +void +test01() +{ + std::stop_source ssrc; + std::stop_token tok = ssrc.get_token(); + VERIFY(tok.stop_possible()); + VERIFY(!tok.stop_requested()); + + std::stop_token copy(tok); + VERIFY(copy == tok); + VERIFY(copy.stop_possible()); + VERIFY(!copy.stop_requested()); + VERIFY(tok.stop_possible()); + VERIFY(!tok.stop_requested()); + + std::stop_token move(std::move(tok)); + VERIFY(move != tok); + VERIFY(move == copy); + VERIFY(move.stop_possible()); + VERIFY(!move.stop_requested()); + VERIFY(!tok.stop_possible()); + VERIFY(!tok.stop_requested()); + VERIFY(copy.stop_possible()); + VERIFY(!copy.stop_requested()); + + ssrc.request_stop(); + VERIFY(move.stop_possible()); + VERIFY(move.stop_requested()); + VERIFY(!tok.stop_possible()); + VERIFY(!tok.stop_requested()); + VERIFY(copy.stop_possible()); + VERIFY(copy.stop_requested()); + + tok.swap(move); + VERIFY(tok == copy); + VERIFY(!move.stop_possible()); + VERIFY(!move.stop_requested()); + VERIFY(tok.stop_possible()); + VERIFY(tok.stop_requested()); + VERIFY(copy.stop_possible()); + VERIFY(copy.stop_requested()); + + swap(move, copy); + VERIFY(tok == move); + VERIFY(move.stop_possible()); + VERIFY(move.stop_requested()); + VERIFY(tok.stop_possible()); + VERIFY(tok.stop_requested()); + VERIFY(!copy.stop_possible()); + VERIFY(!copy.stop_requested()); +} + +void +test02() +{ + std::stop_source src1, src2; + std::stop_token tok = src1.get_token(); + VERIFY(tok.stop_possible()); + VERIFY(!tok.stop_requested()); + + std::stop_token copy = src2.get_token(); + VERIFY(copy != tok); + copy = tok; + VERIFY(copy == tok); + copy = src2.get_token(); + VERIFY(copy != tok); +} + +void +test03() { // create stop_source std::stop_source ssrc; @@ -77,8 +146,6 @@ int main() b = ssrc.request_stop(); VERIFY(!b); - // TODO verify the standard requires this -#if 0 // register another callback bool cb3called{false}; std::stop_callback scb3{stok, [&] @@ -92,5 +159,11 @@ int main() VERIFY(!cb1called); VERIFY(cb2called); VERIFY(cb3called); -#endif +} + +int main() +{ + test01(); + test02(); + test03(); } ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] Add support for C++2a stop_token 2019-11-15 23:44 ` Jonathan Wakely @ 2019-11-18 12:28 ` Jonathan Wakely 0 siblings, 0 replies; 10+ messages in thread From: Jonathan Wakely @ 2019-11-18 12:28 UTC (permalink / raw) To: Thomas Rodgers; +Cc: gcc-patches, libstdc++ [-- Attachment #1: Type: text/plain, Size: 2122 bytes --] On 15/11/19 23:43 +0000, Jonathan Wakely wrote: >On 15/11/19 14:40 +0000, Jonathan Wakely wrote: >>On 15/11/19 14:38 +0000, Jonathan Wakely wrote: >>>On 13/11/19 17:59 -0800, Thomas Rodgers wrote: >>>>+ // TODO verify the standard requires this >>>>+#if 0 >>>>+ // register another callback >>>>+ bool cb3called{false}; >>>>+ std::stop_callback scb3{stok, [&] >>>>+ { >>>>+ cb3called = true; >>>>+ }}; >>>>+ VERIFY(ssrc.stop_possible()); >>>>+ VERIFY(ssrc.stop_requested()); >>>>+ VERIFY(stok.stop_possible()); >>>>+ VERIFY(stok.stop_requested()); >>>>+ VERIFY(!cb1called); >>>>+ VERIFY(cb2called); >>>>+ VERIFY(cb3called); >>>>+#endif >>> >>>The working draft definitely requires this: >>> >>>Effects: Initializes callback with std::forward<C>(cb). If st.stop_requested() is true, then >>>std::forward<Callback>(callback)() is evaluated in the current thread before the constructor >>>returns. >> >>I've committed this fix to the nostopstate initializer, so it defines >>a variable, instead of declaring a function. I've also added a test >>that uses the stop_source(nostopstate_t) constructor, and a few other >>tweaks to include the new header where it's needed. > >This fixes some other issues I noticed, and should fix the failues for >the single-threaded multilib on AIX. And a couple more bugs fixed by this patch. The std::jthread::get_id() function was missing a return statement. The is_invocable check needs to be done using decayed types, as they'll be forwarded to std::invoke as rvalues. Also reduce header dependencies for the <thread> header. We don't need to include <functional> for std::jthread because <bits/invoke.h> is already included, which defines std::__invoke. We can also remove <bits/functexcept.h> which isn't used at all. Finally, when _GLIBCXX_HAS_GTHREADS is not defined there's no point including any other headers, since we're not going to define anything in <thread> anyway. Tested powerpc64le-linux, committed to trunk. [-- Attachment #2: patch.txt --] [-- Type: text/x-patch, Size: 12582 bytes --] commit f0c4ca8e20d9aadd8891bec246aecfc54177163c Author: Jonathan Wakely <jwakely@redhat.com> Date: Mon Nov 18 11:45:44 2019 +0000 libstdc++: Fix std::jthread bugs The std::jthread::get_id() function was missing a return statement. The is_invocable check needs to be done using decayed types, as they'll be forwarded to std::invoke as rvalues. * include/std/thread: Reduce header dependencies. (jthread::get_id()): Add missing return. (jthread::get_stop_token()): Avoid unnecessary stop_source temporary. (jthread::_S_create): Check is_invocable using decayed types. Add static assertion. * testsuite/30_threads/jthread/1.cc: Add dg-require-gthreads. * testsuite/30_threads/jthread/2.cc: Likewise. * testsuite/30_threads/jthread/3.cc: New test. * testsuite/30_threads/jthread/jthread.cc: Add missing directives for pthread and gthread support. Use VERIFY instead of assert. diff --git a/libstdc++-v3/include/std/thread b/libstdc++-v3/include/std/thread index 010921b2160..825ce4096fc 100644 --- a/libstdc++-v3/include/std/thread +++ b/libstdc++-v3/include/std/thread @@ -35,23 +35,26 @@ # include <bits/c++0x_warning.h> #else -#include <chrono> -#include <memory> -#include <tuple> -#include <cerrno> - -#if __cplusplus > 201703L -#define __cpp_lib_jthread 201907L -#include <functional> -#include <stop_token> -#endif - -#include <bits/functexcept.h> -#include <bits/functional_hash.h> -#include <bits/invoke.h> -#include <bits/gthr.h> +#include <bits/c++config.h> #if defined(_GLIBCXX_HAS_GTHREADS) +#include <bits/gthr.h> + +#include <chrono> // std::chrono::* +#include <memory> // std::unique_ptr +#include <tuple> // std::tuple + +#if __cplusplus > 201703L +# include <stop_token> // std::stop_source, std::stop_token, std::nostopstate +#endif + +#ifdef _GLIBCXX_USE_NANOSLEEP +# include <cerrno> // errno, EINTR +# include <time.h> // nanosleep +#endif + +#include <bits/functional_hash.h> // std::hash +#include <bits/invoke.h> // std::__invoke namespace std _GLIBCXX_VISIBILITY(default) { @@ -483,7 +486,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION [[nodiscard]] id get_id() const noexcept { - _M_thread.get_id(); + return _M_thread.get_id(); } [[nodiscard]] native_handle_type @@ -512,7 +515,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION bool request_stop() noexcept { - return get_stop_source().request_stop(); + return _M_stop_source.request_stop(); } friend void swap(jthread& __lhs, jthread& __rhs) noexcept @@ -525,12 +528,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION static thread _S_create(stop_source& __ssrc, _Callable&& __f, _Args&&... __args) { - if constexpr(is_invocable_v<_Callable, stop_token, _Args...>) + if constexpr(is_invocable_v<decay_t<_Callable>, stop_token, + decay_t<_Args>...>) return thread{std::forward<_Callable>(__f), __ssrc.get_token(), std::forward<_Args>(__args)...}; else - return thread{std::forward<_Callable>(__f), - std::forward<_Args>(__args)...}; + { + static_assert(is_invocable_v<decay_t<_Callable>, + decay_t<_Args>...>, + "std::thread arguments must be invocable after" + " conversion to rvalues"); + return thread{std::forward<_Callable>(__f), + std::forward<_Args>(__args)...}; + } } stop_source _M_stop_source; @@ -539,9 +549,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #endif // __cpp_lib_jthread _GLIBCXX_END_NAMESPACE_VERSION } // namespace - #endif // _GLIBCXX_HAS_GTHREADS - #endif // C++11 - #endif // _GLIBCXX_THREAD diff --git a/libstdc++-v3/testsuite/30_threads/jthread/1.cc b/libstdc++-v3/testsuite/30_threads/jthread/1.cc index 1fb5650dbc6..574c1f25dc3 100644 --- a/libstdc++-v3/testsuite/30_threads/jthread/1.cc +++ b/libstdc++-v3/testsuite/30_threads/jthread/1.cc @@ -17,6 +17,7 @@ // { dg-options "-std=gnu++2a" } // { dg-do compile { target c++2a } } +// { dg-require-gthreads "" } #include <thread> diff --git a/libstdc++-v3/testsuite/30_threads/jthread/2.cc b/libstdc++-v3/testsuite/30_threads/jthread/2.cc index 621965c8910..52413c8d2a3 100644 --- a/libstdc++-v3/testsuite/30_threads/jthread/2.cc +++ b/libstdc++-v3/testsuite/30_threads/jthread/2.cc @@ -17,6 +17,7 @@ // { dg-options "-std=gnu++2a" } // { dg-do compile { target c++2a } } +// { dg-require-gthreads "" } #include <version> diff --git a/libstdc++-v3/testsuite/30_threads/jthread/3.cc b/libstdc++-v3/testsuite/30_threads/jthread/3.cc new file mode 100644 index 00000000000..e4a2fbb3620 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/jthread/3.cc @@ -0,0 +1,45 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a -pthread" } +// { dg-do compile { target c++2a } } +// { dg-require-effective-target pthread } +// { dg-require-gthreads "" } + +#include <thread> + +struct RvalCallable +{ + void operator()() && { } +}; + +void test01() +{ + RvalCallable r; + std::jthread t(r); +} + +struct RvalCallableWithToken +{ + void operator()(std::stop_token) && { } +}; + +void test02() +{ + RvalCallableWithToken r; + std::jthread t(r); +} diff --git a/libstdc++-v3/testsuite/30_threads/jthread/jthread.cc b/libstdc++-v3/testsuite/30_threads/jthread/jthread.cc index c29db212167..aeb9f1f1b7f 100644 --- a/libstdc++-v3/testsuite/30_threads/jthread/jthread.cc +++ b/libstdc++-v3/testsuite/30_threads/jthread/jthread.cc @@ -15,15 +15,17 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. -// { dg-options "-std=gnu++2a" } -// { dg-do compile { target c++2a } } +// { dg-options "-std=gnu++2a -pthread" } +// { dg-do run { target c++2a } } +// { dg-require-effective-target pthread } +// { dg-require-gthreads "" } #include <thread> #include <chrono> -#include <cassert> #include <atomic> +#include <testsuite_hooks.h> -using namespace::std::literals; +using namespace std::literals; //------------------------------------------------------ @@ -31,9 +33,9 @@ void test_no_stop_token() { // test the basic jthread API (not taking stop_token arg) - assert(std::jthread::hardware_concurrency() == std::thread::hardware_concurrency()); + VERIFY(std::jthread::hardware_concurrency() == std::thread::hardware_concurrency()); std::stop_token stoken; - assert(!stoken.stop_possible()); + VERIFY(!stoken.stop_possible()); { std::jthread::id t1ID{std::this_thread::get_id()}; std::atomic<bool> t1AllSet{false}; @@ -47,12 +49,12 @@ void test_no_stop_token() for (int i=0; !t1AllSet.load(); ++i) { std::this_thread::sleep_for(10ms); } - assert(t1.joinable()); - assert(t1ID == t1.get_id()); + VERIFY(t1.joinable()); + VERIFY(t1ID == t1.get_id()); stoken = t1.get_stop_token(); - assert(!stoken.stop_requested()); - } - assert(stoken.stop_requested()); + VERIFY(!stoken.stop_requested()); + } + VERIFY(stoken.stop_requested()); } //------------------------------------------------------ @@ -63,8 +65,8 @@ void test_stop_token() std::stop_source ssource; std::stop_source origsource; - assert(ssource.stop_possible()); - assert(!ssource.stop_requested()); + VERIFY(ssource.stop_possible()); + VERIFY(!ssource.stop_requested()); { std::jthread::id t1ID{std::this_thread::get_id()}; std::atomic<bool> t1AllSet{false}; @@ -83,26 +85,26 @@ void test_stop_token() std::this_thread::sleep_for(10ms); } // and check all values: - assert(t1.joinable()); - assert(t1ID == t1.get_id()); + VERIFY(t1.joinable()); + VERIFY(t1ID == t1.get_id()); std::this_thread::sleep_for(470ms); origsource = std::move(ssource); ssource = t1.get_stop_source(); - assert(!ssource.stop_requested()); + VERIFY(!ssource.stop_requested()); auto ret = ssource.request_stop(); - assert(ret); + VERIFY(ret); ret = ssource.request_stop(); - assert(!ret); - assert(ssource.stop_requested()); - assert(!t1done.load()); - assert(!origsource.stop_requested()); + VERIFY(!ret); + VERIFY(ssource.stop_requested()); + VERIFY(!t1done.load()); + VERIFY(!origsource.stop_requested()); std::this_thread::sleep_for(470ms); origsource.request_stop(); - } - assert(origsource.stop_requested()); - assert(ssource.stop_requested()); + } + VERIFY(origsource.stop_requested()); + VERIFY(ssource.stop_requested()); } //------------------------------------------------------ @@ -110,7 +112,7 @@ void test_stop_token() void test_join() { std::stop_source ssource; - assert(ssource.stop_possible()); + VERIFY(ssource.stop_possible()); { std::jthread t1([](std::stop_token stoken) { for (int i=0; !stoken.stop_requested(); ++i) { @@ -126,10 +128,10 @@ void test_join() }); // wait for all thread to finish: t2.join(); - assert(!t2.joinable()); - assert(t1.joinable()); + VERIFY(!t2.joinable()); + VERIFY(t1.joinable()); t1.join(); - assert(!t1.joinable()); + VERIFY(!t1.joinable()); } } @@ -138,7 +140,7 @@ void test_join() void test_detach() { std::stop_source ssource; - assert(ssource.stop_possible()); + VERIFY(ssource.stop_possible()); std::atomic<bool> t1FinallyInterrupted{false}; { std::jthread t0; @@ -152,8 +154,8 @@ void test_detach() t1ID = std::this_thread::get_id(); t1InterruptToken = stoken; t1IsInterrupted = stoken.stop_requested(); - assert(stoken.stop_possible()); - assert(!stoken.stop_requested()); + VERIFY(stoken.stop_possible()); + VERIFY(!stoken.stop_requested()); t1AllSet.store(true); for (int i=0; !stoken.stop_requested(); ++i) { std::this_thread::sleep_for(100ms); @@ -163,31 +165,31 @@ void test_detach() for (int i=0; !t1AllSet.load(); ++i) { std::this_thread::sleep_for(10ms); } - assert(!t0.joinable()); - assert(t1.joinable()); - assert(t1ID == t1.get_id()); - assert(t1IsInterrupted == false); - assert(t1InterruptToken == t1.get_stop_source().get_token()); + VERIFY(!t0.joinable()); + VERIFY(t1.joinable()); + VERIFY(t1ID == t1.get_id()); + VERIFY(t1IsInterrupted == false); + VERIFY(t1InterruptToken == t1.get_stop_source().get_token()); ssource = t1.get_stop_source(); - assert(t1InterruptToken.stop_possible()); - assert(!t1InterruptToken.stop_requested()); + VERIFY(t1InterruptToken.stop_possible()); + VERIFY(!t1InterruptToken.stop_requested()); t1.detach(); - assert(!t1.joinable()); + VERIFY(!t1.joinable()); } - assert(!t1FinallyInterrupted.load()); + VERIFY(!t1FinallyInterrupted.load()); ssource.request_stop(); - assert(ssource.stop_requested()); + VERIFY(ssource.stop_requested()); for (int i=0; !t1FinallyInterrupted.load() && i < 100; ++i) { std::this_thread::sleep_for(100ms); } - assert(t1FinallyInterrupted.load()); + VERIFY(t1FinallyInterrupted.load()); } int main() { std::set_terminate([](){ - assert(false); + VERIFY(false); }); test_no_stop_token(); @@ -195,4 +197,3 @@ int main() test_join(); test_detach(); } - ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2019-11-18 12:28 UTC | newest] Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2019-10-23 0:59 [PATCH] Add support for C++2a stop_token Thomas Rodgers 2019-10-23 19:50 ` Thomas Rodgers 2019-10-24 15:45 ` Jonathan Wakely 2019-11-14 2:00 ` Thomas Rodgers 2019-11-14 16:12 ` Jonathan Wakely 2019-11-15 3:13 ` Thomas Rodgers 2019-11-15 14:38 ` Jonathan Wakely 2019-11-15 14:41 ` Jonathan Wakely 2019-11-15 23:44 ` Jonathan Wakely 2019-11-18 12:28 ` Jonathan Wakely
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).