Home > Enterprise >  Boost asio steady_timer work on different strand than intended
Boost asio steady_timer work on different strand than intended

Time:08-27

I am trying to use asio :: steady_timer in asio coroutine (using asio :: awaitable). steady_timer should work on a different strand (executor1) than spawned coroutine strand (executor), but asio handler tracking support shows that otherwise I clearly give the timer its strand it working on asio Coroutine strand, any thoughts why?

Code:

#include <iostream>
#include <optional>

#define ASIO_ENABLE_HANDLER_TRACKING
#include <asio.hpp>

#define HANDLER_LOCATION BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, __func__))

asio::awaitable<void> start(asio::strand<asio::io_context::executor_type>& ex)
{
    asio::steady_timer timer{ex};

    timer.expires_after(std::chrono::milliseconds{500});
    auto timer_awaitable = std::optional<asio::awaitable<void>>();
    {
        ASIO_HANDLER_LOCATION((__FILE__, __LINE__, "my_connection_impl::connect_and_send" ));
        //timer_awaitable.emplace(timer.async_wait(asio::bind_executor(ex, asio::use_awaitable)));  
        timer_awaitable.emplace(timer.async_wait(asio::use_awaitable));               
    }
    co_await std::move(*timer_awaitable);
    std::cerr<<"timer async_wait \n";

    co_return;
}

int main()
{
    asio::io_context io_context;
    asio::strand<asio::io_context::executor_type> executor{io_context.get_executor()};
    asio::strand<asio::io_context::executor_type> executor1{io_context.get_executor()};

    asio::co_spawn(executor, start(executor1), asio::detached);

    io_context.run();
    std::cerr<<"end \n";

    return 0;
}

Output:

@asio|1661509595.252484|0^1|in 'co_spawn_entry_point' (.../asio_test/build/_deps/boostasio-src/asio:153)
@asio|1661509595.252484|0*1|[email protected]
@asio|1661509595.252657|0^2|in 'co_spawn_entry_point' (.../asio_test/build/_deps/boostasio-src/asio:153)
@asio|1661509595.252657|0*2|[email protected]
@asio|1661509595.252684|>2|
@asio|1661509595.252691|>1|
@asio|1661509595.252779|1*3|[email protected]_wait
@asio|1661509595.252825|<1|
@asio|1661509595.252835|<2|
@asio|1661509595.753514|>3|ec=system:0
@asio|1661509595.753577|3*4|[email protected]
@asio|1661509595.753589|>4|
timer async_wait 
@asio|1661509595.753698|4|[email protected]
@asio|1661509595.753723|<4|
@asio|1661509595.753731|<3|
end 

One way to fix that is to use: timer_awaitable.emplace(timer.async_wait(asio::bind_executor(ex, asio::use_awaitable))); instead of: timer_awaitable.emplace(timer.async_wait(asio::use_awaitable));

But is this mean that when co_await resumes this coroutine from now it will work on different strand?

CodePudding user response:

You pass the executor by reference. This is atypical. Executors are light-weight and cheaply copyable.

In your case this happens to be ok, because the lifetime of executor1 exceeds the coroutine, and it isn't being modified on another thread, but in general, avoid reference arguments to coros (see e.g Boost asio C 20 Coroutines: co_spawn a coroutine with a by-reference parameter unexpected result).

Other than it seems to be only a matter of expectations.

The executor that is bound into an IO object (in your case, timer) is used by default(!) for unbound completion handlers. This is e.g.:

asio::steady_timer timer {ex};
timer.async_await([](error_code) { });

Here the completion handler is unbound, and will default to the executor you expect (ex). Handlers can have associated executors in a number of ways, e.g. the explicit:

asio::steady_timer timer {ex};
timer.async_await(asio::bind_executor(my_ex, [](error_code) { });

Here the completion will always happen on my_ex regardless of the timer's executor.

In the case of coroutines, the executor associated with the completion token is implicitly co_await asio::this_coro::executor. If you want to forcibly resume the current coro on a different executor, the cleanest way would be to co_spawn to it.

You can also override the executor manually, like I show in this answer: asio How to change the executor inside an awaitable?:

    timer_awaitable = timer.async_wait(bind_executor(ex, asio::use_awaitable));               

But is this mean that when co_await resumes this coroutine from now it will work on different strand?

Yes.

  • Related