adapt_callbacks.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. // Copyright Nat Goodspeed 2015.
  2. // Distributed under the Boost Software License, Version 1.0.
  3. // (See accompanying file LICENSE_1_0.txt or copy at
  4. // http://www.boost.org/LICENSE_1_0.txt)
  5. #include <cassert>
  6. #include <chrono>
  7. #include <exception>
  8. #include <iostream>
  9. #include <sstream>
  10. #include <thread>
  11. #include <tuple> // std::tie()
  12. #include <boost/context/detail/apply.hpp>
  13. #include <boost/fiber/all.hpp>
  14. #if defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
  15. /*****************************************************************************
  16. * helper code to help callback
  17. *****************************************************************************/
  18. template< typename Fn, typename ... Args >
  19. class helper {
  20. private:
  21. typename std::decay< Fn >::type fn_;
  22. std::tuple< typename std::decay< Args >::type ... > args_;
  23. public:
  24. helper( Fn && fn, Args && ... args) :
  25. fn_( std::forward< Fn >( fn) ),
  26. args_( std::make_tuple( std::forward< Args >( args) ... ) ) {
  27. }
  28. helper( helper && other) = default;
  29. helper & operator=( helper && other) = default;
  30. helper( helper const&) = default;
  31. helper & operator=( helper const&) = default;
  32. void operator()() {
  33. boost::context::detail::apply( fn_, args_);
  34. }
  35. };
  36. template< typename Fn, typename ... Args >
  37. helper< Fn, Args ... > help( Fn && fn, Args && ... args) {
  38. return helper< Fn, Args ... >( std::forward< Fn >( fn), std::forward< Args >( args) ... );
  39. }
  40. #endif
  41. /*****************************************************************************
  42. * example async API
  43. *****************************************************************************/
  44. //[AsyncAPI
  45. class AsyncAPI {
  46. public:
  47. // constructor acquires some resource that can be read and written
  48. AsyncAPI();
  49. // callbacks accept an int error code; 0 == success
  50. typedef int errorcode;
  51. // write callback only needs to indicate success or failure
  52. template< typename Fn >
  53. void init_write( std::string const& data, Fn && callback);
  54. // read callback needs to accept both errorcode and data
  55. template< typename Fn >
  56. void init_read( Fn && callback);
  57. // ... other operations ...
  58. //<-
  59. void inject_error( errorcode ec);
  60. private:
  61. std::string data_;
  62. errorcode injected_;
  63. //->
  64. };
  65. //]
  66. /*****************************************************************************
  67. * fake AsyncAPI implementation... pay no attention to the little man behind
  68. * the curtain...
  69. *****************************************************************************/
  70. AsyncAPI::AsyncAPI() :
  71. injected_( 0) {
  72. }
  73. void AsyncAPI::inject_error( errorcode ec) {
  74. injected_ = ec;
  75. }
  76. template< typename Fn >
  77. void AsyncAPI::init_write( std::string const& data, Fn && callback) {
  78. // make a local copy of injected_
  79. errorcode injected( injected_);
  80. // reset it synchronously with caller
  81. injected_ = 0;
  82. // update data_ (this might be an echo service)
  83. if ( ! injected) {
  84. data_ = data;
  85. }
  86. // Simulate an asynchronous I/O operation by launching a detached thread
  87. // that sleeps a bit before calling completion callback. Echo back to
  88. // caller any previously-injected errorcode.
  89. #if ! defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
  90. std::thread( [injected,callback=std::forward< Fn >( callback)]() mutable {
  91. std::this_thread::sleep_for( std::chrono::milliseconds(100) );
  92. callback( injected);
  93. }).detach();
  94. #else
  95. std::thread(
  96. std::move(
  97. help( std::forward< Fn >( callback), injected) ) ).detach();
  98. #endif
  99. }
  100. template< typename Fn >
  101. void AsyncAPI::init_read( Fn && callback) {
  102. // make a local copy of injected_
  103. errorcode injected( injected_);
  104. // reset it synchronously with caller
  105. injected_ = 0;
  106. // local copy of data_ so we can capture in lambda
  107. std::string data( data_);
  108. // Simulate an asynchronous I/O operation by launching a detached thread
  109. // that sleeps a bit before calling completion callback. Echo back to
  110. // caller any previously-injected errorcode.
  111. #if ! defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
  112. std::thread( [injected,callback=std::forward< Fn >( callback),data]() mutable {
  113. std::this_thread::sleep_for( std::chrono::milliseconds(100) );
  114. callback( injected, data);
  115. }).detach();
  116. #else
  117. std::thread(
  118. std::move(
  119. help( std::forward< Fn >( callback), injected, data) ) ).detach();
  120. #endif
  121. }
  122. /*****************************************************************************
  123. * adapters
  124. *****************************************************************************/
  125. // helper function used in a couple of the adapters
  126. std::runtime_error make_exception( std::string const& desc, AsyncAPI::errorcode);
  127. //[callbacks_write_ec
  128. AsyncAPI::errorcode write_ec( AsyncAPI & api, std::string const& data) {
  129. boost::fibers::promise< AsyncAPI::errorcode > promise;
  130. boost::fibers::future< AsyncAPI::errorcode > future( promise.get_future() );
  131. // In general, even though we block waiting for future::get() and therefore
  132. // won't destroy 'promise' until promise::set_value() has been called, we
  133. // are advised that with threads it's possible for ~promise() to be
  134. // entered before promise::set_value() has returned. While that shouldn't
  135. // happen with fibers::promise, a robust way to deal with the lifespan
  136. // issue is to bind 'promise' into our lambda. Since promise is move-only,
  137. // use initialization capture.
  138. #if ! defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
  139. api.init_write(
  140. data,
  141. [promise=std::move( promise)]( AsyncAPI::errorcode ec) mutable {
  142. promise.set_value( ec);
  143. });
  144. #else // defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
  145. api.init_write(
  146. data,
  147. std::bind([](boost::fibers::promise< AsyncAPI::errorcode > & promise,
  148. AsyncAPI::errorcode ec) {
  149. promise.set_value( ec);
  150. },
  151. std::move( promise),
  152. std::placeholders::_1) );
  153. #endif // BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES
  154. return future.get();
  155. }
  156. //]
  157. //[callbacks_write
  158. void write( AsyncAPI & api, std::string const& data) {
  159. AsyncAPI::errorcode ec = write_ec( api, data);
  160. if ( ec) {
  161. throw make_exception("write", ec);
  162. }
  163. }
  164. //]
  165. //[callbacks_read_ec
  166. std::pair< AsyncAPI::errorcode, std::string > read_ec( AsyncAPI & api) {
  167. typedef std::pair< AsyncAPI::errorcode, std::string > result_pair;
  168. boost::fibers::promise< result_pair > promise;
  169. boost::fibers::future< result_pair > future( promise.get_future() );
  170. // We promise that both 'promise' and 'future' will survive until our
  171. // lambda has been called.
  172. #if ! defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
  173. api.init_read([promise=std::move( promise)]( AsyncAPI::errorcode ec, std::string const& data) mutable {
  174. promise.set_value( result_pair( ec, data) );
  175. });
  176. #else // defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
  177. api.init_read(
  178. std::bind([]( boost::fibers::promise< result_pair > & promise,
  179. AsyncAPI::errorcode ec, std::string const& data) mutable {
  180. promise.set_value( result_pair( ec, data) );
  181. },
  182. std::move( promise),
  183. std::placeholders::_1,
  184. std::placeholders::_2) );
  185. #endif // BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES
  186. return future.get();
  187. }
  188. //]
  189. //[callbacks_read
  190. std::string read( AsyncAPI & api) {
  191. boost::fibers::promise< std::string > promise;
  192. boost::fibers::future< std::string > future( promise.get_future() );
  193. // Both 'promise' and 'future' will survive until our lambda has been
  194. // called.
  195. #if ! defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
  196. api.init_read([&promise]( AsyncAPI::errorcode ec, std::string const& data) mutable {
  197. if ( ! ec) {
  198. promise.set_value( data);
  199. } else {
  200. promise.set_exception(
  201. std::make_exception_ptr(
  202. make_exception("read", ec) ) );
  203. }
  204. });
  205. #else // defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
  206. api.init_read(
  207. std::bind([]( boost::fibers::promise< std::string > & promise,
  208. AsyncAPI::errorcode ec, std::string const& data) mutable {
  209. if ( ! ec) {
  210. promise.set_value( data);
  211. } else {
  212. promise.set_exception(
  213. std::make_exception_ptr(
  214. make_exception("read", ec) ) );
  215. }
  216. },
  217. std::move( promise),
  218. std::placeholders::_1,
  219. std::placeholders::_2) );
  220. #endif // BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES
  221. return future.get();
  222. }
  223. //]
  224. /*****************************************************************************
  225. * helpers
  226. *****************************************************************************/
  227. std::runtime_error make_exception( std::string const& desc, AsyncAPI::errorcode ec) {
  228. std::ostringstream buffer;
  229. buffer << "Error in AsyncAPI::" << desc << "(): " << ec;
  230. return std::runtime_error( buffer.str() );
  231. }
  232. /*****************************************************************************
  233. * driving logic
  234. *****************************************************************************/
  235. int main( int argc, char *argv[]) {
  236. AsyncAPI api;
  237. // successful write(): prime AsyncAPI with some data
  238. write( api, "abcd");
  239. // successful read(): retrieve it
  240. std::string data( read( api) );
  241. assert( data == "abcd");
  242. // successful write_ec()
  243. AsyncAPI::errorcode ec( write_ec( api, "efgh") );
  244. assert( ec == 0);
  245. // write_ec() with error
  246. api.inject_error(1);
  247. ec = write_ec( api, "ijkl");
  248. assert( ec == 1);
  249. // write() with error
  250. std::string thrown;
  251. api.inject_error(2);
  252. try {
  253. write(api, "mnop");
  254. } catch ( std::exception const& e) {
  255. thrown = e.what();
  256. }
  257. assert( thrown == make_exception("write", 2).what() );
  258. // successful read_ec()
  259. //[callbacks_read_ec_call
  260. std::tie( ec, data) = read_ec( api);
  261. //]
  262. assert( ! ec);
  263. assert( data == "efgh"); // last successful write_ec()
  264. // read_ec() with error
  265. api.inject_error(3);
  266. std::tie( ec, data) = read_ec( api);
  267. assert( ec == 3);
  268. // 'data' in unspecified state, don't test
  269. // read() with error
  270. thrown.clear();
  271. api.inject_error(4);
  272. try {
  273. data = read(api);
  274. } catch ( std::exception const& e) {
  275. thrown = e.what();
  276. }
  277. assert( thrown == make_exception("read", 4).what() );
  278. std::cout << "done." << std::endl;
  279. return EXIT_SUCCESS;
  280. }