Commit e73ca078 by Jonathan Wakely Committed by Jonathan Wakely

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.

From-SVN: r278325
parent d5fbe5e0
2019-11-15 Jonathan Wakely <jwakely@redhat.com>
* 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.
2019-11-15 Edward Smith-Rowland <3dw4rd@verizon.net> 2019-11-15 Edward Smith-Rowland <3dw4rd@verizon.net>
Implement the default_searcher part of C++20 p1032 Misc constexpr bits. Implement the default_searcher part of C++20 p1032 Misc constexpr bits.
......
...@@ -31,24 +31,25 @@ ...@@ -31,24 +31,25 @@
#if __cplusplus > 201703L #if __cplusplus > 201703L
#include <type_traits>
#include <memory>
#include <mutex>
#include <atomic> #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) namespace std _GLIBCXX_VISIBILITY(default)
{ {
_GLIBCXX_BEGIN_NAMESPACE_VERSION _GLIBCXX_BEGIN_NAMESPACE_VERSION
class stop_source; /// Tag type indicating a stop_source should have no shared-stop-state.
template<typename _Callback>
class stop_callback;
struct nostopstate_t { explicit nostopstate_t() = default; }; struct nostopstate_t { explicit nostopstate_t() = default; };
inline constexpr nostopstate_t nostopstate{}; inline constexpr nostopstate_t nostopstate{};
/// Allow testing whether a stop request has been made on a `stop_source`.
class stop_token class stop_token
{ {
public: public:
...@@ -63,7 +64,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ...@@ -63,7 +64,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
operator=(const stop_token& __rhs) noexcept = default; operator=(const stop_token& __rhs) noexcept = default;
stop_token& stop_token&
operator=(stop_token&& __rhs) noexcept; operator=(stop_token&& __rhs) noexcept = default;
[[nodiscard]] [[nodiscard]]
bool bool
...@@ -79,6 +80,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ...@@ -79,6 +80,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
return stop_possible() && _M_state->_M_stop_requested(); return stop_possible() && _M_state->_M_stop_requested();
} }
void
swap(stop_token& __rhs) noexcept
{ _M_state.swap(__rhs._M_state); }
[[nodiscard]] [[nodiscard]]
friend bool friend bool
operator==(const stop_token& __a, const stop_token& __b) operator==(const stop_token& __a, const stop_token& __b)
...@@ -90,26 +95,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ...@@ -90,26 +95,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend bool friend bool
operator!=(const stop_token& __a, const stop_token& __b) operator!=(const stop_token& __a, const stop_token& __b)
{ {
return __a._M_state == __b._M_state; return __a._M_state != __b._M_state;
} }
friend void
swap(stop_token& __lhs, stop_token& __rhs) noexcept
{ __lhs.swap(__rhs); }
private: private:
friend stop_source; friend class stop_source;
template<typename _Callback> template<typename _Callback>
friend class stop_callback; friend class stop_callback;
struct _Stop_cb { struct _Stop_cb
{
void(*_M_callback)(_Stop_cb*); void(*_M_callback)(_Stop_cb*);
_Stop_cb* _M_prev = nullptr; _Stop_cb* _M_prev = nullptr;
_Stop_cb* _M_next = nullptr; _Stop_cb* _M_next = nullptr;
template<typename _Cb> template<typename _Cb>
_Stop_cb(_Cb&& __cb) _Stop_cb(_Cb&& __cb)
: _M_callback(std::move(__cb)) : _M_callback(std::forward<_Cb>(__cb))
{ } { }
bool bool
_M_linked() const _M_linked() const noexcept
{ {
return (_M_prev != nullptr) return (_M_prev != nullptr)
|| (_M_next != nullptr); || (_M_next != nullptr);
...@@ -123,17 +133,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ...@@ -123,17 +133,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
} }
}; };
struct _Stop_state_t { struct _Stop_state_t
std::atomic<bool> _M_stopped; {
std::mutex _M_mtx; std::atomic<bool> _M_stopped{false};
_Stop_cb* _M_head = nullptr; _Stop_cb* _M_head = nullptr;
#ifdef _GLIBCXX_HAS_GTHREADS
std::mutex _M_mtx;
#endif
_Stop_state_t() _Stop_state_t() = default;
: _M_stopped{false}
{ }
bool bool
_M_stop_requested() _M_stop_requested() noexcept
{ {
return _M_stopped; return _M_stopped;
} }
...@@ -144,7 +155,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ...@@ -144,7 +155,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
bool __stopped = false; bool __stopped = false;
if (_M_stopped.compare_exchange_strong(__stopped, true)) 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) while (_M_head)
{ {
auto __p = _M_head; auto __p = _M_head;
...@@ -159,7 +172,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ...@@ -159,7 +172,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
bool bool
_M_register_callback(_Stop_cb* __cb) _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) if (_M_stopped)
return false; return false;
...@@ -175,7 +190,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ...@@ -175,7 +190,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
void void
_M_remove_callback(_Stop_cb* __cb) _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) if (__cb == _M_head)
{ {
_M_head = _M_head->_M_next; _M_head = _M_head->_M_next;
...@@ -202,18 +219,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ...@@ -202,18 +219,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
using _Stop_state = std::shared_ptr<_Stop_state_t>; using _Stop_state = std::shared_ptr<_Stop_state_t>;
_Stop_state _M_state; _Stop_state _M_state;
explicit stop_token(_Stop_state __state) explicit
: _M_state{std::move(__state)} stop_token(const _Stop_state& __state) noexcept
: _M_state{__state}
{ } { }
}; };
class stop_source { /// A type that allows a stop request to be made.
using _Stop_state_t = stop_token::_Stop_state_t; class stop_source
using _Stop_state = stop_token::_Stop_state; {
public: public:
stop_source() 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 explicit stop_source(std::nostopstate_t) noexcept
...@@ -274,7 +291,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ...@@ -274,7 +291,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
void void
swap(stop_source& __other) noexcept swap(stop_source& __other) noexcept
{ {
std::swap(_M_state, __other._M_state); _M_state.swap(__other._M_state);
} }
[[nodiscard]] [[nodiscard]]
...@@ -291,49 +308,53 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ...@@ -291,49 +308,53 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
return __a._M_state != __b._M_state; return __a._M_state != __b._M_state;
} }
friend void
swap(stop_source& __lhs, stop_source& __rhs) noexcept
{
__lhs.swap(__rhs);
}
private: 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> template<typename _Callback>
class [[nodiscard]] stop_callback class [[nodiscard]] stop_callback
: private stop_token::_Stop_cb : private stop_token::_Stop_cb
{ {
using _Stop_cb = stop_token::_Stop_cb;
using _Stop_state = stop_token::_Stop_state;
public: public:
using callback_type = _Callback; using callback_type = _Callback;
template<typename _Cb, template<typename _Cb,
std::enable_if_t<std::is_constructible_v<_Callback, _Cb>, int> = 0> enable_if_t<is_constructible_v<_Callback, _Cb>, int> = 0>
explicit stop_callback(const stop_token& __token, _Cb&& __cb) explicit
noexcept(std::is_nothrow_constructible_v<_Callback, _Cb>) stop_callback(const stop_token& __token, _Cb&& __cb)
: _Stop_cb([](_Stop_cb* __that) noexcept noexcept(is_nothrow_constructible_v<_Callback, _Cb>)
{ : _Stop_cb(&_S_execute), _M_cb(std::forward<_Cb>(__cb))
static_cast<stop_callback*>(__that)->_M_execute();
}),
_M_cb(std::move(__cb))
{ {
auto res = __token._M_state->_M_register_callback(this); if (auto __state = __token._M_state)
if (__token._M_state && res)
{ {
_M_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, template<typename _Cb,
std::enable_if_t<std::is_constructible_v<_Callback, _Cb>, int> = 0> enable_if_t<is_constructible_v<_Callback, _Cb>, int> = 0>
explicit stop_callback(stop_token&& __token, _Cb&& __cb) explicit
noexcept(std::is_nothrow_constructible_v<_Callback, _Cb>) stop_callback(stop_token&& __token, _Cb&& __cb)
: _Stop_cb([](_Stop_cb* __that) noexcept noexcept(is_nothrow_constructible_v<_Callback, _Cb>)
{ : _Stop_cb(&_S_execute), _M_cb(std::forward<_Cb>(__cb))
static_cast<stop_callback*>(__that)->_M_execute();
}),
_M_cb(std::move(__cb))
{ {
if (__token._M_state && __token._M_state->_M_register_callback(this)) if (auto& __state = __token._M_state)
{ {
std::swap(_M_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);
} }
} }
...@@ -352,12 +373,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ...@@ -352,12 +373,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
private: private:
_Callback _M_cb; _Callback _M_cb;
_Stop_state _M_state = nullptr; stop_token::_Stop_state _M_state = nullptr;
void static void
_M_execute() noexcept _S_execute(_Stop_cb* __that) noexcept
{ {
_M_cb(); static_cast<stop_callback*>(__that)->_M_cb();
} }
}; };
...@@ -366,5 +387,5 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ...@@ -366,5 +387,5 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
_GLIBCXX_END_NAMESPACE_VERSION _GLIBCXX_END_NAMESPACE_VERSION
} // namespace } // namespace
#endif // __cplusplus >= 201703L #endif // __cplusplus > 201703L
#endif // _GLIBCXX_STOP_TOKEN #endif // _GLIBCXX_STOP_TOKEN
...@@ -184,10 +184,12 @@ ...@@ -184,10 +184,12 @@
#define __cpp_lib_constexpr_invoke 201907L #define __cpp_lib_constexpr_invoke 201907L
#define __cpp_lib_erase_if 201900L #define __cpp_lib_erase_if 201900L
#define __cpp_lib_interpolate 201902L #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_list_remove_return_type 201806L
#define __cpp_lib_math_constants 201907L #define __cpp_lib_math_constants 201907L
#define __cpp_lib_span 201902L #define __cpp_lib_span 201902L
#define __cpp_lib_jthread 201907L
#if __cpp_impl_three_way_comparison >= 201907L #if __cpp_impl_three_way_comparison >= 201907L
# define __cpp_lib_three_way_comparison 201711L # define __cpp_lib_three_way_comparison 201711L
#endif #endif
......
// 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();
}
...@@ -68,8 +68,27 @@ test02() ...@@ -68,8 +68,27 @@ test02()
VERIFY( !ssrc.stop_requested() ); 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() int main()
{ {
test01(); test01();
test02(); test02();
test03();
} }
...@@ -21,7 +21,76 @@ ...@@ -21,7 +21,76 @@
#include <stop_token> #include <stop_token>
#include <testsuite_hooks.h> #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 // create stop_source
std::stop_source ssrc; std::stop_source ssrc;
...@@ -77,8 +146,6 @@ int main() ...@@ -77,8 +146,6 @@ int main()
b = ssrc.request_stop(); b = ssrc.request_stop();
VERIFY(!b); VERIFY(!b);
// TODO verify the standard requires this
#if 0
// register another callback // register another callback
bool cb3called{false}; bool cb3called{false};
std::stop_callback scb3{stok, [&] std::stop_callback scb3{stok, [&]
...@@ -92,5 +159,11 @@ int main() ...@@ -92,5 +159,11 @@ int main()
VERIFY(!cb1called); VERIFY(!cb1called);
VERIFY(cb2called); VERIFY(cb2called);
VERIFY(cb3called); VERIFY(cb3called);
#endif }
int main()
{
test01();
test02();
test03();
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment