coroutine.hpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. //
  2. // coroutine.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_COROUTINE_HPP
  11. #define BOOST_ASIO_COROUTINE_HPP
  12. namespace boost {
  13. namespace asio {
  14. namespace detail {
  15. class coroutine_ref;
  16. } // namespace detail
  17. /// Provides support for implementing stackless coroutines.
  18. /**
  19. * The @c coroutine class may be used to implement stackless coroutines. The
  20. * class itself is used to store the current state of the coroutine.
  21. *
  22. * Coroutines are copy-constructible and assignable, and the space overhead is
  23. * a single int. They can be used as a base class:
  24. *
  25. * @code class session : coroutine
  26. * {
  27. * ...
  28. * }; @endcode
  29. *
  30. * or as a data member:
  31. *
  32. * @code class session
  33. * {
  34. * ...
  35. * coroutine coro_;
  36. * }; @endcode
  37. *
  38. * or even bound in as a function argument using lambdas or @c bind(). The
  39. * important thing is that as the application maintains a copy of the object
  40. * for as long as the coroutine must be kept alive.
  41. *
  42. * @par Pseudo-keywords
  43. *
  44. * A coroutine is used in conjunction with certain "pseudo-keywords", which
  45. * are implemented as macros. These macros are defined by a header file:
  46. *
  47. * @code #include <boost/asio/yield.hpp>@endcode
  48. *
  49. * and may conversely be undefined as follows:
  50. *
  51. * @code #include <boost/asio/unyield.hpp>@endcode
  52. *
  53. * <b>reenter</b>
  54. *
  55. * The @c reenter macro is used to define the body of a coroutine. It takes a
  56. * single argument: a pointer or reference to a coroutine object. For example,
  57. * if the base class is a coroutine object you may write:
  58. *
  59. * @code reenter (this)
  60. * {
  61. * ... coroutine body ...
  62. * } @endcode
  63. *
  64. * and if a data member or other variable you can write:
  65. *
  66. * @code reenter (coro_)
  67. * {
  68. * ... coroutine body ...
  69. * } @endcode
  70. *
  71. * When @c reenter is executed at runtime, control jumps to the location of the
  72. * last @c yield or @c fork.
  73. *
  74. * The coroutine body may also be a single statement, such as:
  75. *
  76. * @code reenter (this) for (;;)
  77. * {
  78. * ...
  79. * } @endcode
  80. *
  81. * @b Limitation: The @c reenter macro is implemented using a switch. This
  82. * means that you must take care when using local variables within the
  83. * coroutine body. The local variable is not allowed in a position where
  84. * reentering the coroutine could bypass the variable definition.
  85. *
  86. * <b>yield <em>statement</em></b>
  87. *
  88. * This form of the @c yield keyword is often used with asynchronous operations:
  89. *
  90. * @code yield socket_->async_read_some(buffer(*buffer_), *this); @endcode
  91. *
  92. * This divides into four logical steps:
  93. *
  94. * @li @c yield saves the current state of the coroutine.
  95. * @li The statement initiates the asynchronous operation.
  96. * @li The resume point is defined immediately following the statement.
  97. * @li Control is transferred to the end of the coroutine body.
  98. *
  99. * When the asynchronous operation completes, the function object is invoked
  100. * and @c reenter causes control to transfer to the resume point. It is
  101. * important to remember to carry the coroutine state forward with the
  102. * asynchronous operation. In the above snippet, the current class is a
  103. * function object object with a coroutine object as base class or data member.
  104. *
  105. * The statement may also be a compound statement, and this permits us to
  106. * define local variables with limited scope:
  107. *
  108. * @code yield
  109. * {
  110. * mutable_buffers_1 b = buffer(*buffer_);
  111. * socket_->async_read_some(b, *this);
  112. * } @endcode
  113. *
  114. * <b>yield return <em>expression</em> ;</b>
  115. *
  116. * This form of @c yield is often used in generators or coroutine-based parsers.
  117. * For example, the function object:
  118. *
  119. * @code struct interleave : coroutine
  120. * {
  121. * istream& is1;
  122. * istream& is2;
  123. * char operator()(char c)
  124. * {
  125. * reenter (this) for (;;)
  126. * {
  127. * yield return is1.get();
  128. * yield return is2.get();
  129. * }
  130. * }
  131. * }; @endcode
  132. *
  133. * defines a trivial coroutine that interleaves the characters from two input
  134. * streams.
  135. *
  136. * This type of @c yield divides into three logical steps:
  137. *
  138. * @li @c yield saves the current state of the coroutine.
  139. * @li The resume point is defined immediately following the semicolon.
  140. * @li The value of the expression is returned from the function.
  141. *
  142. * <b>yield ;</b>
  143. *
  144. * This form of @c yield is equivalent to the following steps:
  145. *
  146. * @li @c yield saves the current state of the coroutine.
  147. * @li The resume point is defined immediately following the semicolon.
  148. * @li Control is transferred to the end of the coroutine body.
  149. *
  150. * This form might be applied when coroutines are used for cooperative
  151. * threading and scheduling is explicitly managed. For example:
  152. *
  153. * @code struct task : coroutine
  154. * {
  155. * ...
  156. * void operator()()
  157. * {
  158. * reenter (this)
  159. * {
  160. * while (... not finished ...)
  161. * {
  162. * ... do something ...
  163. * yield;
  164. * ... do some more ...
  165. * yield;
  166. * }
  167. * }
  168. * }
  169. * ...
  170. * };
  171. * ...
  172. * task t1, t2;
  173. * for (;;)
  174. * {
  175. * t1();
  176. * t2();
  177. * } @endcode
  178. *
  179. * <b>yield break ;</b>
  180. *
  181. * The final form of @c yield is used to explicitly terminate the coroutine.
  182. * This form is comprised of two steps:
  183. *
  184. * @li @c yield sets the coroutine state to indicate termination.
  185. * @li Control is transferred to the end of the coroutine body.
  186. *
  187. * Once terminated, calls to is_complete() return true and the coroutine cannot
  188. * be reentered.
  189. *
  190. * Note that a coroutine may also be implicitly terminated if the coroutine
  191. * body is exited without a yield, e.g. by return, throw or by running to the
  192. * end of the body.
  193. *
  194. * <b>fork <em>statement</em></b>
  195. *
  196. * The @c fork pseudo-keyword is used when "forking" a coroutine, i.e. splitting
  197. * it into two (or more) copies. One use of @c fork is in a server, where a new
  198. * coroutine is created to handle each client connection:
  199. *
  200. * @code reenter (this)
  201. * {
  202. * do
  203. * {
  204. * socket_.reset(new tcp::socket(my_context_));
  205. * yield acceptor->async_accept(*socket_, *this);
  206. * fork server(*this)();
  207. * } while (is_parent());
  208. * ... client-specific handling follows ...
  209. * } @endcode
  210. *
  211. * The logical steps involved in a @c fork are:
  212. *
  213. * @li @c fork saves the current state of the coroutine.
  214. * @li The statement creates a copy of the coroutine and either executes it
  215. * immediately or schedules it for later execution.
  216. * @li The resume point is defined immediately following the semicolon.
  217. * @li For the "parent", control immediately continues from the next line.
  218. *
  219. * The functions is_parent() and is_child() can be used to differentiate
  220. * between parent and child. You would use these functions to alter subsequent
  221. * control flow.
  222. *
  223. * Note that @c fork doesn't do the actual forking by itself. It is the
  224. * application's responsibility to create a clone of the coroutine and call it.
  225. * The clone can be called immediately, as above, or scheduled for delayed
  226. * execution using something like boost::asio::post().
  227. *
  228. * @par Alternate macro names
  229. *
  230. * If preferred, an application can use macro names that follow a more typical
  231. * naming convention, rather than the pseudo-keywords. These are:
  232. *
  233. * @li @c BOOST_ASIO_CORO_REENTER instead of @c reenter
  234. * @li @c BOOST_ASIO_CORO_YIELD instead of @c yield
  235. * @li @c BOOST_ASIO_CORO_FORK instead of @c fork
  236. */
  237. class coroutine
  238. {
  239. public:
  240. /// Constructs a coroutine in its initial state.
  241. coroutine() : value_(0) {}
  242. /// Returns true if the coroutine is the child of a fork.
  243. bool is_child() const { return value_ < 0; }
  244. /// Returns true if the coroutine is the parent of a fork.
  245. bool is_parent() const { return !is_child(); }
  246. /// Returns true if the coroutine has reached its terminal state.
  247. bool is_complete() const { return value_ == -1; }
  248. private:
  249. friend class detail::coroutine_ref;
  250. int value_;
  251. };
  252. namespace detail {
  253. class coroutine_ref
  254. {
  255. public:
  256. coroutine_ref(coroutine& c) : value_(c.value_), modified_(false) {}
  257. coroutine_ref(coroutine* c) : value_(c->value_), modified_(false) {}
  258. coroutine_ref(const coroutine_ref&) = default;
  259. ~coroutine_ref() { if (!modified_) value_ = -1; }
  260. operator int() const { return value_; }
  261. int& operator=(int v) { modified_ = true; return value_ = v; }
  262. private:
  263. void operator=(const coroutine_ref&);
  264. int& value_;
  265. bool modified_;
  266. };
  267. } // namespace detail
  268. } // namespace asio
  269. } // namespace boost
  270. #define BOOST_ASIO_CORO_REENTER(c) \
  271. switch (::boost::asio::detail::coroutine_ref _coro_value = c) \
  272. case -1: if (_coro_value) \
  273. { \
  274. goto terminate_coroutine; \
  275. terminate_coroutine: \
  276. _coro_value = -1; \
  277. goto bail_out_of_coroutine; \
  278. bail_out_of_coroutine: \
  279. break; \
  280. } \
  281. else /* fall-through */ case 0:
  282. #define BOOST_ASIO_CORO_YIELD_IMPL(n) \
  283. for (_coro_value = (n);;) \
  284. if (_coro_value == 0) \
  285. { \
  286. case (n): ; \
  287. break; \
  288. } \
  289. else \
  290. switch (_coro_value ? 0 : 1) \
  291. for (;;) \
  292. /* fall-through */ case -1: if (_coro_value) \
  293. goto terminate_coroutine; \
  294. else for (;;) \
  295. /* fall-through */ case 1: if (_coro_value) \
  296. goto bail_out_of_coroutine; \
  297. else /* fall-through */ case 0:
  298. #define BOOST_ASIO_CORO_FORK_IMPL(n) \
  299. for (_coro_value = -(n);; _coro_value = (n)) \
  300. if (_coro_value == (n)) \
  301. { \
  302. case -(n): ; \
  303. break; \
  304. } \
  305. else
  306. #if defined(_MSC_VER)
  307. # define BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD_IMPL(__COUNTER__ + 1)
  308. # define BOOST_ASIO_CORO_FORK BOOST_ASIO_CORO_FORK_IMPL(__COUNTER__ + 1)
  309. #else // defined(_MSC_VER)
  310. # define BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD_IMPL(__LINE__)
  311. # define BOOST_ASIO_CORO_FORK BOOST_ASIO_CORO_FORK_IMPL(__LINE__)
  312. #endif // defined(_MSC_VER)
  313. #endif // BOOST_ASIO_COROUTINE_HPP