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