I'm using boost::context::execution_context (version 2) to write a C++ 11 library and I want to propagate exceptions from an execution_context to the calling execution.
I'd like to handle exceptions inside of a lambda that a client gives to my library function; however, I have encountered a strange issue where the exception is not handled by boost::context correctly in some cases.
This works as expected and is very similar to some of boost's 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;
}
}
Output:
[ RUN ] execution_context.works
Runtime Error Caught
[ OK ] execution_context.works (0 ms)
But the following change does not work:
We add a class which wraps the 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_); }
};
Now we make the same test as before but using the defined 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;
}
}
Output:
[ 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
The only difference I notice is that the execution_context is contained in a class, and that causes the exception to be handled improperly. That makes no sense.
Environment
I'm using GTest.
Compiler
> g++ --version
g++ (GCC) 5.3.1 20160406 (Red Hat 5.3.1-6)
...
System
> 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
boost version 1.69.0 compiled for amd64
The issue is with moving
ctxinstance. Suppose you have two instances ofexecution_context:in [1] we are calling move assignment operator which "steals" resources of
srcand puts them intodest. Ifexecution_contexthas a pointer, this pointer after moving will be 0 insrcinstance, so this object is useless and should not be used. Any operation on it may invoke some undesired behaviour.In short version your code looks like:
we have two contexts,
fooand body of lambda passed into source constructor. Whensource()is called the context is switched andfoois resumed andlambdais executed. In this lambda context offoois destroyed because it is just moved intoCoreinstance. So how do you want to resumefooexecution? You cannot.Problem with Core:
ctx_is non-reference data member, so in [2] move constructor is called which steals ctx's resources.The next issue would be with
donemethod if it didn't throw exception: Look at this:cis local inside lambda.donereturns reference to data member ofCorewhich is destroyed when lambda ends. So you have dangling reference.Fix: you can just store reference to context inside
Core. Then original context offoowill be safe.