| // <experimental/executor> -*- C++ -*- | |
| // Copyright (C) 2015-2025 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 experimental/executor | |
| * This is a TS C++ Library header. | |
| * @ingroup networking-ts | |
| */ | |
| #ifndef _GLIBCXX_EXPERIMENTAL_EXECUTOR | |
| #define _GLIBCXX_EXPERIMENTAL_EXECUTOR 1 | |
| #ifdef _GLIBCXX_SYSHDR | |
| #pragma GCC system_header | |
| #endif | |
| #include <bits/requires_hosted.h> // experimental is currently omitted | |
| #if __cplusplus >= 201402L | |
| #include <algorithm> | |
| #include <condition_variable> | |
| #include <functional> | |
| #include <future> | |
| #include <list> | |
| #include <queue> | |
| #include <thread> | |
| #include <tuple> | |
| #include <unordered_map> | |
| #include <experimental/netfwd> | |
| #include <bits/unique_ptr.h> | |
| #include <experimental/bits/net.h> | |
| namespace std _GLIBCXX_VISIBILITY(default) | |
| { | |
| _GLIBCXX_BEGIN_NAMESPACE_VERSION | |
| namespace experimental | |
| { | |
| namespace net | |
| { | |
| inline namespace v1 | |
| { | |
| /** @addtogroup networking-ts | |
| * @{ | |
| */ | |
| /// Customization point for asynchronous operations. | |
| template<typename _CompletionToken, typename _Signature, typename = void> | |
| class async_result; | |
| /// Convenience utility to help implement asynchronous operations. | |
| template<typename _CompletionToken, typename _Signature> | |
| class async_completion; | |
| template<typename _Tp, typename _ProtoAlloc, typename = __void_t<>> | |
| struct __associated_allocator_impl | |
| { | |
| using type = _ProtoAlloc; | |
| static type | |
| _S_get(const _Tp&, const _ProtoAlloc& __a) noexcept { return __a; } | |
| }; | |
| template<typename _Tp, typename _ProtoAlloc> | |
| struct __associated_allocator_impl<_Tp, _ProtoAlloc, | |
| __void_t<typename _Tp::allocator_type>> | |
| { | |
| using type = typename _Tp::allocator_type; | |
| static type | |
| _S_get(const _Tp& __t, const _ProtoAlloc&) noexcept | |
| { return __t.get_allocator(); } | |
| }; | |
| /// Helper to associate an allocator with a type. | |
| template<typename _Tp, typename _ProtoAllocator = allocator<void>> | |
| struct associated_allocator | |
| : __associated_allocator_impl<_Tp, _ProtoAllocator> | |
| { | |
| static auto | |
| get(const _Tp& __t, | |
| const _ProtoAllocator& __a = _ProtoAllocator()) noexcept | |
| { | |
| using _Impl = __associated_allocator_impl<_Tp, _ProtoAllocator>; | |
| return _Impl::_S_get(__t, __a); | |
| } | |
| }; | |
| /// Alias template for associated_allocator. | |
| template<typename _Tp, typename _ProtoAllocator = allocator<void>> | |
| using associated_allocator_t | |
| = typename associated_allocator<_Tp, _ProtoAllocator>::type; | |
| // get_associated_allocator: | |
| template<typename _Tp> | |
| inline associated_allocator_t<_Tp> | |
| get_associated_allocator(const _Tp& __t) noexcept | |
| { return associated_allocator<_Tp>::get(__t); } | |
| template<typename _Tp, typename _ProtoAllocator> | |
| inline associated_allocator_t<_Tp, _ProtoAllocator> | |
| get_associated_allocator(const _Tp& __t, | |
| const _ProtoAllocator& __a) noexcept | |
| { return associated_allocator<_Tp, _ProtoAllocator>::get(__t, __a); } | |
| enum class fork_event { prepare, parent, child }; | |
| /// An extensible, type-safe, polymorphic set of services. | |
| class execution_context; | |
| class service_already_exists : public logic_error | |
| { | |
| public: | |
| // _GLIBCXX_RESOLVE_LIB_DEFECTS | |
| // 3414. service_already_exists has no usable constructors | |
| service_already_exists() : logic_error("service already exists") { } | |
| }; | |
| template<typename _Tp> struct is_executor; | |
| struct executor_arg_t { }; | |
| constexpr executor_arg_t executor_arg = executor_arg_t(); | |
| /// Trait for determining whether to construct an object with an executor. | |
| template<typename _Tp, typename _Executor> struct uses_executor; | |
| template<typename _Tp, typename _Executor, typename = __void_t<>> | |
| struct __associated_executor_impl | |
| { | |
| using type = _Executor; | |
| static type | |
| _S_get(const _Tp&, const _Executor& __e) noexcept { return __e; } | |
| }; | |
| template<typename _Tp, typename _Executor> | |
| struct __associated_executor_impl<_Tp, _Executor, | |
| __void_t<typename _Tp::executor_type>> | |
| { | |
| using type = typename _Tp::executor_type; | |
| static type | |
| _S_get(const _Tp& __t, const _Executor&) noexcept | |
| { return __t.get_executor(); } | |
| }; | |
| /// Helper to associate an executor with a type. | |
| template<typename _Tp, typename _Executor = system_executor> | |
| struct associated_executor | |
| : __associated_executor_impl<_Tp, _Executor> | |
| { | |
| static auto | |
| get(const _Tp& __t, const _Executor& __e = _Executor()) noexcept | |
| { return __associated_executor_impl<_Tp, _Executor>::_S_get(__t, __e); } | |
| }; | |
| template<typename _Tp, typename _Executor = system_executor> | |
| using associated_executor_t | |
| = typename associated_executor<_Tp, _Executor>::type; | |
| template<typename _ExecutionContext> | |
| using __is_exec_context | |
| = is_convertible<_ExecutionContext&, execution_context&>; | |
| template<typename _Tp> | |
| using __executor_t = typename _Tp::executor_type; | |
| // get_associated_executor: | |
| template<typename _Tp> | |
| inline associated_executor_t<_Tp> | |
| get_associated_executor(const _Tp& __t) noexcept | |
| { return associated_executor<_Tp>::get(__t); } | |
| template<typename _Tp, typename _Executor> | |
| inline | |
| enable_if_t<is_executor<_Executor>::value, | |
| associated_executor_t<_Tp, _Executor>> | |
| get_associated_executor(const _Tp& __t, const _Executor& __ex) | |
| { return associated_executor<_Tp, _Executor>::get(__t, __ex); } | |
| template<typename _Tp, typename _ExecutionContext> | |
| inline | |
| enable_if_t<__is_exec_context<_ExecutionContext>::value, | |
| associated_executor_t<_Tp, __executor_t<_ExecutionContext>>> | |
| get_associated_executor(const _Tp& __t, _ExecutionContext& __ctx) noexcept | |
| { return net::get_associated_executor(__t, __ctx.get_executor()); } | |
| /// Helper to bind an executor to an object or function. | |
| template<typename _Tp, typename _Executor> | |
| class executor_binder; | |
| template<typename _Tp, typename _Executor, typename _Signature> | |
| class async_result<executor_binder<_Tp, _Executor>, _Signature>; | |
| template<typename _Tp, typename _Executor, typename _ProtoAllocator> | |
| struct associated_allocator<executor_binder<_Tp, _Executor>, | |
| _ProtoAllocator>; | |
| template<typename _Tp, typename _Executor, typename _Executor1> | |
| struct associated_executor<executor_binder<_Tp, _Executor>, _Executor1>; | |
| // bind_executor: | |
| template<typename _Executor, typename _Tp> | |
| inline | |
| enable_if_t<is_executor<_Executor>::value, | |
| executor_binder<decay_t<_Tp>, _Executor>> | |
| bind_executor(const _Executor& __ex, _Tp&& __t) | |
| { return { std::forward<_Tp>(__t), __ex }; } | |
| template<typename _ExecutionContext, typename _Tp> | |
| inline | |
| enable_if_t<__is_exec_context<_ExecutionContext>::value, | |
| executor_binder<decay_t<_Tp>, __executor_t<_ExecutionContext>>> | |
| bind_executor(_ExecutionContext& __ctx, _Tp&& __t) | |
| { return { __ctx.get_executor(), forward<_Tp>(__t) }; } | |
| /// A scope-guard type to record when work is started and finished. | |
| template<typename _Executor> | |
| class executor_work_guard; | |
| // make_work_guard: | |
| template<typename _Executor> | |
| inline | |
| enable_if_t<is_executor<_Executor>::value, executor_work_guard<_Executor>> | |
| make_work_guard(const _Executor& __ex) | |
| { return executor_work_guard<_Executor>(__ex); } | |
| template<typename _ExecutionContext> | |
| inline | |
| enable_if_t<__is_exec_context<_ExecutionContext>::value, | |
| executor_work_guard<__executor_t<_ExecutionContext>>> | |
| make_work_guard(_ExecutionContext& __ctx) | |
| { return net::make_work_guard(__ctx.get_executor()); } | |
| template<typename _Tp> | |
| inline | |
| enable_if_t<__not_<__or_<is_executor<_Tp>, __is_exec_context<_Tp>>>::value, | |
| executor_work_guard<associated_executor_t<_Tp>>> | |
| make_work_guard(const _Tp& __t) | |
| { return net::get_associated_executor(__t); } | |
| template<typename _Tp, typename _Up> | |
| auto | |
| make_work_guard(const _Tp& __t, _Up&& __u) | |
| -> decltype(net::make_work_guard( | |
| net::get_associated_executor(__t, forward<_Up>(__u)))) | |
| { | |
| return net::make_work_guard( | |
| net::get_associated_executor(__t, forward<_Up>(__u))); | |
| } | |
| /// Allows function objects to execute on any thread. | |
| class system_executor; | |
| /// The execution context associated with system_executor objects. | |
| class system_context; | |
| inline bool | |
| operator==(const system_executor&, const system_executor&) { return true; } | |
| inline bool | |
| operator!=(const system_executor&, const system_executor&) { return false; } | |
| /// Exception thrown by empty executors. | |
| class bad_executor; | |
| /// Polymorphic wrapper for types satisfying the Executor requirements. | |
| class executor; | |
| bool | |
| operator==(const executor&, const executor&) noexcept; | |
| bool | |
| operator==(const executor&, nullptr_t) noexcept; | |
| bool | |
| operator==(nullptr_t, const executor&) noexcept; | |
| bool | |
| operator!=(const executor&, const executor&) noexcept; | |
| bool | |
| operator!=(const executor&, nullptr_t) noexcept; | |
| bool | |
| operator!=(nullptr_t, const executor&) noexcept; | |
| void swap(executor&, executor&) noexcept; | |
| // dispatch: | |
| template<typename _CompletionToken> | |
| __deduced_t<_CompletionToken, void()> | |
| dispatch(_CompletionToken&& __token); | |
| template<typename _Executor, typename _CompletionToken> | |
| __deduced_t<_CompletionToken, void()> | |
| dispatch(const _Executor& __ex, _CompletionToken&& __token); | |
| template<typename _ExecutionContext, typename _CompletionToken> | |
| __deduced_t<_CompletionToken, void()> | |
| dispatch(_ExecutionContext& __ctx, _CompletionToken&& __token); | |
| // post: | |
| template<typename _CompletionToken> | |
| __deduced_t<_CompletionToken, void()> | |
| post(_CompletionToken&& __token); | |
| template<typename _Executor, typename _CompletionToken> | |
| enable_if_t<is_executor<_Executor>::value, | |
| __deduced_t<_CompletionToken, void()>> | |
| post(const _Executor& __ex, _CompletionToken&& __token); | |
| template<typename _ExecutionContext, typename _CompletionToken> | |
| enable_if_t<__is_exec_context<_ExecutionContext>::value, | |
| __deduced_t<_CompletionToken, void()>> | |
| post(_ExecutionContext& __ctx, _CompletionToken&& __token); | |
| // defer: | |
| template<typename _CompletionToken> | |
| __deduced_t<_CompletionToken, void()> | |
| defer(_CompletionToken&& __token); | |
| template<typename _Executor, typename _CompletionToken> | |
| __deduced_t<_CompletionToken, void()> | |
| defer(const _Executor& __ex, _CompletionToken&& __token); | |
| template<typename _ExecutionContext, typename _CompletionToken> | |
| __deduced_t<_CompletionToken, void()> | |
| defer(_ExecutionContext& __ctx, _CompletionToken&& __token); | |
| template<typename _Executor> | |
| class strand; | |
| template<typename _Executor> | |
| bool | |
| operator==(const strand<_Executor>& __a, const strand<_Executor>& __b); | |
| template<typename _Executor> | |
| bool | |
| operator!=(const strand<_Executor>& __a, const strand<_Executor>& __b) | |
| { return !(__a == __b); } | |
| template<typename _CompletionToken, typename _Signature, typename> | |
| class async_result | |
| { | |
| public: | |
| using completion_handler_type = _CompletionToken; | |
| using return_type = void; | |
| explicit async_result(completion_handler_type&) {} | |
| async_result(const async_result&) = delete; | |
| async_result& operator=(const async_result&) = delete; | |
| return_type get() {} | |
| }; | |
| template<typename _CompletionToken, typename _Signature> | |
| class async_completion | |
| { | |
| using __result_type | |
| = async_result<decay_t<_CompletionToken>, _Signature>; | |
| public: | |
| using completion_handler_type | |
| = typename __result_type::completion_handler_type; | |
| private: | |
| using __handler_type = __conditional_t< | |
| is_same<_CompletionToken, completion_handler_type>::value, | |
| completion_handler_type&, | |
| completion_handler_type>; | |
| public: | |
| explicit | |
| async_completion(_CompletionToken& __t) | |
| : completion_handler(std::forward<__handler_type>(__t)), | |
| result(completion_handler) | |
| { } | |
| async_completion(const async_completion&) = delete; | |
| async_completion& operator=(const async_completion&) = delete; | |
| __handler_type completion_handler; | |
| __result_type result; | |
| }; | |
| class execution_context | |
| { | |
| public: | |
| class service | |
| { | |
| protected: | |
| // construct / copy / destroy: | |
| explicit | |
| service(execution_context& __owner) : _M_context(__owner) { } | |
| service(const service&) = delete; | |
| service& operator=(const service&) = delete; | |
| virtual ~service() { } // TODO should not be inline | |
| // service observers: | |
| execution_context& context() const noexcept { return _M_context; } | |
| private: | |
| // service operations: | |
| virtual void shutdown() noexcept = 0; | |
| virtual void notify_fork(fork_event) { } | |
| friend class execution_context; | |
| execution_context& _M_context; | |
| }; | |
| // construct / copy / destroy: | |
| execution_context() { } | |
| execution_context(const execution_context&) = delete; | |
| execution_context& operator=(const execution_context&) = delete; | |
| virtual ~execution_context() | |
| { | |
| shutdown(); | |
| destroy(); | |
| } | |
| // execution context operations: | |
| void | |
| notify_fork(fork_event __e) | |
| { | |
| auto __l = [=](auto& __svc) { __svc._M_ptr->notify_fork(__e); }; | |
| if (__e == fork_event::prepare) | |
| std::for_each(_M_services.rbegin(), _M_services.rend(), __l); | |
| else | |
| std::for_each(_M_services.begin(), _M_services.end(), __l); | |
| } | |
| protected: | |
| // execution context protected operations: | |
| void | |
| shutdown() | |
| { | |
| std::for_each(_M_services.rbegin(), _M_services.rend(), | |
| [=](auto& __svc) { | |
| if (__svc._M_active) | |
| { | |
| __svc._M_ptr->shutdown(); | |
| __svc._M_active = false; | |
| } | |
| }); | |
| } | |
| void | |
| destroy() | |
| { | |
| while (_M_services.size()) | |
| _M_services.pop_back(); | |
| _M_keys.clear(); | |
| } | |
| protected: | |
| template<typename _Service> | |
| static void | |
| _S_deleter(service* __svc) { delete static_cast<_Service*>(__svc); } | |
| struct _ServicePtr | |
| { | |
| template<typename _Service> | |
| explicit | |
| _ServicePtr(_Service* __svc) | |
| : _M_ptr(__svc, &_S_deleter<_Service>), _M_active(true) { } | |
| std::unique_ptr<service, void(*)(service*)> _M_ptr; | |
| bool _M_active; | |
| }; | |
| #if defined(_GLIBCXX_HAS_GTHREADS) | |
| using mutex_type = std::mutex; | |
| #else | |
| struct mutex_type | |
| { | |
| void lock() const { } | |
| void unlock() const { } | |
| }; | |
| #endif | |
| mutable mutex_type _M_mutex; | |
| // Sorted in order of beginning of service object lifetime. | |
| std::list<_ServicePtr> _M_services; | |
| template<typename _Service, typename... _Args> | |
| service* | |
| _M_add_svc(_Args&&... __args) | |
| { | |
| _M_services.push_back( | |
| _ServicePtr{new _Service{*this, std::forward<_Args>(__args)...}} ); | |
| return _M_services.back()._M_ptr.get(); | |
| } | |
| using __key_type = void(*)(); | |
| template<typename _Key> | |
| static __key_type | |
| _S_key() { return reinterpret_cast<__key_type>(&_S_key<_Key>); } | |
| std::unordered_map<__key_type, service*> _M_keys; | |
| template<typename _Service> | |
| friend typename _Service::key_type& | |
| use_service(execution_context&); | |
| template<typename _Service, typename... _Args> | |
| friend _Service& | |
| make_service(execution_context&, _Args&&...); | |
| template<typename _Service> | |
| friend bool | |
| has_service(const execution_context&) noexcept; | |
| }; | |
| // service access: | |
| template<typename _Service> | |
| typename _Service::key_type& | |
| use_service(execution_context& __ctx) | |
| { | |
| using _Key = typename _Service::key_type; | |
| static_assert(is_base_of<execution_context::service, _Key>::value, | |
| "a service type must derive from execution_context::service"); | |
| static_assert(is_base_of<_Key, _Service>::value, | |
| "a service type must match or derive from its key_type"); | |
| auto __key = execution_context::_S_key<_Key>(); | |
| lock_guard<execution_context::mutex_type> __lock(__ctx._M_mutex); | |
| auto& __svc = __ctx._M_keys[__key]; | |
| if (__svc == nullptr) | |
| { | |
| __try { | |
| __svc = __ctx._M_add_svc<_Service>(); | |
| } __catch(...) { | |
| __ctx._M_keys.erase(__key); | |
| __throw_exception_again; | |
| } | |
| } | |
| return static_cast<_Key&>(*__svc); | |
| } | |
| template<typename _Service, typename... _Args> | |
| _Service& | |
| make_service(execution_context& __ctx, _Args&&... __args) | |
| { | |
| using _Key = typename _Service::key_type; | |
| static_assert(is_base_of<execution_context::service, _Key>::value, | |
| "a service type must derive from execution_context::service"); | |
| static_assert(is_base_of<_Key, _Service>::value, | |
| "a service type must match or derive from its key_type"); | |
| auto __key = execution_context::_S_key<_Key>(); | |
| lock_guard<execution_context::mutex_type> __lock(__ctx._M_mutex); | |
| auto& __svc = __ctx._M_keys[__key]; | |
| if (__svc != nullptr) | |
| throw service_already_exists(); | |
| __try { | |
| __svc = __ctx._M_add_svc<_Service>(std::forward<_Args>(__args)...); | |
| } __catch(...) { | |
| __ctx._M_keys.erase(__key); | |
| __throw_exception_again; | |
| } | |
| return static_cast<_Service&>(*__svc); | |
| } | |
| template<typename _Service> | |
| inline bool | |
| has_service(const execution_context& __ctx) noexcept | |
| { | |
| using _Key = typename _Service::key_type; | |
| static_assert(is_base_of<execution_context::service, _Key>::value, | |
| "a service type must derive from execution_context::service"); | |
| static_assert(is_base_of<_Key, _Service>::value, | |
| "a service type must match or derive from its key_type"); | |
| lock_guard<execution_context::mutex_type> __lock(__ctx._M_mutex); | |
| return __ctx._M_keys.count(execution_context::_S_key<_Key>()); | |
| } | |
| template<typename _Tp, typename = __void_t<>> | |
| struct __is_executor_impl : false_type | |
| { }; | |
| // Check Executor requirements. | |
| template<typename _Tp, typename _Up = remove_const_t<_Tp>> | |
| auto | |
| __executor_reqs(_Up* __x = 0, const _Up* __cx = 0, void(*__f)() = 0, | |
| const allocator<int>& __a = {}) | |
| -> enable_if_t<__is_value_constructible<_Tp>::value, __void_t< | |
| decltype(*__cx == *__cx), | |
| decltype(*__cx != *__cx), | |
| decltype(__x->context()), | |
| decltype(__x->on_work_started()), | |
| decltype(__x->on_work_finished()), | |
| decltype(__x->dispatch(std::move(__f), __a)), | |
| decltype(__x->post(std::move(__f), __a)), | |
| decltype(__x->defer(std::move(__f), __a)) | |
| >>; | |
| template<typename _Tp> | |
| struct __is_executor_impl<_Tp, decltype(__executor_reqs<_Tp>())> | |
| : true_type | |
| { }; | |
| template<typename _Tp> | |
| struct is_executor : __is_executor_impl<_Tp> | |
| { }; | |
| template<typename _Tp> | |
| constexpr bool is_executor_v = is_executor<_Tp>::value; | |
| template<typename _Tp, typename _Executor, typename = __void_t<>> | |
| struct __uses_executor_impl : false_type | |
| { }; | |
| template<typename _Tp, typename _Executor> | |
| struct __uses_executor_impl<_Tp, _Executor, | |
| __void_t<typename _Tp::executor_type>> | |
| : is_convertible<_Executor, typename _Tp::executor_type> | |
| { }; | |
| template<typename _Tp, typename _Executor> | |
| struct uses_executor : __uses_executor_impl<_Tp, _Executor>::type | |
| { }; | |
| template<typename _Tp, typename _Executor> | |
| constexpr bool uses_executor_v = uses_executor<_Tp, _Executor>::value; | |
| template<typename _Tp, typename _Executor> | |
| class executor_binder | |
| { | |
| struct __use_exec { }; | |
| public: | |
| // types: | |
| using target_type = _Tp; | |
| using executor_type = _Executor; | |
| // construct / copy / destroy: | |
| executor_binder(_Tp __t, const _Executor& __ex) | |
| : executor_binder(__use_exec{}, std::move(__t), __ex) | |
| { } | |
| executor_binder(const executor_binder&) = default; | |
| executor_binder(executor_binder&&) = default; | |
| template<typename _Up, typename _OtherExecutor> | |
| executor_binder(const executor_binder<_Up, _OtherExecutor>& __other) | |
| : executor_binder(__use_exec{}, __other.get(), __other.get_executor()) | |
| { } | |
| template<typename _Up, typename _OtherExecutor> | |
| executor_binder(executor_binder<_Up, _OtherExecutor>&& __other) | |
| : executor_binder(__use_exec{}, std::move(__other.get()), | |
| __other.get_executor()) | |
| { } | |
| template<typename _Up, typename _OtherExecutor> | |
| executor_binder(executor_arg_t, const _Executor& __ex, | |
| const executor_binder<_Up, _OtherExecutor>& __other) | |
| : executor_binder(__use_exec{}, __other.get(), __ex) | |
| { } | |
| template<typename _Up, typename _OtherExecutor> | |
| executor_binder(executor_arg_t, const _Executor& __ex, | |
| executor_binder<_Up, _OtherExecutor>&& __other) | |
| : executor_binder(__use_exec{}, std::move(__other.get()), __ex) | |
| { } | |
| ~executor_binder(); | |
| // executor binder access: | |
| _Tp& get() noexcept { return _M_target; } | |
| const _Tp& get() const noexcept { return _M_target; } | |
| executor_type get_executor() const noexcept { return _M_ex; } | |
| // executor binder invocation: | |
| template<class... _Args> | |
| result_of_t<_Tp&(_Args&&...)> | |
| operator()(_Args&&... __args) | |
| { return std::__invoke(get(), std::forward<_Args>(__args)...); } | |
| template<class... _Args> | |
| result_of_t<const _Tp&(_Args&&...)> | |
| operator()(_Args&&... __args) const | |
| { return std::__invoke(get(), std::forward<_Args>(__args)...); } | |
| private: | |
| template<typename _Up> | |
| using __use_exec_cond | |
| = __and_<uses_executor<_Tp, _Executor>, | |
| is_constructible<_Tp, executor_arg_t, _Executor, _Up>>; | |
| template<typename _Up, typename _Exec, typename = | |
| enable_if_t<__use_exec_cond<_Up>::value>> | |
| executor_binder(__use_exec, _Up&& __u, _Exec&& __ex) | |
| : _M_ex(std::forward<_Exec>(__ex)), | |
| _M_target(executor_arg, _M_ex, std::forward<_Up>(__u)) | |
| { } | |
| template<typename _Up, typename _Exec, typename = | |
| enable_if_t<!__use_exec_cond<_Up>::value>> | |
| executor_binder(__use_exec, _Up&& __u, const _Exec& __ex) | |
| : _M_ex(std::forward<_Exec>(__ex)), | |
| _M_target(std::forward<_Up>(__u)) | |
| { } | |
| _Executor _M_ex; | |
| _Tp _M_target; | |
| }; | |
| template<typename _Tp, typename _Executor, typename _Signature> | |
| class async_result<executor_binder<_Tp, _Executor>, _Signature> | |
| { | |
| using __inner = async_result<_Tp, _Signature>; | |
| public: | |
| using completion_handler_type = | |
| executor_binder<typename __inner::completion_handler_type, _Executor>; | |
| using return_type = typename __inner::return_type; | |
| explicit | |
| async_result(completion_handler_type& __h) | |
| : _M_target(__h.get()) { } | |
| async_result(const async_result&) = delete; | |
| async_result& operator=(const async_result&) = delete; | |
| return_type get() { return _M_target.get(); } | |
| private: | |
| __inner _M_target; | |
| }; | |
| template<typename _Tp, typename _Executor, typename _ProtoAlloc> | |
| struct associated_allocator<executor_binder<_Tp, _Executor>, _ProtoAlloc> | |
| { | |
| using type = associated_allocator_t<_Tp, _ProtoAlloc>; | |
| static type | |
| get(const executor_binder<_Tp, _Executor>& __b, | |
| const _ProtoAlloc& __a = _ProtoAlloc()) noexcept | |
| { return associated_allocator<_Tp, _ProtoAlloc>::get(__b.get(), __a); } | |
| }; | |
| template<typename _Tp, typename _Executor, typename _Executor1> | |
| struct associated_executor<executor_binder<_Tp, _Executor>, _Executor1> | |
| { | |
| using type = _Executor; | |
| static type | |
| get(const executor_binder<_Tp, _Executor>& __b, | |
| const _Executor1& = _Executor1()) noexcept | |
| { return __b.get_executor(); } | |
| }; | |
| template<typename _Executor> | |
| class executor_work_guard | |
| { | |
| public: | |
| // types: | |
| using executor_type = _Executor; | |
| // construct / copy / destroy: | |
| explicit | |
| executor_work_guard(const executor_type& __ex) noexcept | |
| : _M_ex(__ex), _M_owns(true) | |
| { _M_ex.on_work_started(); } | |
| executor_work_guard(const executor_work_guard& __other) noexcept | |
| : _M_ex(__other._M_ex), _M_owns(__other._M_owns) | |
| { | |
| if (_M_owns) | |
| _M_ex.on_work_started(); | |
| } | |
| executor_work_guard(executor_work_guard&& __other) noexcept | |
| : _M_ex(__other._M_ex), _M_owns(__other._M_owns) | |
| { __other._M_owns = false; } | |
| executor_work_guard& operator=(const executor_work_guard&) = delete; | |
| ~executor_work_guard() | |
| { | |
| if (_M_owns) | |
| _M_ex.on_work_finished(); | |
| } | |
| // executor work guard observers: | |
| executor_type get_executor() const noexcept { return _M_ex; } | |
| bool owns_work() const noexcept { return _M_owns; } | |
| // executor work guard modifiers: | |
| void reset() noexcept | |
| { | |
| if (_M_owns) | |
| _M_ex.on_work_finished(); | |
| _M_owns = false; | |
| } | |
| private: | |
| _Executor _M_ex; | |
| bool _M_owns; | |
| }; | |
| class system_context : public execution_context | |
| { | |
| public: | |
| // types: | |
| using executor_type = system_executor; | |
| // construct / copy / destroy: | |
| system_context() = delete; | |
| system_context(const system_context&) = delete; | |
| system_context& operator=(const system_context&) = delete; | |
| ~system_context() | |
| { | |
| stop(); | |
| join(); | |
| } | |
| // system_context operations: | |
| executor_type get_executor() noexcept; | |
| void stop() | |
| { | |
| lock_guard<mutex_type> __lock(_M_mtx); | |
| _M_stopped = true; | |
| _M_cv.notify_all(); | |
| } | |
| bool stopped() const noexcept | |
| { | |
| lock_guard<mutex_type> __lock(_M_mtx); | |
| return _M_stopped; | |
| } | |
| void join() | |
| { | |
| if (_M_thread.joinable()) | |
| _M_thread.join(); | |
| } | |
| private: | |
| friend system_executor; | |
| struct __tag { explicit __tag() = default; }; | |
| system_context(__tag) { } | |
| #ifndef _GLIBCXX_HAS_GTHREADS | |
| struct thread | |
| { | |
| bool joinable() const { return false; } | |
| void join() { } | |
| }; | |
| struct condition_variable | |
| { | |
| void notify_all() { } | |
| }; | |
| #endif | |
| thread _M_thread; | |
| mutable mutex_type _M_mtx; // XXX can we reuse base's _M_mutex? | |
| condition_variable _M_cv; | |
| queue<function<void()>> _M_tasks; | |
| bool _M_stopped = false; | |
| #ifdef _GLIBCXX_HAS_GTHREADS | |
| void | |
| _M_run() | |
| { | |
| while (true) | |
| { | |
| function<void()> __f; | |
| { | |
| unique_lock<mutex_type> __lock(_M_mtx); | |
| _M_cv.wait(__lock, | |
| [this]{ return _M_stopped || !_M_tasks.empty(); }); | |
| if (_M_stopped) | |
| return; | |
| __f = std::move(_M_tasks.front()); | |
| _M_tasks.pop(); | |
| } | |
| __f(); | |
| } | |
| } | |
| #endif | |
| void | |
| _M_post(std::function<void()> __f __attribute__((__unused__))) | |
| { | |
| lock_guard<mutex_type> __lock(_M_mtx); | |
| if (_M_stopped) | |
| return; | |
| #ifdef _GLIBCXX_HAS_GTHREADS | |
| if (!_M_thread.joinable()) | |
| _M_thread = std::thread(&system_context::_M_run, this); | |
| _M_tasks.push(std::move(__f)); // XXX allocator not used | |
| _M_cv.notify_one(); | |
| #else | |
| __throw_system_error(EOPNOTSUPP); | |
| #endif | |
| } | |
| static system_context& | |
| _S_get() noexcept | |
| { | |
| static system_context __sc(__tag{}); | |
| return __sc; | |
| } | |
| }; | |
| class system_executor | |
| { | |
| public: | |
| // executor operations: | |
| system_executor() { } | |
| system_context& | |
| context() const noexcept { return system_context::_S_get(); } | |
| void on_work_started() const noexcept { } | |
| void on_work_finished() const noexcept { } | |
| template<typename _Func, typename _ProtoAlloc> | |
| void | |
| dispatch(_Func&& __f, const _ProtoAlloc& __a) const | |
| { decay_t<_Func>{std::forward<_Func>(__f)}(); } | |
| template<typename _Func, typename _ProtoAlloc> | |
| void | |
| post(_Func&& __f, const _ProtoAlloc&) const // XXX allocator not used | |
| { | |
| system_context::_S_get()._M_post(std::forward<_Func>(__f)); | |
| } | |
| template<typename _Func, typename _ProtoAlloc> | |
| void | |
| defer(_Func&& __f, const _ProtoAlloc& __a) const | |
| { post(std::forward<_Func>(__f), __a); } | |
| }; | |
| inline system_executor | |
| system_context::get_executor() noexcept | |
| { return {}; } | |
| class bad_executor : public std::exception | |
| { | |
| virtual const char* what() const noexcept { return "bad executor"; } | |
| }; | |
| inline void __throw_bad_executor() // TODO make non-inline | |
| { | |
| #if __cpp_exceptions | |
| throw bad_executor(); | |
| #else | |
| __builtin_abort(); | |
| #endif | |
| } | |
| class executor | |
| { | |
| template<typename _Executor> | |
| using _Context_t = decltype(std::declval<_Executor&>().context()); | |
| public: | |
| // construct / copy / destroy: | |
| executor() noexcept = default; | |
| executor(nullptr_t) noexcept { } | |
| executor(const executor&) noexcept = default; | |
| executor(executor&&) noexcept = default; | |
| template<typename _Executor, | |
| typename = _Require<is_lvalue_reference<_Context_t<_Executor>>>> | |
| executor(_Executor __e) | |
| : _M_target(make_shared<_Tgt1<_Executor>>(std::move(__e))) | |
| { } | |
| template<typename _Executor, typename _ProtoAlloc, | |
| typename = _Require<is_lvalue_reference<_Context_t<_Executor>>>> | |
| executor(allocator_arg_t, const _ProtoAlloc& __a, _Executor __e) | |
| : _M_target(allocate_shared<_Tgt2<_Executor, _ProtoAlloc>>(__a, | |
| std::move(__e), __a)) | |
| { } | |
| executor& operator=(const executor&) noexcept = default; | |
| executor& operator=(executor&&) noexcept = default; | |
| executor& | |
| operator=(nullptr_t) noexcept | |
| { | |
| _M_target = nullptr; | |
| return *this; | |
| } | |
| template<typename _Executor> | |
| executor& | |
| operator=(_Executor __e) | |
| { | |
| executor(std::move(__e)).swap(*this); | |
| return *this; | |
| } | |
| ~executor() = default; | |
| // executor modifiers: | |
| void | |
| swap(executor& __other) noexcept | |
| { _M_target.swap(__other._M_target); } | |
| template<typename _Executor, typename _Alloc> | |
| void | |
| assign(_Executor __e, const _Alloc& __a) | |
| { executor(allocator_arg, __a, std::move(__e)).swap(*this); } | |
| // executor operations: | |
| execution_context& | |
| context() const noexcept | |
| { | |
| __glibcxx_assert( _M_target ); | |
| return _M_target->context(); | |
| } | |
| void | |
| on_work_started() const noexcept | |
| { | |
| __glibcxx_assert( _M_target ); | |
| return _M_target->on_work_started(); | |
| } | |
| void | |
| on_work_finished() const noexcept | |
| { | |
| __glibcxx_assert( _M_target ); | |
| return _M_target->on_work_finished(); | |
| } | |
| template<typename _Func, typename _Alloc> | |
| void | |
| dispatch(_Func&& __f, const _Alloc& __a) const | |
| { | |
| if (!_M_target) | |
| __throw_bad_executor(); | |
| // _M_target->dispatch({allocator_arg, __a, std::forward<_Func>(__f)}); | |
| _M_target->dispatch(std::forward<_Func>(__f)); | |
| } | |
| template<typename _Func, typename _Alloc> | |
| void | |
| post(_Func&& __f, const _Alloc& __a) const | |
| { | |
| if (!_M_target) | |
| __throw_bad_executor(); | |
| // _M_target->post({allocator_arg, __a, std::forward<_Func>(__f)}); | |
| _M_target->post(std::forward<_Func>(__f)); | |
| } | |
| template<typename _Func, typename _Alloc> | |
| void | |
| defer(_Func&& __f, const _Alloc& __a) const | |
| { | |
| if (!_M_target) | |
| __throw_bad_executor(); | |
| // _M_target->defer({allocator_arg, __a, std::forward<_Func>(__f)}); | |
| _M_target->defer(std::forward<_Func>(__f)); | |
| } | |
| // executor capacity: | |
| explicit operator bool() const noexcept | |
| { return static_cast<bool>(_M_target); } | |
| // executor target access: | |
| #if __cpp_rtti | |
| const type_info& | |
| target_type() const noexcept | |
| { | |
| if (_M_target) | |
| return *static_cast<const type_info*>(_M_target->target_type()); | |
| return typeid(void); | |
| } | |
| #endif | |
| template<typename _Executor> | |
| _Executor* | |
| target() noexcept | |
| { | |
| void* __p = nullptr; | |
| if (_M_target) | |
| { | |
| if (_M_target->_M_func == &_Tgt1<remove_cv_t<_Executor>>::_S_func) | |
| __p = _M_target->_M_func(_M_target.get(), nullptr); | |
| #if __cpp_rtti | |
| else | |
| __p = _M_target->target(&typeid(_Executor)); | |
| #endif | |
| } | |
| return static_cast<_Executor*>(__p); | |
| } | |
| template<typename _Executor> | |
| const _Executor* | |
| target() const noexcept | |
| { | |
| const void* __p = nullptr; | |
| if (_M_target) | |
| { | |
| if (_M_target->_M_func == &_Tgt1<remove_cv_t<_Executor>>::_S_func) | |
| return (_Executor*)_M_target->_M_func(_M_target.get(), nullptr); | |
| #if __cpp_rtti | |
| else | |
| __p = _M_target->target(&typeid(_Executor)); | |
| #endif | |
| } | |
| return static_cast<const _Executor*>(__p); | |
| } | |
| private: | |
| struct _Tgt | |
| { | |
| virtual void on_work_started() const noexcept = 0; | |
| virtual void on_work_finished() const noexcept = 0; | |
| virtual execution_context& context() const noexcept = 0; | |
| virtual void dispatch(std::function<void()>) const = 0; | |
| virtual void post(std::function<void()>) const = 0; | |
| virtual void defer(std::function<void()>) const = 0; | |
| virtual const void* target_type() const noexcept = 0; | |
| virtual void* target(const void*) noexcept = 0; | |
| virtual bool _M_equals(_Tgt*) const noexcept = 0; | |
| using _Func = void* (_Tgt*, const _Tgt*); | |
| _Func* _M_func; // Provides access to target without RTTI | |
| }; | |
| template<typename _Ex> | |
| struct _Tgt1 : _Tgt | |
| { | |
| explicit | |
| _Tgt1(_Ex&& __ex) | |
| : _M_ex(std::move(__ex)) | |
| { this->_M_func = &_S_func; } | |
| void | |
| on_work_started() const noexcept override | |
| { _M_ex.on_work_started(); } | |
| void | |
| on_work_finished() const noexcept override | |
| { _M_ex.on_work_finished(); } | |
| execution_context& | |
| context() const noexcept override | |
| { return _M_ex.context(); } | |
| void | |
| dispatch(std::function<void()> __f) const override | |
| { _M_ex.dispatch(std::move(__f), allocator<void>()); } | |
| void | |
| post(std::function<void()> __f) const override | |
| { _M_ex.post(std::move(__f), allocator<void>()); } | |
| void | |
| defer(std::function<void()> __f) const override | |
| { _M_ex.defer(std::move(__f), allocator<void>()); } | |
| const void* | |
| target_type() const noexcept override | |
| { | |
| #if __cpp_rtti | |
| return &typeid(_Ex); | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| void* | |
| target(const void* __ti) noexcept override | |
| { | |
| #if __cpp_rtti | |
| if (*static_cast<const type_info*>(__ti) == typeid(_Ex)) | |
| return std::__addressof(_M_ex); | |
| #endif | |
| return nullptr; | |
| } | |
| bool | |
| _M_equals(_Tgt* __tgt) const noexcept override | |
| { | |
| #if __cpp_rtti | |
| if (const void* __p = __tgt->target(&typeid(_Ex))) | |
| return *static_cast<const _Ex*>(__p) == _M_ex; | |
| #endif | |
| return false; | |
| } | |
| _Ex _M_ex [[__no_unique_address__]]; | |
| static void* | |
| _S_func(_Tgt* __p, const _Tgt* __q) noexcept | |
| { | |
| auto& __ex = static_cast<_Tgt1*>(__p)->_M_ex; | |
| if (__q) | |
| { | |
| if (__ex == static_cast<const _Tgt1*>(__q)->_M_ex) | |
| return __p; | |
| else | |
| return nullptr; | |
| } | |
| else | |
| return std::__addressof(__ex); | |
| } | |
| }; | |
| template<typename _Ex, typename _Alloc> | |
| struct _Tgt2 : _Tgt1<_Ex> | |
| { | |
| explicit | |
| _Tgt2(_Ex&& __ex, const _Alloc& __a) | |
| : _Tgt1<_Ex>(std::move(__ex)), _M_alloc(__a) { } | |
| void | |
| dispatch(std::function<void()> __f) const override | |
| { this->_M_ex.dispatch(std::move(__f), _M_alloc); } | |
| void | |
| post(std::function<void()> __f) const override | |
| { this->_M_ex.post(std::move(__f), _M_alloc); } | |
| void | |
| defer(std::function<void()> __f) const override | |
| { this->_M_ex.defer(std::move(__f), _M_alloc); } | |
| _Alloc _M_alloc [[__no_unique_address__]]; | |
| }; | |
| // Partial specialization for std::allocator<T>. | |
| // Don't store the allocator. | |
| template<typename _Ex, typename _Tp> | |
| struct _Tgt2<_Ex, std::allocator<_Tp>> : _Tgt1<_Ex> | |
| { }; | |
| friend bool | |
| operator==(const executor& __a, const executor& __b) noexcept | |
| { | |
| _Tgt* __ta = __a._M_target.get(); | |
| _Tgt* __tb = __b._M_target.get(); | |
| if (__ta == __tb) | |
| return true; | |
| if (!__ta || !__tb) | |
| return false; | |
| if (__ta->_M_func == __tb->_M_func) | |
| return __ta->_M_func(__ta, __tb); | |
| return __ta->_M_equals(__tb); | |
| } | |
| shared_ptr<_Tgt> _M_target; | |
| }; | |
| template<> struct is_executor<executor> : true_type { }; | |
| /// executor comparisons | |
| inline bool | |
| operator==(const executor& __e, nullptr_t) noexcept | |
| { return !__e; } | |
| inline bool | |
| operator==(nullptr_t, const executor& __e) noexcept | |
| { return !__e; } | |
| inline bool | |
| operator!=(const executor& __a, const executor& __b) noexcept | |
| { return !(__a == __b); } | |
| inline bool | |
| operator!=(const executor& __e, nullptr_t) noexcept | |
| { return (bool)__e; } | |
| inline bool | |
| operator!=(nullptr_t, const executor& __e) noexcept | |
| { return (bool)__e; } | |
| /// Swap two executor objects. | |
| inline void swap(executor& __a, executor& __b) noexcept { __a.swap(__b); } | |
| template<typename _CompletionHandler> | |
| struct __dispatcher | |
| { | |
| explicit | |
| __dispatcher(_CompletionHandler& __h) | |
| : _M_h(std::move(__h)), _M_w(net::make_work_guard(_M_h)) | |
| { } | |
| void operator()() | |
| { | |
| auto __alloc = net::get_associated_allocator(_M_h); | |
| _M_w.get_executor().dispatch(std::move(_M_h), __alloc); | |
| _M_w.reset(); | |
| } | |
| _CompletionHandler _M_h; | |
| decltype(net::make_work_guard(_M_h)) _M_w; | |
| }; | |
| template<typename _CompletionHandler> | |
| inline __dispatcher<_CompletionHandler> | |
| __make_dispatcher(_CompletionHandler& __h) | |
| { return __dispatcher<_CompletionHandler>{__h}; } | |
| // dispatch: | |
| template<typename _CompletionToken> | |
| inline __deduced_t<_CompletionToken, void()> | |
| dispatch(_CompletionToken&& __token) | |
| { | |
| async_completion<_CompletionToken, void()> __cmpl{__token}; | |
| auto __ex = net::get_associated_executor(__cmpl.completion_handler); | |
| auto __alloc = net::get_associated_allocator(__cmpl.completion_handler); | |
| __ex.dispatch(std::move(__cmpl.completion_handler), __alloc); | |
| return __cmpl.result.get(); | |
| } | |
| template<typename _Executor, typename _CompletionToken> | |
| inline | |
| enable_if_t<is_executor<_Executor>::value, | |
| __deduced_t<_CompletionToken, void()>> | |
| dispatch(const _Executor& __ex, _CompletionToken&& __token) | |
| { | |
| async_completion<_CompletionToken, void()> __cmpl{__token}; | |
| auto __alloc = net::get_associated_allocator(__cmpl.completion_handler); | |
| __ex.dispatch(net::__make_dispatcher(__cmpl.completion_handler), | |
| __alloc); | |
| return __cmpl.result.get(); | |
| } | |
| template<typename _ExecutionContext, typename _CompletionToken> | |
| inline | |
| enable_if_t<__is_exec_context<_ExecutionContext>::value, | |
| __deduced_t<_CompletionToken, void()>> | |
| dispatch(_ExecutionContext& __ctx, _CompletionToken&& __token) | |
| { | |
| return net::dispatch(__ctx.get_executor(), | |
| forward<_CompletionToken>(__token)); | |
| } | |
| // post: | |
| template<typename _CompletionToken> | |
| inline __deduced_t<_CompletionToken, void()> | |
| post(_CompletionToken&& __token) | |
| { | |
| async_completion<_CompletionToken, void()> __cmpl{__token}; | |
| auto __ex = net::get_associated_executor(__cmpl.completion_handler); | |
| auto __alloc = net::get_associated_allocator(__cmpl.completion_handler); | |
| __ex.post(std::move(__cmpl.completion_handler), __alloc); | |
| return __cmpl.result.get(); | |
| } | |
| template<typename _Executor, typename _CompletionToken> | |
| inline | |
| enable_if_t<is_executor<_Executor>::value, | |
| __deduced_t<_CompletionToken, void()>> | |
| post(const _Executor& __ex, _CompletionToken&& __token) | |
| { | |
| async_completion<_CompletionToken, void()> __cmpl{__token}; | |
| auto __alloc = net::get_associated_allocator(__cmpl.completion_handler); | |
| __ex.post(net::__make_dispatcher(__cmpl.completion_handler), __alloc); | |
| return __cmpl.result.get(); | |
| } | |
| template<typename _ExecutionContext, typename _CompletionToken> | |
| inline | |
| enable_if_t<__is_exec_context<_ExecutionContext>::value, | |
| __deduced_t<_CompletionToken, void()>> | |
| post(_ExecutionContext& __ctx, _CompletionToken&& __token) | |
| { | |
| return net::post(__ctx.get_executor(), | |
| forward<_CompletionToken>(__token)); | |
| } | |
| // defer: | |
| template<typename _CompletionToken> | |
| inline __deduced_t<_CompletionToken, void()> | |
| defer(_CompletionToken&& __token) | |
| { | |
| async_completion<_CompletionToken, void()> __cmpl{__token}; | |
| auto __ex = net::get_associated_executor(__cmpl.completion_handler); | |
| auto __alloc = net::get_associated_allocator(__cmpl.completion_handler); | |
| __ex.defer(std::move(__cmpl.completion_handler), __alloc); | |
| return __cmpl.result.get(); | |
| } | |
| template<typename _Executor, typename _CompletionToken> | |
| inline | |
| enable_if_t<is_executor<_Executor>::value, | |
| __deduced_t<_CompletionToken, void()>> | |
| defer(const _Executor& __ex, _CompletionToken&& __token) | |
| { | |
| async_completion<_CompletionToken, void()> __cmpl{__token}; | |
| auto __alloc = net::get_associated_allocator(__cmpl.completion_handler); | |
| __ex.defer(net::__make_dispatcher(__cmpl.completion_handler), __alloc); | |
| return __cmpl.result.get(); | |
| } | |
| template<typename _ExecutionContext, typename _CompletionToken> | |
| inline | |
| enable_if_t<__is_exec_context<_ExecutionContext>::value, | |
| __deduced_t<_CompletionToken, void()>> | |
| defer(_ExecutionContext& __ctx, _CompletionToken&& __token) | |
| { | |
| return net::defer(__ctx.get_executor(), | |
| forward<_CompletionToken>(__token)); | |
| } | |
| template<typename _Executor> | |
| class strand | |
| { | |
| public: | |
| // types: | |
| using inner_executor_type = _Executor; | |
| // construct / copy / destroy: | |
| strand(); // TODO make state | |
| explicit strand(_Executor __ex) : _M_inner_ex(__ex) { } // TODO make state | |
| template<typename _Alloc> | |
| strand(allocator_arg_t, const _Alloc& __a, _Executor __ex) | |
| : _M_inner_ex(__ex) { } // TODO make state | |
| strand(const strand& __other) noexcept | |
| : _M_state(__other._M_state), _M_inner_ex(__other._M_inner_ex) { } | |
| strand(strand&& __other) noexcept | |
| : _M_state(std::move(__other._M_state)), | |
| _M_inner_ex(std::move(__other._M_inner_ex)) { } | |
| template<typename _OtherExecutor> | |
| strand(const strand<_OtherExecutor>& __other) noexcept | |
| : _M_state(__other._M_state), _M_inner_ex(__other._M_inner_ex) { } | |
| template<typename _OtherExecutor> | |
| strand(strand<_OtherExecutor>&& __other) noexcept | |
| : _M_state(std::move(__other._M_state)), | |
| _M_inner_ex(std::move(__other._M_inner_ex)) { } | |
| strand& | |
| operator=(const strand& __other) noexcept | |
| { | |
| static_assert(is_copy_assignable<_Executor>::value, | |
| "inner executor type must be CopyAssignable"); | |
| // TODO lock __other | |
| // TODO copy state | |
| _M_inner_ex = __other._M_inner_ex; | |
| return *this; | |
| } | |
| strand& | |
| operator=(strand&& __other) noexcept | |
| { | |
| static_assert(is_move_assignable<_Executor>::value, | |
| "inner executor type must be MoveAssignable"); | |
| // TODO move state | |
| _M_inner_ex = std::move(__other._M_inner_ex); | |
| return *this; | |
| } | |
| template<typename _OtherExecutor> | |
| strand& | |
| operator=(const strand<_OtherExecutor>& __other) noexcept | |
| { | |
| static_assert(is_convertible<_OtherExecutor, _Executor>::value, | |
| "inner executor type must be compatible"); | |
| // TODO lock __other | |
| // TODO copy state | |
| _M_inner_ex = __other._M_inner_ex; | |
| return *this; | |
| } | |
| template<typename _OtherExecutor> | |
| strand& | |
| operator=(strand<_OtherExecutor>&& __other) noexcept | |
| { | |
| static_assert(is_convertible<_OtherExecutor, _Executor>::value, | |
| "inner executor type must be compatible"); | |
| // TODO move state | |
| _M_inner_ex = std::move(__other._M_inner_ex); | |
| return *this; | |
| } | |
| ~strand() | |
| { | |
| // the task queue outlives this object if non-empty | |
| // TODO create circular ref in queue? | |
| } | |
| // strand operations: | |
| inner_executor_type | |
| get_inner_executor() const noexcept | |
| { return _M_inner_ex; } | |
| bool | |
| running_in_this_thread() const noexcept | |
| { return _M_state->running_in_this_thread(); } | |
| execution_context& | |
| context() const noexcept | |
| { return _M_inner_ex.context(); } | |
| void on_work_started() const noexcept { _M_inner_ex.on_work_started(); } | |
| void on_work_finished() const noexcept { _M_inner_ex.on_work_finished(); } | |
| template<typename _Func, typename _Alloc> | |
| void | |
| dispatch(_Func&& __f, const _Alloc& __a) const | |
| { | |
| if (running_in_this_thread()) | |
| decay_t<_Func>{std::forward<_Func>(__f)}(); | |
| else | |
| post(std::forward<_Func>(__f), __a); | |
| } | |
| template<typename _Func, typename _Alloc> | |
| void | |
| post(_Func&& __f, const _Alloc& __a) const; // TODO | |
| template<typename _Func, typename _Alloc> | |
| void | |
| defer(_Func&& __f, const _Alloc& __a) const | |
| { post(std::forward<_Func>(__f), __a); } | |
| private: | |
| friend bool | |
| operator==(const strand& __a, const strand& __b) | |
| { return __a._M_state == __b._M_state; } | |
| // TODO add synchronised queue | |
| struct _State | |
| { | |
| #if defined(_GLIBCXX_HAS_GTHREADS) | |
| bool | |
| running_in_this_thread() const noexcept | |
| { return std::this_thread::get_id() == _M_running_on; } | |
| std::thread::id _M_running_on; | |
| #else | |
| bool running_in_this_thread() const { return true; } | |
| #endif | |
| }; | |
| shared_ptr<_State> _M_state; | |
| _Executor _M_inner_ex; | |
| }; | |
| #if defined(_GLIBCXX_HAS_GTHREADS) | |
| // Completion token for asynchronous operations initiated with use_future. | |
| template<typename _Func, typename _Alloc> | |
| struct __use_future_ct | |
| { | |
| std::tuple<_Func, _Alloc> _M_t; | |
| }; | |
| template<typename _Func, typename _Tp> | |
| struct __use_future_ct<_Func, std::allocator<_Tp>> | |
| { | |
| _Func _M_f; | |
| }; | |
| template<typename _ProtoAllocator = allocator<void>> | |
| class use_future_t | |
| { | |
| public: | |
| // use_future_t types: | |
| using allocator_type = _ProtoAllocator; | |
| // use_future_t members: | |
| constexpr | |
| use_future_t() | |
| noexcept(is_nothrow_default_constructible<_ProtoAllocator>::value) | |
| : _M_alloc() { } | |
| explicit | |
| use_future_t(const _ProtoAllocator& __a) noexcept : _M_alloc(__a) { } | |
| template<typename _OtherAllocator> | |
| use_future_t<_OtherAllocator> | |
| rebind(const _OtherAllocator& __a) const noexcept | |
| { return use_future_t<_OtherAllocator>(__a); } | |
| allocator_type get_allocator() const noexcept { return _M_alloc; } | |
| template<typename _Func> | |
| auto | |
| operator()(_Func&& __f) const | |
| { | |
| using _Token = __use_future_ct<decay_t<_Func>, _ProtoAllocator>; | |
| return _Token{ {std::forward<_Func>(__f), _M_alloc} }; | |
| } | |
| private: | |
| _ProtoAllocator _M_alloc; | |
| }; | |
| template<typename _Tp> | |
| class use_future_t<std::allocator<_Tp>> | |
| { | |
| public: | |
| // use_future_t types: | |
| using allocator_type = std::allocator<_Tp>; | |
| // use_future_t members: | |
| constexpr use_future_t() noexcept = default; | |
| explicit | |
| use_future_t(const allocator_type& __a) noexcept { } | |
| template<class _Up> | |
| use_future_t<std::allocator<_Up>> | |
| rebind(const std::allocator<_Up>& __a) const noexcept | |
| { return use_future_t<std::allocator<_Up>>(__a); } | |
| allocator_type get_allocator() const noexcept { return {}; } | |
| template<typename _Func> | |
| auto | |
| operator()(_Func&& __f) const | |
| { | |
| using _Token = __use_future_ct<decay_t<_Func>, allocator_type>; | |
| return _Token{std::forward<_Func>(__f)}; | |
| } | |
| }; | |
| constexpr use_future_t<> use_future = use_future_t<>(); | |
| template<typename _Func, typename _Alloc, typename _Res, typename... _Args> | |
| class async_result<__use_future_ct<_Func, _Alloc>, _Res(_Args...)>; | |
| template<typename _Result, typename _Executor> | |
| struct __use_future_ex; | |
| // Completion handler for asynchronous operations initiated with use_future. | |
| template<typename _Func, typename... _Args> | |
| struct __use_future_ch | |
| { | |
| template<typename _Alloc> | |
| explicit | |
| __use_future_ch(__use_future_ct<_Func, _Alloc>&& __token) | |
| : _M_f{ std::move(std::get<0>(__token._M_t)) }, | |
| _M_promise{ std::get<1>(__token._M_t) } | |
| { } | |
| template<typename _Tp> | |
| explicit | |
| __use_future_ch(__use_future_ct<_Func, std::allocator<_Tp>>&& __token) | |
| : _M_f{ std::move(__token._M_f) } | |
| { } | |
| void | |
| operator()(_Args&&... __args) | |
| { | |
| __try | |
| { | |
| _M_promise.set_value(_M_f(std::forward<_Args>(__args)...)); | |
| } | |
| __catch(__cxxabiv1::__forced_unwind&) | |
| { | |
| __throw_exception_again; | |
| } | |
| __catch(...) | |
| { | |
| _M_promise.set_exception(std::current_exception()); | |
| } | |
| } | |
| using __result = result_of_t<_Func(decay_t<_Args>...)>; | |
| future<__result> get_future() { return _M_promise.get_future(); } | |
| private: | |
| template<typename _Result, typename _Executor> | |
| friend struct __use_future_ex; | |
| _Func _M_f; | |
| mutable promise<__result> _M_promise; | |
| }; | |
| // Specialization of async_result for operations initiated with use_future. | |
| template<typename _Func, typename _Alloc, typename _Res, typename... _Args> | |
| class async_result<__use_future_ct<_Func, _Alloc>, _Res(_Args...)> | |
| { | |
| public: | |
| using completion_handler_type = __use_future_ch<_Func, _Args...>; | |
| using return_type = future<typename completion_handler_type::__result>; | |
| explicit | |
| async_result(completion_handler_type& __h) | |
| : _M_future(__h.get_future()) | |
| { } | |
| async_result(const async_result&) = delete; | |
| async_result& operator=(const async_result&) = delete; | |
| return_type get() { return std::move(_M_future); } | |
| private: | |
| return_type _M_future; | |
| }; | |
| template<typename _Result, typename _Executor> | |
| struct __use_future_ex | |
| { | |
| template<typename _Handler> | |
| __use_future_ex(const _Handler& __h, _Executor __ex) | |
| : _M_t(__h._M_promise, __ex) | |
| { } | |
| template<typename _Fn, typename _Alloc> | |
| void | |
| dispatch(_Fn&& __fn) | |
| { | |
| __try | |
| { | |
| std::get<1>(_M_t).dispatch(std::forward<_Fn>(__fn)); | |
| } | |
| __catch(__cxxabiv1::__forced_unwind&) | |
| { | |
| __throw_exception_again; | |
| } | |
| __catch(...) | |
| { | |
| std::get<0>(_M_t).set_exception(std::current_exception()); | |
| } | |
| } | |
| template<typename _Fn, typename _Alloc> | |
| void | |
| post(_Fn&& __fn) | |
| { | |
| __try | |
| { | |
| std::get<1>(_M_t).post(std::forward<_Fn>(__fn)); | |
| } | |
| __catch(__cxxabiv1::__forced_unwind&) | |
| { | |
| __throw_exception_again; | |
| } | |
| __catch(...) | |
| { | |
| std::get<0>(_M_t).set_exception(std::current_exception()); | |
| } | |
| } | |
| template<typename _Fn, typename _Alloc> | |
| void | |
| defer(_Fn&& __fn) | |
| { | |
| __try | |
| { | |
| std::get<1>(_M_t).defer(std::forward<_Fn>(__fn)); | |
| } | |
| __catch(__cxxabiv1::__forced_unwind&) | |
| { | |
| __throw_exception_again; | |
| } | |
| __catch(...) | |
| { | |
| std::get<0>(_M_t).set_exception(std::current_exception()); | |
| } | |
| } | |
| private: | |
| tuple<promise<_Result>&, _Executor> _M_t; | |
| }; | |
| template<typename _Func, typename... _Args, typename _Executor> | |
| struct associated_executor<__use_future_ch<_Func, _Args...>, _Executor> | |
| { | |
| private: | |
| using __handler = __use_future_ch<_Func, _Args...>; | |
| using type = __use_future_ex<typename __handler::__result, _Executor>; | |
| static type | |
| get(const __handler& __h, const _Executor& __ex) | |
| { return { __h, __ex }; } | |
| }; | |
| #if 0 | |
| // [async.use.future.traits] | |
| template<typename _Allocator, typename _Ret, typename... _Args> | |
| class handler_type<use_future_t<_Allocator>, _Ret(_Args...)> // TODO uglify name | |
| { | |
| template<typename... _Args> | |
| struct __is_error_result : false_type { }; | |
| template<typename... _Args> | |
| struct __is_error_result<error_code, _Args...> : true_type { }; | |
| template<typename... _Args> | |
| struct __is_error_result<exception_ptr, _Args...> : true_type { }; | |
| static exception_ptr | |
| _S_exptr(exception_ptr& __ex) | |
| { return std::move(__ex); } | |
| static exception_ptr | |
| _S_exptr(const error_code& __ec) | |
| { return make_exception_ptr(system_error(__ec)); } | |
| template<bool _IsError, typename... _UArgs> | |
| struct _Type; | |
| // N == 0 | |
| template<bool _IsError> | |
| struct _Type<_IsError> | |
| { | |
| std::promise<void> _M_promise; | |
| void | |
| operator()() | |
| { | |
| _M_promise.set_value(); | |
| } | |
| }; | |
| // N == 1, U0 is error_code or exception_ptr | |
| template<typename _UArg0> | |
| struct _Type<true, _UArg0> | |
| { | |
| std::promise<void> _M_promise; | |
| template<typename _Arg0> | |
| void | |
| operator()(_Arg0&& __a0) | |
| { | |
| if (__a0) | |
| _M_promise.set_exception(_S_exptr(__a0)); | |
| else | |
| _M_promise.set_value(); | |
| } | |
| }; | |
| // N == 1, U0 is not error_code or exception_ptr | |
| template<typename _UArg0> | |
| struct _Type<false, _UArg0> | |
| { | |
| std::promise<_UArg0> _M_promise; | |
| template<typename _Arg0> | |
| void | |
| operator()(_Arg0&& __a0) | |
| { | |
| _M_promise.set_value(std::forward<_Arg0>(__a0)); | |
| } | |
| }; | |
| // N == 2, U0 is error_code or exception_ptr | |
| template<typename _UArg0, typename _UArg1> | |
| struct _Type<true, _UArg0, _UArg1> | |
| { | |
| std::promise<_UArg1> _M_promise; | |
| template<typename _Arg0, typename _Arg1> | |
| void | |
| operator()(_Arg0&& __a0, _Arg1&& __a1) | |
| { | |
| if (__a0) | |
| _M_promise.set_exception(_S_exptr(__a0)); | |
| else | |
| _M_promise.set_value(std::forward<_Arg1>(__a1)); | |
| } | |
| }; | |
| // N >= 2, U0 is not error_code or exception_ptr | |
| template<typename... _UArgs> | |
| struct _Type<false, _UArgs...> | |
| { | |
| static_assert(sizeof...(_UArgs) > 1, "wrong partial specialization"); | |
| std::promise<tuple<_UArgs...>> _M_promise; | |
| template<typename... _Args> | |
| void | |
| operator()(_Args&&... __args) | |
| { | |
| _M_promise.set_value( | |
| std::forward_as_tuple(std::forward<_Args>(__args)...)); | |
| } | |
| }; | |
| // N > 2, U0 is error_code or exception_ptr | |
| template<typename _UArg0, typename... _UArgs> | |
| struct _Type<true, _UArg0, _UArgs...> | |
| { | |
| static_assert(sizeof...(_UArgs) > 1, "wrong partial specialization"); | |
| std::promise<tuple<_UArgs...>> _M_promise; | |
| template<typename _Arg0, typename... _Args> | |
| void | |
| operator()(_Arg0&& __a0, _Args&&... __args) | |
| { | |
| if (__a0) | |
| _M_promise.set_exception(_S_exptr(__a0)); | |
| else | |
| _M_promise.set_value( | |
| std::forward_as_tuple(std::forward<_Args>(__args)...)); | |
| } | |
| }; | |
| public: | |
| using type = | |
| _Type<__is_error_result<_Args...>::value, decay_t<_Args>...>; | |
| }; | |
| template<typename _Alloc, typename _Ret, typename... _Args> | |
| struct async_result<use_future_t<_Alloc>, _Ret(_Args...)> | |
| { | |
| using completion_handler_type | |
| = typename handler_type<use_future_t<_Alloc>, _Ret(_Args...)>::type; | |
| using return_type = void; // XXX TODO ???; | |
| explicit | |
| async_result(completion_handler_type& __h) : _M_handler(__h) { } | |
| auto get() { return _M_handler._M_provider.get_future(); } | |
| async_result(const async_result&) = delete; | |
| async_result& operator=(const async_result&) = delete; | |
| return_type get() { return _M_handler._M_promise.get_future(); } | |
| private: | |
| completion_handler_type& _M_handler; | |
| }; | |
| // TODO specialize associated_executor for | |
| // async_result<use_future_t<A>, Sig>::completion_handler_type | |
| // to use a __use_future_ex | |
| // (probably need to move _Type outside of handler_type so we don't have | |
| // a non-deduced context) | |
| #endif | |
| // [async.packaged.task.specializations] | |
| template<typename _Ret, typename... _Args, typename _Signature> | |
| class async_result<packaged_task<_Ret(_Args...)>, _Signature> | |
| { | |
| public: | |
| using completion_handler_type = packaged_task<_Ret(_Args...)>; | |
| using return_type = future<_Ret>; | |
| explicit | |
| async_result(completion_handler_type& __h) | |
| : _M_future(__h.get_future()) { } | |
| async_result(const async_result&) = delete; | |
| async_result& operator=(const async_result&) = delete; | |
| return_type get() { return std::move(_M_future); } | |
| private: | |
| return_type _M_future; | |
| }; | |
| #endif // _GLIBCXX_HAS_GTHREADS | |
| /// @} | |
| } // namespace v1 | |
| } // namespace net | |
| } // namespace experimental | |
| template<typename _Alloc> | |
| struct uses_allocator<experimental::net::executor, _Alloc> | |
| : true_type {}; | |
| _GLIBCXX_END_NAMESPACE_VERSION | |
| } // namespace std | |
| #endif // C++14 | |
| #endif // _GLIBCXX_EXPERIMENTAL_EXECUTOR |