From 1a1c7b68c66e7c3f617b1cdd00624859ce5aacb0 Mon Sep 17 00:00:00 2001 From: Christos Stratopoulos Date: Thu, 5 Dec 2019 11:57:35 -0500 Subject: [PATCH] Base implementation of an ASIO completion token integration * Bump the asio version to 1.41 * Move headers that require an external dependency to include/continuable/support * Ref #28 * Ref #27 --- dep/asio/asio | 2 +- doc/installation.dox | 2 +- examples/example-asio/CMakeLists.txt | 25 ++- .../example-asio/example-asio-integration.cpp | 129 +++++++++++++++ examples/example-asio/example-asio.cpp | 2 +- include/continuable/detail/support/asio.hpp | 154 ++++++++++++++++++ include/continuable/support/asio.hpp | 83 ++++++++++ .../gtest.hpp} | 6 +- test/unit-test/test-continuable.hpp | 2 +- 9 files changed, 392 insertions(+), 13 deletions(-) create mode 100644 examples/example-asio/example-asio-integration.cpp create mode 100644 include/continuable/detail/support/asio.hpp create mode 100644 include/continuable/support/asio.hpp rename include/continuable/{continuable-testing.hpp => support/gtest.hpp} (97%) diff --git a/dep/asio/asio b/dep/asio/asio index 0a52abc..8087252 160000 --- a/dep/asio/asio +++ b/dep/asio/asio @@ -1 +1 @@ -Subproject commit 0a52abce85ab83b920cc7257d8a2703fe69bc79c +Subproject commit 8087252a0c3c2f0baad96ddbd6554db17a846376 diff --git a/doc/installation.dox b/doc/installation.dox index 9038ca4..d4bc705 100644 --- a/doc/installation.dox +++ b/doc/installation.dox @@ -49,7 +49,7 @@ Continuable is a header-only library with one required header-only dependency: erasure wrapper to convert a \ref continuable_base into a \ref continuable. Additionally GTest is required as optional dependency for the asynchronous -unit testing macros defined in `continuable/continuable-testing.hpp` +unit testing macros defined in `continuable/support/gtest.hpp` if those are used: - [google/googletest](https://github.com/google/googletest) is used as diff --git a/examples/example-asio/CMakeLists.txt b/examples/example-asio/CMakeLists.txt index e9cebd5..e9b446d 100644 --- a/examples/example-asio/CMakeLists.txt +++ b/examples/example-asio/CMakeLists.txt @@ -1,15 +1,28 @@ +add_library(asio-example-deps INTERFACE) + +target_include_directories(asio-example-deps + INTERFACE + ${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(asio-example-deps + INTERFACE + asio + continuable) + add_executable(example-asio ${CMAKE_CURRENT_LIST_DIR}/example-asio.cpp) -target_include_directories(example-asio - PRIVATE - ${CMAKE_CURRENT_LIST_DIR}) - target_link_libraries(example-asio PRIVATE - asio - continuable) + asio-example-deps) target_compile_definitions(example-asio PUBLIC -DCONTINUABLE_WITH_NO_EXCEPTIONS) + +add_executable(example-asio-integration + ${CMAKE_CURRENT_LIST_DIR}/example-asio-integration.cpp) + +target_link_libraries(example-asio-integration + PRIVATE + asio-example-deps) diff --git a/examples/example-asio/example-asio-integration.cpp b/examples/example-asio/example-asio-integration.cpp new file mode 100644 index 0000000..ad2e3aa --- /dev/null +++ b/examples/example-asio/example-asio-integration.cpp @@ -0,0 +1,129 @@ + +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v4.0.0 + + Copyright(c) 2015 - 2019 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#include + +#include +#include + +// Queries the NIST daytime service and prints the current date and time +void daytime_service(); + +// Checks that a cancelled timer async_wait fails with +// `asio::error::operation_aborted` +void cancelled_async_wait(); + +// Indicates fatal error due to an unexpected failure in the continuation chain. +void unexpected_error(cti::exception_t); + +// Check that the failure was an aborted operation, as expected. +void check_aborted_operation(cti::exception_t); + +int main(int, char**) { + daytime_service(); + cancelled_async_wait(); + + return 0; +} + +void daytime_service() { + using asio::ip::tcp; + + asio::io_context ioc(1); + tcp::resolver resolver(ioc); + tcp::socket socket(ioc); + std::string buf; + + resolver.async_resolve("time.nist.gov", "daytime", cti::asio_token) + .then([&socket](tcp::resolver::results_type endpoints) { + return asio::async_connect(socket, endpoints, cti::asio_token); + }) + .then([&socket, &buf] { + return asio::async_read_until(socket, asio::dynamic_buffer(buf), '\n', + cti::asio_token); + }) + .then([&buf](std::size_t) { puts(buf.data()); }) + .fail(&unexpected_error); + + ioc.run(); +} + +void cancelled_async_wait() { + asio::io_context ioc(1); + asio::steady_timer t(ioc); + + t.expires_after(std::chrono::seconds(999)); + + t.async_wait(cti::asio_token) + .then([] { + puts("This should never be called"); + std::terminate(); + }) + .fail(&check_aborted_operation); + + t.cancel_one(); + ioc.run(); +} + +void unexpected_error(cti::exception_t e) { +#if defined(CONTINUABLE_HAS_EXCEPTIONS) + std::rethrow_exception(e); +#else + puts("Continuation failed with unexpected error"); + puts(e.message().data()); + std::terminate(); +#endif +} + +void check_aborted_operation(cti::exception_t ex) { + auto is_expected_error = [](auto err_val) { + if (err_val == asio::error_code(asio::error::operation_aborted)) { + puts("Continuation failed due to aborted async operation, as expected."); + return true; + } + return false; + }; + +#if defined(CONTINUABLE_HAS_EXCEPTIONS) + try { + std::rethrow_exception(ex); + } catch (asio::system_error const& err) { + if (is_expected_error(err.code())) { + return; + } + } +#else + if (is_expected_error(ex)) { + return; + } +#endif + + unexpected_error(ex); +} diff --git a/examples/example-asio/example-asio.cpp b/examples/example-asio/example-asio.cpp index 2a12098..0351600 100644 --- a/examples/example-asio/example-asio.cpp +++ b/examples/example-asio/example-asio.cpp @@ -5,7 +5,7 @@ \_,(_)| | | || ||_|(_||_)|(/_ https://github.com/Naios/continuable - v3.0.0 + v4.0.0 Copyright(c) 2015 - 2019 Denis Blank diff --git a/include/continuable/detail/support/asio.hpp b/include/continuable/detail/support/asio.hpp new file mode 100644 index 0000000..04122d4 --- /dev/null +++ b/include/continuable/detail/support/asio.hpp @@ -0,0 +1,154 @@ +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v4.0.0 + + Copyright(c) 2015 - 2019 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#ifndef CONTINUABLE_DETAIL_ASIO_HPP_INCLUDED +#define CONTINUABLE_DETAIL_ASIO_HPP_INCLUDED + +#include + +#if defined(ASIO_STANDALONE) +#include +#include +#include + +#if defined(CONTINUABLE_HAS_EXCEPTIONS) +#include +#endif + +#if (ASIO_VERSION / 100 % 1000) <= 12 +#define CTI_DETAIL_ASIO_HAS_NO_INTEGRATION +#elif (ASIO_VERSION / 100 % 1000) <= 14 +#define CTI_DETAIL_ASIO_HAS_EXPLICIT_RET_TYPE_INTEGRATION +#endif + +#define CTI_DETAIL_ASIO_NAMESPACE_BEGIN namespace asio { +#define CTI_DETAIL_ASIO_NAMESPACE_END } +#else +#include +#include +#include + +#if defined(CONTINUABLE_HAS_EXCEPTIONS) +#include +#endif + +#if (BOOST_VERSION / 100 % 1000) <= 69 +#define CTI_DETAIL_ASIO_HAS_NO_INTEGRATION +#elif (BOOST_VERSION / 100 % 1000) <= 71 +#define CTI_DETAIL_ASIO_HAS_EXPLICIT_RET_TYPE_INTEGRATION +#endif + +#define CTI_DETAIL_ASIO_NAMESPACE_BEGIN \ + namespace boost { \ + namespace asio { +#define CTI_DETAIL_ASIO_NAMESPACE_END \ + } \ + } +#endif + +#if defined(CTI_DETAIL_ASIO_HAS_NO_INTEGRATION) +#error "First-class ASIO support for continuable requires the form of "\ + "`async_result` with an `initiate` static member function, which was added " \ + "in standalone ASIO 1.13.0 and Boost ASIO 1.70. Older versions can be " \ + "integrated manually with `cti::promisify`." +#endif + +#include + +#include + +#if defined(CONTINUABLE_HAS_EXCEPTIONS) +#include +#endif + +namespace cti { +namespace detail { +namespace asio { + +#if defined(ASIO_STANDALONE) +using error_code_t = ::asio::error_code; + +#if defined(CONTINUABLE_HAS_EXCEPTIONS) +using system_error_t = ::asio::system_error; +#endif +#else +using error_code_t = ::boost::system::error_code; + +#if defined(CONTINUABLE_HAS_EXCEPTIONS) +using system_error_t = ::boost::system::system_error; +#endif +#endif + +// Binds `promise` to the first argument of a continuable resolver, giving it +// the signature of an ASIO handler. +template +auto promise_resolver_handler(Promise&& promise) noexcept { + return [promise = std::forward(promise)]( + error_code_t e, auto&&... args) mutable noexcept { + if (e) { +#if defined(CONTINUABLE_HAS_EXCEPTIONS) + promise.set_exception( + std::make_exception_ptr(system_error_t(std::move(e)))); +#else + promise.set_exception(cti::exception_t(e.value(), e.category())); +#endif + } else { + promise.set_value(std::forward(args)...); + } + }; +} + +// Helper struct wrapping a call to `cti::make_continuable` and, if needed, +// providing an erased, explicit `return_type` for `async_result`. +template +struct initiate_make_continuable; + +template +struct initiate_make_continuable { +#if defined(CTI_DETAIL_ASIO_HAS_EXPLICIT_RET_TYPE_INTEGRATION) + using erased_return_type = cti::continuable; +#endif + + template + auto operator()(Continuation&& continuation) { + return detail::base::attorney::create_from( + std::forward(continuation), detail::identity{}, + detail::util::ownership{}); + } +}; + +template +struct initiate_make_continuable + : initiate_make_continuable {}; + +} // namespace asio +} // namespace detail +} // namespace cti + +#endif // CONTINUABLE_DETAIL_ASIO_HPP_INCLUDED diff --git a/include/continuable/support/asio.hpp b/include/continuable/support/asio.hpp new file mode 100644 index 0000000..387fef3 --- /dev/null +++ b/include/continuable/support/asio.hpp @@ -0,0 +1,83 @@ +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v4.0.0 + + Copyright(c) 2015 - 2019 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#ifndef CONTINUABLE_SUPPORT_ASIO_HPP_INCLUDED +#define CONTINUABLE_SUPPORT_ASIO_HPP_INCLUDED + +#include +#include +#include + +namespace cti { + +// Type used as an ASIO completion token to specify an asynchronous operation +// should return a continuable. +struct asio_token_t {}; + +// Special value for instance of `asio_token_t`. +constexpr asio_token_t asio_token{}; + +} // namespace cti + +CTI_DETAIL_ASIO_NAMESPACE_BEGIN + +template +class async_result { +public: +#if defined(CTI_DETAIL_ASIO_HAS_EXPLICIT_RET_TYPE_INTEGRATION) + using return_type = typename cti::detail::asio::initiate_make_continuable< + Signature>::erased_return_type; +#endif + + template + static auto initiate(Initiation initiation, cti::asio_token_t, Args... args) { + return cti::detail::asio::initiate_make_continuable{}( + [initiation = std::move(initiation), + init_args = + std::make_tuple(std::move(args)...)](auto&& promise) mutable { + cti::detail::traits::unpack( + [initiation = std::move(initiation), + handler = cti::detail::asio::promise_resolver_handler( + std::forward(promise))]( + auto&&... args) mutable { + std::move(initiation)(std::move(handler), + std::forward(args)...); + }, + std::move(init_args)); + }); + } +}; + +CTI_DETAIL_ASIO_NAMESPACE_END + +#undef CTI_DETAIL_ASIO_NAMESPACE_BEGIN +#undef CTI_DETAIL_ASIO_NAMESPACE_END +#undef CTI_DETAIL_ASIO_HAS_EXPLICIT_RET_TYPE_INTEGRATION + +#endif // CONTINUABLE_SUPPORT_ASIO_HPP_INCLUDED diff --git a/include/continuable/continuable-testing.hpp b/include/continuable/support/gtest.hpp similarity index 97% rename from include/continuable/continuable-testing.hpp rename to include/continuable/support/gtest.hpp index d3bbc81..fb780f8 100644 --- a/include/continuable/continuable-testing.hpp +++ b/include/continuable/support/gtest.hpp @@ -28,8 +28,8 @@ SOFTWARE. **/ -#ifndef CONTINUABLE_TESTING_HPP_INCLUDED -#define CONTINUABLE_TESTING_HPP_INCLUDED +#ifndef CONTINUABLE_SUPPORT_GTEST_HPP_INCLUDED +#define CONTINUABLE_SUPPORT_GTEST_HPP_INCLUDED #include #include @@ -169,4 +169,4 @@ /// \} -#endif // CONTINUABLE_TESTING_HPP_INCLUDED +#endif // CONTINUABLE_SUPPORT_GTEST_HPP_INCLUDED diff --git a/test/unit-test/test-continuable.hpp b/test/unit-test/test-continuable.hpp index 11aa0c0..6df04a4 100644 --- a/test/unit-test/test-continuable.hpp +++ b/test/unit-test/test-continuable.hpp @@ -31,8 +31,8 @@ #include #include -#include #include +#include #include using cti::detail::identity;