// // compose.cpp // ~~~~~~~~~~~ // // Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // 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) // // Disable autolinking for unit tests. #if !defined(BOOST_ALL_NO_LIB) #define BOOST_ALL_NO_LIB 1 #endif // !defined(BOOST_ALL_NO_LIB) // Test that header file is self-contained. #include "asio/compose.hpp" #include #include "asio/bind_cancellation_slot.hpp" #include "asio/cancellation_signal.hpp" #include "asio/io_context.hpp" #include "asio/post.hpp" #include "asio/system_timer.hpp" #include "unit_test.hpp" //------------------------------------------------------------------------------ class impl_0_completion_args { public: explicit impl_0_completion_args(asio::io_context& ioc) : ioc_(ioc), state_(starting) { } template void operator()(Self& self) { switch (state_) { case starting: state_ = posting; asio::post(ioc_, static_cast(self)); break; case posting: self.complete(); break; default: break; } } private: asio::io_context& ioc_; enum { starting, posting } state_; }; template ASIO_INITFN_RESULT_TYPE(CompletionToken, void()) async_0_completion_args(asio::io_context& ioc, CompletionToken&& token) { return asio::async_compose( impl_0_completion_args(ioc), token); } void compose_0_args_handler(int* count) { ++(*count); } struct compose_0_args_lvalue_handler { int* count_; void operator()() { ++(*count_); } }; void compose_0_completion_args_test() { namespace bindns = std; asio::io_context ioc; int count = 0; async_0_completion_args(ioc, bindns::bind(&compose_0_args_handler, &count)); // No handlers can be called until run() is called. ASIO_CHECK(!ioc.stopped()); ASIO_CHECK(count == 0); ioc.run(); // The run() call will not return until all work has finished. ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ioc.restart(); count = 0; compose_0_args_lvalue_handler lvalue_handler = { &count }; async_0_completion_args(ioc, lvalue_handler); // No handlers can be called until run() is called. ASIO_CHECK(!ioc.stopped()); ASIO_CHECK(count == 0); ioc.run(); // The run() call will not return until all work has finished. ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); } //------------------------------------------------------------------------------ class impl_1_completion_arg { public: explicit impl_1_completion_arg(asio::io_context& ioc) : ioc_(ioc), state_(starting) { } template void operator()(Self& self) { switch (state_) { case starting: state_ = posting; asio::post(ioc_, static_cast(self)); break; case posting: self.complete(42); break; default: break; } } private: asio::io_context& ioc_; enum { starting, posting } state_; }; template ASIO_INITFN_RESULT_TYPE(CompletionToken, void(int)) async_1_completion_arg(asio::io_context& ioc, CompletionToken&& token) { return asio::async_compose( impl_1_completion_arg(ioc), token); } void compose_1_arg_handler(int* count, int* result_out, int result) { ++(*count); *result_out = result; } struct compose_1_arg_lvalue_handler { int* count_; int* result_out_; void operator()(int result) { ++(*count_); *result_out_ = result; } }; void compose_1_completion_arg_test() { namespace bindns = std; using bindns::placeholders::_1; asio::io_context ioc; int count = 0; int result = 0; async_1_completion_arg(ioc, bindns::bind(&compose_1_arg_handler, &count, &result, _1)); // No handlers can be called until run() is called. ASIO_CHECK(!ioc.stopped()); ASIO_CHECK(count == 0); ASIO_CHECK(result == 0); ioc.run(); // The run() call will not return until all work has finished. ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == 42); ioc.restart(); count = 0; result = 0; compose_1_arg_lvalue_handler lvalue_handler = { &count, &result }; async_1_completion_arg(ioc, lvalue_handler); // No handlers can be called until run() is called. ASIO_CHECK(!ioc.stopped()); ASIO_CHECK(count == 0); ASIO_CHECK(result == 0); ioc.run(); // The run() call will not return until all work has finished. ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == 42); } //------------------------------------------------------------------------------ typedef asio::enable_terminal_cancellation default_filter; template class impl_cancellable { public: explicit impl_cancellable(CancellationFilter cancellation_filter, asio::system_timer& timer) : cancellation_filter_(cancellation_filter), timer_(timer), state_(starting) { } template void operator()(Self& self, const asio::error_code& ec = asio::error_code()) { switch (state_) { case starting: if (!asio::is_same::value) self.reset_cancellation_state(cancellation_filter_); state_ = waiting; timer_.expires_after(asio::chrono::milliseconds(100)); timer_.async_wait(static_cast(self)); break; case waiting: self.complete(!ec); break; default: break; } } private: CancellationFilter cancellation_filter_; asio::system_timer& timer_; enum { starting, waiting } state_; }; template ASIO_INITFN_RESULT_TYPE(CompletionToken, void(bool)) async_cancellable(CancellationFilter cancellation_filter, asio::system_timer& timer, CompletionToken&& token) { return asio::async_compose( impl_cancellable(cancellation_filter, timer), token); } void compose_partial_cancellation_handler( int* count, bool* result_out, bool result) { ++(*count); *result_out = result; } void compose_default_cancellation_test() { namespace bindns = std; using bindns::placeholders::_1; asio::io_context ioc; asio::system_timer timer(ioc); asio::cancellation_signal signal; int count = 0; bool result = false; async_cancellable(default_filter(), timer, bindns::bind(&compose_partial_cancellation_handler, &count, &result, _1)); ioc.run(); // No cancellation, operation completes successfully. ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == true); ioc.restart(); count = 0; result = 0; async_cancellable(default_filter(), timer, asio::bind_cancellation_slot(signal.slot(), bindns::bind(&compose_partial_cancellation_handler, &count, &result, _1))); // Total cancellation unsupported. Operation completes successfully. signal.emit(asio::cancellation_type::total); ioc.run(); ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == true); ioc.restart(); count = 0; result = 0; async_cancellable(default_filter(), timer, asio::bind_cancellation_slot(signal.slot(), bindns::bind(&compose_partial_cancellation_handler, &count, &result, _1))); // Partial cancellation unsupported. Operation completes successfully. signal.emit(asio::cancellation_type::partial); ioc.run(); ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == true); ioc.restart(); count = 0; result = 0; async_cancellable(default_filter(), timer, asio::bind_cancellation_slot(signal.slot(), bindns::bind(&compose_partial_cancellation_handler, &count, &result, _1))); // Terminal cancellation works. Operation completes with failure. signal.emit(asio::cancellation_type::terminal); ioc.run(); ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == false); } void compose_partial_cancellation_test() { namespace bindns = std; using bindns::placeholders::_1; asio::io_context ioc; asio::system_timer timer(ioc); asio::cancellation_signal signal; int count = 0; bool result = false; async_cancellable(asio::enable_partial_cancellation(), timer, bindns::bind(&compose_partial_cancellation_handler, &count, &result, _1)); ioc.run(); // No cancellation, operation completes successfully. ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == true); ioc.restart(); count = 0; result = 0; async_cancellable(asio::enable_partial_cancellation(), timer, asio::bind_cancellation_slot(signal.slot(), bindns::bind(&compose_partial_cancellation_handler, &count, &result, _1))); // Total cancellation unsupported. Operation completes successfully. signal.emit(asio::cancellation_type::total); ioc.run(); ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == true); ioc.restart(); count = 0; result = 0; async_cancellable(asio::enable_partial_cancellation(), timer, asio::bind_cancellation_slot(signal.slot(), bindns::bind(&compose_partial_cancellation_handler, &count, &result, _1))); // Partial cancellation works. Operation completes with failure. signal.emit(asio::cancellation_type::partial); ioc.run(); ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == false); ioc.restart(); count = 0; result = 0; async_cancellable(asio::enable_partial_cancellation(), timer, asio::bind_cancellation_slot(signal.slot(), bindns::bind(&compose_partial_cancellation_handler, &count, &result, _1))); // Terminal cancellation works. Operation completes with failure. signal.emit(asio::cancellation_type::terminal); ioc.run(); ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == false); } void compose_total_cancellation_test() { namespace bindns = std; using bindns::placeholders::_1; asio::io_context ioc; asio::system_timer timer(ioc); asio::cancellation_signal signal; int count = 0; bool result = false; async_cancellable(asio::enable_total_cancellation(), timer, bindns::bind(&compose_partial_cancellation_handler, &count, &result, _1)); ioc.run(); // No cancellation, operation completes successfully. ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == true); ioc.restart(); count = 0; result = 0; async_cancellable(asio::enable_total_cancellation(), timer, asio::bind_cancellation_slot(signal.slot(), bindns::bind(&compose_partial_cancellation_handler, &count, &result, _1))); // Total cancellation works. Operation completes with failure. signal.emit(asio::cancellation_type::total); ioc.run(); ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == false); ioc.restart(); count = 0; result = 0; async_cancellable(asio::enable_total_cancellation(), timer, asio::bind_cancellation_slot(signal.slot(), bindns::bind(&compose_partial_cancellation_handler, &count, &result, _1))); // Partial cancellation works. Operation completes with failure. signal.emit(asio::cancellation_type::partial); ioc.run(); ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == false); ioc.restart(); count = 0; result = 0; async_cancellable(asio::enable_total_cancellation(), timer, asio::bind_cancellation_slot(signal.slot(), bindns::bind(&compose_partial_cancellation_handler, &count, &result, _1))); // Terminal cancellation works. Operation completes with failure. signal.emit(asio::cancellation_type::terminal); ioc.run(); ASIO_CHECK(ioc.stopped()); ASIO_CHECK(count == 1); ASIO_CHECK(result == false); } //------------------------------------------------------------------------------ ASIO_TEST_SUITE ( "compose", ASIO_TEST_CASE(compose_0_completion_args_test) ASIO_TEST_CASE(compose_1_completion_arg_test) ASIO_TEST_CASE(compose_default_cancellation_test) ASIO_TEST_CASE(compose_partial_cancellation_test) ASIO_TEST_CASE(compose_total_cancellation_test) )