重叠的Boost协程的异常安全性

时间:2019-05-15 18:19:34

标签: c++ exception boost boost-asio

以下代码在同一线程上产生2个作为Asio处理程序的函数(在后台使用Boost.Coroutine)。 每个函数都以这样的顺序抛出和捕获异常,以使由于协程上下文切换而导致catch块中的代码重叠,如下所示:

fn1 throw
fn1 start catch
fn2 throw
fn1 end catch
fn2 end catch

事实证明(根据下面的日志),当fn1的catch结束时,fn2的异常对象被破坏,而当fn2的catch结束时,fn1的异常对象被破坏。 有效地,这意味着catch块在被其他协程中断后不能再访问其异常对象。 这是预期的行为还是编译器/ Boost中的错误? 在这样的catch块中使用上下文切换是否完全安全?

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

0 个答案:

没有答案