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 <boost/fiber/all.hpp>
7 #include <memory> // std::shared_ptr
8 #include <thread>
9 #include <chrono>
10 #include <iostream>
11 #include <sstream>
12 #include <exception>
13 #include <cassert>
14
15 /*****************************************************************************
16 * example async API
17 *****************************************************************************/
18 // introduce class-scope typedef
19 struct AsyncAPIBase {
20 // error callback accepts an int error code; 0 == success
21 typedef int errorcode;
22 };
23
24 //[Response
25 // every async operation receives a subclass instance of this abstract base
26 // class through which to communicate its result
27 struct Response {
28 typedef std::shared_ptr< Response > ptr;
29
30 // called if the operation succeeds
31 virtual void success( std::string const& data) = 0;
32
33 // called if the operation fails
34 virtual void error( AsyncAPIBase::errorcode ec) = 0;
35 };
36 //]
37
38 // the actual async API
39 class AsyncAPI: public AsyncAPIBase {
40 public:
41 // constructor acquires some resource that can be read
42 AsyncAPI( std::string const& data);
43
44 //[method_init_read
45 // derive Response subclass, instantiate, pass Response::ptr
46 void init_read( Response::ptr);
47 //]
48
49 // ... other operations ...
50 void inject_error( errorcode ec);
51
52 private:
53 std::string data_;
54 errorcode injected_;
55 };
56
57 /*****************************************************************************
58 * fake AsyncAPI implementation... pay no attention to the little man behind
59 * the curtain...
60 *****************************************************************************/
AsyncAPI(std::string const & data)61 AsyncAPI::AsyncAPI( std::string const& data) :
62 data_( data),
63 injected_( 0) {
64 }
65
inject_error(errorcode ec)66 void AsyncAPI::inject_error( errorcode ec) {
67 injected_ = ec;
68 }
69
init_read(Response::ptr response)70 void AsyncAPI::init_read( Response::ptr response) {
71 // make a local copy of injected_
72 errorcode injected( injected_);
73 // reset it synchronously with caller
74 injected_ = 0;
75 // local copy of data_ so we can capture in lambda
76 std::string data( data_);
77 // Simulate an asynchronous I/O operation by launching a detached thread
78 // that sleeps a bit before calling either completion method.
79 std::thread( [injected, response, data](){
80 std::this_thread::sleep_for( std::chrono::milliseconds(100) );
81 if ( ! injected) {
82 // no error, call success()
83 response->success( data);
84 } else {
85 // injected error, call error()
86 response->error( injected);
87 }
88 }).detach();
89 }
90
91 /*****************************************************************************
92 * adapters
93 *****************************************************************************/
94 // helper function
95 std::runtime_error make_exception( std::string const& desc, AsyncAPI::errorcode);
96
97 //[PromiseResponse
98 class PromiseResponse: public Response {
99 public:
100 // called if the operation succeeds
success(std::string const & data)101 virtual void success( std::string const& data) {
102 promise_.set_value( data);
103 }
104
105 // called if the operation fails
error(AsyncAPIBase::errorcode ec)106 virtual void error( AsyncAPIBase::errorcode ec) {
107 promise_.set_exception(
108 std::make_exception_ptr(
109 make_exception("read", ec) ) );
110 }
111
get_future()112 boost::fibers::future< std::string > get_future() {
113 return promise_.get_future();
114 }
115
116 private:
117 boost::fibers::promise< std::string > promise_;
118 };
119 //]
120
121 //[method_read
read(AsyncAPI & api)122 std::string read( AsyncAPI & api) {
123 // Because init_read() requires a shared_ptr, we must allocate our
124 // ResponsePromise on the heap, even though we know its lifespan.
125 auto promisep( std::make_shared< PromiseResponse >() );
126 boost::fibers::future< std::string > future( promisep->get_future() );
127 // Both 'promisep' and 'future' will survive until our lambda has been
128 // called.
129 api.init_read( promisep);
130 return future.get();
131 }
132 //]
133
134 /*****************************************************************************
135 * helpers
136 *****************************************************************************/
make_exception(std::string const & desc,AsyncAPI::errorcode ec)137 std::runtime_error make_exception( std::string const& desc, AsyncAPI::errorcode ec) {
138 std::ostringstream buffer;
139 buffer << "Error in AsyncAPI::" << desc << "(): " << ec;
140 return std::runtime_error( buffer.str() );
141 }
142
143 /*****************************************************************************
144 * driving logic
145 *****************************************************************************/
main(int argc,char * argv[])146 int main(int argc, char *argv[]) {
147 // prime AsyncAPI with some data
148 AsyncAPI api("abcd");
149
150 // successful read(): retrieve it
151 std::string data( read( api) );
152 assert(data == "abcd");
153
154 // read() with error
155 std::string thrown;
156 api.inject_error(1);
157 try {
158 data = read( api);
159 } catch ( std::exception const& e) {
160 thrown = e.what();
161 }
162 assert(thrown == make_exception("read", 1).what() );
163
164 std::cout << "done." << std::endl;
165
166 return EXIT_SUCCESS;
167 }
168