boost::asio:: yield контекст: неожиданное принудительное исключение размотки

Я пытаюсь написать свою пользовательскую асинхронную функцию для boost:: asio, как описано здесь.

однако я получаю boost:: coroutines:: detail:: forced_unwind исключение на линии с результат.get

#include <boost/chrono.hpp>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>

#include <iostream>

namespace asio = ::boost::asio;


template <typename Timer, typename Token>
auto my_timer (Timer& timer, Token&& token)
{
  typename asio::handler_type<Token,
      void (::boost::system::error_code const)>::type
      handler (std::forward<Token> (token));

  asio::async_result<decltype (handler)> result (handler);

  timer.async_wait (handler);
  return result.get (); // Got forced_unwind exception here.
}

int main ()
{
  asio::io_service io;
  asio::steady_timer timer (io, ::boost::chrono::seconds (1));

  asio::spawn (io, [&] (asio::yield_context yield)
      {
      try {
        std::cout << "my_timer entern";
        my_timer (timer, yield);
        std::cout << "my_timer returnsn";
      }
      catch (const boost::coroutines::detail::forced_unwind& e)
      { 
        std::cout << "boost::coroutines::detail::forced_unwindn"; 
      }
    }
  );

  io.run ();
}

тот же код Coliru

обновление:

поведение существует на:

Darwin 14.0.0 (MacOS 10.10) 
clang version 3.6.0 (trunk 216817) and gcc version 4.9.1 (MacPorts gcc49 4.9.1_1) 
boost 1.57

и

Red Hat 6.5
gcc version 4.7.2 20121015 (Red Hat 4.7.2-5) (GCC)
boost 1.57 and 1.56
(the example code was trivially modified because gcc 4.7 does not support c++14 mode)

2 ответов


короче говоря, вам нужно создать экземпляр обработчика, например, разместив его в io_service, прежде чем пытаться получить async_result для того, чтобы сохранить корутину в живых.


импульс.Asio предотвращает невозобновляемую корутину от бесконечного приостановления, уничтожая корутин, в результате чего стек корутина разматывается. Объект coroutine будет бросать boost::coroutines::detail::forced_unwind во время его разрушение, вызывающие приостановлено стека. Asio выполняет это by:

  • на yield_context CompletionToken поддерживает weak_ptr в сопрограммы.
  • когда специализированной handler_type::type обработчик построен, он получает shared_ptr для coroutine через CompletionToken в weak_ptr. Когда обработчик передается как обработчик завершения асинхронных операций, обработчик и его shared_ptr копируются. Когда вызывается обработчик, он возобновляет работу сопрограммы.
  • при вызове async_result::get(), специализация сбросит coroutine shared_ptr принадлежит обработчику, который был передан async_result во время конструкции, и после этого дайте корутину.

вот попытка проиллюстрировать выполнение кода. Пути в | укажите активный стек,: указывает на подвешенный стек, а стрелки используются для указания передачи управления:

boost::asio::io_service io_service;
boost::asio::spawn(io_service, &my_timer);
`-- dispatch a coroutine creator
    into the io_service.
io_service.run();
|-- invoke the coroutine entry
|   handler.
|   |-- create coroutine
|   |   (count: 1)
|   |-- start coroutine        ----> my_timer()
:   :                                |-- create handler1 (count: 2)
:   :                                |-- create asnyc_result1(handler1)
:   :                                |-- timer.async_wait(handler)
:   :                                |   |-- create handler2 (count: 3)
:   :                                |   |-- create async_result2(handler2)
:   :                                |   |-- create operation and copy
:   :                                |   |   handler3 (count: 4)
:   :                                |   `-- async_result2.get()
:   :                                |       |-- handler2.reset() (count: 3)
|   `-- return                 <---- |       `-- yield
|       `-- ~entry handler           :
|           (count: 2)               :
|-- io_service has work (the         :
|   async_wait operation)            :
|   ...async wait completes...       :
|-- invoke handler3                  :
|   |-- resume                 ----> |-- async_result1.get()
:   :                                |   |--  handler1.reset() (count: 1)
|   `-- return                 <---- |   `-- yield
|       `-- ~handler3                :       :
|           |  (count: 0)            :       :
|           `-- ~coroutine()   ----> |       `-- throw forced_unwind

исправить эту проблему, handler необходимо скопировать и вызывается через asio_handler_invoke() когда пришло время возобновить работу корутины. Например, следующее сообщение будет содержать обработчик завершения1 на io_service это вызывает копию handler:

timer.async_wait (handler);

timer.get_io_service().post(
  std::bind([](decltype(handler) handler)
  {
    boost::system::error_code error;
    // Handler must be invoked through asio_handler_invoke hooks
    // to properly synchronize with the coroutine's execution
    // context.
    using boost::asio::asio_handler_invoke;
    asio_handler_invoke(std::bind(handler, error), &handler);
  }, handler)
);
return result.get ();

как показала здесь, С этим дополнительным кодом, выход становится:

my_timer enter
my_timer returns

1. Код обработчика завершения, вероятно, может быть немного очищен, но как я отвечал как возобновить импульс.Асио stackful coroutine из другой нити, я заметил, что некоторые компиляторы выбирают неправильный asio_handler_invoke крюк.


это деталь реализации Boost Coroutine.

как описано здесь: исключения

Important важно

код, выполняемый функцией coroutine, не должен препятствовать распространению detail::forced_unwind exception. Поглощение этого исключения приведет к сбою размотки стека. Таким образом, любой код, который ловит все исключения, должен re-throw любой на рассмотрении detail::forced_unwind исключения.

так, ты явно требуются чтобы передать это исключение. Явно Закодируйте обработчик, например:

Жить На Coliru

try {
  std::cout << "my_timer enter\n";
  my_timer(timer, yield);
  std::cout << "my_timer returns\n";
}
catch (boost::coroutines::detail::forced_unwind const& e)
{ 
   throw; // required for Boost Coroutine!
}
catch (std::exception const& e)
{ 
   std::cout << "exception '" << e.what() << "'\n";
}

это конкретное исключение является детализацией реализации и должно

  • ожидается в контексте coroutine
  • не проглатывается, или семантика RAII будет нарушена, что приведет к утечкам ресурсов и, возможно, неопределенному поведению с вашим RAII тип.

справедливости ради, это делает небезопасным "наивно" использовать существующий (устаревший) код, который может не позволить себе эту гарантию. Я думаю, что это очень сильная причина для

  • руководящие принципы против неспецифических уловов, за исключением голой rethrow
  • централизованные стратегии исключений (например,использование функции Липпинкотта для обработчиков исключений)

    остерегайтесь, что последняя идея может быть прямо запрещено в Coroutines тоже:

    Important важно

    Не прыгайте изнутри блока catch и не повторяйте исключение в другом контексте выполнения.

    обновление: как @DeadMG только что прокомментировал эту статью, мы можем тривиально преобразовать функцию Липпинкотта в функцию обертывания, которая может удовлетворять требованиям для Coroutine при централизации обработки исключений.