提升上下文:异常传播的问题
boost context: problems with exception propagation
我正在使用 boost::context::execution_context
(版本 2)编写 C++ 11 库,我想将异常从 execution_context
传播到调用执行。
我想在客户端提供给我的库函数的 lambda 中处理异常;但是,我遇到了一个奇怪的问题,在某些情况下 boost::context 无法正确处理异常。
这按预期工作并且与一些 boost 非常相似 tests and examples:
TEST(execution_context, works) {
// Client callable
auto &&f = [](boost::context::execution_context<void> &&ctx) {
throw std::runtime_error("help!");
return std::move(ctx);
};
// Library code
std::exception_ptr exc{};
boost::context::execution_context<void> source(
[&exc, &f](boost::context::execution_context<void> &&ctx) {
try {
ctx = f(std::move(ctx));
} catch (boost::context::detail::forced_unwind const &) {
throw;
} catch (...) {
exc = std::current_exception();
}
return std::move(ctx);
});
try {
source = source();
if (exc) {
std::rethrow_exception(exc);
}
} catch (std::runtime_error const &) {
std::cout << "Runtime Error Caught" << std::endl;
}
}
输出:
[ RUN ] execution_context.works
Runtime Error Caught
[ OK ] execution_context.works (0 ms)
但以下更改不起作用:
我们添加一个 class 来包装 execution_context
:
class Core {
boost::context::execution_context<void> ctx_;
public:
explicit Core(boost::context::execution_context<void> &&ctx)
: ctx_{std::move(ctx)} {}
auto &&done() { return std::move(ctx_); }
};
现在我们进行与之前相同的测试,但使用定义的 class:
TEST(execution_context, fails) {
// Client callable
auto &&f = [](Core c) {
throw std::runtime_error("help!");
return c.done();
};
// Library code
std::exception_ptr exc{};
boost::context::execution_context<void> source(
[&exc, &f](boost::context::execution_context<void> &&ctx) {
try {
ctx = f(Core(std::move(ctx)));
} catch (boost::context::detail::forced_unwind const &) {
throw;
} catch (...) {
exc = std::current_exception();
}
return std::move(ctx);
});
try {
source = source();
if (exc) {
std::rethrow_exception(exc);
}
} catch (std::runtime_error const &) {
std::cout << "Runtime Error Caught" << std::endl;
}
}
输出:
[ RUN ] execution_context.fails
unknown file: Failure
Unknown C++ exception thrown in the test body.
generators.t.tsk: /home/plewis/dpkg/refroot/amd64/opt/include/boost/context/detail/exception.hpp:37: boost::context::detail::forced_unwind::~forced_unwind(): Assertion `caught' failed.
zsh: abort (core dumped) ./test.t.tsk
我注意到的唯一区别是 execution_context
包含在 class 中,这会导致异常处理不当。这没有意义。
环境
我正在使用 GTest。
编译器
> g++ --version
g++ (GCC) 5.3.1 20160406 (Red Hat 5.3.1-6)
...
系统
> uname -a
Linux myhostname 2.6.32-642.6.2.el6.x86_64 #1 SMP Mon Oct 24 10:22:33 EDT 2016 x86_64 x86_64 x86_64 GNU/Linux
提升
boost version 1.69.0 compiled for amd64
问题在于移动 ctx
实例。假设您有两个 execution_context
:
实例
execution_context src, dest;
// src and dest have some states
dest = std::move(src); // [1]
在 [1] 中,我们调用 src
的 "steals" 资源的移动赋值运算符,并将它们放入 dest
。如果execution_context
有一个指针,移动后这个指针在src
实例中就是0,所以这个对象是无用的,不应该被使用。对它的任何操作都可能引发一些不良行为。
简而言之,您的代码如下所示:
void foo ()
{
auto &&f = [](Core c) {};
boost::context::execution_context<void> source(
[&exc, &f](boost::context::execution_context<void> &&ctx) {
// many lines
return std::move(ctx);
});
source = source();
}
我们有两个上下文,foo
和传入源构造函数的 lambda 主体。当 source()
被调用时,上下文被切换并且 foo
被恢复并且 lambda
被执行。在此 lambda 上下文中,foo
被销毁,因为它刚刚移入 Core
实例。那么你想如何恢复foo
执行呢?你不能。
核心问题:
class Core {
boost::context::execution_context<void> ctx_;
public:
explicit Core(boost::context::execution_context<void> &&ctx)
: ctx_{std::move(ctx)} {} // [2]
auto &&done() { return std::move(ctx_); }
};
ctx_
是 non-reference 数据成员,所以在 [2] 中调用了 move 构造函数,它窃取了 ctx 的资源。
下一个问题是 done
方法,如果它没有抛出异常的话:
看看这个:
auto &&f = [](Core c)
{
// throw std::runtime_error("help!"); COMMENTED
return c.done();
};
c
在 lambda 内部是局部的。 done
returns 对 Core
的数据成员的引用,它在 lambda 结束时被销毁。所以你有悬空参考。
修复:您可以只在 Core
中存储对上下文的引用。那么 foo
的原始上下文将是安全的。
我正在使用 boost::context::execution_context
(版本 2)编写 C++ 11 库,我想将异常从 execution_context
传播到调用执行。
我想在客户端提供给我的库函数的 lambda 中处理异常;但是,我遇到了一个奇怪的问题,在某些情况下 boost::context 无法正确处理异常。
这按预期工作并且与一些 boost 非常相似 tests and examples:
TEST(execution_context, works) {
// Client callable
auto &&f = [](boost::context::execution_context<void> &&ctx) {
throw std::runtime_error("help!");
return std::move(ctx);
};
// Library code
std::exception_ptr exc{};
boost::context::execution_context<void> source(
[&exc, &f](boost::context::execution_context<void> &&ctx) {
try {
ctx = f(std::move(ctx));
} catch (boost::context::detail::forced_unwind const &) {
throw;
} catch (...) {
exc = std::current_exception();
}
return std::move(ctx);
});
try {
source = source();
if (exc) {
std::rethrow_exception(exc);
}
} catch (std::runtime_error const &) {
std::cout << "Runtime Error Caught" << std::endl;
}
}
输出:
[ RUN ] execution_context.works
Runtime Error Caught
[ OK ] execution_context.works (0 ms)
但以下更改不起作用:
我们添加一个 class 来包装 execution_context
:
class Core {
boost::context::execution_context<void> ctx_;
public:
explicit Core(boost::context::execution_context<void> &&ctx)
: ctx_{std::move(ctx)} {}
auto &&done() { return std::move(ctx_); }
};
现在我们进行与之前相同的测试,但使用定义的 class:
TEST(execution_context, fails) {
// Client callable
auto &&f = [](Core c) {
throw std::runtime_error("help!");
return c.done();
};
// Library code
std::exception_ptr exc{};
boost::context::execution_context<void> source(
[&exc, &f](boost::context::execution_context<void> &&ctx) {
try {
ctx = f(Core(std::move(ctx)));
} catch (boost::context::detail::forced_unwind const &) {
throw;
} catch (...) {
exc = std::current_exception();
}
return std::move(ctx);
});
try {
source = source();
if (exc) {
std::rethrow_exception(exc);
}
} catch (std::runtime_error const &) {
std::cout << "Runtime Error Caught" << std::endl;
}
}
输出:
[ RUN ] execution_context.fails
unknown file: Failure
Unknown C++ exception thrown in the test body.
generators.t.tsk: /home/plewis/dpkg/refroot/amd64/opt/include/boost/context/detail/exception.hpp:37: boost::context::detail::forced_unwind::~forced_unwind(): Assertion `caught' failed.
zsh: abort (core dumped) ./test.t.tsk
我注意到的唯一区别是 execution_context
包含在 class 中,这会导致异常处理不当。这没有意义。
环境
我正在使用 GTest。
编译器
> g++ --version
g++ (GCC) 5.3.1 20160406 (Red Hat 5.3.1-6)
...
系统
> uname -a
Linux myhostname 2.6.32-642.6.2.el6.x86_64 #1 SMP Mon Oct 24 10:22:33 EDT 2016 x86_64 x86_64 x86_64 GNU/Linux
提升
boost version 1.69.0 compiled for amd64
问题在于移动 ctx
实例。假设您有两个 execution_context
:
execution_context src, dest;
// src and dest have some states
dest = std::move(src); // [1]
在 [1] 中,我们调用 src
的 "steals" 资源的移动赋值运算符,并将它们放入 dest
。如果execution_context
有一个指针,移动后这个指针在src
实例中就是0,所以这个对象是无用的,不应该被使用。对它的任何操作都可能引发一些不良行为。
简而言之,您的代码如下所示:
void foo ()
{
auto &&f = [](Core c) {};
boost::context::execution_context<void> source(
[&exc, &f](boost::context::execution_context<void> &&ctx) {
// many lines
return std::move(ctx);
});
source = source();
}
我们有两个上下文,foo
和传入源构造函数的 lambda 主体。当 source()
被调用时,上下文被切换并且 foo
被恢复并且 lambda
被执行。在此 lambda 上下文中,foo
被销毁,因为它刚刚移入 Core
实例。那么你想如何恢复foo
执行呢?你不能。
核心问题:
class Core {
boost::context::execution_context<void> ctx_;
public:
explicit Core(boost::context::execution_context<void> &&ctx)
: ctx_{std::move(ctx)} {} // [2]
auto &&done() { return std::move(ctx_); }
};
ctx_
是 non-reference 数据成员,所以在 [2] 中调用了 move 构造函数,它窃取了 ctx 的资源。
下一个问题是 done
方法,如果它没有抛出异常的话:
看看这个:
auto &&f = [](Core c)
{
// throw std::runtime_error("help!"); COMMENTED
return c.done();
};
c
在 lambda 内部是局部的。 done
returns 对 Core
的数据成员的引用,它在 lambda 结束时被销毁。所以你有悬空参考。
修复:您可以只在 Core
中存储对上下文的引用。那么 foo
的原始上下文将是安全的。