TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_ASIO_BOOST_HPP
11 : #define BOOST_CAPY_ASIO_BOOST_HPP
12 :
13 : /** @file boost.hpp
14 : * @brief Boost.Asio integration for capy coroutines.
15 : *
16 : * This header provides complete integration between capy's coroutine framework
17 : * and Boost.Asio. Include this header when using capy with Boost.Asio.
18 : *
19 : * @par Features
20 : * - Property query/require support for `asio_executor_adapter`
21 : * - `asio_boost_standard_executor` wrapper for Boost.Asio executors
22 : * - `async_result` specialization for `as_io_awaitable_t`
23 : * - Three-argument `asio_spawn` overloads with completion tokens
24 : *
25 : * @par Example
26 : * @code
27 : * #include <boost/capy/asio/boost.hpp>
28 : * #include <boost/asio.hpp>
29 : *
30 : * capy::io_task<void> my_coro(boost::asio::ip::tcp::socket& sock) {
31 : * char buf[1024];
32 : * auto [n] = co_await sock.async_read_some(
33 : * boost::asio::buffer(buf), capy::as_io_awaitable);
34 : * // ...
35 : * }
36 : *
37 : * int main() {
38 : * boost::asio::io_context io;
39 : * auto exec = capy::wrap_asio_executor(io.get_executor());
40 : * capy::asio_spawn(exec, my_coro(socket))(boost::asio::detached);
41 : * io.run();
42 : * }
43 : * @endcode
44 : *
45 : * @see standalone.hpp For standalone Asio support
46 : * @ingroup asio
47 : */
48 :
49 : #include <boost/capy/concept/io_awaitable.hpp>
50 : #include <boost/capy/concept/decomposes_to.hpp>
51 :
52 : #include <boost/capy/asio/as_io_awaitable.hpp>
53 : #include <boost/capy/asio/buffers.hpp>
54 : #include <boost/capy/asio/detail/completion_handler.hpp>
55 : #include <boost/capy/asio/executor_adapter.hpp>
56 : #include <boost/capy/asio/spawn.hpp>
57 : #include <boost/capy/asio/executor_from_asio.hpp>
58 :
59 :
60 : #include <boost/asio/append.hpp>
61 : #include <boost/asio/associated_allocator.hpp>
62 : #include <boost/asio/associated_cancellation_slot.hpp>
63 : #include <boost/asio/associated_immediate_executor.hpp>
64 : #include <boost/asio/buffer.hpp>
65 : #include <boost/asio/dispatch.hpp>
66 : #include <boost/asio/cancellation_signal.hpp>
67 : #include <boost/asio/cancellation_type.hpp>
68 : #include <boost/asio/execution_context.hpp>
69 : #include <boost/asio/execution/allocator.hpp>
70 : #include <boost/asio/execution/blocking.hpp>
71 : #include <boost/asio/execution/context.hpp>
72 : #include <boost/asio/execution/outstanding_work.hpp>
73 : #include <boost/asio/execution/relationship.hpp>
74 : #include <tuple>
75 : #include <type_traits>
76 :
77 : namespace boost::capy
78 : {
79 :
80 : /** @addtogroup asio
81 : * @{
82 : */
83 :
84 : /// @name Execution Property Queries for asio_executor_adapter
85 : /// @{
86 :
87 : /** @brief Queries the execution context from an asio_executor_adapter.
88 : *
89 : * Returns the Boost.Asio execution context associated with the adapter.
90 : * The context is provided via an adapter service that bridges capy's
91 : * execution_context with Asio's.
92 : *
93 : * @param exec The executor adapter to query
94 : * @return Reference to the associated `boost::asio::execution_context`
95 : */
96 : template<typename Executor, typename Allocator, int Bits>
97 : boost::asio::execution_context&
98 HIT 1 : query(const asio_executor_adapter<Executor, Allocator, Bits> & exec,
99 : boost::asio::execution::context_t) noexcept
100 : {
101 : using service = detail::asio_adapter_context_service<
102 : boost::asio::execution_context>;
103 1 : return exec.context().
104 1 : template use_service<service>();
105 : }
106 :
107 : /** @brief Queries the blocking property from an asio_executor_adapter.
108 : * @param exec The executor adapter to query
109 : * @return The current blocking property value
110 : */
111 : template<typename Executor, typename Allocator, int Bits>
112 : constexpr boost::asio::execution::blocking_t
113 4 : query(const asio_executor_adapter<Executor, Allocator, Bits> &,
114 : boost::asio::execution::blocking_t) noexcept
115 : {
116 : using ex = asio_executor_adapter<Executor, Allocator, Bits>;
117 :
118 : switch (Bits & ex::blocking_mask)
119 : {
120 : case ex::blocking_never:
121 MIS 0 : return boost::asio::execution::blocking.never;
122 : case ex::blocking_always:
123 : return boost::asio::execution::blocking.always;
124 : case ex::blocking_possibly:
125 HIT 4 : return boost::asio::execution::blocking.possibly;
126 : default: return {};
127 : }
128 : }
129 :
130 : /// @}
131 :
132 : /// @name Execution Property Requirements for asio_executor_adapter
133 : /// @{
134 :
135 : /** @brief Requires blocking.possibly property.
136 : * @return New adapter with blocking.possibly set
137 : */
138 : template<typename Executor, typename Allocator, int Bits>
139 : constexpr auto
140 MIS 0 : require(const asio_executor_adapter<Executor, Allocator, Bits> & exec,
141 : boost::asio::execution::blocking_t::possibly_t)
142 : {
143 : using ex = asio_executor_adapter<Executor, Allocator, Bits>;
144 0 : constexpr int new_bits = (Bits & ~ex::blocking_mask) | ex::blocking_possibly;
145 0 : return asio_executor_adapter<Executor, Allocator, new_bits>(exec);
146 : }
147 :
148 : /** @brief Requires blocking.never property.
149 : * @return New adapter that never blocks
150 : */
151 : template<typename Executor, typename Allocator, int Bits>
152 : constexpr auto
153 HIT 6 : require(const asio_executor_adapter<Executor, Allocator, Bits> & exec,
154 : boost::asio::execution::blocking_t::never_t)
155 : {
156 : using ex = asio_executor_adapter<Executor, Allocator, Bits>;
157 6 : constexpr int new_bits = (Bits & ~ex::blocking_mask) | ex::blocking_never;
158 6 : return asio_executor_adapter<Executor, Allocator, new_bits>(exec);
159 : }
160 :
161 : /** @brief Requires blocking.always property.
162 : * @return New adapter that always blocks until execution completes
163 : */
164 : template<typename Executor, typename Allocator, int Bits>
165 : constexpr auto
166 : require(const asio_executor_adapter<Executor, Allocator, Bits> & exec,
167 : boost::asio::execution::blocking_t::always_t)
168 : {
169 : using ex = asio_executor_adapter<Executor, Allocator, Bits>;
170 : constexpr int new_bits = (Bits & ~ex::blocking_mask) | ex::blocking_always;
171 : return asio_executor_adapter<Executor, Allocator, new_bits>(exec);
172 : }
173 :
174 : /** @brief Queries the outstanding_work property.
175 : * @param exec The executor adapter to query
176 : * @return The current work tracking setting
177 : */
178 : template<typename Executor, typename Allocator, int Bits>
179 MIS 0 : static constexpr boost::asio::execution::outstanding_work_t query(
180 : const asio_executor_adapter<Executor, Allocator, Bits> &,
181 : boost::asio::execution::outstanding_work_t) noexcept
182 : {
183 : using ex = asio_executor_adapter<Executor, Allocator, Bits>;
184 : switch (Bits & ex::work_mask)
185 : {
186 : case ex::work_tracked:
187 0 : return boost::asio::execution::outstanding_work.tracked;
188 : case ex::work_untracked:
189 0 : return boost::asio::execution::outstanding_work.untracked;
190 : default: return {};
191 : }
192 : }
193 :
194 : /** @brief Requires outstanding_work.tracked property.
195 : * @return New adapter that tracks outstanding work
196 : */
197 : template<typename Executor, typename Allocator, int Bits>
198 : constexpr auto
199 HIT 8 : require(const asio_executor_adapter<Executor, Allocator, Bits> & exec,
200 : boost::asio::execution::outstanding_work_t::tracked_t)
201 : {
202 : using ex = asio_executor_adapter<Executor, Allocator, Bits>;
203 8 : constexpr int new_bits = (Bits & ~ex::work_mask) | ex::work_tracked;
204 8 : return asio_executor_adapter<Executor, Allocator, new_bits>(exec);
205 : }
206 :
207 : /** @brief Requires outstanding_work.untracked property.
208 : * @return New adapter that does not track outstanding work
209 : */
210 : template<typename Executor, typename Allocator, int Bits>
211 : constexpr auto
212 MIS 0 : require(const asio_executor_adapter<Executor, Allocator, Bits> & exec,
213 : boost::asio::execution::outstanding_work_t::untracked_t)
214 : {
215 : using ex = asio_executor_adapter<Executor, Allocator, Bits>;
216 0 : constexpr int new_bits = (Bits & ~ex::work_mask) | ex::work_untracked;
217 0 : return asio_executor_adapter<Executor, Allocator, new_bits>(exec);
218 : }
219 :
220 : /** @brief Queries the allocator property.
221 : * @return The adapter's current allocator
222 : */
223 : template <typename Executor, typename Allocator, int Bits, typename OtherAllocator>
224 : constexpr Allocator query(
225 : const asio_executor_adapter<Executor, Allocator, Bits> & exec,
226 : boost::asio::execution::allocator_t<OtherAllocator>) noexcept
227 : {
228 : return exec.get_allocator();
229 : }
230 :
231 : /** @brief Requires a specific allocator.
232 : * @param a The allocator property containing the new allocator
233 : * @return New adapter using the specified allocator
234 : */
235 : template <typename Executor, typename Allocator, int Bits, typename OtherAllocator>
236 : constexpr auto
237 HIT 10 : require(const asio_executor_adapter<Executor, Allocator, Bits> & exec,
238 : boost::asio::execution::allocator_t<OtherAllocator> a)
239 : {
240 : return asio_executor_adapter<Executor, OtherAllocator, Bits>(
241 10 : exec, a.value()
242 10 : );
243 : }
244 :
245 : /** @brief Requires the default allocator (uses frame allocator).
246 : * @return New adapter using the frame allocator from the context
247 : */
248 : template <typename Executor, typename Allocator, int Bits>
249 : constexpr auto
250 : require(const asio_executor_adapter<Executor, Allocator, Bits> & exec,
251 : boost::asio::execution::allocator_t<void>)
252 : noexcept(std::is_nothrow_move_constructible_v<Executor>)
253 : {
254 : return asio_executor_adapter<
255 : Executor,
256 : std::pmr::polymorphic_allocator<void>,
257 : Bits>
258 : (
259 : exec,
260 : exec.context().get_frame_allocator()
261 : );
262 : }
263 :
264 : /// @}
265 :
266 : namespace detail
267 : {
268 :
269 : template<typename Executor>
270 : struct asio_work_tracker_service : boost::asio::execution_context::service
271 : {
272 : static boost::asio::execution_context::id id;
273 :
274 1 : asio_work_tracker_service(boost::asio::execution_context & ctx)
275 1 : : boost::asio::execution_context::service(ctx) {}
276 :
277 : using tracked_executor =
278 : typename boost::asio::prefer_result<
279 : Executor,
280 : boost::asio::execution::outstanding_work_t::tracked_t
281 : >::type;
282 :
283 : alignas(tracked_executor) char buffer[sizeof(tracked_executor) ];
284 :
285 :
286 : std::mutex mutex;
287 : std::size_t work = 0u;
288 :
289 2 : void shutdown()
290 : {
291 2 : std::lock_guard _(mutex);
292 2 : if (std::exchange(work, 0) > 0u)
293 MIS 0 : reinterpret_cast<tracked_executor*>(buffer)->~tracked_executor();
294 HIT 2 : }
295 :
296 :
297 1 : void work_started(const Executor & exec)
298 : {
299 1 : std::lock_guard _(mutex);
300 1 : if (work ++ == 0u)
301 1 : new (buffer) tracked_executor(
302 : boost::asio::prefer(exec,
303 : boost::asio::execution::outstanding_work.tracked));
304 1 : }
305 :
306 1 : void work_finished()
307 : {
308 1 : std::lock_guard _(mutex);
309 1 : if (--work == 0u)
310 1 : reinterpret_cast<tracked_executor*>(buffer)->~tracked_executor();
311 1 : }
312 :
313 : };
314 :
315 :
316 : template<typename Executor>
317 : boost::asio::execution_context::id asio_work_tracker_service<Executor>::id;
318 :
319 :
320 : }
321 :
322 : /** @brief Wraps a Boost.Asio standard executor for use with capy.
323 : *
324 : * This class adapts Boost.Asio executors that follow the P0443/P2300
325 : * standard executor model to be usable as capy executors. It provides
326 : * work tracking through an `asio_work_tracker_service` and integrates
327 : * with capy's execution context system.
328 : *
329 : * @tparam Executor A Boost.Asio executor satisfying `AsioBoostStandardExecutor`
330 : *
331 : * @par Example
332 : * @code
333 : * boost::asio::io_context io;
334 : * auto wrapped = asio_boost_standard_executor(io.get_executor());
335 : *
336 : * // Use with capy coroutines
337 : * capy::run(wrapped, my_io_task());
338 : * @endcode
339 : *
340 : * @see wrap_asio_executor For automatic executor type detection
341 : * @see asio_net_ts_executor For legacy Networking TS executors
342 : */
343 : template<detail::AsioBoostStandardExecutor Executor>
344 : struct asio_boost_standard_executor
345 : {
346 : /** @brief Constructs from a Boost.Asio executor.
347 : * @param executor The Boost.Asio executor to wrap
348 : */
349 1 : asio_boost_standard_executor(Executor executor)
350 : noexcept(std::is_nothrow_move_constructible_v<Executor>)
351 1 : : executor_(std::move(executor))
352 : {
353 1 : }
354 :
355 : /** @brief Move constructor. */
356 5 : asio_boost_standard_executor(asio_boost_standard_executor && rhs)
357 : noexcept(std::is_nothrow_move_constructible_v<Executor>)
358 5 : : executor_(std::move(rhs.executor_))
359 : {
360 5 : }
361 :
362 : /** @brief Copy constructor. */
363 1 : asio_boost_standard_executor(const asio_boost_standard_executor & rhs)
364 : noexcept(std::is_nothrow_copy_constructible_v<Executor>)
365 1 : : executor_(rhs.executor_)
366 : {
367 1 : }
368 :
369 : /** @brief Returns the associated capy execution context.
370 : * @return Reference to the capy execution_context
371 : */
372 2 : execution_context& context() const noexcept
373 : {
374 2 : auto & ec = boost::asio::query(executor_, boost::asio::execution::context);
375 : return boost::asio::use_service<
376 : detail::asio_context_service<boost::asio::execution_context>
377 2 : >(ec);
378 : }
379 :
380 : /** @brief Notifies that work has started.
381 : *
382 : * Delegates to the work tracker service to maintain a tracked executor
383 : * while work is outstanding.
384 : */
385 1 : void on_work_started() const noexcept
386 : {
387 1 : auto & ec = boost::asio::query(executor_, boost::asio::execution::context);
388 : boost::asio::use_service<
389 : detail::asio_work_tracker_service<Executor>
390 1 : >(ec).work_started(executor_);
391 1 : }
392 :
393 : /** @brief Notifies that work has finished.
394 : *
395 : * Decrements the work counter in the tracker service.
396 : */
397 1 : void on_work_finished() const noexcept
398 : {
399 1 : auto & ec = boost::asio::query(executor_, boost::asio::execution::context);
400 : boost::asio::use_service<
401 : detail::asio_work_tracker_service<Executor>
402 1 : >(ec).work_finished();
403 1 : }
404 :
405 : /** @brief Dispatches a continuation for execution.
406 : *
407 : * May execute inline if the executor allows, otherwise posts.
408 : * Uses the context's frame allocator for handler allocation.
409 : *
410 : * @param c The continuation to dispatch
411 : * @return A noop coroutine handle
412 : */
413 1 : std::coroutine_handle<> dispatch(continuation & c) const
414 : {
415 2 : boost::asio::prefer(
416 1 : executor_,
417 1 : boost::asio::execution::allocator(
418 1 : std::pmr::polymorphic_allocator<void>(
419 1 : context().get_frame_allocator()
420 : )
421 : )
422 1 : ).execute(detail::asio_coroutine_unique_handle(c.h));
423 1 : return std::noop_coroutine();
424 : }
425 :
426 : /** @brief Posts a continuation for deferred execution.
427 : *
428 : * The continuation will never be executed inline. Uses blocking.never
429 : * and relationship.fork properties for proper async behavior.
430 : *
431 : * @param c The continuation to post
432 : */
433 MIS 0 : void post(continuation & c) const
434 : {
435 : boost::asio::prefer(
436 0 : boost::asio::require(executor_, boost::asio::execution::blocking.never),
437 : boost::asio::execution::relationship.fork,
438 0 : boost::asio::execution::allocator(
439 0 : std::pmr::polymorphic_allocator<void>(
440 0 : context().get_frame_allocator()
441 : )
442 : )
443 0 : ).execute(detail::asio_coroutine_unique_handle(c.h));
444 0 : }
445 :
446 : /** @brief Equality comparison. */
447 0 : bool operator==(const asio_boost_standard_executor & rhs) const noexcept
448 : {
449 0 : return executor_ == rhs.executor_;
450 : }
451 :
452 : /** @brief Inequality comparison. */
453 : bool operator!=(const asio_boost_standard_executor & rhs) const noexcept
454 : {
455 : return executor_ != rhs.executor_;
456 : }
457 :
458 : private:
459 : Executor executor_;
460 : };
461 :
462 :
463 : /** @brief Spawns a capy coroutine with a Boost.Asio completion token (executor overload).
464 : *
465 : * Convenience overload that combines the two-step spawn process into one call.
466 : * Equivalent to `asio_spawn(exec, awaitable)(token)`.
467 : *
468 : * @tparam ExecutorType The executor type
469 : * @tparam Awaitable The coroutine type
470 : * @tparam Token A Boost.Asio completion token
471 : * @param exec The executor to run on
472 : * @param awaitable The coroutine to spawn
473 : * @param token The completion token
474 : * @return Depends on the token type
475 : */
476 : template<Executor ExecutorType,
477 : IoAwaitable Awaitable,
478 : boost::asio::completion_token_for<
479 : detail::completion_signature_for_io_awaitable<Awaitable>
480 : > Token>
481 HIT 4 : auto asio_spawn(ExecutorType exec, Awaitable && awaitable, Token token)
482 : {
483 4 : return asio_spawn(exec, std::forward<Awaitable>(awaitable))(std::move(token));
484 : }
485 :
486 : /** @brief Spawns a capy coroutine with a Boost.Asio completion token (context overload).
487 : *
488 : * Convenience overload that extracts the executor from a context.
489 : *
490 : * @tparam Context The execution context type
491 : * @tparam Awaitable The coroutine type
492 : * @tparam Token A Boost.Asio completion token
493 : * @param ctx The execution context
494 : * @param awaitable The coroutine to spawn
495 : * @param token The completion token
496 : * @return Depends on the token type
497 : */
498 : template<ExecutionContext Context,
499 : IoAwaitable Awaitable,
500 : boost::asio::completion_token_for<
501 : detail::completion_signature_for_io_awaitable<Awaitable>
502 : > Token>
503 : auto asio_spawn(Context & ctx, Awaitable && awaitable, Token token)
504 : {
505 : return asio_spawn(ctx.get_executor(), std::forward<Awaitable>(awaitable))(std::move(token));
506 : }
507 :
508 : /** @} */ // end of asio group
509 :
510 : }
511 :
512 : template<typename ... Ts>
513 : struct boost::asio::async_result<boost::capy::as_io_awaitable_t, void(Ts...)>
514 : : boost::capy::detail::async_result_impl<
515 : boost::asio::cancellation_signal,
516 : boost::asio::cancellation_type,
517 : Ts...>
518 : {
519 : };
520 :
521 :
522 : namespace boost::capy::detail
523 : {
524 :
525 :
526 : struct boost_asio_init;
527 :
528 :
529 : template<typename Allocator>
530 : struct boost_asio_promise_type_allocator_base
531 : {
532 : template<typename Handler, Executor Ex, IoAwaitable Awaitable>
533 : void * operator new (std::size_t n, boost_asio_init &,
534 : Handler & handler,
535 : Ex &, Awaitable &)
536 : {
537 : using allocator_type = std::allocator_traits<Allocator>
538 : ::template rebind_alloc<char>;
539 : allocator_type allocator(boost::asio::get_associated_allocator(handler));
540 :
541 : // round n up to max_align
542 : if (const auto d = n % sizeof(std::max_align_t); d > 0u)
543 : n += (sizeof(std::max_align_t) - d);
544 :
545 : auto mem = std::allocator_traits<allocator_type>::
546 : template rebind_traits<char>::
547 : allocate(allocator, n + sizeof(allocator_type));
548 :
549 : void* p = static_cast<char*>(mem) + n;
550 : new (p) allocator_type(std::move(allocator));
551 :
552 : return mem;
553 : }
554 : void operator delete(void * ptr, std::size_t n)
555 : {
556 : if (const auto d = n % sizeof(std::max_align_t); d > 0u)
557 : n += (sizeof(std::max_align_t) - d);
558 :
559 : using allocator_type = std::allocator_traits<Allocator>
560 : ::template rebind_alloc<char>;
561 : auto allocator_p = reinterpret_cast<allocator_type*>(
562 : static_cast<char*>(ptr) + n);
563 : auto allocator = std::move(*allocator_p);
564 :
565 : allocator_p->~allocator_type();
566 : allocator.deallocate(static_cast<char*>(ptr), n + sizeof(allocator_type));
567 : }
568 : };
569 :
570 : template<>
571 : struct boost_asio_promise_type_allocator_base<std::allocator<void>>
572 : {
573 : };
574 :
575 : template<typename Handler, Executor Ex, IoAwaitable Awaitable>
576 : struct boost_asio_init_promise_type
577 : : boost_asio_promise_type_allocator_base<
578 : boost::asio::associated_allocator_t<Handler>>
579 : {
580 : using args_type = completion_tuple_for_io_awaitable<Awaitable>;
581 :
582 4 : boost_asio_init_promise_type(
583 : boost_asio_init &,
584 : Handler & h,
585 : Ex & exec,
586 : Awaitable &)
587 4 : : handler(h), ex(exec)
588 : {
589 4 : }
590 :
591 : Handler & handler;
592 : Ex &ex;
593 :
594 4 : void get_return_object() {}
595 MIS 0 : void unhandled_exception() {throw;}
596 0 : void return_void() {}
597 :
598 HIT 4 : std::suspend_never initial_suspend() noexcept {return {};}
599 MIS 0 : std::suspend_never final_suspend() noexcept {return {};}
600 :
601 : struct completer
602 : {
603 : Handler handler;
604 : Ex ex;
605 : args_type args;
606 :
607 :
608 HIT 4 : bool await_ready() const {return false;}
609 :
610 4 : auto await_suspend(
611 : std::coroutine_handle<boost_asio_init_promise_type> h)
612 : {
613 4 : auto handler_ =
614 : std::apply(
615 8 : [&](auto ... args)
616 : {
617 4 : return boost::asio::append(std::move(handler),
618 8 : std::move(args)...);
619 : },
620 4 : detail::decomposed_types(std::move(args)));
621 :
622 4 : auto exec = boost::asio::get_associated_immediate_executor(
623 : handler_,
624 8 : asio_executor_adapter(std::move(ex)));
625 4 : h.destroy();
626 4 : boost::asio::dispatch(exec, std::move(handler_));
627 4 : }
628 MIS 0 : void await_resume() const {}
629 : };
630 :
631 HIT 4 : completer yield_value(args_type value)
632 : {
633 8 : return {std::move(handler), std::move(ex), std::move(value)};
634 1 : }
635 :
636 : struct wrapper
637 : {
638 : Awaitable r;
639 : const Ex &ex;
640 : io_env env;
641 : std::stop_source stop_src;
642 : boost::asio::cancellation_slot cancel_slot;
643 :
644 : continuation c;
645 :
646 4 : wrapper(Awaitable && r, const Ex &ex)
647 4 : : r(std::move(r)), ex(ex)
648 : {
649 4 : }
650 :
651 4 : bool await_ready() {return r.await_ready(); }
652 :
653 1 : auto await_suspend(
654 : std::coroutine_handle<boost_asio_init_promise_type> tr)
655 : {
656 1 : env.executor = ex;
657 1 : env.stop_token = stop_src.get_token();
658 : cancel_slot =
659 1 : boost::asio::get_associated_cancellation_slot(tr.promise().handler);
660 :
661 1 : if (cancel_slot.is_connected())
662 MIS 0 : cancel_slot.assign(
663 0 : [this](boost::asio::cancellation_type ct)
664 : {
665 0 : if ((ct & boost::asio::cancellation_type::terminal)
666 0 : != boost::asio::cancellation_type::none)
667 0 : stop_src.request_stop();
668 : });
669 HIT 1 : env.frame_allocator = get_current_frame_allocator();
670 :
671 : using suspend_kind = decltype(r.await_suspend(tr, &env));
672 : if constexpr (std::is_void_v<suspend_kind>)
673 MIS 0 : r.await_suspend(tr, &env);
674 : else if constexpr (std::same_as<suspend_kind, bool>)
675 : return r.await_suspend(tr, &env);
676 : else
677 : {
678 HIT 1 : c.h = r.await_suspend(tr, &env);
679 1 : return ex.dispatch(c);
680 : }
681 MIS 0 : }
682 :
683 HIT 4 : completion_tuple_for_io_awaitable<Awaitable> await_resume()
684 : {
685 4 : if (cancel_slot.is_connected())
686 MIS 0 : cancel_slot.clear();
687 :
688 : using type = decltype(r.await_resume());
689 : if constexpr (!noexcept(r.await_resume()))
690 : {
691 : if constexpr (std::is_void_v<type>)
692 : try
693 : {
694 HIT 1 : r.await_resume();
695 1 : return {std::exception_ptr()};
696 : }
697 MIS 0 : catch (...)
698 : {
699 0 : return std::current_exception();
700 : }
701 : else
702 : try
703 : {
704 : return {std::current_exception(), r.await_resume()};
705 : }
706 : catch (...)
707 : {
708 : return {std::current_exception(), type()};
709 : }
710 : }
711 : else
712 : {
713 : if constexpr (std::is_void_v<type>)
714 : {
715 : r.await_resume();
716 : return {};
717 : }
718 : else
719 HIT 3 : return {r.await_resume()};
720 : }
721 : }
722 : };
723 :
724 4 : wrapper await_transform(Awaitable & r)
725 : {
726 4 : return wrapper{std::move(r), ex};
727 : }
728 : };
729 :
730 :
731 : struct boost_asio_init
732 : {
733 : template<typename Handler, Executor Ex, IoAwaitable Awaitable>
734 4 : void operator()(
735 : Handler ,
736 : Ex,
737 : Awaitable awaitable)
738 : {
739 : auto res = co_await awaitable;
740 : co_yield std::move(res);
741 8 : }
742 : };
743 :
744 : template<typename Awaitable, typename Token>
745 : requires
746 : boost::asio::completion_token_for<
747 : Token,
748 : completion_signature_for_io_awaitable<Awaitable>
749 : >
750 : struct initialize_asio_spawn_helper<Awaitable, Token>
751 : {
752 : template<typename Executor>
753 4 : static auto init(Executor ex, Awaitable r, Token && tk)
754 : -> decltype( boost::asio::async_initiate<
755 : Token,
756 : completion_signature_for_io_awaitable<Awaitable>>(
757 : boost_asio_init{},
758 : tk, std::move(ex), std::move(r)
759 : ))
760 : {
761 : return boost::asio::async_initiate<
762 : Token,
763 3 : completion_signature_for_io_awaitable<Awaitable>>(
764 : boost_asio_init{},
765 4 : tk, std::move(ex), std::move(r)
766 4 : );
767 : }
768 : };
769 :
770 :
771 : }
772 :
773 :
774 : template<typename Handler, typename Executor, typename Awaitable>
775 : struct std::coroutine_traits<void,
776 : boost::capy::detail::boost_asio_init&,
777 : Handler,
778 : Executor,
779 : Awaitable>
780 : {
781 : using promise_type
782 : = boost::capy::detail::boost_asio_init_promise_type<
783 : Handler,
784 : Executor,
785 : Awaitable>;
786 : };
787 :
788 :
789 :
790 : namespace boost::capy
791 : {
792 :
793 :
794 : namespace detail
795 : {
796 :
797 : template<typename Sequence>
798 : class asio_boost_buffer_range
799 : {
800 : public:
801 : using sequence_type = Sequence;
802 : using iterator = asio_buffer_iterator<
803 : decltype(boost::asio::buffer_sequence_begin(std::declval<const Sequence&>()))>;
804 : using const_iterator = iterator;
805 :
806 : asio_boost_buffer_range() = default;
807 :
808 3 : explicit asio_boost_buffer_range(Sequence seq)
809 : noexcept(std::is_nothrow_move_constructible_v<Sequence>)
810 3 : : seq_(std::move(seq))
811 : {
812 3 : }
813 :
814 : asio_boost_buffer_range(const asio_boost_buffer_range&) = default;
815 : asio_boost_buffer_range(asio_boost_buffer_range&&) = default;
816 : asio_boost_buffer_range& operator=(const asio_boost_buffer_range&) = default;
817 : asio_boost_buffer_range& operator=(asio_boost_buffer_range&&) = default;
818 :
819 6 : iterator begin() const
820 : noexcept(noexcept(boost::asio::buffer_sequence_begin(std::declval<const Sequence&>())))
821 : {
822 6 : return iterator(boost::asio::buffer_sequence_begin(seq_));
823 : }
824 :
825 6 : iterator end() const
826 : noexcept(noexcept(boost::asio::buffer_sequence_end(std::declval<const Sequence&>())))
827 : {
828 6 : return iterator(boost::asio::buffer_sequence_end(seq_));
829 : }
830 :
831 : /// Returns the underlying sequence.
832 : const Sequence& base() const noexcept { return seq_; }
833 :
834 : private:
835 : Sequence seq_{};
836 : };
837 :
838 : // Deduction guide
839 : template<typename Sequence>
840 : asio_boost_buffer_range(Sequence) -> asio_boost_buffer_range<Sequence>;
841 :
842 :
843 3 : asio_mutable_buffer asio_buffer_transformer_t::
844 : operator()(const boost::asio::mutable_buffer &mb) const noexcept
845 : {
846 3 : return {mb.data(), mb.size()};
847 : }
848 :
849 3 : asio_const_buffer asio_buffer_transformer_t::
850 : operator()(const boost::asio::const_buffer &cb) const noexcept
851 : {
852 3 : return {cb.data(), cb.size()};
853 : }
854 :
855 : }
856 :
857 : /** Convert a Boost.Asio buffer sequence for bidirectional Asio/capy compatibility.
858 :
859 : Wraps a Boost.Asio buffer sequence in a transforming range that converts
860 : each buffer element to asio_const_buffer or asio_mutable_buffer.
861 : The returned range satisfies both Boost.Asio's buffer sequence requirements
862 : and capy's buffer sequence concepts.
863 :
864 : This overload uses `boost::asio::buffer_sequence_begin` and
865 : `boost::asio::buffer_sequence_end` for iteration.
866 :
867 : @par Example: Asio to Capy
868 : @code
869 : std::vector<boost::asio::mutable_buffer> asio_bufs = ...;
870 : auto seq = capy::as_asio_buffer_sequence(asio_bufs);
871 : std::size_t total = capy::buffer_size(seq); // Use capy algorithms
872 : @endcode
873 :
874 : @param seq The Boost.Asio buffer sequence to convert
875 : @return A transforming range over the buffer sequence
876 :
877 : @see as_asio_buffer_sequence in buffers.hpp for capy buffer sequences
878 : */
879 : template<typename T>
880 : requires requires (const T & seq)
881 : {
882 : {boost::asio::buffer_sequence_begin(seq)} -> std::bidirectional_iterator;
883 : {boost::asio::buffer_sequence_end(seq)} -> std::bidirectional_iterator;
884 : {*boost::asio::buffer_sequence_begin(seq)} -> std::convertible_to<boost::asio::const_buffer>;
885 : {*boost::asio::buffer_sequence_end(seq)} -> std::convertible_to<boost::asio::const_buffer>;
886 : }
887 3 : auto as_asio_buffer_sequence(const T & seq)
888 : {
889 3 : return detail::asio_boost_buffer_range(seq);
890 : }
891 :
892 2 : asio_const_buffer::operator boost::asio::const_buffer() const
893 : {
894 2 : return {data(), size()};
895 : }
896 :
897 : asio_mutable_buffer::operator boost::asio::const_buffer() const
898 : {
899 : return {data(), size()};
900 : }
901 :
902 2 : asio_mutable_buffer::operator boost::asio::mutable_buffer() const
903 : {
904 2 : return {data(), size()};
905 : }
906 :
907 : }
908 :
909 : #endif //BOOST_CAPY_ASIO_BOOST_HPP
910 :
|