100.00% Lines (188/188) 100.00% Functions (45/45)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Steve Gerbino 2   // Copyright (c) 2026 Steve Gerbino
3   // Copyright (c) 2026 Michael Vandeberg 3   // Copyright (c) 2026 Michael Vandeberg
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
9   // 9   //
10   10  
11   #ifndef BOOST_CAPY_WHEN_ALL_HPP 11   #ifndef BOOST_CAPY_WHEN_ALL_HPP
12   #define BOOST_CAPY_WHEN_ALL_HPP 12   #define BOOST_CAPY_WHEN_ALL_HPP
13   13  
14   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/detail/io_result_combinators.hpp> 15   #include <boost/capy/detail/io_result_combinators.hpp>
16   #include <boost/capy/continuation.hpp> 16   #include <boost/capy/continuation.hpp>
17   #include <boost/capy/concept/executor.hpp> 17   #include <boost/capy/concept/executor.hpp>
18   #include <boost/capy/concept/io_awaitable.hpp> 18   #include <boost/capy/concept/io_awaitable.hpp>
19   #include <coroutine> 19   #include <coroutine>
  20 + #include <boost/capy/detail/abandonable.hpp>
  21 + #include <boost/capy/detail/owned_range.hpp>
  22 + #include <boost/capy/ex/abandon.hpp>
20   #include <boost/capy/ex/frame_alloc_mixin.hpp> 23   #include <boost/capy/ex/frame_alloc_mixin.hpp>
21   #include <boost/capy/ex/io_env.hpp> 24   #include <boost/capy/ex/io_env.hpp>
22   #include <boost/capy/ex/frame_allocator.hpp> 25   #include <boost/capy/ex/frame_allocator.hpp>
23   #include <boost/capy/task.hpp> 26   #include <boost/capy/task.hpp>
24   27  
25   #include <array> 28   #include <array>
26   #include <atomic> 29   #include <atomic>
27   #include <exception> 30   #include <exception>
28   #include <memory> 31   #include <memory>
29   #include <optional> 32   #include <optional>
30   #include <ranges> 33   #include <ranges>
31   #include <stdexcept> 34   #include <stdexcept>
32   #include <stop_token> 35   #include <stop_token>
33   #include <tuple> 36   #include <tuple>
34   #include <type_traits> 37   #include <type_traits>
35   #include <utility> 38   #include <utility>
36   #include <vector> 39   #include <vector>
37   40  
38   namespace boost { 41   namespace boost {
39   namespace capy { 42   namespace capy {
40   43  
41   namespace detail { 44   namespace detail {
42   45  
43   /** Holds the result of a single task within when_all. 46   /** Holds the result of a single task within when_all.
44   */ 47   */
45   template<typename T> 48   template<typename T>
46   struct result_holder 49   struct result_holder
47   { 50   {
48   std::optional<T> value_; 51   std::optional<T> value_;
49   52  
HITCBC 50   119 void set(T v) 53   121 void set(T v)
51   { 54   {
HITCBC 52   119 value_ = std::move(v); 55   121 value_ = std::move(v);
HITCBC 53   119 } 56   121 }
54   57  
HITCBC 55   105 T get() && 58   105 T get() &&
56   { 59   {
HITCBC 57   105 return std::move(*value_); 60   105 return std::move(*value_);
58   } 61   }
59   }; 62   };
60   63  
61   /** Core shared state for when_all operations. 64   /** Core shared state for when_all operations.
62   65  
63   Contains all members and methods common to both heterogeneous (variadic) 66   Contains all members and methods common to both heterogeneous (variadic)
64   and homogeneous (range) when_all implementations. State classes embed 67   and homogeneous (range) when_all implementations. State classes embed
65   this via composition to avoid CRTP destructor ordering issues. 68   this via composition to avoid CRTP destructor ordering issues.
66   69  
67   @par Thread Safety 70   @par Thread Safety
68   Atomic operations protect exception capture and completion count. 71   Atomic operations protect exception capture and completion count.
69   */ 72   */
70   struct when_all_core 73   struct when_all_core
71   { 74   {
72   std::atomic<std::size_t> remaining_count_; 75   std::atomic<std::size_t> remaining_count_;
73   76  
74   // Exception storage - first error wins, others discarded 77   // Exception storage - first error wins, others discarded
75   std::atomic<bool> has_exception_{false}; 78   std::atomic<bool> has_exception_{false};
76   std::exception_ptr first_exception_; 79   std::exception_ptr first_exception_;
77   80  
78   std::stop_source stop_source_; 81   std::stop_source stop_source_;
79   82  
80   // Bridges parent's stop token to our stop_source 83   // Bridges parent's stop token to our stop_source
81   struct stop_callback_fn 84   struct stop_callback_fn
82   { 85   {
83   std::stop_source* source_; 86   std::stop_source* source_;
HITCBC 84 - 3 void operator()() const { source_->request_stop(); } 87 + 5 void operator()() const
  88 + {
  89 + // Stopping can lose the last branch and destroy the join,
  90 + // with its source, before the request returns; the copy
  91 + // keeps the stop state alive until then.
HITGNC   92 + 5 std::stop_source keep = *source_;
HITGNC   93 + 5 keep.request_stop();
HITGNC   94 + 5 }
85   }; 95   };
86   using stop_callback_t = std::stop_callback<stop_callback_fn>; 96   using stop_callback_t = std::stop_callback<stop_callback_fn>;
87   std::optional<stop_callback_t> parent_stop_callback_; 97   std::optional<stop_callback_t> parent_stop_callback_;
88   98  
89   continuation continuation_; 99   continuation continuation_;
90   io_env const* caller_env_ = nullptr; 100   io_env const* caller_env_ = nullptr;
91   101  
  102 + // Set when a branch is abandoned before finishing: the parent is
  103 + // then abandoned rather than resumed.
  104 + std::atomic<bool> lost_{false};
  105 +
  106 + /// Drop a share held by a runner that never finished.
HITGNC   107 + 8 void lose_branch() noexcept
  108 + {
HITGNC   109 + 8 lost_.store(true, std::memory_order_relaxed);
HITGNC   110 + 8 stop_source_.request_stop();
HITGNC   111 + 16 if(remaining_count_.fetch_sub(1, std::memory_order_acq_rel) == 1)
  112 + {
HITGNC   113 + 4 auto parent = continuation_;
HITGNC   114 + 4 capy::abandon(parent);
  115 + }
HITGNC   116 + 8 }
  117 +
HITCBC 92   82 explicit when_all_core(std::size_t count) noexcept 118   98 explicit when_all_core(std::size_t count) noexcept
HITCBC 93   82 : remaining_count_(count) 119   98 : remaining_count_(count)
94   { 120   {
HITCBC 95   82 } 121   98 }
96   122  
97   /** Capture an exception (first one wins). */ 123   /** Capture an exception (first one wins). */
HITCBC 98   21 void capture_exception(std::exception_ptr ep) 124   21 void capture_exception(std::exception_ptr ep)
99   { 125   {
HITCBC 100   21 bool expected = false; 126   21 bool expected = false;
HITCBC 101   21 if(has_exception_.compare_exchange_strong( 127   21 if(has_exception_.compare_exchange_strong(
102   expected, true, std::memory_order_relaxed)) 128   expected, true, std::memory_order_relaxed))
HITCBC 103   19 first_exception_ = ep; 129   19 first_exception_ = ep;
HITCBC 104   21 } 130   21 }
105   }; 131   };
106   132  
107   /** Shared state for heterogeneous when_all (variadic overload). 133   /** Shared state for heterogeneous when_all (variadic overload).
108   134  
109   @tparam Ts The result types of the tasks. 135   @tparam Ts The result types of the tasks.
110   */ 136   */
111   template<typename... Ts> 137   template<typename... Ts>
112   struct when_all_state 138   struct when_all_state
113   { 139   {
114   static constexpr std::size_t task_count = sizeof...(Ts); 140   static constexpr std::size_t task_count = sizeof...(Ts);
115   141  
116   when_all_core core_; 142   when_all_core core_;
117   std::tuple<result_holder<Ts>...> results_; 143   std::tuple<result_holder<Ts>...> results_;
118   std::array<continuation, task_count> runner_handles_{}; 144   std::array<continuation, task_count> runner_handles_{};
119   145  
120   std::atomic<bool> has_error_{false}; 146   std::atomic<bool> has_error_{false};
121   std::error_code first_error_; 147   std::error_code first_error_;
122   148  
HITCBC 123   66 when_all_state() 149   78 when_all_state()
HITCBC 124   66 : core_(task_count) 150   78 : core_(task_count)
125   { 151   {
HITCBC 126   66 } 152   78 }
127   153  
128   /** Record the first error (subsequent errors are discarded). */ 154   /** Record the first error (subsequent errors are discarded). */
HITCBC 129   46 void record_error(std::error_code ec) 155   46 void record_error(std::error_code ec)
130   { 156   {
HITCBC 131   46 bool expected = false; 157   46 bool expected = false;
HITCBC 132   46 if(has_error_.compare_exchange_strong( 158   46 if(has_error_.compare_exchange_strong(
133   expected, true, std::memory_order_relaxed)) 159   expected, true, std::memory_order_relaxed))
HITCBC 134   32 first_error_ = ec; 160   32 first_error_ = ec;
HITCBC 135   46 } 161   46 }
136   }; 162   };
137   163  
138   /** Shared state for homogeneous when_all (range overload). 164   /** Shared state for homogeneous when_all (range overload).
139   165  
140   Stores extracted io_result payloads in a vector indexed by task 166   Stores extracted io_result payloads in a vector indexed by task
141   position. Tracks the first error_code for error propagation. 167   position. Tracks the first error_code for error propagation.
142   168  
143   @tparam T The payload type extracted from io_result. 169   @tparam T The payload type extracted from io_result.
144   */ 170   */
145   template<typename T> 171   template<typename T>
146   struct when_all_homogeneous_state 172   struct when_all_homogeneous_state
147   { 173   {
148   when_all_core core_; 174   when_all_core core_;
149   std::vector<std::optional<T>> results_; 175   std::vector<std::optional<T>> results_;
150   std::unique_ptr<continuation[]> runner_handles_; 176   std::unique_ptr<continuation[]> runner_handles_;
151   177  
152   std::atomic<bool> has_error_{false}; 178   std::atomic<bool> has_error_{false};
153   std::error_code first_error_; 179   std::error_code first_error_;
154   180  
HITCBC 155   13 explicit when_all_homogeneous_state(std::size_t count) 181   15 explicit when_all_homogeneous_state(std::size_t count)
HITCBC 156   13 : core_(count) 182   15 : core_(count)
HITCBC 157   26 , results_(count) 183   30 , results_(count)
HITCBC 158   13 , runner_handles_(std::make_unique<continuation[]>(count)) 184   15 , runner_handles_(std::make_unique<continuation[]>(count))
159   { 185   {
HITCBC 160   13 } 186   15 }
161   187  
HITCBC 162   21 void set_result(std::size_t index, T value) 188   27 void set_result(std::size_t index, T value)
163   { 189   {
HITCBC 164   21 results_[index].emplace(std::move(value)); 190   27 results_[index].emplace(std::move(value));
HITCBC 165   21 } 191   27 }
166   192  
167   /** Record the first error (subsequent errors are discarded). */ 193   /** Record the first error (subsequent errors are discarded). */
HITCBC 168   7 void record_error(std::error_code ec) 194   7 void record_error(std::error_code ec)
169   { 195   {
HITCBC 170   7 bool expected = false; 196   7 bool expected = false;
HITCBC 171   7 if(has_error_.compare_exchange_strong( 197   7 if(has_error_.compare_exchange_strong(
172   expected, true, std::memory_order_relaxed)) 198   expected, true, std::memory_order_relaxed))
HITCBC 173   5 first_error_ = ec; 199   5 first_error_ = ec;
HITCBC 174   7 } 200   7 }
175   }; 201   };
176   202  
177   /** Specialization for void io_result children (no payload storage). */ 203   /** Specialization for void io_result children (no payload storage). */
178   template<> 204   template<>
179   struct when_all_homogeneous_state<std::tuple<>> 205   struct when_all_homogeneous_state<std::tuple<>>
180   { 206   {
181   when_all_core core_; 207   when_all_core core_;
182   std::unique_ptr<continuation[]> runner_handles_; 208   std::unique_ptr<continuation[]> runner_handles_;
183   209  
184   std::atomic<bool> has_error_{false}; 210   std::atomic<bool> has_error_{false};
185   std::error_code first_error_; 211   std::error_code first_error_;
186   212  
HITCBC 187   3 explicit when_all_homogeneous_state(std::size_t count) 213   5 explicit when_all_homogeneous_state(std::size_t count)
HITCBC 188   3 : core_(count) 214   5 : core_(count)
HITCBC 189   3 , runner_handles_(std::make_unique<continuation[]>(count)) 215   5 , runner_handles_(std::make_unique<continuation[]>(count))
190   { 216   {
HITCBC 191   3 } 217   5 }
192   218  
193   /** Record the first error (subsequent errors are discarded). */ 219   /** Record the first error (subsequent errors are discarded). */
HITCBC 194   1 void record_error(std::error_code ec) 220   1 void record_error(std::error_code ec)
195   { 221   {
HITCBC 196   1 bool expected = false; 222   1 bool expected = false;
HITCBC 197   1 if(has_error_.compare_exchange_strong( 223   1 if(has_error_.compare_exchange_strong(
198   expected, true, std::memory_order_relaxed)) 224   expected, true, std::memory_order_relaxed))
HITCBC 199   1 first_error_ = ec; 225   1 first_error_ = ec;
HITCBC 200   1 } 226   1 }
201   }; 227   };
202   228  
203   /** Wrapper coroutine that intercepts task completion for when_all. 229   /** Wrapper coroutine that intercepts task completion for when_all.
204   230  
205   Parameterized on StateType to work with both heterogeneous (variadic) 231   Parameterized on StateType to work with both heterogeneous (variadic)
206   and homogeneous (range) state types. All state types expose their 232   and homogeneous (range) state types. All state types expose their
207   shared members through a `core_` member of type when_all_core. 233   shared members through a `core_` member of type when_all_core.
208   234  
209   @tparam StateType The state type (when_all_state or when_all_homogeneous_state). 235   @tparam StateType The state type (when_all_state or when_all_homogeneous_state).
210   */ 236   */
211   template<typename StateType> 237   template<typename StateType>
212 - struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE when_all_runner 238 + struct when_all_runner
213   { 239   {
214   struct promise_type 240   struct promise_type
215 - : frame_alloc_mixin 241 + : detail::process_frame_mixin
216   { 242   {
217   StateType* state_ = nullptr; 243   StateType* state_ = nullptr;
218   std::size_t index_ = 0; 244   std::size_t index_ = 0;
219   io_env env_; 245   io_env env_;
  246 + // Owns the branch's segment.
  247 + chain_owner segment_;
  248 + bool done_ = false;
  249 +
HITGNC   250 + 209 ~promise_type()
  251 + {
HITGNC   252 + 209 if(!done_ && state_)
HITGNC   253 + 8 state_->core_.lose_branch();
HITGNC   254 + 209 }
220   255  
HITCBC 221   174 when_all_runner get_return_object() noexcept 256   209 when_all_runner get_return_object() noexcept
222   { 257   {
223   return when_all_runner( 258   return when_all_runner(
HITCBC 224   174 std::coroutine_handle<promise_type>::from_promise(*this)); 259   209 std::coroutine_handle<promise_type>::from_promise(*this));
225   } 260   }
226   261  
HITCBC 227   174 std::suspend_always initial_suspend() noexcept 262   209 std::suspend_always initial_suspend() noexcept
228   { 263   {
HITCBC 229   174 return {}; 264   209 return {};
230   } 265   }
231   266  
HITCBC 232   174 auto final_suspend() noexcept 267   201 auto final_suspend() noexcept
233   { 268   {
234   struct awaiter 269   struct awaiter
235   { 270   {
236   promise_type* p_; 271   promise_type* p_;
HITCBC 237   174 bool await_ready() const noexcept { return false; } 272   201 bool await_ready() const noexcept { return false; }
HITCBC 238   174 auto await_suspend(std::coroutine_handle<> h) noexcept 273   201 auto await_suspend(std::coroutine_handle<> h) noexcept
239   { 274   {
HITCBC 240   174 auto& core = p_->state_->core_; 275   201 auto& core = p_->state_->core_;
HITCBC 241   174 auto* counter = &core.remaining_count_; 276   201 auto* counter = &core.remaining_count_;
HITGNC   277 + 201 auto* lost = &core.lost_;
HITCBC 242   174 auto* caller_env = core.caller_env_; 278   201 auto* caller_env = core.caller_env_;
HITCBC 243   174 auto& cont = core.continuation_; 279   201 auto& cont = core.continuation_;
244   280  
HITGNC   281 + 201 p_->done_ = true;
  282 + // Resumed to unwind its abandoned branch: lost, not
  283 + // finished. Stop is requested while this branch
  284 + // still holds its share.
HITGNC   285 + 201 if(p_->segment_.abandoned())
  286 + {
HITGNC   287 + 19 lost->store(true, std::memory_order_relaxed);
HITGNC   288 + 19 core.stop_source_.request_stop();
  289 + }
HITCBC 245   174 h.destroy(); 290   201 h.destroy();
246   291  
HITCBC 247   174 auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel); 292   201 auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
HITCBC 248 - 174 if(remaining == 1) 293 + 201 if(remaining != 1)
HITCBC 249 - 82 return detail::symmetric_transfer(caller_env->executor.dispatch(cont)); 294 + 107 return detail::symmetric_transfer(std::noop_coroutine());
HITCBC 250 - 92 return detail::symmetric_transfer(std::noop_coroutine()); 295 + 94 if(lost->load(std::memory_order_relaxed))
  296 + {
HITGNC   297 + 10 auto parent = cont;
HITGNC   298 + 10 capy::abandon(parent);
HITGNC   299 + 10 return detail::symmetric_transfer(std::noop_coroutine());
  300 + }
HITGNC   301 + 84 return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
251   } 302   }
252   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 303   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
253   }; 304   };
HITCBC 254   174 return awaiter{this}; 305   201 return awaiter{this};
255   } 306   }
256   307  
HITCBC 257   153 void return_void() noexcept {} 308   180 void return_void() noexcept {}
258   309  
HITCBC 259   21 void unhandled_exception() noexcept 310   21 void unhandled_exception() noexcept
260   { 311   {
HITCBC 261   21 state_->core_.capture_exception(std::current_exception()); 312   21 state_->core_.capture_exception(std::current_exception());
HITCBC 262   21 state_->core_.stop_source_.request_stop(); 313   21 state_->core_.stop_source_.request_stop();
HITCBC 263   21 } 314   21 }
264   315  
265   template<class Awaitable> 316   template<class Awaitable>
266   struct transform_awaiter 317   struct transform_awaiter
267   { 318   {
268   std::decay_t<Awaitable> a_; 319   std::decay_t<Awaitable> a_;
269   promise_type* p_; 320   promise_type* p_;
270   321  
HITCBC 271   174 bool await_ready() { return a_.await_ready(); } 322   203 bool await_ready() { return a_.await_ready(); }
HITCBC 272   174 decltype(auto) await_resume() { return a_.await_resume(); } 323   201 decltype(auto) await_resume() { return a_.await_resume(); }
273   324  
274   template<class Promise> 325   template<class Promise>
HITCBC 275   174 auto await_suspend(std::coroutine_handle<Promise> h) 326   203 auto await_suspend(std::coroutine_handle<Promise> h)
276   { 327   {
277   using R = decltype(a_.await_suspend(h, &p_->env_)); 328   using R = decltype(a_.await_suspend(h, &p_->env_));
278   if constexpr (std::is_same_v<R, std::coroutine_handle<>>) 329   if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
HITCBC 279   174 return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_)); 330   203 return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
280   else 331   else
281   return a_.await_suspend(h, &p_->env_); 332   return a_.await_suspend(h, &p_->env_);
282   } 333   }
283   }; 334   };
284   335  
285   template<class Awaitable> 336   template<class Awaitable>
HITCBC 286   174 auto await_transform(Awaitable&& a) 337   203 auto await_transform(Awaitable&& a)
287   { 338   {
288   using A = std::decay_t<Awaitable>; 339   using A = std::decay_t<Awaitable>;
289   if constexpr (IoAwaitable<A>) 340   if constexpr (IoAwaitable<A>)
290   { 341   {
291   return transform_awaiter<Awaitable>{ 342   return transform_awaiter<Awaitable>{
HITCBC 292   348 std::forward<Awaitable>(a), this}; 343   203 std::forward<Awaitable>(a), this};
293   } 344   }
294   else 345   else
295   { 346   {
296   static_assert(sizeof(A) == 0, "requires IoAwaitable"); 347   static_assert(sizeof(A) == 0, "requires IoAwaitable");
297   } 348   }
ECB 298   174 } 349   }
299   }; 350   };
300   351  
301   std::coroutine_handle<promise_type> h_; 352   std::coroutine_handle<promise_type> h_;
302   353  
HITCBC 303   174 explicit when_all_runner(std::coroutine_handle<promise_type> h) noexcept 354   209 explicit when_all_runner(std::coroutine_handle<promise_type> h) noexcept
HITCBC 304   174 : h_(h) 355   209 : h_(h)
305   { 356   {
HITCBC 306   174 } 357   209 }
307   358  
308   // Enable move for all clang versions - some versions need it 359   // Enable move for all clang versions - some versions need it
309   when_all_runner(when_all_runner&& other) noexcept 360   when_all_runner(when_all_runner&& other) noexcept
310   : h_(std::exchange(other.h_, nullptr)) 361   : h_(std::exchange(other.h_, nullptr))
311   { 362   {
312   } 363   }
313   364  
314   when_all_runner(when_all_runner const&) = delete; 365   when_all_runner(when_all_runner const&) = delete;
315   when_all_runner& operator=(when_all_runner const&) = delete; 366   when_all_runner& operator=(when_all_runner const&) = delete;
316   when_all_runner& operator=(when_all_runner&&) = delete; 367   when_all_runner& operator=(when_all_runner&&) = delete;
317   368  
HITCBC 318   174 auto release() noexcept 369   209 auto release() noexcept
319   { 370   {
HITCBC 320   174 return std::exchange(h_, nullptr); 371   209 return std::exchange(h_, nullptr);
321   } 372   }
322   }; 373   };
323   374  
324   /** Create an io_result-aware runner for a single awaitable (range path). 375   /** Create an io_result-aware runner for a single awaitable (range path).
325   376  
326   Checks the error code, records errors and requests stop on failure, 377   Checks the error code, records errors and requests stop on failure,
327   or extracts the payload on success. 378   or extracts the payload on success.
328   */ 379   */
329   template<IoAwaitable Awaitable, typename StateType> 380   template<IoAwaitable Awaitable, typename StateType>
330   when_all_runner<StateType> 381   when_all_runner<StateType>
HITCBC 331 - 37 make_when_all_homogeneous_runner(Awaitable inner, StateType* state, std::size_t index) 382 + 48 make_when_all_homogeneous_runner(Awaitable* inner, StateType* state, std::size_t index)
332   { 383   {
333 - auto result = co_await std::move(inner); 384 + // Taken from the join's storage only once running, into a local:
  385 + // a runner destroyed unstarted owns no branch, and one destroyed
  386 + // unfinished destroys it before its promise reports the loss.
  387 + Awaitable a(std::move(*inner));
  388 + auto r = co_await abandonable<Awaitable>{a};
  389 + if(!r)
  390 + co_return;
  391 + auto& result = *r;
334   392  
335   if(std::get<0>(result)) 393   if(std::get<0>(result))
336   { 394   {
337   state->record_error(std::get<0>(result)); 395   state->record_error(std::get<0>(result));
338   state->core_.stop_source_.request_stop(); 396   state->core_.stop_source_.request_stop();
339   } 397   }
340   else 398   else
341   { 399   {
342   using PayloadT = io_result_payload_t< 400   using PayloadT = io_result_payload_t<
343   awaitable_result_t<Awaitable>>; 401   awaitable_result_t<Awaitable>>;
344   if constexpr (!std::is_same_v<PayloadT, std::tuple<>>) 402   if constexpr (!std::is_same_v<PayloadT, std::tuple<>>)
345   { 403   {
346   state->set_result(index, 404   state->set_result(index,
347   extract_io_payload(std::move(result))); 405   extract_io_payload(std::move(result)));
348   } 406   }
349   } 407   }
HITCBC 350   74 } 408   96 }
351   409  
352   /** Create a runner for io_result children that requests stop on ec. */ 410   /** Create a runner for io_result children that requests stop on ec. */
353   template<std::size_t Index, IoAwaitable Awaitable, typename... Ts> 411   template<std::size_t Index, IoAwaitable Awaitable, typename... Ts>
354   when_all_runner<when_all_state<Ts...>> 412   when_all_runner<when_all_state<Ts...>>
HITCBC 355 - 137 make_when_all_io_runner(Awaitable inner, when_all_state<Ts...>* state) 413 + 161 make_when_all_io_runner(Awaitable* inner, when_all_state<Ts...>* state)
356   { 414   {
357 - auto result = co_await std::move(inner); 415 + // Taken from the join's storage only once running, into a local:
358 - auto ec = std::get<0>(result); 416 + // a runner destroyed unstarted owns no branch, and one destroyed
359 - std::get<Index>(state->results_).set(std::move(result)); 417 + // unfinished destroys it before its promise reports the loss.
  418 + Awaitable a(std::move(*inner));
  419 + auto r = co_await abandonable<Awaitable>{a};
  420 + if(!r)
  421 + co_return;
  422 + auto ec = std::get<0>(*r);
  423 + std::get<Index>(state->results_).set(std::move(*r));
360   424  
361   if(ec) 425   if(ec)
362   { 426   {
363   state->record_error(ec); 427   state->record_error(ec);
364   state->core_.stop_source_.request_stop(); 428   state->core_.stop_source_.request_stop();
365   } 429   }
HITCBC 366   274 } 430   322 }
367   431  
368   /** Launcher that uses io_result-aware runners. */ 432   /** Launcher that uses io_result-aware runners. */
369   template<IoAwaitable... Awaitables> 433   template<IoAwaitable... Awaitables>
370   class when_all_io_launcher 434   class when_all_io_launcher
371   { 435   {
372   using state_type = when_all_state<awaitable_result_t<Awaitables>...>; 436   using state_type = when_all_state<awaitable_result_t<Awaitables>...>;
373   437  
374   std::tuple<Awaitables...>* awaitables_; 438   std::tuple<Awaitables...>* awaitables_;
375   state_type* state_; 439   state_type* state_;
376   440  
377   public: 441   public:
HITCBC 378   66 when_all_io_launcher( 442   78 when_all_io_launcher(
379   std::tuple<Awaitables...>* awaitables, 443   std::tuple<Awaitables...>* awaitables,
380   state_type* state) 444   state_type* state)
HITCBC 381   66 : awaitables_(awaitables) 445   78 : awaitables_(awaitables)
HITCBC 382   66 , state_(state) 446   78 , state_(state)
383   { 447   {
HITCBC 384   66 } 448   78 }
385   449  
HITCBC 386   66 bool await_ready() const noexcept 450   78 bool await_ready() const noexcept
387   { 451   {
HITCBC 388   66 return sizeof...(Awaitables) == 0; 452   78 return sizeof...(Awaitables) == 0;
389   } 453   }
390   454  
HITCBC 391   66 std::coroutine_handle<> await_suspend( 455   78 std::coroutine_handle<> await_suspend(
392   std::coroutine_handle<> continuation, io_env const* caller_env) 456   std::coroutine_handle<> continuation, io_env const* caller_env)
393   { 457   {
HITCBC 394   66 state_->core_.continuation_.h = continuation; 458   78 state_->core_.continuation_.h = continuation;
HITGNC   459 + 78 state_->core_.continuation_.owner = caller_env->owner;
HITCBC 395   66 state_->core_.caller_env_ = caller_env; 460   78 state_->core_.caller_env_ = caller_env;
396   461  
HITCBC 397   66 if(caller_env->stop_token.stop_possible()) 462   78 if(caller_env->stop_token.stop_possible())
398   { 463   {
HITCBC 399   4 state_->core_.parent_stop_callback_.emplace( 464   8 state_->core_.parent_stop_callback_.emplace(
HITCBC 400   2 caller_env->stop_token, 465   4 caller_env->stop_token,
HITCBC 401   2 when_all_core::stop_callback_fn{&state_->core_.stop_source_}); 466   4 when_all_core::stop_callback_fn{&state_->core_.stop_source_});
402   467  
HITCBC 403   2 if(caller_env->stop_token.stop_requested()) 468   4 if(caller_env->stop_token.stop_requested())
HITCBC 404   1 state_->core_.stop_source_.request_stop(); 469   1 state_->core_.stop_source_.request_stop();
405   } 470   }
406   471  
HITCBC 407   66 auto token = state_->core_.stop_source_.get_token(); 472   78 auto token = state_->core_.stop_source_.get_token();
HITCBC 408   66 launch_all(std::index_sequence_for<Awaitables...>{}, 473   78 launch_all(std::index_sequence_for<Awaitables...>{},
409   caller_env->executor, token); 474   caller_env->executor, token);
410   475  
HITCBC 411   132 return std::noop_coroutine(); 476   156 return std::noop_coroutine();
HITCBC 412   66 } 477   78 }
413   478  
HITCBC 414   66 void await_resume() const noexcept {} 479   66 void await_resume() const noexcept {}
415   480  
416   private: 481   private:
417   template<std::size_t... Is> 482   template<std::size_t... Is>
HITCBC 418   66 void launch_all(std::index_sequence<Is...>, 483   78 void launch_all(std::index_sequence<Is...>,
419   executor_ref ex, std::stop_token token) 484   executor_ref ex, std::stop_token token)
420   { 485   {
HITCBC 421   66 (..., launch_one<Is>(ex, token)); 486   78 (..., launch_one<Is>(ex, token));
HITCBC 422   66 } 487   78 }
423   488  
424   template<std::size_t I> 489   template<std::size_t I>
HITCBC 425   137 void launch_one(executor_ref caller_ex, std::stop_token token) 490   161 void launch_one(executor_ref caller_ex, std::stop_token token)
426   { 491   {
HITCBC 427   137 auto runner = make_when_all_io_runner<I>( 492   161 auto runner = make_when_all_io_runner<I>(
HITCBC 428 - 137 std::move(std::get<I>(*awaitables_)), state_); 493 + 161 &std::get<I>(*awaitables_), state_);
429   494  
HITCBC 430   137 auto h = runner.release(); 495   161 auto h = runner.release();
HITCBC 431   137 h.promise().state_ = state_; 496   161 h.promise().state_ = state_;
HITGNC   497 + 161 h.promise().segment_.bind(h, caller_ex);
HITCBC 432   137 h.promise().env_ = io_env{caller_ex, token, 498   161 h.promise().env_ = io_env{caller_ex, token,
HITCBC 433 - 137 state_->core_.caller_env_->frame_allocator}; 499 + 161 state_->core_.caller_env_->frame_allocator,
HITGNC   500 + 322 &h.promise().segment_};
434   501  
HITCBC 435   137 state_->runner_handles_[I].h = std::coroutine_handle<>{h}; 502   161 state_->runner_handles_[I].h = std::coroutine_handle<>{h};
HITCBC 436   137 state_->core_.caller_env_->executor.post(state_->runner_handles_[I]); 503   161 state_->core_.caller_env_->executor.post(state_->runner_handles_[I]);
HITCBC 437   274 } 504   322 }
438   }; 505   };
439   506  
440   /** Helper to extract a single result from state. 507   /** Helper to extract a single result from state.
441   This is a separate function to work around a GCC-11 ICE that occurs 508   This is a separate function to work around a GCC-11 ICE that occurs
442   when using nested immediately-invoked lambdas with pack expansion. 509   when using nested immediately-invoked lambdas with pack expansion.
443   */ 510   */
444   template<std::size_t I, typename... Ts> 511   template<std::size_t I, typename... Ts>
HITCBC 445   105 auto extract_single_result(when_all_state<Ts...>& state) 512   105 auto extract_single_result(when_all_state<Ts...>& state)
446   { 513   {
HITCBC 447   105 return std::move(std::get<I>(state.results_)).get(); 514   105 return std::move(std::get<I>(state.results_)).get();
448   } 515   }
449   516  
450   /** Extract all results from state as a tuple. 517   /** Extract all results from state as a tuple.
451   */ 518   */
452   template<typename... Ts> 519   template<typename... Ts>
HITCBC 453   50 auto extract_results(when_all_state<Ts...>& state) 520   50 auto extract_results(when_all_state<Ts...>& state)
454   { 521   {
HITCBC 455   82 return [&]<std::size_t... Is>(std::index_sequence<Is...>) { 522   82 return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
456   // Explicit element types: CTAD would collapse a single 523   // Explicit element types: CTAD would collapse a single
457   // io_result child via the tuple copy deduction guide 524   // io_result child via the tuple copy deduction guide
458   return std::tuple< 525   return std::tuple<
459   decltype(extract_single_result<Is>(state))...>( 526   decltype(extract_single_result<Is>(state))...>(
HITCBC 460   50 extract_single_result<Is>(state)...); 527   50 extract_single_result<Is>(state)...);
HITCBC 461   100 }(std::index_sequence_for<Ts...>{}); 528   100 }(std::index_sequence_for<Ts...>{});
462   } 529   }
463   530  
464   /** Starts all homogeneous runners concurrently. 531   /** Starts all homogeneous runners concurrently.
465   532  
466   Two-phase approach: create all runners first, then post all. 533   Two-phase approach: create all runners first, then post all.
467   This avoids lifetime issues if a task completes synchronously. 534   This avoids lifetime issues if a task completes synchronously.
468   */ 535   */
469   template<typename Range> 536   template<typename Range>
470   class when_all_homogeneous_launcher 537   class when_all_homogeneous_launcher
471   { 538   {
472   using Awaitable = std::ranges::range_value_t<Range>; 539   using Awaitable = std::ranges::range_value_t<Range>;
473   using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>; 540   using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>;
474   541  
475   Range* range_; 542   Range* range_;
476   when_all_homogeneous_state<PayloadT>* state_; 543   when_all_homogeneous_state<PayloadT>* state_;
477   544  
478   public: 545   public:
HITCBC 479   16 when_all_homogeneous_launcher( 546   20 when_all_homogeneous_launcher(
480   Range* range, 547   Range* range,
481   when_all_homogeneous_state<PayloadT>* state) 548   when_all_homogeneous_state<PayloadT>* state)
HITCBC 482   16 : range_(range) 549   20 : range_(range)
HITCBC 483   16 , state_(state) 550   20 , state_(state)
484   { 551   {
HITCBC 485   16 } 552   20 }
486   553  
HITCBC 487   16 bool await_ready() const noexcept 554   20 bool await_ready() const noexcept
488   { 555   {
HITCBC 489   16 return std::ranges::empty(*range_); 556   20 return std::ranges::empty(*range_);
490   } 557   }
491   558  
HITCBC 492   16 std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env) 559   20 std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
493   { 560   {
HITCBC 494   16 state_->core_.continuation_.h = continuation; 561   20 state_->core_.continuation_.h = continuation;
HITGNC   562 + 20 state_->core_.continuation_.owner = caller_env->owner;
HITCBC 495   16 state_->core_.caller_env_ = caller_env; 563   20 state_->core_.caller_env_ = caller_env;
496   564  
HITCBC 497   16 if(caller_env->stop_token.stop_possible()) 565   20 if(caller_env->stop_token.stop_possible())
498   { 566   {
HITCBC 499   4 state_->core_.parent_stop_callback_.emplace( 567   4 state_->core_.parent_stop_callback_.emplace(
HITCBC 500   2 caller_env->stop_token, 568   2 caller_env->stop_token,
HITCBC 501   2 when_all_core::stop_callback_fn{&state_->core_.stop_source_}); 569   2 when_all_core::stop_callback_fn{&state_->core_.stop_source_});
502   570  
HITCBC 503   2 if(caller_env->stop_token.stop_requested()) 571   2 if(caller_env->stop_token.stop_requested())
HITCBC 504   1 state_->core_.stop_source_.request_stop(); 572   1 state_->core_.stop_source_.request_stop();
505   } 573   }
506   574  
HITCBC 507   16 auto token = state_->core_.stop_source_.get_token(); 575   20 auto token = state_->core_.stop_source_.get_token();
508   576  
509   // Phase 1: Create all runners without dispatching. 577   // Phase 1: Create all runners without dispatching.
HITCBC 510   16 std::size_t index = 0; 578   20 std::size_t index = 0;
HITCBC 511   53 for(auto&& a : *range_) 579   68 for(auto&& a : *range_)
512   { 580   {
HITCBC 513   37 auto runner = make_when_all_homogeneous_runner( 581   48 auto runner = make_when_all_homogeneous_runner(
ECB 514 - 37 std::move(a), state_, index); 582 + &a, state_, index);
515   583  
HITCBC 516   37 auto h = runner.release(); 584   48 auto h = runner.release();
HITCBC 517   37 h.promise().state_ = state_; 585   48 h.promise().state_ = state_;
HITCBC 518   37 h.promise().index_ = index; 586   48 h.promise().index_ = index;
HITCBC 519 - 37 h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator}; 587 + 48 h.promise().segment_.bind(h, caller_env->executor);
HITGNC   588 + 48 h.promise().env_ = io_env{caller_env->executor, token,
HITGNC   589 + 96 caller_env->frame_allocator, &h.promise().segment_};
520   590  
HITCBC 521   37 state_->runner_handles_[index].h = std::coroutine_handle<>{h}; 591   48 state_->runner_handles_[index].h = std::coroutine_handle<>{h};
HITCBC 522   37 ++index; 592   48 ++index;
523   } 593   }
524   594  
525   // Phase 2: Post all runners. Any may complete synchronously. 595   // Phase 2: Post all runners. Any may complete synchronously.
526   // After last post, state_ and this may be destroyed. 596   // After last post, state_ and this may be destroyed.
HITCBC 527   16 auto* handles = state_->runner_handles_.get(); 597   20 auto* handles = state_->runner_handles_.get();
HITCBC 528   16 std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed); 598   20 std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed);
HITCBC 529   53 for(std::size_t i = 0; i < count; ++i) 599   68 for(std::size_t i = 0; i < count; ++i)
HITCBC 530   37 caller_env->executor.post(handles[i]); 600   48 caller_env->executor.post(handles[i]);
531   601  
HITCBC 532   32 return std::noop_coroutine(); 602   40 return std::noop_coroutine();
HITCBC 533   53 } 603   68 }
534   604  
HITCBC 535   16 void await_resume() const noexcept 605   18 void await_resume() const noexcept
536   { 606   {
HITCBC 537   16 } 607   18 }
538   }; 608   };
539   609  
540   } // namespace detail 610   } // namespace detail
541   611  
542   /** Execute a range of io_result-returning awaitables concurrently. 612   /** Execute a range of io_result-returning awaitables concurrently.
543   613  
544   Starts all awaitables simultaneously and waits for all to complete. 614   Starts all awaitables simultaneously and waits for all to complete.
545   On success, extracted payloads are collected in a vector preserving 615   On success, extracted payloads are collected in a vector preserving
546   input order. The first error_code makes a stop request that every 616   input order. The first error_code makes a stop request that every
547   sibling observes, and is propagated in the outer io_result. 617   sibling observes, and is propagated in the outer io_result.
548   Exceptions always beat error codes. 618   Exceptions always beat error codes.
549   619  
550   @li All child awaitables run concurrently on the caller's executor. 620   @li All child awaitables run concurrently on the caller's executor.
551   @li Payloads are returned as a vector in input order. 621   @li Payloads are returned as a vector in input order.
552   @li First error_code wins and makes a stop request that siblings observe. 622   @li First error_code wins and makes a stop request that siblings observe.
553   @li Exception always beats error_code. 623   @li Exception always beats error_code.
554   @li Completes only after all children have finished. 624   @li Completes only after all children have finished.
555   625  
556   @par Await-effects 626   @par Await-effects
557   627  
558   Takes ownership of the range, creates one wrapper coroutine per 628   Takes ownership of the range, creates one wrapper coroutine per
559   element, then posts every wrapper to the caller's executor. All 629   element, then posts every wrapper to the caller's executor. All
560   children therefore run concurrently, each awaited with the caller's 630   children therefore run concurrently, each awaited with the caller's
561   executor and frame allocator and with a stop token owned by this 631   executor and frame allocator and with a stop token owned by this
562   operation. 632   operation.
563   633  
564   Awaiting an empty range throws `std::invalid_argument` before any 634   Awaiting an empty range throws `std::invalid_argument` before any
565   child is started. 635   child is started.
566   636  
567   A stop request is made on the operation's own stop token when: 637   A stop request is made on the operation's own stop token when:
568   638  
569   @li a child await-returns a non-zero `ec`, or 639   @li a child await-returns a non-zero `ec`, or
570   @li a child exits via an exception, or 640   @li a child exits via an exception, or
571   @li the caller's stop token is triggered. 641   @li the caller's stop token is triggered.
572   642  
573   Every sibling observes that request through the stop token it was 643   Every sibling observes that request through the stop token it was
574   awaited with. The request does not end the operation: the await 644   awaited with. The request does not end the operation: the await
575   completes only after every child has finished. 645   completes only after every child has finished.
576   646  
577   @par Await-returns 647   @par Await-returns
578   An object of type `io_result<std::vector<PayloadT>>` destructuring as 648   An object of type `io_result<std::vector<PayloadT>>` destructuring as
579   `[ec, values]`, where `PayloadT` is the payload of one child's 649   `[ec, values]`, where `PayloadT` is the payload of one child's
580   `io_result`. 650   `io_result`.
581   651  
582   `ec` is the first non-zero `ec` await-returned by a child, in 652   `ec` is the first non-zero `ec` await-returned by a child, in
583   completion order rather than input order. The `ec` of every other 653   completion order rather than input order. The `ec` of every other
584   child is discarded. 654   child is discarded.
585   655  
586   On success, `values` holds one payload per element of the input 656   On success, `values` holds one payload per element of the input
587   range, in input order. If `ec` is set, `values` is empty: the 657   range, in input order. If `ec` is set, `values` is empty: the
588   payloads of the children that did succeed are discarded. 658   payloads of the children that did succeed are discarded.
589   659  
590   If any child exits via an exception, the first such exception is 660   If any child exits via an exception, the first such exception is
591   rethrown instead of await-returning, even when a child also reported 661   rethrown instead of await-returning, even when a child also reported
592   an `ec`. 662   an `ec`.
593   663  
594   @par Await-postcondition 664   @par Await-postcondition
595   Every child has finished. `ec` is success only if every child 665   Every child has finished. `ec` is success only if every child
596   await-returned success. If `ec` is success, `values` holds one 666   await-returned success. If `ec` is success, `values` holds one
597   payload per input awaitable; otherwise `values` is empty. 667   payload per input awaitable; otherwise `values` is empty.
598   668  
599   @par Remarks 669   @par Remarks
600   Supports _IoAwaitable cancellation_. 670   Supports _IoAwaitable cancellation_.
601   671  
602   @par Thread Safety 672   @par Thread Safety
603   The returned task must be awaited from a single execution context. 673   The returned task must be awaited from a single execution context.
604   Child awaitables execute concurrently but complete through the caller's 674   Child awaitables execute concurrently but complete through the caller's
605   executor. 675   executor.
606   676  
607   @param awaitables Range of io_result-returning awaitables to execute 677   @param awaitables Range of io_result-returning awaitables to execute
608   concurrently (must not be empty). 678   concurrently (must not be empty).
609   679  
610   @return A task yielding io_result<vector<PayloadT>> where PayloadT 680   @return A task yielding io_result<vector<PayloadT>> where PayloadT
611   is the payload extracted from each child's io_result. 681   is the payload extracted from each child's io_result.
612   682  
613   @throws std::invalid_argument if range is empty (thrown before 683   @throws std::invalid_argument if range is empty (thrown before
614   coroutine suspends). 684   coroutine suspends).
615   685  
616   @par Exception Safety 686   @par Exception Safety
617   If a child throws, the first child exception is rethrown after 687   If a child throws, the first child exception is rethrown after
618   all children complete (exception beats error_code). 688   all children complete (exception beats error_code).
619   689  
  690 + @note If a branch's coroutine is abandoned before it finishes, for
  691 + example by teardown of its execution context, `when_all`
  692 + requests stop on the remaining branches, and the awaiting
  693 + parent is abandoned rather than resumed once the last branch
  694 + has finished or been abandoned.
  695 +
620   @par Example 696   @par Example
621   @par !example example_1 697   @par !example example_1
622   698  
623   699  
624   @see IoAwaitableRange, when_all 700   @see IoAwaitableRange, when_all
625   */ 701   */
626   template<IoAwaitableRange R> 702   template<IoAwaitableRange R>
627   requires detail::is_io_result_v< 703   requires detail::is_io_result_v<
628   awaitable_result_t<std::ranges::range_value_t<R>>> 704   awaitable_result_t<std::ranges::range_value_t<R>>>
629   && (!std::is_same_v< 705   && (!std::is_same_v<
630   detail::io_result_payload_t< 706   detail::io_result_payload_t<
631   awaitable_result_t<std::ranges::range_value_t<R>>>, 707   awaitable_result_t<std::ranges::range_value_t<R>>>,
632   std::tuple<>>) 708   std::tuple<>>)
HITCBC 633   14 [[nodiscard]] auto when_all(R&& awaitables) 709   17 [[nodiscard]] auto when_all(R&& awaitables)
634   -> task<io_result<std::vector< 710   -> task<io_result<std::vector<
635   detail::io_result_payload_t< 711   detail::io_result_payload_t<
636   awaitable_result_t<std::ranges::range_value_t<R>>>>>> 712   awaitable_result_t<std::ranges::range_value_t<R>>>>>>
637   { 713   {
638   using Awaitable = std::ranges::range_value_t<R>; 714   using Awaitable = std::ranges::range_value_t<R>;
639   using PayloadT = detail::io_result_payload_t< 715   using PayloadT = detail::io_result_payload_t<
640   awaitable_result_t<Awaitable>>; 716   awaitable_result_t<Awaitable>>;
641 - using OwnedRange = std::remove_cvref_t<R>; 717 + using OwnedRange = detail::owned_range_t<R>;
642   718  
643   auto count = std::ranges::size(awaitables); 719   auto count = std::ranges::size(awaitables);
644   if(count == 0) 720   if(count == 0)
645   throw std::invalid_argument("when_all requires at least one awaitable"); 721   throw std::invalid_argument("when_all requires at least one awaitable");
646   722  
647 - OwnedRange owned_awaitables = std::forward<R>(awaitables); 723 + OwnedRange owned_awaitables = detail::own_range(std::forward<R>(awaitables));
648   724  
649   detail::when_all_homogeneous_state<PayloadT> state(count); 725   detail::when_all_homogeneous_state<PayloadT> state(count);
650   726  
651   co_await detail::when_all_homogeneous_launcher<OwnedRange>( 727   co_await detail::when_all_homogeneous_launcher<OwnedRange>(
652   &owned_awaitables, &state); 728   &owned_awaitables, &state);
653   729  
654   if(state.core_.first_exception_) 730   if(state.core_.first_exception_)
655   std::rethrow_exception(state.core_.first_exception_); 731   std::rethrow_exception(state.core_.first_exception_);
656   732  
657   if(state.has_error_.load(std::memory_order_relaxed)) 733   if(state.has_error_.load(std::memory_order_relaxed))
658   co_return io_result<std::vector<PayloadT>>{state.first_error_, {}}; 734   co_return io_result<std::vector<PayloadT>>{state.first_error_, {}};
659   735  
660   std::vector<PayloadT> results; 736   std::vector<PayloadT> results;
661   results.reserve(count); 737   results.reserve(count);
662   for(auto& opt : state.results_) 738   for(auto& opt : state.results_)
663   results.push_back(std::move(*opt)); 739   results.push_back(std::move(*opt));
664   740  
665   co_return io_result<std::vector<PayloadT>>{std::error_code(), std::move(results)}; 741   co_return io_result<std::vector<PayloadT>>{std::error_code(), std::move(results)};
HITCBC 666   28 } 742   34 }
667   743  
668   /** Execute a range of void io_result-returning awaitables concurrently. 744   /** Execute a range of void io_result-returning awaitables concurrently.
669   745  
670   Starts all awaitables simultaneously and waits for all to complete. 746   Starts all awaitables simultaneously and waits for all to complete.
671   Since all awaitables return io_result<>, no payload values are 747   Since all awaitables return io_result<>, no payload values are
672   collected. The first error_code makes a stop request that every 748   collected. The first error_code makes a stop request that every
673   sibling observes, and is propagated. Exceptions always beat error 749   sibling observes, and is propagated. Exceptions always beat error
674   codes. 750   codes.
675   751  
676   @par Await-effects 752   @par Await-effects
677   753  
678   Takes ownership of the range, creates one wrapper coroutine per 754   Takes ownership of the range, creates one wrapper coroutine per
679   element, then posts every wrapper to the caller's executor. All 755   element, then posts every wrapper to the caller's executor. All
680   children therefore run concurrently, each awaited with the caller's 756   children therefore run concurrently, each awaited with the caller's
681   executor and frame allocator and with a stop token owned by this 757   executor and frame allocator and with a stop token owned by this
682   operation. 758   operation.
683   759  
684   Awaiting an empty range throws `std::invalid_argument` before any 760   Awaiting an empty range throws `std::invalid_argument` before any
685   child is started. 761   child is started.
686   762  
687   A stop request is made on the operation's own stop token when: 763   A stop request is made on the operation's own stop token when:
688   764  
689   @li a child await-returns a non-zero `ec`, or 765   @li a child await-returns a non-zero `ec`, or
690   @li a child exits via an exception, or 766   @li a child exits via an exception, or
691   @li the caller's stop token is triggered. 767   @li the caller's stop token is triggered.
692   768  
693   Every sibling observes that request through the stop token it was 769   Every sibling observes that request through the stop token it was
694   awaited with. The request does not end the operation: the await 770   awaited with. The request does not end the operation: the await
695   completes only after every child has finished. 771   completes only after every child has finished.
696   772  
697   @par Await-returns 773   @par Await-returns
698   An object of type `io_result<>` destructuring as `[ec]`. The children 774   An object of type `io_result<>` destructuring as `[ec]`. The children
699   have no payloads, so nothing else is reported. 775   have no payloads, so nothing else is reported.
700   776  
701   `ec` is the first non-zero `ec` await-returned by a child, in 777   `ec` is the first non-zero `ec` await-returned by a child, in
702   completion order rather than input order. The `ec` of every other 778   completion order rather than input order. The `ec` of every other
703   child is discarded. 779   child is discarded.
704   780  
705   If any child exits via an exception, the first such exception is 781   If any child exits via an exception, the first such exception is
706   rethrown instead of await-returning, even when a child also reported 782   rethrown instead of await-returning, even when a child also reported
707   an `ec`. 783   an `ec`.
708   784  
709   @par Await-postcondition 785   @par Await-postcondition
710   Every child has finished. `ec` is success only if every child 786   Every child has finished. `ec` is success only if every child
711   await-returned success. 787   await-returned success.
712   788  
713   @par Remarks 789   @par Remarks
714   Supports _IoAwaitable cancellation_. 790   Supports _IoAwaitable cancellation_.
715   791  
716   @par Thread Safety 792   @par Thread Safety
717   The returned task must be awaited from a single execution context. 793   The returned task must be awaited from a single execution context.
718   Child awaitables execute concurrently but complete through the caller's 794   Child awaitables execute concurrently but complete through the caller's
719   executor. 795   executor.
720   796  
721   @param awaitables Range of io_result<>-returning awaitables to 797   @param awaitables Range of io_result<>-returning awaitables to
722   execute concurrently (must not be empty). 798   execute concurrently (must not be empty).
723   799  
724   @return A task yielding io_result<> whose ec is the first child 800   @return A task yielding io_result<> whose ec is the first child
725   error, or default-constructed on success. 801   error, or default-constructed on success.
726   802  
727   @throws std::invalid_argument if range is empty. 803   @throws std::invalid_argument if range is empty.
728   804  
729   @par Exception Safety 805   @par Exception Safety
730   If a child throws, the first child exception is rethrown after 806   If a child throws, the first child exception is rethrown after
731   all children complete (exception beats error_code). 807   all children complete (exception beats error_code).
732   808  
  809 + @note If a branch's coroutine is abandoned before it finishes, for
  810 + example by teardown of its execution context, `when_all`
  811 + requests stop on the remaining branches, and the awaiting
  812 + parent is abandoned rather than resumed once the last branch
  813 + has finished or been abandoned.
  814 +
733   @par Example 815   @par Example
734   @par !example example_2 816   @par !example example_2
735   817  
736   818  
737   @see IoAwaitableRange, when_all 819   @see IoAwaitableRange, when_all
738   */ 820   */
739   template<IoAwaitableRange R> 821   template<IoAwaitableRange R>
740   requires detail::is_io_result_v< 822   requires detail::is_io_result_v<
741   awaitable_result_t<std::ranges::range_value_t<R>>> 823   awaitable_result_t<std::ranges::range_value_t<R>>>
742   && std::is_same_v< 824   && std::is_same_v<
743   detail::io_result_payload_t< 825   detail::io_result_payload_t<
744   awaitable_result_t<std::ranges::range_value_t<R>>>, 826   awaitable_result_t<std::ranges::range_value_t<R>>>,
745   std::tuple<>> 827   std::tuple<>>
HITCBC 746   4 [[nodiscard]] auto when_all(R&& awaitables) -> task<io_result<>> 828   6 [[nodiscard]] auto when_all(R&& awaitables) -> task<io_result<>>
747   { 829   {
748 - using OwnedRange = std::remove_cvref_t<R>; 830 + using OwnedRange = detail::owned_range_t<R>;
749   831  
750   auto count = std::ranges::size(awaitables); 832   auto count = std::ranges::size(awaitables);
751   if(count == 0) 833   if(count == 0)
752   throw std::invalid_argument("when_all requires at least one awaitable"); 834   throw std::invalid_argument("when_all requires at least one awaitable");
753   835  
754 - OwnedRange owned_awaitables = std::forward<R>(awaitables); 836 + OwnedRange owned_awaitables = detail::own_range(std::forward<R>(awaitables));
755   837  
756   detail::when_all_homogeneous_state<std::tuple<>> state(count); 838   detail::when_all_homogeneous_state<std::tuple<>> state(count);
757   839  
758   co_await detail::when_all_homogeneous_launcher<OwnedRange>( 840   co_await detail::when_all_homogeneous_launcher<OwnedRange>(
759   &owned_awaitables, &state); 841   &owned_awaitables, &state);
760   842  
761   if(state.core_.first_exception_) 843   if(state.core_.first_exception_)
762   std::rethrow_exception(state.core_.first_exception_); 844   std::rethrow_exception(state.core_.first_exception_);
763   845  
764   if(state.has_error_.load(std::memory_order_relaxed)) 846   if(state.has_error_.load(std::memory_order_relaxed))
765   co_return io_result<>{state.first_error_}; 847   co_return io_result<>{state.first_error_};
766   848  
767   co_return io_result<>{}; 849   co_return io_result<>{};
HITCBC 768   8 } 850   12 }
769   851  
770   /** Execute io_result-returning awaitables concurrently, inspecting error codes. 852   /** Execute io_result-returning awaitables concurrently, inspecting error codes.
771   853  
772   Overload selected when all children return io_result<Ts...>. 854   Overload selected when all children return io_result<Ts...>.
773   The error_code is lifted out of each child into a single outer 855   The error_code is lifted out of each child into a single outer
774   io_result. On success all values are returned; on failure the 856   io_result. On success all values are returned; on failure the
775   first error_code wins. 857   first error_code wins.
776   858  
777   @par Await-effects 859   @par Await-effects
778   860  
779   Creates and posts one wrapper coroutine per argument to the caller's 861   Creates and posts one wrapper coroutine per argument to the caller's
780   executor, in argument order. All children therefore run concurrently, 862   executor, in argument order. All children therefore run concurrently,
781   each awaited with the caller's executor and frame allocator and with 863   each awaited with the caller's executor and frame allocator and with
782   a stop token owned by this operation. The overload requires at least 864   a stop token owned by this operation. The overload requires at least
783   one awaitable, so there is no empty case. 865   one awaitable, so there is no empty case.
784   866  
785   A stop request is made on the operation's own stop token when: 867   A stop request is made on the operation's own stop token when:
786   868  
787   @li a child await-returns a non-zero `ec`, or 869   @li a child await-returns a non-zero `ec`, or
788   @li a child exits via an exception, or 870   @li a child exits via an exception, or
789   @li the caller's stop token is triggered. 871   @li the caller's stop token is triggered.
790   872  
791   Every sibling observes that request through the stop token it was 873   Every sibling observes that request through the stop token it was
792   awaited with. The request does not end the operation: the await 874   awaited with. The request does not end the operation: the await
793   completes only after every child has finished. 875   completes only after every child has finished.
794   876  
795   @par Await-returns 877   @par Await-returns
796   An object of type `io_result<P1, ..., Pn>` destructuring as 878   An object of type `io_result<P1, ..., Pn>` destructuring as
797   `[ec, v1, ..., vn]`, where `Pi` is the payload of the i-th child's 879   `[ec, v1, ..., vn]`, where `Pi` is the payload of the i-th child's
798   `io_result`. 880   `io_result`.
799   881  
800   `ec` is the first non-zero `ec` await-returned by a child, in 882   `ec` is the first non-zero `ec` await-returned by a child, in
801   completion order rather than argument order. The `ec` of every other 883   completion order rather than argument order. The `ec` of every other
802   child is discarded. 884   child is discarded.
803   885  
804   Each `vi` is the payload the i-th child itself await-returned, even 886   Each `vi` is the payload the i-th child itself await-returned, even
805   when that child or a sibling reported an `ec`. A failed child 887   when that child or a sibling reported an `ec`. A failed child
806   therefore still contributes whatever payload it produced. This 888   therefore still contributes whatever payload it produced. This
807   differs from the range overloads, which discard all payloads once any 889   differs from the range overloads, which discard all payloads once any
808   child fails. 890   child fails.
809   891  
810   If any child exits via an exception, the first such exception is 892   If any child exits via an exception, the first such exception is
811   rethrown instead of await-returning, even when a child also reported 893   rethrown instead of await-returning, even when a child also reported
812   an `ec`. 894   an `ec`.
813   895  
814   @par Await-postcondition 896   @par Await-postcondition
815   Every child has finished. Each `vi` holds the i-th child's payload, 897   Every child has finished. Each `vi` holds the i-th child's payload,
816   and `ec` is success only if every child await-returned success. 898   and `ec` is success only if every child await-returned success.
817   899  
818   @par Remarks 900   @par Remarks
819   Supports _IoAwaitable cancellation_. 901   Supports _IoAwaitable cancellation_.
820   902  
821   @par Thread Safety 903   @par Thread Safety
822   The returned task must be awaited from a single execution context. 904   The returned task must be awaited from a single execution context.
823   Child awaitables execute concurrently but complete through the caller's 905   Child awaitables execute concurrently but complete through the caller's
824   executor. 906   executor.
825   907  
826   @par Exception Safety 908   @par Exception Safety
827   If a child throws, the first child exception is rethrown after 909   If a child throws, the first child exception is rethrown after
828   all children complete (exception beats error_code). 910   all children complete (exception beats error_code).
  911 +
  912 + @note If a branch's coroutine is abandoned before it finishes, for
  913 + example by teardown of its execution context, `when_all`
  914 + requests stop on the remaining branches, and the awaiting
  915 + parent is abandoned rather than resumed once the last branch
  916 + has finished or been abandoned.
829   917  
830   @param awaitables One or more awaitables each returning 918   @param awaitables One or more awaitables each returning
831   io_result<Ts...>. 919   io_result<Ts...>.
832   920  
833   @return A task yielding io_result<R1, R2, ..., Rn> where each Ri 921   @return A task yielding io_result<R1, R2, ..., Rn> where each Ri
834   follows the payload flattening rules. 922   follows the payload flattening rules.
835   */ 923   */
836   template<IoAwaitable... As> 924   template<IoAwaitable... As>
837   requires (sizeof...(As) > 0) 925   requires (sizeof...(As) > 0)
838   && detail::all_io_result_awaitables<As...> 926   && detail::all_io_result_awaitables<As...>
HITCBC 839   66 [[nodiscard]] auto when_all(As... awaitables) 927   78 [[nodiscard]] auto when_all(As... awaitables)
840   -> task<io_result< 928   -> task<io_result<
841   detail::io_result_payload_t<awaitable_result_t<As>>...>> 929   detail::io_result_payload_t<awaitable_result_t<As>>...>>
842   { 930   {
843   using result_type = io_result< 931   using result_type = io_result<
844   detail::io_result_payload_t<awaitable_result_t<As>>...>; 932   detail::io_result_payload_t<awaitable_result_t<As>>...>;
845   933  
846   detail::when_all_state<awaitable_result_t<As>...> state; 934   detail::when_all_state<awaitable_result_t<As>...> state;
847   std::tuple<As...> awaitable_tuple(std::move(awaitables)...); 935   std::tuple<As...> awaitable_tuple(std::move(awaitables)...);
848   936  
849   co_await detail::when_all_io_launcher<As...>(&awaitable_tuple, &state); 937   co_await detail::when_all_io_launcher<As...>(&awaitable_tuple, &state);
850   938  
851   // Exception always wins over error_code 939   // Exception always wins over error_code
852   if(state.core_.first_exception_) 940   if(state.core_.first_exception_)
853   std::rethrow_exception(state.core_.first_exception_); 941   std::rethrow_exception(state.core_.first_exception_);
854   942  
855   auto r = detail::build_when_all_io_result<result_type>( 943   auto r = detail::build_when_all_io_result<result_type>(
856   detail::extract_results(state)); 944   detail::extract_results(state));
857   if(state.has_error_.load(std::memory_order_relaxed)) 945   if(state.has_error_.load(std::memory_order_relaxed))
858   std::get<0>(r) = state.first_error_; 946   std::get<0>(r) = state.first_error_;
859   co_return r; 947   co_return r;
HITCBC 860   132 } 948   156 }
861   949  
862   } // namespace capy 950   } // namespace capy
863   } // namespace boost 951   } // namespace boost
864   952  
865   #endif 953   #endif