• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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