xref: /openbmc/sdbusplus/include/sdbusplus/async/stdexec/__detail/__start_detached.hpp (revision 10d0b4b7d1498cfd5c3d37edea271a54d1984e41)
1 /*
2  * Copyright (c) 2021-2024 NVIDIA Corporation
3  *
4  * Licensed under the Apache License Version 2.0 with LLVM Exceptions
5  * (the "License"); you may not use this file except in compliance with
6  * the License. You may obtain a copy of the License at
7  *
8  *   https://llvm.org/LICENSE.txt
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 
18 #include "__execution_fwd.hpp"
19 
20 #include <memory>
21 
22 #include "__meta.hpp"
23 #include "__env.hpp"
24 #include "__receivers.hpp"
25 #include "__env.hpp"
26 #include "__scope.hpp"
27 #include "__submit.hpp"
28 #include "__transform_sender.hpp"
29 
30 namespace stdexec {
31   /////////////////////////////////////////////////////////////////////////////
32   // [execution.senders.consumer.start_detached]
33   namespace __start_detached {
34     struct __submit_receiver {
35       using receiver_concept = receiver_t;
36       using __t = __submit_receiver;
37       using __id = __submit_receiver;
38 
39       template <class... _As>
set_valuestdexec::__start_detached::__submit_receiver40       void set_value(_As&&...) noexcept {
41       }
42 
43       template <class _Error>
44       [[noreturn]]
set_errorstdexec::__start_detached::__submit_receiver45       void set_error(_Error&&) noexcept {
46         // A detached operation failed. There is noplace for the error to go.
47         // This is unrecoverable, so we terminate.
48         std::terminate();
49       }
50 
set_stoppedstdexec::__start_detached::__submit_receiver51       void set_stopped() noexcept {
52       }
53 
54       [[nodiscard]]
get_envstdexec::__start_detached::__submit_receiver55       auto get_env() const noexcept -> __root_env {
56         return {};
57       }
58     };
59 
60     template <class _SenderId, class _EnvId>
61     struct __operation : __immovable {
62       using _Sender = __cvref_t<_SenderId>;
63       using _Env = __t<_EnvId>;
64 
__operationstdexec::__start_detached::__operation65       explicit __operation(connect_t, _Sender&& __sndr, _Env __env)
66         : __env_(static_cast<_Env&&>(__env))
67         , __op_data_(static_cast<_Sender&&>(__sndr), __receiver{this}) {
68       }
69 
__operationstdexec::__start_detached::__operation70       explicit __operation(_Sender&& __sndr, _Env __env)
71         : __operation(connect, static_cast<_Sender&&>(__sndr), static_cast<_Env&&>(__env)) {
72         // If the operation completes synchronously, then the following line will cause
73         // the destruction of *this, which is not a problem because we used a delegating
74         // constructor, so *this is considered fully constructed.
75         __op_data_.submit(static_cast<_Sender&&>(__sndr), __receiver{this});
76       }
77 
__destroy_deletestdexec::__start_detached::__operation78       static void __destroy_delete(__operation* __self) noexcept {
79         if constexpr (__callable<get_allocator_t, _Env>) {
80           auto __alloc = stdexec::get_allocator(__self->__env_);
81           using _Alloc = decltype(__alloc);
82           using _OpAlloc = std::allocator_traits<_Alloc>::template rebind_alloc<__operation>;
83           _OpAlloc __op_alloc{__alloc};
84           std::allocator_traits<_OpAlloc>::destroy(__op_alloc, __self);
85           std::allocator_traits<_OpAlloc>::deallocate(__op_alloc, __self, 1);
86         } else {
87           delete __self;
88         }
89       }
90 
91       // The start_detached receiver deletes the operation state.
92       struct __receiver {
93         using receiver_concept = receiver_t;
94         using __t = __receiver;
95         using __id = __receiver;
96 
97         template <class... _As>
set_valuestdexec::__start_detached::__operation::__receiver98         void set_value(_As&&...) noexcept {
99           __operation::__destroy_delete(__op_); // NB: invalidates *this
100         }
101 
102         template <class _Error>
103         [[noreturn]]
set_errorstdexec::__start_detached::__operation::__receiver104         void set_error(_Error&&) noexcept {
105           // A detached operation failed. There is noplace for the error to go.
106           // This is unrecoverable, so we terminate.
107           std::terminate();
108         }
109 
set_stoppedstdexec::__start_detached::__operation::__receiver110         void set_stopped() noexcept {
111           __operation::__destroy_delete(__op_); // NB: invalidates *this
112         }
113 
get_envstdexec::__start_detached::__operation::__receiver114         auto get_env() const noexcept -> const _Env& {
115           return __op_->__env_;
116         }
117 
118         __operation* __op_;
119       };
120 
121       STDEXEC_ATTRIBUTE(no_unique_address) _Env __env_;
122       STDEXEC_ATTRIBUTE(no_unique_address) submit_result<_Sender, __receiver> __op_data_;
123     };
124 
125     template <class _Sender, class _Env>
126     concept __use_submit = __submittable<_Sender, __submit_receiver> && __same_as<_Env, __root_env>
127                         && __same_as<void, __submit_result_t<_Sender, __submit_receiver>>;
128 
129     struct start_detached_t {
130       template <sender_in<__root_env> _Sender>
131         requires __callable<
132           apply_sender_t,
133           __late_domain_of_t<_Sender, __root_env, __early_domain_of_t<_Sender>>,
134           start_detached_t,
135           _Sender
136         >
operator ()stdexec::__start_detached::start_detached_t137       void operator()(_Sender&& __sndr) const {
138         auto __domain = __get_late_domain(__sndr, __root_env{}, __get_early_domain(__sndr));
139         stdexec::apply_sender(__domain, *this, static_cast<_Sender&&>(__sndr));
140       }
141 
142       template <class _Env, sender_in<__as_root_env_t<_Env>> _Sender>
143         requires __callable<
144           apply_sender_t,
145           __late_domain_of_t<_Sender, __as_root_env_t<_Env>, __early_domain_of_t<_Sender>>,
146           start_detached_t,
147           _Sender,
148           __as_root_env_t<_Env>
149         >
operator ()stdexec::__start_detached::start_detached_t150       void operator()(_Sender&& __sndr, _Env&& __env) const {
151         auto __env2 = __as_root_env(static_cast<_Env&&>(__env));
152         auto __domain = __get_late_domain(__sndr, __env2, __get_early_domain(__sndr));
153         stdexec::apply_sender(__domain, *this, static_cast<_Sender&&>(__sndr), __env2);
154       }
155 
156       // Below is the default implementation for `start_detached`.
157       template <class _Sender, class _Env = __root_env>
158         requires sender_in<_Sender, __as_root_env_t<_Env>>
apply_senderstdexec::__start_detached::start_detached_t159       void apply_sender(_Sender&& __sndr, _Env&& __env = {}) const noexcept(false) {
160         using _Op = __operation<__cvref_id<_Sender>, __id<__decay_t<_Env>>>;
161 
162 #if !STDEXEC_APPLE_CLANG() // There seems to be a codegen bug in apple clang that causes
163                            // `start_detached` to segfault when the code path below is
164                            // taken.
165         // BUGBUG NOT TO SPEC: the use of the non-standard `submit` algorithm here is a
166         // conforming extension.
167         if constexpr (__use_submit<_Sender, _Env>) {
168           // If submit(sndr, rcvr) returns void, then no state needs to be kept alive
169           // for the operation. We can just call submit and return.
170           stdexec::__submit::__submit(static_cast<_Sender&&>(__sndr), __submit_receiver{});
171         } else
172 #endif
173           if constexpr (__callable<get_allocator_t, _Env>) {
174           // Use the provided allocator if any to allocate the operation state.
175           auto __alloc = get_allocator(__env);
176           using _Alloc = decltype(__alloc);
177           using _OpAlloc = std::allocator_traits<_Alloc>::template rebind_alloc<_Op>;
178           // We use the allocator to allocate the op state and also to construct it.
179           _OpAlloc __op_alloc{__alloc};
180           _Op* __op = std::allocator_traits<_OpAlloc>::allocate(__op_alloc, 1);
__anon311ab2ec0102() 181           __scope_guard __g{[__op, &__op_alloc]() noexcept {
182             std::allocator_traits<_OpAlloc>::deallocate(__op_alloc, __op, 1);
183           }};
184           // This can potentially throw. If it does, the scope guard will deallocate the
185           // storage automatically.
186           std::allocator_traits<_OpAlloc>::construct(
187             __op_alloc, __op, static_cast<_Sender&&>(__sndr), static_cast<_Env&&>(__env));
188           // The operation state is now constructed, dismiss the scope guard.
189           __g.__dismiss();
190         } else {
191           // The caller did not provide an allocator, so we use the default allocator.
192           [[maybe_unused]]
193           _Op* __op = new _Op(static_cast<_Sender&&>(__sndr), static_cast<_Env&&>(__env));
194           // The operation has now started and is responsible for deleting itself when it
195           // completes.
196         }
197       }
198     };
199   } // namespace __start_detached
200 
201   using __start_detached::start_detached_t;
202   inline constexpr start_detached_t start_detached{};
203 } // namespace stdexec
204