100.00% Lines (13/13) 100.00% Functions (6/6)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
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/corosio 7   // Official repository: https://github.com/cppalliance/corosio
8   // 8   //
9   9  
10   #ifndef BOOST_COROSIO_POSIX_STREAM_DESCRIPTOR_HPP 10   #ifndef BOOST_COROSIO_POSIX_STREAM_DESCRIPTOR_HPP
11   #define BOOST_COROSIO_POSIX_STREAM_DESCRIPTOR_HPP 11   #define BOOST_COROSIO_POSIX_STREAM_DESCRIPTOR_HPP
12   12  
13   #include <boost/corosio/detail/config.hpp> 13   #include <boost/corosio/detail/config.hpp>
14   #include <boost/corosio/detail/platform.hpp> 14   #include <boost/corosio/detail/platform.hpp>
15   15  
16   #if BOOST_COROSIO_POSIX || defined(BOOST_COROSIO_MRDOCS) 16   #if BOOST_COROSIO_POSIX || defined(BOOST_COROSIO_MRDOCS)
17   17  
18   #include <boost/corosio/detail/except.hpp> 18   #include <boost/corosio/detail/except.hpp>
19   #include <boost/corosio/detail/native_handle.hpp> 19   #include <boost/corosio/detail/native_handle.hpp>
20   #include <boost/corosio/detail/op_base.hpp> 20   #include <boost/corosio/detail/op_base.hpp>
21   #include <boost/corosio/error.hpp> 21   #include <boost/corosio/error.hpp>
22   #include <boost/corosio/io/io_stream.hpp> 22   #include <boost/corosio/io/io_stream.hpp>
23   #include <boost/corosio/wait_type.hpp> 23   #include <boost/corosio/wait_type.hpp>
24   #include <boost/capy/ex/executor_ref.hpp> 24   #include <boost/capy/ex/executor_ref.hpp>
25   #include <boost/capy/ex/execution_context.hpp> 25   #include <boost/capy/ex/execution_context.hpp>
26   #include <boost/capy/concept/executor.hpp> 26   #include <boost/capy/concept/executor.hpp>
27   27  
28   #include <concepts> 28   #include <concepts>
29   #include <boost/capy/continuation.hpp> 29   #include <boost/capy/continuation.hpp>
30   30  
31   #include <coroutine> 31   #include <coroutine>
32   #include <stop_token> 32   #include <stop_token>
33   #include <system_error> 33   #include <system_error>
34   #include <type_traits> 34   #include <type_traits>
35   35  
36   /* Adoption of an already-open pollable POSIX descriptor. 36   /* Adoption of an already-open pollable POSIX descriptor.
37   37  
38   The two contract points that are not obvious from the 38   The two contract points that are not obvious from the
39   declarations: 39   declarations:
40   40  
41   assign() requires a closed object and never touches an open one. 41   assign() requires a closed object and never touches an open one.
42   Every failure, validation or kernel refusal, leaves the object 42   Every failure, validation or kernel refusal, leaves the object
43   closed and the fd with the caller. 43   closed and the fd with the caller.
44   44  
45   On the reactor backends O_NONBLOCK is applied lazily, at the first 45   On the reactor backends O_NONBLOCK is applied lazily, at the first
46   read_some/write_some, and never restored; io_uring never touches 46   read_some/write_some, and never restored; io_uring never touches
47   it. A wait()-only user never triggers it, which is what makes 47   it. A wait()-only user never triggers it, which is what makes
48   adopting STDIN_FILENO safe: flipping the flag would change the 48   adopting STDIN_FILENO safe: flipping the flag would change the
49   parent shell's terminal, because the flag lives on the shared open 49   parent shell's terminal, because the flag lives on the shared open
50   file description, not on the descriptor. 50   file description, not on the descriptor.
51   */ 51   */
52   52  
53   namespace boost::corosio { 53   namespace boost::corosio {
54   54  
55   /** Drives an already-open POSIX descriptor from an `io_context`. 55   /** Drives an already-open POSIX descriptor from an `io_context`.
56   56  
57   Wraps an already-open pollable file descriptor and drives it 57   Wraps an already-open pollable file descriptor and drives it
58   from the `io_context`. The kinds in scope are character devices, 58   from the `io_context`. The kinds in scope are character devices,
59   `inotify`, `eventfd`, `timerfd`, `pidfd`, pipes, ttys, and socket 59   `inotify`, `eventfd`, `timerfd`, `pidfd`, pipes, ttys, and socket
60   kinds corosio does not otherwise wrap. The descriptor must come 60   kinds corosio does not otherwise wrap. The descriptor must come
61   from the caller; this type never creates one. 61   from the caller; this type never creates one.
62   62  
63   The type name is deliberately platform-qualified. Portability 63   The type name is deliberately platform-qualified. Portability
64   comes from the interfaces it implements, not from the name. A 64   comes from the interfaces it implements, not from the name. A
65   `posix_stream_descriptor` is an @ref io_stream. `capy::read`, 65   `posix_stream_descriptor` is an @ref io_stream. `capy::read`,
66   `capy::write`, other `capy::Stream`-constrained algorithms and 66   `capy::write`, other `capy::Stream`-constrained algorithms and
67   TLS layering therefore work on it exactly as they do on a 67   TLS layering therefore work on it exactly as they do on a
68   socket. 68   socket.
69   69  
70   @par Ownership 70   @par Ownership
71   `assign()` takes ownership and `close()` closes the 71   `assign()` takes ownership and `close()` closes the
72   descriptor. To integrate with a library that owns the fd, adopt 72   descriptor. To integrate with a library that owns the fd, adopt
73   a `dup()` of it: readiness lives on the open file description, 73   a `dup()` of it: readiness lives on the open file description,
74   which both descriptors share. 74   which both descriptors share.
75   75  
76   @par Descriptor Flags 76   @par Descriptor Flags
77   `assign()` and `wait()` never modify the descriptor on any 77   `assign()` and `wait()` never modify the descriptor on any
78   backend. On epoll, kqueue and select the first `read_some()` or 78   backend. On epoll, kqueue and select the first `read_some()` or
79   `write_some()` sets `O_NONBLOCK` and never restores it. On 79   `write_some()` sets `O_NONBLOCK` and never restores it. On
80   io_uring nothing is ever modified. A transfer the kernel cannot 80   io_uring nothing is ever modified. A transfer the kernel cannot
81   complete through its internal poll waits in a kernel worker 81   complete through its internal poll waits in a kernel worker
82   thread. Cancellation reaches it only if the driver's wait is 82   thread. Cancellation reaches it only if the driver's wait is
83   interruptible. The flag lives on the shared 83   interruptible. The flag lives on the shared
84   open file description, so restoring it would race every other 84   open file description, so restoring it would race every other
85   holder. A 85   holder. A
86   `dup()` is no escape: the duplicate shares that same description, 86   `dup()` is no escape: the duplicate shares that same description,
87   so the flag change reaches the other holder anyway. When another 87   so the flag change reaches the other holder anyway. When another
88   party owns the descriptor and cannot tolerate `O_NONBLOCK`, use 88   party owns the descriptor and cannot tolerate `O_NONBLOCK`, use
89   `wait()` -- which never modifies the descriptor -- and do the I/O 89   `wait()` -- which never modifies the descriptor -- and do the I/O
90   yourself. 90   yourself.
91   91  
92   @par Rejected Descriptors 92   @par Rejected Descriptors
93   Regular files, block devices, and directories are rejected with 93   Regular files, block devices, and directories are rejected with
94   `errc::operation_not_supported`. @ref stream_file and 94   `errc::operation_not_supported`. @ref stream_file and
95   @ref random_access_file adopt regular files and block devices. A 95   @ref random_access_file adopt regular files and block devices. A
96   directory is adoptable by no corosio type. A character device 96   directory is adoptable by no corosio type. A character device
97   the I/O backend cannot watch, such as `/dev/null`, is adopted on 97   the I/O backend cannot watch, such as `/dev/null`, is adopted on
98   every I/O backend. On epoll, kqueue, and io_uring, an operation 98   every I/O backend. On epoll, kqueue, and io_uring, an operation
99   on it that would have to wait for readiness completes with 99   on it that would have to wait for readiness completes with
100   `errc::operation_not_supported`. The exception is a transfer on 100   `errc::operation_not_supported`. The exception is a transfer on
101   io_uring when the descriptor is blocking: it waits in a kernel 101   io_uring when the descriptor is blocking: it waits in a kernel
102   worker thread instead. On select, the device is always ready for 102   worker thread instead. On select, the device is always ready for
103   reading and writing. Its `wait(wait_type::error)` waits until 103   reading and writing. Its `wait(wait_type::error)` waits until
104   cancelled, except on macOS, where it completes at once with an 104   cancelled, except on macOS, where it completes at once with an
105   error. On select, a descriptor at or above `FD_SETSIZE` is 105   error. On select, a descriptor at or above `FD_SETSIZE` is
106   rejected with `errc::too_many_files_open`. Where a kernel refusal 106   rejected with `errc::too_many_files_open`. Where a kernel refusal
107   surfaces depends on the backend. The epoll and kqueue backends 107   surfaces depends on the backend. The epoll and kqueue backends
108   register the descriptor during `assign()`, so a refusal fails 108   register the descriptor during `assign()`, so a refusal fails
109   there. What remains to refuse is resource exhaustion (`ENOMEM`, 109   there. What remains to refuse is resource exhaustion (`ENOMEM`,
110   `ENOSPC`). 110   `ENOSPC`).
111   kqueue watches writes only once a write-direction operation first 111   kqueue watches writes only once a write-direction operation first
112   has to wait. A descriptor that refuses write watching is still 112   has to wait. A descriptor that refuses write watching is still
113   adopted, and such a write or `wait(wait_type::write)` completes 113   adopted, and such a write or `wait(wait_type::write)` completes
114   with the kernel's refusal. The io_uring backend has no adopt-time 114   with the kernel's refusal. The io_uring backend has no adopt-time
115   registration, so `assign()` succeeds and takes ownership. The 115   registration, so `assign()` succeeds and takes ownership. The
116   refusal appears at the first `read_some()` or `write_some()`. 116   refusal appears at the first `read_some()` or `write_some()`.
117   select registers nothing with the kernel, so it has no refusal to 117   select registers nothing with the kernel, so it has no refusal to
118   report. 118   report.
119   119  
120   @par Signals 120   @par Signals
121   Writing to a descriptor whose peer has closed raises `SIGPIPE` 121   Writing to a descriptor whose peer has closed raises `SIGPIPE`
122   in the default disposition -- unlike the socket types, which 122   in the default disposition -- unlike the socket types, which
123   suppress it. `MSG_NOSIGNAL` is a `send()` flag with no `writev` 123   suppress it. `MSG_NOSIGNAL` is a `send()` flag with no `writev`
124   equivalent, and `SO_NOSIGPIPE` is a socket option, so neither 124   equivalent, and `SO_NOSIGPIPE` is a socket option, so neither
125   applies to an arbitrary descriptor. Callers must install 125   applies to an arbitrary descriptor. Callers must install
126   `SIG_IGN` for `SIGPIPE` if that is not already the process's 126   `SIG_IGN` for `SIGPIPE` if that is not already the process's
127   disposition. 127   disposition.
128   128  
129   @par Thread Safety 129   @par Thread Safety
130   Distinct objects: Safe.@n 130   Distinct objects: Safe.@n
131   Shared objects: Unsafe. A descriptor must not have concurrent 131   Shared objects: Unsafe. A descriptor must not have concurrent
132   operations of the same type (e.g. two simultaneous reads). One 132   operations of the same type (e.g. two simultaneous reads). One
133   read and one write may be in flight simultaneously. 133   read and one write may be in flight simultaneously.
134   134  
135   @see io_stream, stream_file, wait_type 135   @see io_stream, stream_file, wait_type
136   */ 136   */
137   class BOOST_COROSIO_DECL posix_stream_descriptor : public io_stream 137   class BOOST_COROSIO_DECL posix_stream_descriptor : public io_stream
138   { 138   {
139   public: 139   public:
140   /** Define backend hooks for descriptor operations. 140   /** Define backend hooks for descriptor operations.
141   141  
142   Platform backends (epoll, kqueue, select, io_uring) derive 142   Platform backends (epoll, kqueue, select, io_uring) derive
143   from this to implement descriptor I/O. 143   from this to implement descriptor I/O.
144   */ 144   */
145   struct implementation : io_stream::implementation 145   struct implementation : io_stream::implementation
146   { 146   {
147   /** Initiate an asynchronous wait for descriptor readiness. 147   /** Initiate an asynchronous wait for descriptor readiness.
148   148  
149   Completes when the descriptor becomes ready in the 149   Completes when the descriptor becomes ready in the
150   given direction, or an error condition is reported. No 150   given direction, or an error condition is reported. No
151   bytes are transferred and no descriptor flag is changed. 151   bytes are transferred and no descriptor flag is changed.
152   152  
153   @param cont Continuation to resume on completion; it lives in 153   @param cont Continuation to resume on completion; it lives in
154   the awaiting frame until then. 154   the awaiting frame until then.
155   @param ex Executor for dispatching the completion. 155   @param ex Executor for dispatching the completion.
156   @param w The direction to wait on. 156   @param w The direction to wait on.
157   @param token Stop token for cancellation. 157   @param token Stop token for cancellation.
158   @param ec Output error code. 158   @param ec Output error code.
159   @return Coroutine handle to resume immediately. 159   @return Coroutine handle to resume immediately.
160   */ 160   */
161   virtual std::coroutine_handle<> wait( 161   virtual std::coroutine_handle<> wait(
162   capy::continuation& cont, 162   capy::continuation& cont,
163   capy::executor_ref ex, 163   capy::executor_ref ex,
164   wait_type w, 164   wait_type w,
165   std::stop_token token, 165   std::stop_token token,
166   std::error_code* ec) = 0; 166   std::error_code* ec) = 0;
167   167  
168   /// Return the platform descriptor, or -1 when not open. 168   /// Return the platform descriptor, or -1 when not open.
169   virtual native_handle_type native_handle() const noexcept = 0; 169   virtual native_handle_type native_handle() const noexcept = 0;
170   170  
171   /** Release ownership of the native descriptor. 171   /** Release ownership of the native descriptor.
172   172  
173   Stops tracking the descriptor and cancels its pending 173   Stops tracking the descriptor and cancels its pending
174   operations, without closing it. The caller takes 174   operations, without closing it. The caller takes
175   ownership. 175   ownership.
176   176  
177   @return The native descriptor. 177   @return The native descriptor.
178   */ 178   */
179   virtual native_handle_type release_descriptor() noexcept = 0; 179   virtual native_handle_type release_descriptor() noexcept = 0;
180   180  
181   /** Request cancellation of pending asynchronous operations. 181   /** Request cancellation of pending asynchronous operations.
182   182  
183   All outstanding operations complete with a code that 183   All outstanding operations complete with a code that
184   compares equal to `capy::cond::canceled`. 184   compares equal to `capy::cond::canceled`.
185   */ 185   */
186   virtual void cancel() noexcept = 0; 186   virtual void cancel() noexcept = 0;
187   }; 187   };
188   188  
189   /// Represent the awaitable returned by @ref wait. 189   /// Represent the awaitable returned by @ref wait.
190   struct wait_awaitable : detail::void_op_base<wait_awaitable> 190   struct wait_awaitable : detail::void_op_base<wait_awaitable>
191   { 191   {
192   private: 192   private:
193   friend posix_stream_descriptor; 193   friend posix_stream_descriptor;
194   194  
HITCBC 195   35 wait_awaitable(posix_stream_descriptor& d, wait_type w) noexcept 195   35 wait_awaitable(posix_stream_descriptor& d, wait_type w) noexcept
HITCBC 196   70 : d_(d) 196   70 : d_(d)
HITCBC 197   35 , w_(w) 197   35 , w_(w)
198   { 198   {
HITCBC 199   35 } 199   35 }
200   200  
201   friend detail::void_op_base<wait_awaitable>; 201   friend detail::void_op_base<wait_awaitable>;
202   202  
203   posix_stream_descriptor& d_; 203   posix_stream_descriptor& d_;
204   wait_type w_; 204   wait_type w_;
205   205  
206   std::coroutine_handle<> 206   std::coroutine_handle<>
HITCBC 207   35 dispatch(capy::continuation& cont, capy::executor_ref ex) const 207   35 dispatch(capy::continuation& cont, capy::executor_ref ex) const
208   { 208   {
HITCBC 209   35 return d_.get().wait(cont, ex, w_, token_, &ec_); 209   35 return d_.get().wait(cont, ex, w_, token_, &ec_);
210   } 210   }
211   }; 211   };
212   212  
213   /** Closes the descriptor if open, cancelling pending operations. */ 213   /** Closes the descriptor if open, cancelling pending operations. */
214   ~posix_stream_descriptor() override; 214   ~posix_stream_descriptor() override;
215   215  
216   /** Construct from an execution context. 216   /** Construct from an execution context.
217   217  
218   @param ctx The execution context that owns this object. 218   @param ctx The execution context that owns this object.
219   */ 219   */
220   explicit posix_stream_descriptor(capy::execution_context& ctx); 220   explicit posix_stream_descriptor(capy::execution_context& ctx);
221   221  
222   /** Construct from an executor. 222   /** Construct from an executor.
223   223  
224   The overload excludes `posix_stream_descriptor` itself so that it 224   The overload excludes `posix_stream_descriptor` itself so that it
225   cannot displace the move constructor. 225   cannot displace the move constructor.
226   226  
227   @tparam Ex A type satisfying `capy::Executor`. 227   @tparam Ex A type satisfying `capy::Executor`.
228   @param ex The executor whose context owns this object. 228   @param ex The executor whose context owns this object.
229   */ 229   */
230   template<class Ex> 230   template<class Ex>
231   requires(!std::same_as< 231   requires(!std::same_as<
232   std::remove_cvref_t<Ex>, 232   std::remove_cvref_t<Ex>,
233   posix_stream_descriptor>) && 233   posix_stream_descriptor>) &&
234   capy::Executor<Ex> 234   capy::Executor<Ex>
235   explicit posix_stream_descriptor(Ex const& ex) 235   explicit posix_stream_descriptor(Ex const& ex)
236   : posix_stream_descriptor(ex.context()) 236   : posix_stream_descriptor(ex.context())
237   { 237   {
238   } 238   }
239   239  
240   /** Transfer ownership of the descriptor from @p other. 240   /** Transfer ownership of the descriptor from @p other.
241   241  
242   After the move, @p other is in a moved-from state and may only 242   After the move, @p other is in a moved-from state and may only
243   be destroyed or assigned to. 243   be destroyed or assigned to.
244   244  
245   @param other The object to move from. 245   @param other The object to move from.
246   @pre No awaitables returned by @p other's methods exist. 246   @pre No awaitables returned by @p other's methods exist.
247   */ 247   */
248   posix_stream_descriptor(posix_stream_descriptor&& other) noexcept 248   posix_stream_descriptor(posix_stream_descriptor&& other) noexcept
249   : io_object(std::move(other)) 249   : io_object(std::move(other))
250   { 250   {
251   } 251   }
252   252  
253   /** Close any held descriptor and transfer ownership from @p other. 253   /** Close any held descriptor and transfer ownership from @p other.
254   254  
255   After the move, @p other is in a moved-from state and may only 255   After the move, @p other is in a moved-from state and may only
256   be destroyed or assigned to. 256   be destroyed or assigned to.
257   257  
258   @param other The object to move from. 258   @param other The object to move from.
259   @return `*this`. 259   @return `*this`.
260   @pre No awaitables returned by either object's methods exist. 260   @pre No awaitables returned by either object's methods exist.
261   */ 261   */
262   posix_stream_descriptor& operator=(posix_stream_descriptor&& other) noexcept 262   posix_stream_descriptor& operator=(posix_stream_descriptor&& other) noexcept
263   { 263   {
264   io_object::operator=(std::move(other)); 264   io_object::operator=(std::move(other));
265   return *this; 265   return *this;
266   } 266   }
267   267  
268   /// Copy construction is disabled; the descriptor is uniquely owned. 268   /// Copy construction is disabled; the descriptor is uniquely owned.
269   posix_stream_descriptor(posix_stream_descriptor const&) = delete; 269   posix_stream_descriptor(posix_stream_descriptor const&) = delete;
270   /// Copy assignment is disabled; the descriptor is uniquely owned. 270   /// Copy assignment is disabled; the descriptor is uniquely owned.
271   posix_stream_descriptor& operator=(posix_stream_descriptor const&) = delete; 271   posix_stream_descriptor& operator=(posix_stream_descriptor const&) = delete;
272   272  
273   /** Adopt an existing native descriptor. 273   /** Adopt an existing native descriptor.
274   274  
275   The object must be closed. To replace a held descriptor, 275   The object must be closed. To replace a held descriptor,
276   `close()` or `release()` it first. On success the object takes 276   `close()` or `release()` it first. On success the object takes
277   ownership and @p fd is closed by `close()` or the destructor. 277   ownership and @p fd is closed by `close()` or the destructor.
278   278  
279   No descriptor flag is modified here, `O_NONBLOCK` included. 279   No descriptor flag is modified here, `O_NONBLOCK` included.
280   280  
281   @param fd The native descriptor to adopt. 281   @param fd The native descriptor to adopt.
282   282  
283   @return `error::already_open` if this object is open. 283   @return `error::already_open` if this object is open.
284   `errc::bad_file_descriptor` when @p fd is negative or 284   `errc::bad_file_descriptor` when @p fd is negative or
285   closed. `errc::operation_not_supported` when @p fd names 285   closed. `errc::operation_not_supported` when @p fd names
286   a regular file, block device, or directory. 286   a regular file, block device, or directory.
287   `errc::too_many_files_open` on select when @p fd is at or 287   `errc::too_many_files_open` on select when @p fd is at or
288   above `FD_SETSIZE`. Otherwise the error the system 288   above `FD_SETSIZE`. Otherwise the error the system
289   reported, or an empty code. 289   reported, or an empty code.
290   290  
291   @par Exception Safety 291   @par Exception Safety
292   Throws nothing. On failure the object is unchanged and @p fd 292   Throws nothing. On failure the object is unchanged and @p fd
293   stays with the caller. 293   stays with the caller.
294   294  
295   @see release 295   @see release
296   */ 296   */
297   [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept; 297   [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept;
298   298  
299   /** Release ownership of the native descriptor. 299   /** Release ownership of the native descriptor.
300   300  
301   The object becomes not-open and pending operations are 301   The object becomes not-open and pending operations are
302   cancelled. The caller is responsible for closing the result. 302   cancelled. The caller is responsible for closing the result.
303   303  
304   @return The native descriptor. 304   @return The native descriptor.
305   305  
306   @throws std::system_error `errc::bad_file_descriptor` if the 306   @throws std::system_error `errc::bad_file_descriptor` if the
307   object is not open. 307   object is not open.
308   308  
309   @post `is_open() == false` 309   @post `is_open() == false`
310   */ 310   */
311   native_handle_type release(); 311   native_handle_type release();
312   312  
313   /** Close the descriptor. 313   /** Close the descriptor.
314   314  
315   Pending operations complete with a code that compares equal 315   Pending operations complete with a code that compares equal
316   to `capy::cond::canceled`. Does nothing when not open. 316   to `capy::cond::canceled`. Does nothing when not open.
317   */ 317   */
318   void close() noexcept; 318   void close() noexcept;
319   319  
320   /** Check whether a descriptor is held. 320   /** Check whether a descriptor is held.
321   321  
322   @return `true` if a descriptor is held. 322   @return `true` if a descriptor is held.
323   */ 323   */
HITCBC 324   474 bool is_open() const noexcept 324   474 bool is_open() const noexcept
325   { 325   {
HITCBC 326   474 return h_ && get().native_handle() >= 0; 326   474 return h_ && get().native_handle() >= 0;
327   } 327   }
328   328  
329   /** Get the native descriptor. 329   /** Get the native descriptor.
330   330  
331   @return The native descriptor, or -1 when not open. 331   @return The native descriptor, or -1 when not open.
332   */ 332   */
333   native_handle_type native_handle() const noexcept; 333   native_handle_type native_handle() const noexcept;
334   334  
335   /** Cancel pending asynchronous operations. 335   /** Cancel pending asynchronous operations.
336   336  
337   Outstanding operations complete with a code that compares 337   Outstanding operations complete with a code that compares
338   equal to `capy::cond::canceled`. 338   equal to `capy::cond::canceled`.
339   */ 339   */
340   void cancel() noexcept; 340   void cancel() noexcept;
341   341  
342   /** Wait for readiness without transferring bytes. 342   /** Wait for readiness without transferring bytes.
343   343  
344   Never reads, writes or modifies the descriptor -- including 344   Never reads, writes or modifies the descriptor -- including
345   its flags -- which is what makes it safe on a descriptor 345   its flags -- which is what makes it safe on a descriptor
346   another library owns. 346   another library owns.
347   347  
348   @param w The direction to wait on. 348   @param w The direction to wait on.
349   349  
350   @return An awaitable yielding `capy::results<>`. Yields 350   @return An awaitable yielding `capy::results<>`. Yields
351   `errc::bad_file_descriptor` when not open. 351   `errc::bad_file_descriptor` when not open.
352   352  
353   @par Example 353   @par Example
354   @par !example wait 354   @par !example wait
355   355  
356   @see wait_type 356   @see wait_type
357   */ 357   */
HITCBC 358   35 [[nodiscard]] wait_awaitable wait(wait_type w) 358   35 [[nodiscard]] wait_awaitable wait(wait_type w)
359   { 359   {
HITCBC 360   35 return wait_awaitable(*this, w); 360   35 return wait_awaitable(*this, w);
361   } 361   }
362   362  
363   protected: 363   protected:
364   /// Default-construct (for derived types that initialize `io_object` directly). 364   /// Default-construct (for derived types that initialize `io_object` directly).
HITCBC 365   14 posix_stream_descriptor() noexcept = default; 365   14 posix_stream_descriptor() noexcept = default;
366   366  
367   /** Construct from a handle. 367   /** Construct from a handle.
368   368  
369   @param h The handle this object takes ownership of. 369   @param h The handle this object takes ownership of.
370   */ 370   */
371   explicit posix_stream_descriptor(handle h) noexcept 371   explicit posix_stream_descriptor(handle h) noexcept
372   : io_object(std::move(h)) 372   : io_object(std::move(h))
373   { 373   {
374   } 374   }
375   375  
376   private: 376   private:
377   /// Return the implementation downcast to this type's interface. 377   /// Return the implementation downcast to this type's interface.
HITCBC 378   759 implementation& get() const noexcept 378   759 implementation& get() const noexcept
379   { 379   {
HITCBC 380   759 return *static_cast<implementation*>(h_.get()); 380   759 return *static_cast<implementation*>(h_.get());
381   } 381   }
382   }; 382   };
383   383  
384   } // namespace boost::corosio 384   } // namespace boost::corosio
385   385  
386   #endif // BOOST_COROSIO_POSIX || BOOST_COROSIO_MRDOCS 386   #endif // BOOST_COROSIO_POSIX || BOOST_COROSIO_MRDOCS
387   387  
388   #endif 388   #endif