// Showcases the "pass the buck" strategy when using Boost.Asio. // // For a high level description of the "pass the buck" strategy see: // - N3388: Using Asio with C++11 by Christopher Kohlhoff // Chapter: 7. Passing the buck: developing efficient abstractions // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3388.pdf // - Thinking Asynchronously: Designing Applications with Boost.Asio by Christopher Kohlhoff // Chapter: Managing Complexity // https://github.com/boostcon/2011_presentations/raw/master/mon/thinking_asynchronously.pdf // // The goal of this exercise is to have composed operations *transparently* use the handler // customization points set by their user. // // This file contains: // // - A helper component that allows building asynchronous operation handlers // that wrap other handlers with boost::bind. The goal of this handler-builder is to create // handlers that use the allocation and invocation customization points // (as defined by Boost.Asio) of the nested ones. // See bindHandler() below. // // - Test infrastructure // - Test composed asynchronous operations whose intermediary operations should use the // customization points defined by the final handler of the composed one. The // handler-builder mentioned above will be used for this. // See TestAsyncOperation::asyncOperation() and TestAsyncOperation2::asyncOperation() below. // // - Test Code // - A handler-wrapper to test that all the intermediary operations of a composed one // are using it. // See TestInvokePrintHandler below. // - Highest level test operations using the composed operations mentioned above in different // scenarios. // See TestOperation, TestOperationInvokePrint and the top level test functions: // testIoService() and testIoServiceThreads. // #define _WIN32_WINNT 0x0501 #include #include #include #include #include #include #include #include #include namespace Asio = boost::asio; namespace Fusion = boost::fusion; // Helper wrapper around boost::bind //---------------------------------- // Global namespace to call ADL overloads of the Boost.Asio handler customization points. // The namespace from which these overloads are called must not contain any of them to avoid infinite // recursion. See also boost/utility/swap.hpp or boost/asio/detail/handler_invoke_helpers.hpp which // use the same technique for calling ADL overloads. namespace BindHandlerInternalAdlCall { template< typename HandlerT > inline void* allocate( std::size_t size, HandlerT& handler ) { using namespace boost::asio; return asio_handler_allocate( size, boost::addressof( handler ) ); } template< typename HandlerT > inline void deallocate( void* pointer, std::size_t size, HandlerT& handler ) { using namespace boost::asio; asio_handler_deallocate( pointer, size, boost::addressof( handler ) ); } template< typename FunctionT, typename HandlerT > inline void invoke( FunctionT const& function, HandlerT& handler ) { using namespace boost::asio; asio_handler_invoke( function, boost::addressof( handler ) ); } } // BindHandlerInternalAdlCall namespace BindHandlerInternal { // Structures keeping the information to be passed to boost::bind. // They keep the function, the nested handler and additional parameters that may be passed to // boost::bind. // // Note: // - boost::ref and/or boost::cref are used for the nested handler when being passed to boost::bind // for two reasons: // - to hide the type of the nested handler so that boost::bind constructed handlers can be nested // - to avoid extra copying of it in the boost::bind created temporary // - boost::ref and/or boost::cref are not used for the additional parameters that are passed to // the boost::bind call to avoid hiding the type of the placeholder arguments if any. // \{ template< typename FunctionT, typename HandlerT > struct BinderBase { BinderBase( FunctionT f, HandlerT const& h ) : function( f ), handler( h ) {} boost::reference_wrapper< HandlerT > handlerRef() { return boost::ref( handler ); } boost::reference_wrapper< HandlerT const > handlerRef() const { return boost::cref( handler ); } FunctionT function; HandlerT handler; }; template< typename FunctionT, typename HandlerT > struct Binder0 : public BinderBase< FunctionT, HandlerT > { typedef BinderBase< FunctionT, HandlerT > Base; Binder0( FunctionT f, HandlerT const& h ) : Base( f, h ) {} template< typename ArgumentsT > void operator()( ArgumentsT const& arguments ) { Fusion::invoke( boost::bind( Base::function, Base::handlerRef() ), arguments ); } template< typename ArgumentsT > void operator()( ArgumentsT const& arguments ) const { Fusion::invoke( boost::bind( Base::function, Base::handlerRef() ), arguments ); } }; template< typename FunctionT, typename HandlerT, typename A1T > struct Binder1 : public BinderBase< FunctionT, HandlerT > { typedef BinderBase< FunctionT, HandlerT > Base; Binder1( FunctionT f, HandlerT const& h, A1T const& a1_ ) : Base( f, h ), a1( a1_ ) {} template< typename ArgumentsT > void operator()( ArgumentsT const& arguments ) { Fusion::invoke( boost::bind( Base::function, Base::handlerRef(), a1 ), arguments ); } template< typename ArgumentsT > void operator()( ArgumentsT const& arguments ) const { Fusion::invoke( boost::bind( Base::function, Base::handlerRef(), a1 ), arguments ); } A1T a1; }; template< typename FunctionT, typename HandlerT, typename A1T, typename A2T > struct Binder2 : public BinderBase< FunctionT, HandlerT > { typedef BinderBase< FunctionT, HandlerT > Base; Binder2( FunctionT f, HandlerT const& h, A1T const& a1_, A2T const& a2_ ) : Base( f, h ), a1( a1_ ), a2( a2_ ) {} template< typename ArgumentsT > void operator()( ArgumentsT const& arguments ) { Fusion::invoke( boost::bind( Base::function, Base::handlerRef(), a1, a2 ), arguments ); } template< typename ArgumentsT > void operator()( ArgumentsT const& arguments ) const { Fusion::invoke( boost::bind( Base::function, Base::handlerRef(), a1, a2 ), arguments ); } A1T a1; A2T a2; }; template< typename FunctionT, typename HandlerT, typename A1T, typename A2T, typename A3T > struct Binder3 : public BinderBase< FunctionT, HandlerT > { typedef BinderBase< FunctionT, HandlerT > Base; Binder3( FunctionT f, HandlerT const& h, A1T const& a1_, A2T const& a2_, A3T const& a3_ ) : Base( f, h ), a1( a1_ ), a2( a2_ ), a3( a3_ ) {} template< typename ArgumentsT > void operator()( ArgumentsT const& arguments ) { Fusion::invoke( boost::bind( Base::function, Base::handlerRef(), a1, a2, a3 ), arguments ); } template< typename ArgumentsT > void operator()( ArgumentsT const& arguments ) const { Fusion::invoke( boost::bind( Base::function, Base::handlerRef(), a1, a2, a3 ), arguments ); } A1T a1; A2T a2; A3T a3; }; template< typename FunctionT, typename HandlerT, typename A1T, typename A2T, typename A3T, typename A4T > struct Binder4 : public BinderBase< FunctionT, HandlerT > { typedef BinderBase< FunctionT, HandlerT > Base; Binder4( FunctionT f, HandlerT const& h, A1T const& a1_, A2T const& a2_, A3T const& a3_, A4T const& a4_ ) : Base( f, h ), a1( a1_ ), a2( a2_ ), a3( a3_ ), a4( a4_ ) {} template< typename ArgumentsT > void operator()( ArgumentsT const& arguments ) { Fusion::invoke( boost::bind( Base::function, Base::handlerRef(), a1, a2, a3, a4 ), arguments ); } template< typename ArgumentsT > void operator()( ArgumentsT const& arguments ) const { Fusion::invoke( boost::bind( Base::function, Base::handlerRef(), a1, a2, a3, a4 ), arguments ); } A1T a1; A2T a2; A3T a3; A4T a4; }; template< typename FunctionT, typename HandlerT, typename A1T, typename A2T, typename A3T, typename A4T, typename A5T > struct Binder5 : public BinderBase< FunctionT, HandlerT > { typedef BinderBase< FunctionT, HandlerT > Base; Binder5( FunctionT f, HandlerT const& h, A1T const& a1_, A2T const& a2_, A3T const& a3_, A4T const& a4_, A5T const& a5_ ) : Base( f, h ), a1( a1_ ), a2( a2_ ), a3( a3_ ), a4( a4_ ), a5( a5_ ) {} template< typename ArgumentsT > void operator()( ArgumentsT const& arguments ) { Fusion::invoke( boost::bind( Base::function, Base::handlerRef(), a1, a2, a3, a4, a5 ), arguments ); } template< typename ArgumentsT > void operator()( ArgumentsT const& arguments ) const { Fusion::invoke( boost::bind( Base::function, Base::handlerRef(), a1, a2, a3, a4, a5 ), arguments ); } A1T a1; A2T a2; A3T a3; A4T a4; A5T a5; }; // \} // The handler nesting another handler plus additional information to be passed to boost::bind. template< typename BinderT > class Handler { public: Handler( BinderT const& binder ) : m_binder( binder ) {} // Invocation of the boost::bind built handler. // \{ void operator()() { Fusion::vector0<> arguments; m_binder( arguments ); } void operator()() const { Fusion::vector0<> arguments; m_binder( arguments ); } template< typename A1T > void operator()( A1T const& a1 ) { Fusion::vector1< A1T const& > arguments( a1 ); m_binder( arguments ); } template< typename A1T > void operator()( A1T const& a1 ) const { Fusion::vector1< A1T const& > arguments( a1 ); m_binder( arguments ); } template< typename A1T, typename A2T > void operator()( A1T const& a1, A2T const& a2 ) { Fusion::vector2< A1T const&, A2T const& > arguments( a1, a2 ); m_binder( arguments ); } template< typename A1T, typename A2T > void operator()( A1T const& a1, A2T const& a2 ) const { Fusion::vector2< A1T const&, A2T const& > arguments( a1, a2 ); m_binder( arguments ); } template< typename A1T, typename A2T, typename A3T > void operator()( A1T const& a1, A2T const& a2, A3T const& a3 ) { Fusion::vector3< A1T const&, A2T const&, A3T const& > arguments( a1, a2, a3 ); m_binder( arguments ); } template< typename A1T, typename A2T, typename A3T > void operator()( A1T const& a1, A2T const& a2, A3T const& a3 ) const { Fusion::vector3< A1T const&, A2T const&, A3T const& > arguments( a1, a2, a3 ); m_binder( arguments ); } template< typename A1T, typename A2T, typename A3T, typename A4T > void operator()( A1T const& a1, A2T const& a2, A3T const& a3, A4T const& a4 ) { Fusion::vector4< A1T const&, A2T const&, A3T const&, A4T const& > arguments( a1, a2, a3, a4 ); m_binder( arguments ); } template< typename A1T, typename A2T, typename A3T, typename A4T > void operator()( A1T const& a1, A2T const& a2, A3T const& a3, A4T const& a4 ) const { Fusion::vector4< A1T const&, A2T const&, A3T const&, A4T const& > arguments( a1, a2, a3, a4 ); m_binder( arguments ); } template< typename A1T, typename A2T, typename A3T, typename A4T, typename A5T > void operator()( A1T const& a1, A2T const& a2, A3T const& a3, A4T const& a4, A5T const& a5 ) { Fusion::vector5< A1T const&, A2T const&, A3T const&, A4T const&, A5T const& > arguments( a1, a2, a3, a4, a5 ); m_binder( arguments ); } template< typename A1T, typename A2T, typename A3T, typename A4T, typename A5T > void operator()( A1T const& a1, A2T const& a2, A3T const& a3, A4T const& a4, A5T const& a5 ) const { Fusion::vector5< A1T const&, A2T const&, A3T const&, A4T const&, A5T const& > arguments( a1, a2, a3, a4, a5 ); m_binder( arguments ); } // \} // Boost.Asio handler customization points. // They forward to the wrapped handler customization points. // \{ friend void* asio_handler_allocate( std::size_t size, Handler* handler ) { return ::BindHandlerInternalAdlCall::allocate( size, handler->m_binder.handlerRef().get() ); } friend void asio_handler_deallocate( void* pointer, std::size_t size, Handler* handler ) { ::BindHandlerInternalAdlCall::deallocate( pointer, size, handler->m_binder.handlerRef().get() ); } template< typename FunctionT > friend void asio_handler_invoke( FunctionT const& function, Handler* handler ) { ::BindHandlerInternalAdlCall::invoke( function, handler->m_binder.handlerRef().get() ); } // \} private: BinderT m_binder; }; } // namespace BindHandlerInternal /// Creates a handler similar to one created by boost::bind that wraps another handler. /// The Boost.Asio handler customization point of the returned handler will forward to the /// original handler that is being wrapped. /// /// The wrapped handler is going to be carried around by the wrapper constructed here. /// The function to be passed to boost::bind will receive back the wrapped handler as its first parameter. // \{ template< typename FunctionT, typename HandlerT > inline BindHandlerInternal::Handler< BindHandlerInternal::Binder0< FunctionT, HandlerT > > bindHandler( FunctionT f, HandlerT const& h ) { using namespace BindHandlerInternal; return Handler< Binder0< FunctionT, HandlerT > >( Binder0< FunctionT, HandlerT >( f, h ) ); } template< typename FunctionT, typename HandlerT, typename A1T > inline BindHandlerInternal::Handler< BindHandlerInternal::Binder1< FunctionT, HandlerT, A1T > > bindHandler( FunctionT f, HandlerT const& h, A1T const& a1 ) { using namespace BindHandlerInternal; return Handler< Binder1< FunctionT, HandlerT, A1T > >( Binder1< FunctionT, HandlerT, A1T >( f, h, a1 ) ); } template< typename FunctionT, typename HandlerT, typename A1T, typename A2T > inline BindHandlerInternal::Handler< BindHandlerInternal::Binder2< FunctionT, HandlerT, A1T, A2T > > bindHandler( FunctionT f, HandlerT const& h, A1T const& a1, A2T const& a2 ) { using namespace BindHandlerInternal; return Handler< Binder2< FunctionT, HandlerT, A1T, A2T > >( Binder2< FunctionT, HandlerT, A1T, A2T >( f, h, a1, a2 ) ); } template< typename FunctionT, typename HandlerT, typename A1T, typename A2T, typename A3T > inline BindHandlerInternal::Handler< BindHandlerInternal::Binder3< FunctionT, HandlerT, A1T, A2T, A3T > > bindHandler( FunctionT f, HandlerT const& h, A1T const& a1, A2T const& a2, A3T const& a3 ) { using namespace BindHandlerInternal; return Handler< Binder3< FunctionT, HandlerT, A1T, A2T, A3T > >( Binder3< FunctionT, HandlerT, A1T, A2T, A3T >( f, h, a1, a2, a3 ) ); } template< typename FunctionT, typename HandlerT, typename A1T, typename A2T, typename A3T, typename A4T > inline BindHandlerInternal::Handler< BindHandlerInternal::Binder4< FunctionT, HandlerT, A1T, A2T, A3T, A4T > > bindHandler( FunctionT f, HandlerT const& h, A1T const& a1, A2T const& a2, A3T const& a3, A4T const& a4 ) { using namespace BindHandlerInternal; return Handler< Binder4< FunctionT, HandlerT, A1T, A2T, A3T, A4T > >( Binder4< FunctionT, HandlerT, A1T, A2T, A3T, A4T >( f, h, a1, a2, a3, a4 ) ); } template< typename FunctionT, typename HandlerT, typename A1T, typename A2T, typename A3T, typename A4T, typename A5T > inline BindHandlerInternal::Handler< BindHandlerInternal::Binder5< FunctionT, HandlerT, A1T, A2T, A3T, A4T, A5T > > bindHandler( FunctionT f, HandlerT const& h, A1T const& a1, A2T const& a2, A3T const& a3, A4T const& a4, A5T const& a5 ) { using namespace BindHandlerInternal; return Handler< Binder5< FunctionT, HandlerT, A1T, A2T, A3T, A4T, A5T > >( Binder5< FunctionT, HandlerT, A1T, A2T, A3T, A4T, A5T >( f, h, a1, a2, a3, a4, a5 ) ); } // \} // Test Infrastructure //-------------------- #include #include #include #include #include #include #include #include #include #include #include #include // Test composed-operation. // The composed operation is simulated by a number of successive async-sleeps of alternating durations // (1000ms or 1000ms - sleepVariationMilli). // // The composed operation is supposed to use the bindHandler() in order to // use the same allocation and invocation strategy as the final handler of the composed operation. namespace TestAsyncOperation { std::size_t const sleepVariationMilli = 10; inline std::size_t operationCount( std::size_t operationId ) { return 4 + operationId % 2; } struct Implementation { template< typename HandlerT > static void asyncOperation( Asio::io_service& ioService, std::size_t operationId, HandlerT handler ) { boost::shared_ptr< Asio::steady_timer > sharedTimer = boost::make_shared< Asio::steady_timer >( boost::ref( ioService ) ); startTimer( operationCount( operationId), sharedTimer, handler ); } template< typename HandlerT > static void startTimer( std::size_t count, boost::shared_ptr< Asio::steady_timer > timer, HandlerT handler ) { BOOST_ASSERT( count ); --count; // Wait periods are smaller or larger for consecutive waits to force execution on different // threads if strands are not used. timer->expires_from_now( boost::chrono::milliseconds( 1000 - sleepVariationMilli * ( count % 2 ) ) ); timer->async_wait( bindHandler( &Implementation::handleTimer< HandlerT >, handler, count, timer, _1 ) ); } template< typename HandlerT > static void handleTimer( HandlerT handler, std::size_t count, boost::shared_ptr< Asio::steady_timer > timer, boost::system::error_code error ) { // Notice that the handler must come as the first parameter as bindHandler() // has been used to construct the handler that got us here. if( error || !count ) { handler( error ); } else { try { startTimer( count, timer, handler ); } catch( std::exception& ) { handler( Asio::error::interrupted ); } } } }; template< typename HandlerT > static void asyncOperation( Asio::io_service& ioService, std::size_t operationId, HandlerT handler ) { Implementation::asyncOperation( ioService, operationId, handler ); } } //namespace TestAsyncOperation // Second level test composed-operation. // It does a sleep followed by the invocation of the first test composed-operation followed by a // second sleep. // // The call to the first test composed-operation is going to test the creation of a bindHandler() // which becomes nested in another bindHandler(). namespace TestAsyncOperation2 { inline std::size_t operationCount( std::size_t operationId ) { return 2; } struct Implementation { template< typename HandlerT > static void asyncOperation( Asio::io_service& ioService, std::size_t operationId, HandlerT handler ) { boost::shared_ptr< Asio::steady_timer > timer = boost::make_shared< Asio::steady_timer >( boost::ref( ioService ) ); timer->expires_from_now( boost::chrono::milliseconds( 1000 ) ); timer->async_wait( bindHandler( &Implementation::handleTimer1< HandlerT >, handler, boost::ref( ioService ), operationId, timer, _1 ) ); } template< typename HandlerT > static void handleTimer1( HandlerT handler, Asio::io_service& ioService, std::size_t operationId, boost::shared_ptr< Asio::steady_timer > timer, boost::system::error_code error ) { // Notice that the handler must come as the first parameter as bindHandler() // has been used to construct the handler that got us here. if( error ) { handler( error ); } else { try { // This is going to cause a BindHandler to be nested in another BindHandler. TestAsyncOperation::asyncOperation( ioService, operationId, bindHandler( &Implementation::handleAsyncOperation< HandlerT >, handler, timer, _1 ) ); } catch( std::exception& ) { handler( Asio::error::interrupted ); } } } template< typename HandlerT > static void handleAsyncOperation( HandlerT handler, boost::shared_ptr< Asio::steady_timer > timer, boost::system::error_code error ) { // Same here, the handler must come as the first parameter. if( error ) { handler( error ); } else { try { timer->expires_from_now( boost::chrono::milliseconds( 1000 ) ); timer->async_wait( bindHandler( &Implementation::handleTimer2< HandlerT >, handler, timer, _1 ) ); } catch( std::exception& ) { handler( Asio::error::interrupted ); } } } template< typename HandlerT > static void handleTimer2( HandlerT handler, boost::shared_ptr< Asio::steady_timer > timer, boost::system::error_code error ) { // And here, the handler must come as the first parameter. handler( error ); } }; template< typename HandlerT > static void asyncOperation( Asio::io_service& ioService, std::size_t operationId, HandlerT handler ) { Implementation::asyncOperation( ioService, operationId, handler ); } } // namespace TestAsyncOperation2 // Test Code //---------- // Mutex to protect console printing. boost::mutex g_testLogMutex; #define TEST_LOG( args ) \ do \ { \ boost::unique_lock< boost::mutex > lock( g_testLogMutex ); \ std::cout << args << std::endl; \ } \ while( 0 ) // Test wrapper of IO handlers that counts and prints its invocations. // It is meant to test: // - the tracing of all the intermediary handler invocations of a composed operation // - a race condition that may occur if a strand is not used in a one io_service, multiple threads // scenario. // // The test scenario is that one such a handler-wrapper wraps a composed operation with an ID. // The 'local' invocation counter counts invocations for one composed operation while the 'total' one // counts invocations for all the composed operations. Hence, if no strand is used, there will be a // race when updating the total counter. Thread-Sleep is used below to force such a race to occur. template< typename HandlerT > class TestInvokePrintHandler { public: TestInvokePrintHandler( HandlerT const& handler, std::size_t operationId, std::size_t& invokeCountTotal, std::size_t& invokeCountLocal ) : m_handler( handler ), m_operationId( operationId ), m_invokeCountTotal( &invokeCountTotal ), m_invokeCountLocal( &invokeCountLocal ) {} void operator()() { m_handler(); } void operator()() const { m_handler(); } template< typename A1T > void operator()( A1T const& a1 ) { m_handler( a1 ); } template< typename A1T > void operator()( A1T const& a1 ) const { m_handler( a1 ); } template< typename A1T, typename A2T > void operator()( A1T const& a1, A2T const& a2 ) { m_handler( a1, a2 ); } template< typename A1T, typename A2T > void operator()( A1T const& a1, A2T const& a2 ) const { m_handler( a1, a2 ); } template< typename FunctionT > friend void asio_handler_invoke( FunctionT const& f, TestInvokePrintHandler* h ) { BOOST_ASSERT( h ); std::size_t const newTotal = *h->m_invokeCountTotal + 1; std::size_t const newLocal = *h->m_invokeCountLocal + 1; TEST_LOG( "T[" << boost::this_thread::get_id() << "] TestInvokePrintHandler:" << " operation: " << h->m_operationId << " invoke-count-total: " << newTotal << " invoke-count-local: " << newLocal ); #if 0 // Induce a proeminent race condition. boost::this_thread::sleep( boost::posix_time::milliseconds( TestAsyncOperation::sleepVariationMilli * 10 ) ); #else // A more subtle race condition. boost::this_thread::sleep( boost::posix_time::milliseconds( TestAsyncOperation::sleepVariationMilli - 3 ) ); #endif *h->m_invokeCountTotal = newTotal; *h->m_invokeCountLocal = newLocal; f(); } private: HandlerT m_handler; std::size_t m_operationId; std::size_t* m_invokeCountTotal; std::size_t* m_invokeCountLocal; }; template< typename HandlerT > TestInvokePrintHandler< HandlerT > makeTestInvokePrintHandler( HandlerT const& handler, std::size_t operationId, std::size_t& invokeCountTotal, std::size_t& invokeCountLocal ) { return TestInvokePrintHandler< HandlerT >( handler, operationId, invokeCountTotal, invokeCountLocal ); } // The highest level composed operation of the test. // It does one TestAsyncOperation2 composed operation. // This test doesn't use the test handler-wrapper (TestInvokePrintHandler) so that a boost::bind // immediately nested in another boost::bind is exercised. template< std::size_t idT > struct TestOperation { static void start( Asio::io_service& ioService ) { TEST_LOG( "T[" << boost::this_thread::get_id() << "] TestOperation" << idT << "::start" ); TestAsyncOperation2::asyncOperation( ioService, idT, boost::bind( &TestOperation::handle, _1 ) ); } static void start( Asio::io_service& ioService, Asio::io_service::strand& strand ) { TEST_LOG( "T[" << boost::this_thread::get_id() << "] TestOperation" << idT << "::start" ); TestAsyncOperation2::asyncOperation( ioService, idT, strand.wrap( boost::bind( &TestOperation::handle, _1 ) ) ); } static void handle( boost::system::error_code error ) { TEST_LOG( "T[" << boost::this_thread::get_id() << "] TestOperation" << idT << "::handle:" << " error: " << error ); } }; // The highest level composed operation of the test. // It does one TestAsyncOperation2 composed operation wrapped in the test handler-wrapper // (TestInvokePrintHandler). template< std::size_t idT > struct TestOperationInvokePrint { static void start( Asio::io_service& ioService, std::size_t& invokeCountTotal, std::size_t& invokeCountLocal ) { TEST_LOG( "T[" << boost::this_thread::get_id() << "] TestOperationInvokePrint" << idT << "::start" ); TestAsyncOperation2::asyncOperation( ioService, idT, makeTestInvokePrintHandler( boost::bind( &TestOperationInvokePrint::handle, _1 ), idT, invokeCountTotal, invokeCountLocal ) ); } static void start( Asio::io_service& ioService, Asio::io_service::strand& strand, std::size_t& invokeCountTotal, std::size_t& invokeCountLocal ) { TEST_LOG( "T[" << boost::this_thread::get_id() << "] TestOperationInvokePrint" << idT << "::start" ); TestAsyncOperation2::asyncOperation( ioService, idT, strand.wrap( makeTestInvokePrintHandler( boost::bind( &TestOperationInvokePrint::handle, _1 ), idT, invokeCountTotal, invokeCountLocal ) ) ); } static void handle( boost::system::error_code error ) { TEST_LOG( "T[" << boost::this_thread::get_id() << "] TestOperationInvokePrint" << idT << "::handle:" << " error: " << error ); } }; // Tests a single thread, single io-service scenario. void testIoService() { TEST_LOG( "\n" ); TEST_LOG( "testIoService" ); Asio::io_service ioService; ioService.post( boost::bind( &TestOperation< 0 >::start, boost::ref( ioService ) ) ); std::size_t invokeCountTotal = 0; std::size_t invokeCountLocal1 = 0; std::size_t invokeCountLocal2 = 0; ioService.post( boost::bind( &TestOperationInvokePrint< 1 >::start, boost::ref( ioService ), boost::ref( invokeCountTotal ), boost::ref( invokeCountLocal1 ) ) ); ioService.post( boost::bind( &TestOperationInvokePrint< 2 >::start, boost::ref( ioService ), boost::ref( invokeCountTotal ), boost::ref( invokeCountLocal2 ) ) ); bool run = true; while( run ) { try { ioService.run(); run = false; } catch( std::exception& e ) { std::cerr << "Run Error: " << e.what() << std::endl; } } // Checking the number of intermediary invocations. // For the case of single-threaded run without any explicit strand, the number of invocations // should be determined only by the intermediary operations of TestAsyncOperation and // TestAsyncOperation2. BOOST_ASSERT( invokeCountLocal1 == TestAsyncOperation::operationCount( 1 ) + TestAsyncOperation2::operationCount( 1 ) ); BOOST_ASSERT( invokeCountLocal2 == TestAsyncOperation::operationCount( 2 ) + TestAsyncOperation2::operationCount( 2 ) ); // Checking the total number of intermediary invocations. BOOST_ASSERT( invokeCountTotal == invokeCountLocal1 + invokeCountLocal2 ); TEST_LOG( "testIoService: end" ); } // Tests a multi-thread thread, single io-service scenario with or without strand. void testIoServiceThreads( bool useStrand ) { TEST_LOG( "\n" ); TEST_LOG( "testIoServiceThreads: " << ( useStrand ? "use-strand" : "no-strand" ) ); Asio::io_service ioService; boost::scoped_ptr< Asio::io_service::work > startWaitWork( new Asio::io_service::work( ioService ) ); boost::thread secondThread( boost::bind( &Asio::io_service::run, &ioService ) ); boost::thread thirdThread( boost::bind( &Asio::io_service::run, &ioService ) ); std::size_t invokeCountTotal = 0; std::size_t invokeCountLocal1 = 0; std::size_t invokeCountLocal2 = 0; if( !useStrand ) { ioService.post( boost::bind( &TestOperation< 0 >::start, boost::ref( ioService ) ) ); ioService.post( boost::bind( &TestOperationInvokePrint< 1 >::start, boost::ref( ioService ), boost::ref( invokeCountTotal ), boost::ref( invokeCountLocal1 ) ) ); ioService.post( boost::bind( &TestOperationInvokePrint< 2 >::start, boost::ref( ioService ), boost::ref( invokeCountTotal ), boost::ref( invokeCountLocal2 ) ) ); } else { Asio::io_service::strand strand( ioService ); ioService.post( boost::bind( &TestOperation< 0 >::start, boost::ref( ioService ), boost::ref( strand ) ) ); ioService.post( boost::bind( &TestOperationInvokePrint< 1 >::start, boost::ref( ioService ), boost::ref( strand ), boost::ref( invokeCountTotal ), boost::ref( invokeCountLocal1 ) ) ); ioService.post( boost::bind( &TestOperationInvokePrint< 2 >::start, boost::ref( ioService ), boost::ref( strand ), boost::ref( invokeCountTotal ), boost::ref( invokeCountLocal2 ) ) ); } startWaitWork.reset(); ioService.run(); secondThread.join(); thirdThread.join(); // Checking the total number of intermediary invocations. // ASSERT-ing on its correctness only when a strand was used. if( useStrand) { BOOST_ASSERT( invokeCountTotal == invokeCountLocal1 + invokeCountLocal2 ); TEST_LOG( "testIoServiceThreads: use-strand: end "); } else if( invokeCountTotal == invokeCountLocal1 + invokeCountLocal2 ) { TEST_LOG( "testIoServiceThreads: no-strand: end "); } else { TEST_LOG( "testIoServiceThreads: no-strand: race detected" ); } } int main( void ) { testIoService(); testIoServiceThreads( true ); testIoServiceThreads( false ); return 0; }