以下代码在同一线程上产生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