Исключительная безопасность для сопрограмм Boost с перекрытием - PullRequest
1 голос
/ 15 мая 2019

Следующий код порождает 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
...