Следующий код порождает 2 функции как обработчики Asio (которые используют Boost.Coroutine под капотом) в том же потоке.
Каждая функция генерирует и перехватывает исключение в таком порядке, чтобы код в блокировках перехвата перекрывался из-за переключения контекста сопрограммы следующим образом:
fn1 throw
fn1 start catch
fn2 throw
fn1 end catch
fn2 end catch
И как выясняется (согласно журналу ниже), когда улов fn1 заканчивается, объект исключения из fn2 уничтожается, а когда улов fn2 заканчивается, объект исключения из fn1 уничтожается.
Фактически это означает, что блок catch не должен обращаться к своему объекту исключения после того, как он был прерван другими сопрограммами.
Это ожидаемое поведение или ошибка в компиляторе / Boost?
Безопасно ли вообще использовать переключение контекста в таких блоках перехвата?
GCC 4.8.5, 4.9.2, 8.2.0, Clang 5.0.0, Boost 1.55, 1.66, Linux, x86_64
Тестовый код:
#define BOOST_COROUTINES_NO_DEPRECATION_WARNING
#define BOOST_COROUTINE_NO_DEPRECATION_WARNING
#include <iostream>
#include <string>
#include <boost/asio/spawn.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/steady_timer.hpp>
namespace ba = boost::asio;
ba::io_service io_service;
#define LOG_TRACE(x) do { std::cout << x << std::endl; } while (0)
class MyEx: public std::runtime_error {
public:
MyEx(std::string msg)
: std::runtime_error(msg)
{
LOG_TRACE("MyEx " << msg << " addr=" << (void*)this);
}
~MyEx()
{
LOG_TRACE("~MyEx " << what() << " addr=" << (void*)this);
}
};
void throw_ex(size_t n)
{
throw MyEx("exception " + std::to_string(n));
}
void sleep_ms(size_t ms, ba::yield_context yield)
{
ba::steady_timer timer(io_service);
timer.expires_from_now(std::chrono::milliseconds(ms));
timer.async_wait(yield);
}
void* curr_ex()
{
auto ep = std::current_exception();
return *reinterpret_cast<void**>(&ep);
}
void fn1(ba::yield_context yield)
{
try {
sleep_ms(100, yield);
throw_ex(1);
}
catch (const MyEx& e) {
LOG_TRACE("1: catch start " << e.what() << " curr_ex=" << curr_ex());
sleep_ms(300, yield);
LOG_TRACE("1: catch end, curr_ex=" << curr_ex());
}
}
void fn2(ba::yield_context yield)
{
try {
sleep_ms(200, yield);
throw_ex(2);
}
catch (const MyEx& e) {
LOG_TRACE("2: catch start " << e.what() << " curr_ex=" << curr_ex());
sleep_ms(300, yield);
LOG_TRACE("2: catch end, curr_ex=" << curr_ex());
}
}
int main ()
{
ba::spawn(io_service, [](ba::yield_context yield){ fn1(yield); });
ba::spawn(io_service, [](ba::yield_context yield){ fn2(yield); });
io_service.run();
return 0;
}
Выход:
MyEx exception 1 addr=0x21fafe0
1: catch start exception 1 curr_ex=0x21fafe0
MyEx exception 2 addr=0x21fb080
2: catch start exception 2 curr_ex=0x21fb080
1: catch end, curr_ex=0x21fb080
~MyEx exception 2 addr=0x21fb080
2: catch end, curr_ex=0x21fafe0
~MyEx exception 1 addr=0x21fafe0