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
ctx
instance. Suppose you have two instances ofexecution_context
:in [1] we are calling move assignment operator which "steals" resources of
src
and puts them intodest
. Ifexecution_context
has a pointer, this pointer after moving will be 0 insrc
instance, 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,
foo
and body of lambda passed into source constructor. Whensource()
is called the context is switched andfoo
is resumed andlambda
is executed. In this lambda context offoo
is destroyed because it is just moved intoCore
instance. So how do you want to resumefoo
execution? 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
done
method if it didn't throw exception: Look at this:c
is local inside lambda.done
returns reference to data member ofCore
which is destroyed when lambda ends. So you have dangling reference.Fix: you can just store reference to context inside
Core
. Then original context offoo
will be safe.