Wondering why I couldn't just capture the reference of asio::handler_type for the coroutine

爷,独闯天下 提交于 2019-12-12 03:39:54

问题


I'm playing with the asio using coroutine, and would like to test how an async function can be called. I have the following code:

void async_foo(boost::asio::io_service& io_service, boost::asio::yield_context& yield)
{
    using handler_type = boost::asio::handler_type<decltype(yield), void()>::type;

    handler_type handler(std::forward<handler_type>(yield));
    boost::asio::async_result<decltype(handler)> result(handler);

    auto timer(std::make_shared<boost::asio::deadline_timer>(io_service, boost::posix_time::seconds(1)));
    // the program crashes if I use &handler here
    timer->async_wait([&handler](const boost::system::error_code) {
            std::cout << "enter" << std::endl;
            handler();
            std::cout << "done" << std::endl;
        });
    result.get();

    std::cout << "function finished" << std::endl;

    return;
}

int main()
{
    boost::asio::io_service io_service;

    boost::asio::spawn(io_service, [&io_service](boost::asio::yield_context yield) {
            std::cout << "hello" << std::endl;
            async_foo(io_service, yield);
            std::cout << "world" << std::endl;
        });
    io_service.run();
    return 0;
}

It's weird that if I put &handler in the capture list the execution flow will be messed up and then it hits segmentation fault. But it runs without any problem if I use "handler" instead (then I need a copy in the lambda of course).

Searched around and couldn't find anything related. Thanks in advance for any help.


回答1:


As Tanner very nicely explains here:

  • While spawn() adds work to the io_service (a handler that will start and jump to the coroutine), the coroutine itself is not work. To prevent the io_service event loop from ending while a coroutine is outstanding, it may be necessary to add work to the io_service before yielding.

If you add an extra trace line after the run():

io_service.run();
std::cout << "BYE" << std::endl;

Then, run a few times until you're lucky enough to not get the SEGV early, you can see output like the following:

hello
enter
done
BYE

Indeed, the io_service returns, destructing pending operations/handlers, which also destroys the stack context of the coro¹. Unwinding that stack destructs the (local) variables that live on that stack:

  • handler
  • result
  • timer

And since timer is not captured in the completion handler for async_wait, the timer is simply canceled, but still tries to invoke the completion handler which now refers to a now-defunct stack variable handler.

Copying handler apparently² does keep the coro alive.


To answer the core question "and would like to test how an async function can be called" I'd suggest the simpler idiom:

void async_foo(boost::asio::io_service& io_service, boost::asio::yield_context& yield)
{
    boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(1));
    boost::system::error_code ec;

    timer.async_wait(yield[ec]);

See it Live On Coliru


¹ -fsanitize=address confirms this ² I know it contains a weak_ptr to the coroutine context, so perhaps Asio internally locks that into a shared_ptr, not too sure about those implementation details myself



来源:https://stackoverflow.com/questions/43403896/wondering-why-i-couldnt-just-capture-the-reference-of-asiohandler-type-for-th

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!