123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- // Copyright Nat Goodspeed 2015.
- // Distributed under the Boost Software License, Version 1.0.
- // (See accompanying file LICENSE_1_0.txt or copy at
- // http://www.boost.org/LICENSE_1_0.txt)
- #include <cassert>
- #include <chrono>
- #include <exception>
- #include <iostream>
- #include <sstream>
- #include <thread>
- #include <tuple> // std::tie()
- #include <boost/context/detail/apply.hpp>
- #include <boost/fiber/all.hpp>
- #if defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
- /*****************************************************************************
- * helper code to help callback
- *****************************************************************************/
- template< typename Fn, typename ... Args >
- class helper {
- private:
- typename std::decay< Fn >::type fn_;
- std::tuple< typename std::decay< Args >::type ... > args_;
- public:
- helper( Fn && fn, Args && ... args) :
- fn_( std::forward< Fn >( fn) ),
- args_( std::make_tuple( std::forward< Args >( args) ... ) ) {
- }
- helper( helper && other) = default;
- helper & operator=( helper && other) = default;
- helper( helper const&) = default;
- helper & operator=( helper const&) = default;
- void operator()() {
- boost::context::detail::apply( fn_, args_);
- }
- };
- template< typename Fn, typename ... Args >
- helper< Fn, Args ... > help( Fn && fn, Args && ... args) {
- return helper< Fn, Args ... >( std::forward< Fn >( fn), std::forward< Args >( args) ... );
- }
- #endif
- /*****************************************************************************
- * example async API
- *****************************************************************************/
- //[AsyncAPI
- class AsyncAPI {
- public:
- // constructor acquires some resource that can be read and written
- AsyncAPI();
- // callbacks accept an int error code; 0 == success
- typedef int errorcode;
- // write callback only needs to indicate success or failure
- template< typename Fn >
- void init_write( std::string const& data, Fn && callback);
- // read callback needs to accept both errorcode and data
- template< typename Fn >
- void init_read( Fn && callback);
- // ... other operations ...
- //<-
- void inject_error( errorcode ec);
- private:
- std::string data_;
- errorcode injected_;
- //->
- };
- //]
- /*****************************************************************************
- * fake AsyncAPI implementation... pay no attention to the little man behind
- * the curtain...
- *****************************************************************************/
- AsyncAPI::AsyncAPI() :
- injected_( 0) {
- }
- void AsyncAPI::inject_error( errorcode ec) {
- injected_ = ec;
- }
- template< typename Fn >
- void AsyncAPI::init_write( std::string const& data, Fn && callback) {
- // make a local copy of injected_
- errorcode injected( injected_);
- // reset it synchronously with caller
- injected_ = 0;
- // update data_ (this might be an echo service)
- if ( ! injected) {
- data_ = data;
- }
- // Simulate an asynchronous I/O operation by launching a detached thread
- // that sleeps a bit before calling completion callback. Echo back to
- // caller any previously-injected errorcode.
- #if ! defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
- std::thread( [injected,callback=std::forward< Fn >( callback)]() mutable {
- std::this_thread::sleep_for( std::chrono::milliseconds(100) );
- callback( injected);
- }).detach();
- #else
- std::thread(
- std::move(
- help( std::forward< Fn >( callback), injected) ) ).detach();
- #endif
- }
- template< typename Fn >
- void AsyncAPI::init_read( Fn && callback) {
- // make a local copy of injected_
- errorcode injected( injected_);
- // reset it synchronously with caller
- injected_ = 0;
- // local copy of data_ so we can capture in lambda
- std::string data( data_);
- // Simulate an asynchronous I/O operation by launching a detached thread
- // that sleeps a bit before calling completion callback. Echo back to
- // caller any previously-injected errorcode.
- #if ! defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
- std::thread( [injected,callback=std::forward< Fn >( callback),data]() mutable {
- std::this_thread::sleep_for( std::chrono::milliseconds(100) );
- callback( injected, data);
- }).detach();
- #else
- std::thread(
- std::move(
- help( std::forward< Fn >( callback), injected, data) ) ).detach();
- #endif
- }
- /*****************************************************************************
- * adapters
- *****************************************************************************/
- // helper function used in a couple of the adapters
- std::runtime_error make_exception( std::string const& desc, AsyncAPI::errorcode);
- //[callbacks_write_ec
- AsyncAPI::errorcode write_ec( AsyncAPI & api, std::string const& data) {
- boost::fibers::promise< AsyncAPI::errorcode > promise;
- boost::fibers::future< AsyncAPI::errorcode > future( promise.get_future() );
- // In general, even though we block waiting for future::get() and therefore
- // won't destroy 'promise' until promise::set_value() has been called, we
- // are advised that with threads it's possible for ~promise() to be
- // entered before promise::set_value() has returned. While that shouldn't
- // happen with fibers::promise, a robust way to deal with the lifespan
- // issue is to bind 'promise' into our lambda. Since promise is move-only,
- // use initialization capture.
- #if ! defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
- api.init_write(
- data,
- [promise=std::move( promise)]( AsyncAPI::errorcode ec) mutable {
- promise.set_value( ec);
- });
- #else // defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
- api.init_write(
- data,
- std::bind([](boost::fibers::promise< AsyncAPI::errorcode > & promise,
- AsyncAPI::errorcode ec) {
- promise.set_value( ec);
- },
- std::move( promise),
- std::placeholders::_1) );
- #endif // BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES
- return future.get();
- }
- //]
- //[callbacks_write
- void write( AsyncAPI & api, std::string const& data) {
- AsyncAPI::errorcode ec = write_ec( api, data);
- if ( ec) {
- throw make_exception("write", ec);
- }
- }
- //]
- //[callbacks_read_ec
- std::pair< AsyncAPI::errorcode, std::string > read_ec( AsyncAPI & api) {
- typedef std::pair< AsyncAPI::errorcode, std::string > result_pair;
- boost::fibers::promise< result_pair > promise;
- boost::fibers::future< result_pair > future( promise.get_future() );
- // We promise that both 'promise' and 'future' will survive until our
- // lambda has been called.
- #if ! defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
- api.init_read([promise=std::move( promise)]( AsyncAPI::errorcode ec, std::string const& data) mutable {
- promise.set_value( result_pair( ec, data) );
- });
- #else // defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
- api.init_read(
- std::bind([]( boost::fibers::promise< result_pair > & promise,
- AsyncAPI::errorcode ec, std::string const& data) mutable {
- promise.set_value( result_pair( ec, data) );
- },
- std::move( promise),
- std::placeholders::_1,
- std::placeholders::_2) );
- #endif // BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES
- return future.get();
- }
- //]
- //[callbacks_read
- std::string read( AsyncAPI & api) {
- boost::fibers::promise< std::string > promise;
- boost::fibers::future< std::string > future( promise.get_future() );
- // Both 'promise' and 'future' will survive until our lambda has been
- // called.
- #if ! defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
- api.init_read([&promise]( AsyncAPI::errorcode ec, std::string const& data) mutable {
- if ( ! ec) {
- promise.set_value( data);
- } else {
- promise.set_exception(
- std::make_exception_ptr(
- make_exception("read", ec) ) );
- }
- });
- #else // defined(BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES)
- api.init_read(
- std::bind([]( boost::fibers::promise< std::string > & promise,
- AsyncAPI::errorcode ec, std::string const& data) mutable {
- if ( ! ec) {
- promise.set_value( data);
- } else {
- promise.set_exception(
- std::make_exception_ptr(
- make_exception("read", ec) ) );
- }
- },
- std::move( promise),
- std::placeholders::_1,
- std::placeholders::_2) );
- #endif // BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES
- return future.get();
- }
- //]
- /*****************************************************************************
- * helpers
- *****************************************************************************/
- std::runtime_error make_exception( std::string const& desc, AsyncAPI::errorcode ec) {
- std::ostringstream buffer;
- buffer << "Error in AsyncAPI::" << desc << "(): " << ec;
- return std::runtime_error( buffer.str() );
- }
- /*****************************************************************************
- * driving logic
- *****************************************************************************/
- int main( int argc, char *argv[]) {
- AsyncAPI api;
- // successful write(): prime AsyncAPI with some data
- write( api, "abcd");
- // successful read(): retrieve it
- std::string data( read( api) );
- assert( data == "abcd");
- // successful write_ec()
- AsyncAPI::errorcode ec( write_ec( api, "efgh") );
- assert( ec == 0);
- // write_ec() with error
- api.inject_error(1);
- ec = write_ec( api, "ijkl");
- assert( ec == 1);
- // write() with error
- std::string thrown;
- api.inject_error(2);
- try {
- write(api, "mnop");
- } catch ( std::exception const& e) {
- thrown = e.what();
- }
- assert( thrown == make_exception("write", 2).what() );
- // successful read_ec()
- //[callbacks_read_ec_call
- std::tie( ec, data) = read_ec( api);
- //]
- assert( ! ec);
- assert( data == "efgh"); // last successful write_ec()
- // read_ec() with error
- api.inject_error(3);
- std::tie( ec, data) = read_ec( api);
- assert( ec == 3);
- // 'data' in unspecified state, don't test
- // read() with error
- thrown.clear();
- api.inject_error(4);
- try {
- data = read(api);
- } catch ( std::exception const& e) {
- thrown = e.what();
- }
- assert( thrown == make_exception("read", 4).what() );
- std::cout << "done." << std::endl;
- return EXIT_SUCCESS;
- }
|