// // spawn.cpp // ~~~~~~~~~ // // Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com) // Copyright (c) 2024 Casey Bodley (cbodley at redhat 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/spawn.hpp" #include "unit_test.hpp" #if defined(ASIO_HAS_BOOST_CONTEXT_FIBER) #include #include #include "archetypes/async_ops.hpp" #include "asio/any_completion_handler.hpp" #include "asio/bind_cancellation_slot.hpp" #include "asio/deferred.hpp" #include "asio/io_context.hpp" #include "asio/steady_timer.hpp" void void_returning_coroutine(asio::yield_context) { } int int_returning_coroutine(asio::yield_context) { return 42; } void test_spawn_with_any_completion_handler() { asio::io_context ctx; bool called = false; asio::spawn(ctx, void_returning_coroutine, asio::any_completion_handler( [&](std::exception_ptr) { called = true; })); ASIO_CHECK(!called); ctx.run(); ASIO_CHECK(called); int result = 0; asio::spawn(ctx, int_returning_coroutine, asio::any_completion_handler( [&](std::exception_ptr, int i) { result = i; })); ASIO_CHECK(result == 0); ctx.restart(); ctx.run(); ASIO_CHECK(result == 42); } void test_spawn_deferred() { asio::io_context ctx; { bool called = false; auto fn = asio::spawn(ctx, void_returning_coroutine, asio::deferred); fn([&](std::exception_ptr) { called = true; }); ASIO_CHECK(!called); ctx.poll(); ASIO_CHECK(ctx.stopped()); ASIO_CHECK(called); } { int result = 0; auto fn = asio::spawn(ctx, int_returning_coroutine, asio::deferred); fn([&](std::exception_ptr, int i) { result = i; }); ASIO_CHECK(result == 0); ctx.restart(); ctx.poll(); ASIO_CHECK(ctx.stopped()); ASIO_CHECK(result == 42); } } void sleeping_coroutine(asio::yield_context yield) { asio::steady_timer timer(yield.get_executor(), asio::steady_timer::time_point::max()); timer.async_wait(yield); } void test_spawn_cancel() { asio::cancellation_signal sig; asio::io_context ctx; std::exception_ptr result = nullptr; bool called = false; asio::spawn(ctx, sleeping_coroutine, asio::bind_cancellation_slot(sig.slot(), [&](std::exception_ptr e) { result = e; called = true; })); ctx.poll(); ASIO_CHECK(!ctx.stopped()); ASIO_CHECK(!called); ASIO_CHECK(result == nullptr); sig.emit(asio::cancellation_type::all); ctx.poll(); ASIO_CHECK(ctx.stopped()); ASIO_CHECK(called); ASIO_CHECK(result != nullptr); try { std::rethrow_exception(result); } catch (const std::system_error& e) { ASIO_CHECK(e.code() == asio::error::operation_aborted); } catch (...) { ASIO_ERROR("expected system_error"); } } void throwing_coroutine(asio::yield_context) { throw std::runtime_error("oops"); } void test_spawn_exception() { asio::io_context ctx; std::exception_ptr result = nullptr; bool called = false; asio::spawn(ctx, throwing_coroutine, [&](std::exception_ptr e) { result = e; called = true; }); ctx.poll(); ASIO_CHECK(ctx.stopped()); ASIO_CHECK(called); ASIO_CHECK(result != nullptr); try { std::rethrow_exception(result); } catch (const std::runtime_error&) { // ok } catch (...) { ASIO_ERROR("expected runtime_error"); } } std::unique_ptr factory_coroutine(asio::yield_context) { return std::unique_ptr(new int(42)); } void test_spawn_return_move_only() { asio::io_context ctx; std::unique_ptr result; bool called = false; asio::spawn(ctx, factory_coroutine, [&](std::exception_ptr, std::unique_ptr r) { result = std::move(r); called = true; }); ctx.poll(); ASIO_CHECK(ctx.stopped()); ASIO_CHECK(called); ASIO_CHECK(result); ASIO_CHECK(*result == 42); } int coroutine_using_async_ops_0(asio::yield_context yield) { using namespace archetypes; try { async_op_0(yield); } catch (...) { ASIO_CHECK(false); } try { async_op_ec_0(true, yield); } catch (...) { ASIO_CHECK(false); } try { async_op_ec_0(false, yield); ASIO_CHECK(false); } catch (asio::system_error& e) { ASIO_CHECK(e.code() == asio::error::operation_aborted); } catch (...) { ASIO_CHECK(false); } try { async_op_ex_0(true, yield); } catch (...) { ASIO_CHECK(false); } try { async_op_ex_0(false, yield); ASIO_CHECK(false); } catch (std::exception& e) { ASIO_CHECK(e.what() == std::string("blah")); } catch (...) { ASIO_CHECK(false); } return 123; } int coroutine_using_async_ops_1(asio::yield_context yield) { using namespace archetypes; try { int i = async_op_1(yield); ASIO_CHECK(i == 42); } catch (...) { ASIO_CHECK(false); } try { int i = async_op_ec_1(true, yield); ASIO_CHECK(i == 42); } catch (...) { ASIO_CHECK(false); } try { int i = async_op_ec_1(false, yield); ASIO_CHECK(false); (void)i; } catch (asio::system_error& e) { ASIO_CHECK(e.code() == asio::error::operation_aborted); } catch (...) { ASIO_CHECK(false); } try { int i = async_op_ex_1(true, yield); ASIO_CHECK(i == 42); } catch (...) { ASIO_CHECK(false); } try { int i = async_op_ex_1(false, yield); ASIO_CHECK(false); (void)i; } catch (std::exception& e) { ASIO_CHECK(e.what() == std::string("blah")); } catch (...) { ASIO_CHECK(false); } return 234; } int coroutine_using_async_ops_2(asio::yield_context yield) { using namespace archetypes; try { int i; double d; std::tie(i, d) = async_op_2(yield); ASIO_CHECK(i == 42); ASIO_CHECK(d == 2.0); } catch (...) { ASIO_CHECK(false); } try { int i; double d; std::tie(i, d) = async_op_ec_2(true, yield); ASIO_CHECK(i == 42); ASIO_CHECK(d == 2.0); } catch (...) { ASIO_CHECK(false); } try { std::tuple t = async_op_ec_2(false, yield); ASIO_CHECK(false); (void)t; } catch (asio::system_error& e) { ASIO_CHECK(e.code() == asio::error::operation_aborted); } catch (...) { ASIO_CHECK(false); } try { int i; double d; std::tie(i, d) = async_op_ex_2(true, yield); ASIO_CHECK(i == 42); ASIO_CHECK(d == 2.0); } catch (...) { ASIO_CHECK(false); } try { std::tuple t = async_op_ex_2(false, yield); ASIO_CHECK(false); (void)t; } catch (std::exception& e) { ASIO_CHECK(e.what() == std::string("blah")); } catch (...) { ASIO_CHECK(false); } return 345; } int coroutine_using_async_ops_3(asio::yield_context yield) { using namespace archetypes; try { int i; double d; char c; std::tie(i, d, c) = async_op_3(yield); ASIO_CHECK(i == 42); ASIO_CHECK(d == 2.0); ASIO_CHECK(c == 'a'); } catch (...) { ASIO_CHECK(false); } try { int i; double d; char c; std::tie(i, d, c) = async_op_ec_3(true, yield); ASIO_CHECK(i == 42); ASIO_CHECK(d == 2.0); ASIO_CHECK(c == 'a'); } catch (...) { ASIO_CHECK(false); } try { std::tuple t = async_op_ec_3(false, yield); ASIO_CHECK(false); (void)t; } catch (asio::system_error& e) { ASIO_CHECK(e.code() == asio::error::operation_aborted); } catch (...) { ASIO_CHECK(false); } try { int i; double d; char c; std::tie(i, d, c) = async_op_ex_3(true, yield); ASIO_CHECK(i == 42); ASIO_CHECK(d == 2.0); ASIO_CHECK(c == 'a'); } catch (...) { ASIO_CHECK(false); } try { std::tuple t = async_op_ex_3(false, yield); ASIO_CHECK(false); (void)t; } catch (std::exception& e) { ASIO_CHECK(e.what() == std::string("blah")); } catch (...) { ASIO_CHECK(false); } return 456; } void test_spawn_async_ops() { asio::io_context ctx; bool called = false; asio::spawn(ctx, coroutine_using_async_ops_0, [&](std::exception_ptr, int i) { ASIO_CHECK(i == 123); called = true; }); ASIO_CHECK(!called); ctx.run(); ASIO_CHECK(called); called = false; asio::spawn(ctx, coroutine_using_async_ops_1, [&](std::exception_ptr, int i) { ASIO_CHECK(i == 234); called = true; }); ASIO_CHECK(!called); ctx.restart(); ctx.run(); ASIO_CHECK(called); called = false; asio::spawn(ctx, coroutine_using_async_ops_2, [&](std::exception_ptr, int i) { ASIO_CHECK(i == 345); called = true; }); ASIO_CHECK(!called); ctx.restart(); ctx.run(); ASIO_CHECK(called); called = false; asio::spawn(ctx, coroutine_using_async_ops_3, [&](std::exception_ptr, int i) { ASIO_CHECK(i == 456); called = true; }); ASIO_CHECK(!called); ctx.restart(); ctx.run(); ASIO_CHECK(called); } ASIO_TEST_SUITE ( "spawn", ASIO_TEST_CASE(test_spawn_with_any_completion_handler) ASIO_TEST_CASE(test_spawn_deferred) ASIO_TEST_CASE(test_spawn_cancel) ASIO_TEST_CASE(test_spawn_exception) ASIO_TEST_CASE(test_spawn_return_move_only) ASIO_TEST_CASE(test_spawn_async_ops) ) #else // defined(ASIO_HAS_BOOST_CONTEXT_FIBER) ASIO_TEST_SUITE ( "spawn", ASIO_TEST_CASE(null_test) ) #endif // defined(ASIO_HAS_BOOST_CONTEXT_FIBER)