basic_writable_pipe.hpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. //
  2. // basic_writable_pipe.hpp
  3. // ~~~~~~~~~~~~~~~~~~~~~~~
  4. //
  5. // Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com)
  6. //
  7. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  8. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  9. //
  10. #ifndef BOOST_ASIO_BASIC_WRITABLE_PIPE_HPP
  11. #define BOOST_ASIO_BASIC_WRITABLE_PIPE_HPP
  12. #if defined(_MSC_VER) && (_MSC_VER >= 1200)
  13. # pragma once
  14. #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
  15. #include <boost/asio/detail/config.hpp>
  16. #if defined(BOOST_ASIO_HAS_PIPE) \
  17. || defined(GENERATING_DOCUMENTATION)
  18. #include <string>
  19. #include <utility>
  20. #include <boost/asio/any_io_executor.hpp>
  21. #include <boost/asio/async_result.hpp>
  22. #include <boost/asio/detail/handler_type_requirements.hpp>
  23. #include <boost/asio/detail/io_object_impl.hpp>
  24. #include <boost/asio/detail/non_const_lvalue.hpp>
  25. #include <boost/asio/detail/throw_error.hpp>
  26. #include <boost/asio/detail/type_traits.hpp>
  27. #include <boost/asio/error.hpp>
  28. #include <boost/asio/execution_context.hpp>
  29. #if defined(BOOST_ASIO_HAS_IOCP)
  30. # include <boost/asio/detail/win_iocp_handle_service.hpp>
  31. #elif defined(BOOST_ASIO_HAS_IO_URING_AS_DEFAULT)
  32. # include <boost/asio/detail/io_uring_descriptor_service.hpp>
  33. #else
  34. # include <boost/asio/detail/reactive_descriptor_service.hpp>
  35. #endif
  36. #include <boost/asio/detail/push_options.hpp>
  37. namespace boost {
  38. namespace asio {
  39. /// Provides pipe functionality.
  40. /**
  41. * The basic_writable_pipe class provides a wrapper over pipe
  42. * functionality.
  43. *
  44. * @par Thread Safety
  45. * @e Distinct @e objects: Safe.@n
  46. * @e Shared @e objects: Unsafe.
  47. */
  48. template <typename Executor = any_io_executor>
  49. class basic_writable_pipe
  50. {
  51. private:
  52. class initiate_async_write_some;
  53. public:
  54. /// The type of the executor associated with the object.
  55. typedef Executor executor_type;
  56. /// Rebinds the pipe type to another executor.
  57. template <typename Executor1>
  58. struct rebind_executor
  59. {
  60. /// The pipe type when rebound to the specified executor.
  61. typedef basic_writable_pipe<Executor1> other;
  62. };
  63. /// The native representation of a pipe.
  64. #if defined(GENERATING_DOCUMENTATION)
  65. typedef implementation_defined native_handle_type;
  66. #elif defined(BOOST_ASIO_HAS_IOCP)
  67. typedef detail::win_iocp_handle_service::native_handle_type
  68. native_handle_type;
  69. #elif defined(BOOST_ASIO_HAS_IO_URING_AS_DEFAULT)
  70. typedef detail::io_uring_descriptor_service::native_handle_type
  71. native_handle_type;
  72. #else
  73. typedef detail::reactive_descriptor_service::native_handle_type
  74. native_handle_type;
  75. #endif
  76. /// A basic_writable_pipe is always the lowest layer.
  77. typedef basic_writable_pipe lowest_layer_type;
  78. /// Construct a basic_writable_pipe without opening it.
  79. /**
  80. * This constructor creates a pipe without opening it.
  81. *
  82. * @param ex The I/O executor that the pipe will use, by default, to dispatch
  83. * handlers for any asynchronous operations performed on the pipe.
  84. */
  85. explicit basic_writable_pipe(const executor_type& ex)
  86. : impl_(0, ex)
  87. {
  88. }
  89. /// Construct a basic_writable_pipe without opening it.
  90. /**
  91. * This constructor creates a pipe without opening it.
  92. *
  93. * @param context An execution context which provides the I/O executor that
  94. * the pipe will use, by default, to dispatch handlers for any asynchronous
  95. * operations performed on the pipe.
  96. */
  97. template <typename ExecutionContext>
  98. explicit basic_writable_pipe(ExecutionContext& context,
  99. constraint_t<
  100. is_convertible<ExecutionContext&, execution_context&>::value,
  101. defaulted_constraint
  102. > = defaulted_constraint())
  103. : impl_(0, 0, context)
  104. {
  105. }
  106. /// Construct a basic_writable_pipe on an existing native pipe.
  107. /**
  108. * This constructor creates a pipe object to hold an existing native
  109. * pipe.
  110. *
  111. * @param ex The I/O executor that the pipe will use, by default, to
  112. * dispatch handlers for any asynchronous operations performed on the
  113. * pipe.
  114. *
  115. * @param native_pipe A native pipe.
  116. *
  117. * @throws boost::system::system_error Thrown on failure.
  118. */
  119. basic_writable_pipe(const executor_type& ex,
  120. const native_handle_type& native_pipe)
  121. : impl_(0, ex)
  122. {
  123. boost::system::error_code ec;
  124. impl_.get_service().assign(impl_.get_implementation(),
  125. native_pipe, ec);
  126. boost::asio::detail::throw_error(ec, "assign");
  127. }
  128. /// Construct a basic_writable_pipe on an existing native pipe.
  129. /**
  130. * This constructor creates a pipe object to hold an existing native
  131. * pipe.
  132. *
  133. * @param context An execution context which provides the I/O executor that
  134. * the pipe will use, by default, to dispatch handlers for any
  135. * asynchronous operations performed on the pipe.
  136. *
  137. * @param native_pipe A native pipe.
  138. *
  139. * @throws boost::system::system_error Thrown on failure.
  140. */
  141. template <typename ExecutionContext>
  142. basic_writable_pipe(ExecutionContext& context,
  143. const native_handle_type& native_pipe,
  144. constraint_t<
  145. is_convertible<ExecutionContext&, execution_context&>::value
  146. > = 0)
  147. : impl_(0, 0, context)
  148. {
  149. boost::system::error_code ec;
  150. impl_.get_service().assign(impl_.get_implementation(),
  151. native_pipe, ec);
  152. boost::asio::detail::throw_error(ec, "assign");
  153. }
  154. /// Move-construct a basic_writable_pipe from another.
  155. /**
  156. * This constructor moves a pipe from one object to another.
  157. *
  158. * @param other The other basic_writable_pipe object from which the move will
  159. * occur.
  160. *
  161. * @note Following the move, the moved-from object is in the same state as if
  162. * constructed using the @c basic_writable_pipe(const executor_type&)
  163. * constructor.
  164. */
  165. basic_writable_pipe(basic_writable_pipe&& other)
  166. : impl_(std::move(other.impl_))
  167. {
  168. }
  169. /// Move-assign a basic_writable_pipe from another.
  170. /**
  171. * This assignment operator moves a pipe from one object to another.
  172. *
  173. * @param other The other basic_writable_pipe object from which the move will
  174. * occur.
  175. *
  176. * @note Following the move, the moved-from object is in the same state as if
  177. * constructed using the @c basic_writable_pipe(const executor_type&)
  178. * constructor.
  179. */
  180. basic_writable_pipe& operator=(basic_writable_pipe&& other)
  181. {
  182. impl_ = std::move(other.impl_);
  183. return *this;
  184. }
  185. // All pipes have access to each other's implementations.
  186. template <typename Executor1>
  187. friend class basic_writable_pipe;
  188. /// Move-construct a basic_writable_pipe from a pipe of another executor type.
  189. /**
  190. * This constructor moves a pipe from one object to another.
  191. *
  192. * @param other The other basic_writable_pipe object from which the move will
  193. * occur.
  194. *
  195. * @note Following the move, the moved-from object is in the same state as if
  196. * constructed using the @c basic_writable_pipe(const executor_type&)
  197. * constructor.
  198. */
  199. template <typename Executor1>
  200. basic_writable_pipe(basic_writable_pipe<Executor1>&& other,
  201. constraint_t<
  202. is_convertible<Executor1, Executor>::value,
  203. defaulted_constraint
  204. > = defaulted_constraint())
  205. : impl_(std::move(other.impl_))
  206. {
  207. }
  208. /// Move-assign a basic_writable_pipe from a pipe of another executor type.
  209. /**
  210. * This assignment operator moves a pipe from one object to another.
  211. *
  212. * @param other The other basic_writable_pipe object from which the move will
  213. * occur.
  214. *
  215. * @note Following the move, the moved-from object is in the same state as if
  216. * constructed using the @c basic_writable_pipe(const executor_type&)
  217. * constructor.
  218. */
  219. template <typename Executor1>
  220. constraint_t<
  221. is_convertible<Executor1, Executor>::value,
  222. basic_writable_pipe&
  223. > operator=(basic_writable_pipe<Executor1>&& other)
  224. {
  225. basic_writable_pipe tmp(std::move(other));
  226. impl_ = std::move(tmp.impl_);
  227. return *this;
  228. }
  229. /// Destroys the pipe.
  230. /**
  231. * This function destroys the pipe, cancelling any outstanding
  232. * asynchronous wait operations associated with the pipe as if by
  233. * calling @c cancel.
  234. */
  235. ~basic_writable_pipe()
  236. {
  237. }
  238. /// Get the executor associated with the object.
  239. const executor_type& get_executor() noexcept
  240. {
  241. return impl_.get_executor();
  242. }
  243. /// Get a reference to the lowest layer.
  244. /**
  245. * This function returns a reference to the lowest layer in a stack of
  246. * layers. Since a basic_writable_pipe cannot contain any further layers, it
  247. * simply returns a reference to itself.
  248. *
  249. * @return A reference to the lowest layer in the stack of layers. Ownership
  250. * is not transferred to the caller.
  251. */
  252. lowest_layer_type& lowest_layer()
  253. {
  254. return *this;
  255. }
  256. /// Get a const reference to the lowest layer.
  257. /**
  258. * This function returns a const reference to the lowest layer in a stack of
  259. * layers. Since a basic_writable_pipe cannot contain any further layers, it
  260. * simply returns a reference to itself.
  261. *
  262. * @return A const reference to the lowest layer in the stack of layers.
  263. * Ownership is not transferred to the caller.
  264. */
  265. const lowest_layer_type& lowest_layer() const
  266. {
  267. return *this;
  268. }
  269. /// Assign an existing native pipe to the pipe.
  270. /*
  271. * This function opens the pipe to hold an existing native pipe.
  272. *
  273. * @param native_pipe A native pipe.
  274. *
  275. * @throws boost::system::system_error Thrown on failure.
  276. */
  277. void assign(const native_handle_type& native_pipe)
  278. {
  279. boost::system::error_code ec;
  280. impl_.get_service().assign(impl_.get_implementation(), native_pipe, ec);
  281. boost::asio::detail::throw_error(ec, "assign");
  282. }
  283. /// Assign an existing native pipe to the pipe.
  284. /*
  285. * This function opens the pipe to hold an existing native pipe.
  286. *
  287. * @param native_pipe A native pipe.
  288. *
  289. * @param ec Set to indicate what error occurred, if any.
  290. */
  291. BOOST_ASIO_SYNC_OP_VOID assign(const native_handle_type& native_pipe,
  292. boost::system::error_code& ec)
  293. {
  294. impl_.get_service().assign(impl_.get_implementation(), native_pipe, ec);
  295. BOOST_ASIO_SYNC_OP_VOID_RETURN(ec);
  296. }
  297. /// Determine whether the pipe is open.
  298. bool is_open() const
  299. {
  300. return impl_.get_service().is_open(impl_.get_implementation());
  301. }
  302. /// Close the pipe.
  303. /**
  304. * This function is used to close the pipe. Any asynchronous write operations
  305. * will be cancelled immediately, and will complete with the
  306. * boost::asio::error::operation_aborted error.
  307. *
  308. * @throws boost::system::system_error Thrown on failure.
  309. */
  310. void close()
  311. {
  312. boost::system::error_code ec;
  313. impl_.get_service().close(impl_.get_implementation(), ec);
  314. boost::asio::detail::throw_error(ec, "close");
  315. }
  316. /// Close the pipe.
  317. /**
  318. * This function is used to close the pipe. Any asynchronous write operations
  319. * will be cancelled immediately, and will complete with the
  320. * boost::asio::error::operation_aborted error.
  321. *
  322. * @param ec Set to indicate what error occurred, if any.
  323. */
  324. BOOST_ASIO_SYNC_OP_VOID close(boost::system::error_code& ec)
  325. {
  326. impl_.get_service().close(impl_.get_implementation(), ec);
  327. BOOST_ASIO_SYNC_OP_VOID_RETURN(ec);
  328. }
  329. /// Release ownership of the underlying native pipe.
  330. /**
  331. * This function causes all outstanding asynchronous write operations to
  332. * finish immediately, and the handlers for cancelled operations will be
  333. * passed the boost::asio::error::operation_aborted error. Ownership of the
  334. * native pipe is then transferred to the caller.
  335. *
  336. * @throws boost::system::system_error Thrown on failure.
  337. *
  338. * @note This function is unsupported on Windows versions prior to Windows
  339. * 8.1, and will fail with boost::asio::error::operation_not_supported on
  340. * these platforms.
  341. */
  342. #if defined(BOOST_ASIO_MSVC) && (BOOST_ASIO_MSVC >= 1400) \
  343. && (!defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0603)
  344. __declspec(deprecated("This function always fails with "
  345. "operation_not_supported when used on Windows versions "
  346. "prior to Windows 8.1."))
  347. #endif
  348. native_handle_type release()
  349. {
  350. boost::system::error_code ec;
  351. native_handle_type s = impl_.get_service().release(
  352. impl_.get_implementation(), ec);
  353. boost::asio::detail::throw_error(ec, "release");
  354. return s;
  355. }
  356. /// Release ownership of the underlying native pipe.
  357. /**
  358. * This function causes all outstanding asynchronous write operations to
  359. * finish immediately, and the handlers for cancelled operations will be
  360. * passed the boost::asio::error::operation_aborted error. Ownership of the
  361. * native pipe is then transferred to the caller.
  362. *
  363. * @param ec Set to indicate what error occurred, if any.
  364. *
  365. * @note This function is unsupported on Windows versions prior to Windows
  366. * 8.1, and will fail with boost::asio::error::operation_not_supported on
  367. * these platforms.
  368. */
  369. #if defined(BOOST_ASIO_MSVC) && (BOOST_ASIO_MSVC >= 1400) \
  370. && (!defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0603)
  371. __declspec(deprecated("This function always fails with "
  372. "operation_not_supported when used on Windows versions "
  373. "prior to Windows 8.1."))
  374. #endif
  375. native_handle_type release(boost::system::error_code& ec)
  376. {
  377. return impl_.get_service().release(impl_.get_implementation(), ec);
  378. }
  379. /// Get the native pipe representation.
  380. /**
  381. * This function may be used to obtain the underlying representation of the
  382. * pipe. This is intended to allow access to native pipe
  383. * functionality that is not otherwise provided.
  384. */
  385. native_handle_type native_handle()
  386. {
  387. return impl_.get_service().native_handle(impl_.get_implementation());
  388. }
  389. /// Cancel all asynchronous operations associated with the pipe.
  390. /**
  391. * This function causes all outstanding asynchronous write operations to
  392. * finish immediately, and the handlers for cancelled operations will be
  393. * passed the boost::asio::error::operation_aborted error.
  394. *
  395. * @throws boost::system::system_error Thrown on failure.
  396. */
  397. void cancel()
  398. {
  399. boost::system::error_code ec;
  400. impl_.get_service().cancel(impl_.get_implementation(), ec);
  401. boost::asio::detail::throw_error(ec, "cancel");
  402. }
  403. /// Cancel all asynchronous operations associated with the pipe.
  404. /**
  405. * This function causes all outstanding asynchronous write operations to
  406. * finish immediately, and the handlers for cancelled operations will be
  407. * passed the boost::asio::error::operation_aborted error.
  408. *
  409. * @param ec Set to indicate what error occurred, if any.
  410. */
  411. BOOST_ASIO_SYNC_OP_VOID cancel(boost::system::error_code& ec)
  412. {
  413. impl_.get_service().cancel(impl_.get_implementation(), ec);
  414. BOOST_ASIO_SYNC_OP_VOID_RETURN(ec);
  415. }
  416. /// Write some data to the pipe.
  417. /**
  418. * This function is used to write data to the pipe. The function call will
  419. * block until one or more bytes of the data has been written successfully,
  420. * or until an error occurs.
  421. *
  422. * @param buffers One or more data buffers to be written to the pipe.
  423. *
  424. * @returns The number of bytes written.
  425. *
  426. * @throws boost::system::system_error Thrown on failure. An error code of
  427. * boost::asio::error::eof indicates that the connection was closed by the
  428. * peer.
  429. *
  430. * @note The write_some operation may not transmit all of the data to the
  431. * peer. Consider using the @ref write function if you need to ensure that
  432. * all data is written before the blocking operation completes.
  433. *
  434. * @par Example
  435. * To write a single data buffer use the @ref buffer function as follows:
  436. * @code
  437. * pipe.write_some(boost::asio::buffer(data, size));
  438. * @endcode
  439. * See the @ref buffer documentation for information on writing multiple
  440. * buffers in one go, and how to use it with arrays, boost::array or
  441. * std::vector.
  442. */
  443. template <typename ConstBufferSequence>
  444. std::size_t write_some(const ConstBufferSequence& buffers)
  445. {
  446. boost::system::error_code ec;
  447. std::size_t s = impl_.get_service().write_some(
  448. impl_.get_implementation(), buffers, ec);
  449. boost::asio::detail::throw_error(ec, "write_some");
  450. return s;
  451. }
  452. /// Write some data to the pipe.
  453. /**
  454. * This function is used to write data to the pipe. The function call will
  455. * block until one or more bytes of the data has been written successfully,
  456. * or until an error occurs.
  457. *
  458. * @param buffers One or more data buffers to be written to the pipe.
  459. *
  460. * @param ec Set to indicate what error occurred, if any.
  461. *
  462. * @returns The number of bytes written. Returns 0 if an error occurred.
  463. *
  464. * @note The write_some operation may not transmit all of the data to the
  465. * peer. Consider using the @ref write function if you need to ensure that
  466. * all data is written before the blocking operation completes.
  467. */
  468. template <typename ConstBufferSequence>
  469. std::size_t write_some(const ConstBufferSequence& buffers,
  470. boost::system::error_code& ec)
  471. {
  472. return impl_.get_service().write_some(
  473. impl_.get_implementation(), buffers, ec);
  474. }
  475. /// Start an asynchronous write.
  476. /**
  477. * This function is used to asynchronously write data to the pipe. It is an
  478. * initiating function for an @ref asynchronous_operation, and always returns
  479. * immediately.
  480. *
  481. * @param buffers One or more data buffers to be written to the pipe.
  482. * Although the buffers object may be copied as necessary, ownership of the
  483. * underlying memory blocks is retained by the caller, which must guarantee
  484. * that they remain valid until the completion handler is called.
  485. *
  486. * @param token The @ref completion_token that will be used to produce a
  487. * completion handler, which will be called when the write completes.
  488. * Potential completion tokens include @ref use_future, @ref use_awaitable,
  489. * @ref yield_context, or a function object with the correct completion
  490. * signature. The function signature of the completion handler must be:
  491. * @code void handler(
  492. * const boost::system::error_code& error, // Result of operation.
  493. * std::size_t bytes_transferred // Number of bytes written.
  494. * ); @endcode
  495. * Regardless of whether the asynchronous operation completes immediately or
  496. * not, the completion handler will not be invoked from within this function.
  497. * On immediate completion, invocation of the handler will be performed in a
  498. * manner equivalent to using boost::asio::post().
  499. *
  500. * @par Completion Signature
  501. * @code void(boost::system::error_code, std::size_t) @endcode
  502. *
  503. * @note The write operation may not transmit all of the data to the peer.
  504. * Consider using the @ref async_write function if you need to ensure that all
  505. * data is written before the asynchronous operation completes.
  506. *
  507. * @par Example
  508. * To write a single data buffer use the @ref buffer function as follows:
  509. * @code
  510. * pipe.async_write_some(boost::asio::buffer(data, size), handler);
  511. * @endcode
  512. * See the @ref buffer documentation for information on writing multiple
  513. * buffers in one go, and how to use it with arrays, boost::array or
  514. * std::vector.
  515. */
  516. template <typename ConstBufferSequence,
  517. BOOST_ASIO_COMPLETION_TOKEN_FOR(void (boost::system::error_code,
  518. std::size_t)) WriteToken = default_completion_token_t<executor_type>>
  519. auto async_write_some(const ConstBufferSequence& buffers,
  520. WriteToken&& token = default_completion_token_t<executor_type>())
  521. -> decltype(
  522. async_initiate<WriteToken,
  523. void (boost::system::error_code, std::size_t)>(
  524. declval<initiate_async_write_some>(), token, buffers))
  525. {
  526. return async_initiate<WriteToken,
  527. void (boost::system::error_code, std::size_t)>(
  528. initiate_async_write_some(this), token, buffers);
  529. }
  530. private:
  531. // Disallow copying and assignment.
  532. basic_writable_pipe(const basic_writable_pipe&) = delete;
  533. basic_writable_pipe& operator=(const basic_writable_pipe&) = delete;
  534. class initiate_async_write_some
  535. {
  536. public:
  537. typedef Executor executor_type;
  538. explicit initiate_async_write_some(basic_writable_pipe* self)
  539. : self_(self)
  540. {
  541. }
  542. const executor_type& get_executor() const noexcept
  543. {
  544. return self_->get_executor();
  545. }
  546. template <typename WriteHandler, typename ConstBufferSequence>
  547. void operator()(WriteHandler&& handler,
  548. const ConstBufferSequence& buffers) const
  549. {
  550. // If you get an error on the following line it means that your handler
  551. // does not meet the documented type requirements for a WriteHandler.
  552. BOOST_ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check;
  553. detail::non_const_lvalue<WriteHandler> handler2(handler);
  554. self_->impl_.get_service().async_write_some(
  555. self_->impl_.get_implementation(), buffers,
  556. handler2.value, self_->impl_.get_executor());
  557. }
  558. private:
  559. basic_writable_pipe* self_;
  560. };
  561. #if defined(BOOST_ASIO_HAS_IOCP)
  562. detail::io_object_impl<detail::win_iocp_handle_service, Executor> impl_;
  563. #elif defined(BOOST_ASIO_HAS_IO_URING_AS_DEFAULT)
  564. detail::io_object_impl<detail::io_uring_descriptor_service, Executor> impl_;
  565. #else
  566. detail::io_object_impl<detail::reactive_descriptor_service, Executor> impl_;
  567. #endif
  568. };
  569. } // namespace asio
  570. } // namespace boost
  571. #include <boost/asio/detail/pop_options.hpp>
  572. #endif // defined(BOOST_ASIO_HAS_PIPE)
  573. // || defined(GENERATING_DOCUMENTATION)
  574. #endif // BOOST_ASIO_BASIC_WRITABLE_PIPE_HPP