100.00% Lines (36/36) 100.00% Functions (13/13)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/capy 7   // Official repository: https://github.com/cppalliance/capy
8   // 8   //
9   9  
10   #ifndef BOOST_CAPY_IO_TASK_HPP 10   #ifndef BOOST_CAPY_IO_TASK_HPP
11   #define BOOST_CAPY_IO_TASK_HPP 11   #define BOOST_CAPY_IO_TASK_HPP
12   12  
13   #include <boost/capy/io_result.hpp> 13   #include <boost/capy/io_result.hpp>
14   #include <boost/capy/task.hpp> 14   #include <boost/capy/task.hpp>
  15 + #include <coroutine>
15   16  
16   namespace boost { 17   namespace boost {
17   namespace capy { 18   namespace capy {
18   19  
19   /** A task type for I/O operations yielding io_result. 20   /** A task type for I/O operations yielding io_result.
20   21  
21   This is a convenience alias for `task<io_result<Ts...>>`. 22   This is a convenience alias for `task<io_result<Ts...>>`.
22   The converting constructor on `io_result<>` allows direct 23   The converting constructor on `io_result<>` allows direct
23   `co_return` of error codes: 24   `co_return` of error codes:
24   25  
25   @code 26   @code
26   io_task<> connect_to_server(socket& s, endpoint ep) 27   io_task<> connect_to_server(socket& s, endpoint ep)
27   { 28   {
28   co_return co_await s.connect(ep); // returns io_result<> 29   co_return co_await s.connect(ep); // returns io_result<>
29   } 30   }
30   31  
31   io_task<> handler(route_params& rp) 32   io_task<> handler(route_params& rp)
32   { 33   {
33   co_return route::next; // error_code converts to io_result<> 34   co_return route::next; // error_code converts to io_result<>
34   } 35   }
35   @endcode 36   @endcode
36   37  
37   @tparam Ts Additional value types beyond error_code. 38   @tparam Ts Additional value types beyond error_code.
38   */ 39   */
39   template<class... Ts> 40   template<class... Ts>
40 - using io_task = task<io_result<Ts...>>; 41 + struct io_task
  42 + {
  43 + struct promise_type : task<io_result<Ts...>>::promise_type
  44 + {
HITGNC   45 + 1614 io_task get_return_object()
  46 + {
HITGNC   47 + 1614 return io_task{std::coroutine_handle<promise_type>::from_promise(*this)};
  48 + }
  49 +
  50 + friend io_task;
  51 +
  52 + // An io_task can yield an io_result value. That means the coroutie suspend if the error is set.
  53 + template<typename ... Us>
  54 + requires (std::constructible_from<Ts> && ...)
HITGNC   55 + 6 auto yield_value(io_result<Us...> res)
  56 + {
  57 + struct awaiter
  58 + {
  59 + io_result<Us...> res;
HITGNC   60 + 6 bool await_ready() {return !res.ec;}
  61 +
HITGNC   62 + 2 std::coroutine_handle<> await_suspend(std::coroutine_handle<promise_type> h)
  63 + {
HITGNC   64 + 2 auto &p = h.promise();
HITGNC   65 + 2 p.return_value({res.ec, Ts()...});
  66 +
HITGNC   67 + 2 return p.continuation();
  68 + }
  69 +
HITGNC   70 + 4 std::tuple<Us...> await_resume()
  71 + {
HITGNC   72 + 4 return std::move(res.values);
  73 + }
  74 + };
  75 +
HITGNC   76 + 6 return awaiter{std::move(res)};
  77 + }
  78 +
  79 +
  80 + };
  81 +
  82 + /// Destroy the task and its coroutine frame if owned.
  83 +
HITGNC   84 + 4056 ~io_task()
  85 + {
HITGNC   86 + 4056 if (h_)
HITGNC   87 + 1607 h_.destroy();
HITGNC   88 + 4056 }
  89 + /// Return false; tasks are never immediately ready.
HITGNC   90 + 1481 bool await_ready() const noexcept
  91 + {
HITGNC   92 + 1481 return false;
  93 + }
  94 +
  95 + /// Return the result or rethrow any stored exception.
HITGNC   96 + 1607 auto await_resume()
  97 + {
HITGNC   98 + 1607 if(h_.promise().has_ep_)
HITGNC   99 + 533 std::rethrow_exception(h_.promise().ep_);
HITGNC   100 + 1074 return std::move(*h_.promise().result_);
  101 + }
  102 +
  103 +
  104 +
  105 + /// Start execution with the caller's context.
HITGNC   106 + 1607 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
  107 + {
HITGNC   108 + 1607 h_.promise().set_continuation(cont);
HITGNC   109 + 1607 h_.promise().set_environment(env);
HITGNC   110 + 1607 return h_;
  111 + }
  112 +
  113 + /** Return the coroutine handle.
  114 +
  115 + @note Do not call `destroy()` on the returned handle while the
  116 + task is being awaited. The task's lifetime is normally managed
  117 + by `run_async`, `run`, or the awaiting parent; manually
  118 + destroying a suspended task that another coroutine is awaiting
  119 + produces undefined behavior. For cooperative cancellation, use
  120 + `std::stop_token`.
  121 +
  122 + @return The coroutine handle.
  123 + */
HITGNC   124 + 7 std::coroutine_handle<promise_type> handle() const noexcept
  125 + {
HITGNC   126 + 7 return h_;
  127 + }
  128 +
  129 + /** Release ownership of the coroutine frame.
  130 +
  131 + After calling this, destroying the task does not destroy the
  132 + coroutine frame. The caller becomes responsible for the frame's
  133 + lifetime.
  134 +
  135 + @note If the caller intends to call `destroy()` on the
  136 + released handle, it must do so only when the task has not
  137 + started or has fully completed. Destroying a suspended task
  138 + that is being awaited produces undefined behavior.
  139 +
  140 + @par Postconditions
  141 + `handle()` returns the original handle, but the task no longer
  142 + owns it.
  143 + */
HITGNC   144 + 7 void release() noexcept
  145 + {
HITGNC   146 + 7 h_ = nullptr;
HITGNC   147 + 7 }
  148 +
  149 + io_task(io_task const&) = delete;
  150 + io_task& operator=(io_task const&) = delete;
  151 +
  152 + /// Construct by moving, transferring ownership.
HITGNC   153 + 2442 io_task(io_task&& other) noexcept
HITGNC   154 + 2442 : h_(std::exchange(other.h_, nullptr))
  155 + {
HITGNC   156 + 2442 }
  157 +
  158 + /// Assign by moving, transferring ownership.
  159 + io_task& operator=(io_task&& other) noexcept
  160 + {
  161 + if(this != &other)
  162 + {
  163 + if(h_)
  164 + h_.destroy();
  165 + h_ = std::exchange(other.h_, nullptr);
  166 + }
  167 + return *this;
  168 + }
  169 +
  170 +
  171 + private:
HITGNC   172 + 1614 explicit io_task(std::coroutine_handle<promise_type> h)
HITGNC   173 + 1614 : h_(h)
  174 + {
HITGNC   175 + 1614 }
  176 +
  177 + std::coroutine_handle<promise_type> h_;
  178 +
  179 +
  180 + };
41   181  
42   } // namespace capy 182   } // namespace capy
43   } // namespace boost 183   } // namespace boost
44   184  
45   #endif 185   #endif