I have copied the essence from the unit
test and put it into my Visual Studio (VS2013 Version 12.31101.00 Update4)
environment, but still crashes. The project is a natvie C++ project (not
mixed mode .NET) and in "Configuration Properties/C/C++/Code Generation/Enable
C++ Exceptions" is set to "Yes(/EHsc)". However there is
another option : "Yes with SEH Exceptions (/EHa)", but does not
seem to fix the issue.
Meanwhile I have implemented a workaround
(see below): for each context I create a thread. As soon as this new thread
is started I switch it to another context (created by boost::context::make_fcontext)
and block it there on a condition variable. Then I use the original stack
of the thread. This "hijacked" stack can cope with exceptions,
just as I would expect. When I want to release the context then I just
simply release the blocked thread and switch it back to its original context.
This way I can make sure that the stack of the thread is also correctly
unwound.
#include
"stdafx.h"
#include
"boost/context/all.hpp"
#include
<boost/assert.hpp>
#include
"CppUnitTest.h"
#include
<iostream>
using
namespace
Microsoft::VisualStudio::CppUnitTestFramework;
template<
std::size_t
Max, std::size_t
Default, std::size_t
Min >
class
simple_stack_allocator
{
public:
static
std::size_t
maximum_stacksize()
{
return
Max;
}
static
std::size_t
default_stacksize()
{
return
Default;
}
static
std::size_t
minimum_stacksize()
{
return
Min;
}
void
* allocate(std::size_t
size)
const
{
BOOST_ASSERT(minimum_stacksize()
<= size);
BOOST_ASSERT(maximum_stacksize()
>= size);
void
* limit = std::malloc(size);
if
(!limit) throw
std::bad_alloc();
return
static_cast<
char
* >(limit)+size;
}
void
deallocate(void
* vp,
std::size_t
size)
const
{
BOOST_ASSERT(vp);
BOOST_ASSERT(minimum_stacksize()
<= size);
BOOST_ASSERT(maximum_stacksize()
>= size);
void
* limit = static_cast<
char
* >(vp)-size;
std::free(limit);
}
};
void
fException()
{
std::cout
<< "I am still ok"
<< std::endl; // Executes
well
throw
std::runtime_error("crash!");
// Execution crashes at this
point
}
boost::context::fcontext_t
fcm, fc1;
void
f1(intptr_t)
{
try
{
std::cout << "Greetings
from context f1" << std::endl;
//
Executes well
fException();
std::cout << "Never
reach this" << std::endl;
//
Break point here does not hit
}
catch
(std::runtime_error
const&
e)
{
std::cout << "runtime_error
exception caught" << std::endl;
//
Break point here does not hit
}
catch
(...)
{
std::cout << "General
exception caught" << std::endl;
//
Break point here does not hit
}
boost::context::jump_fcontext(&fc1,
fcm, 0); //
Break point here does not hit
}
namespace
ctx = boost::context;
typedef
simple_stack_allocator<
8 * 1024
* 1024, // 8MB
64 * 1024,
// 64kB
8 * 1024
// 8kB
> stack_allocator;
void
test()
{
stack_allocator
alloc;
void
* stackPointer = alloc.allocate(stack_allocator::default_stacksize());
fc1 = boost::context::make_fcontext(stackPointer,
stack_allocator::minimum_stacksize(),
f1);
std::cout
<< "Greetings from
main thread stack" << std::endl;
boost::context::jump_fcontext(&fcm,
fc1, 0);
}
TEST_CLASS(BoostContextTest)
{
public:
TEST_METHOD(ExceptionContextTest)
{
test();
}
};
// ======================================================================================================================================================================================
// This class starts a thread and
switches that thread to a context where it is parking.
// Meanwhile we can use the original
stack of the thread. This way the stack of the thread temporarily hijacked
// The reason why the stack of
a real thread is needed for the context is the exception handling.
// The context that boost::context
makes does not work with exceptions
class
HijackedThread
{
public:
HijackedThread(std::size_t
stackSize,
boost::context::fcontext_t&
contextHandle)
: _threadParkingContextStack(new
ContextStack(64
* 1024))
, _threadContextHandle(contextHandle)
, _threadParkingContextHandle(nullptr)
, _threadInParkingContext(false)
, _releaseParkingThread(false)
{
CreateAndParkThread(stackSize);
WaitForThreadToPark();
}
~HijackedThread()
{
ReleaseParkingThread();
}
bool
IsMethodSet()
{
auto
isMethodSet = (bool)_method;
return
isMethodSet;
}
void
SetMethod(std::function<void()>
method)
{
_method = method;
}
private:
void
CreateAndParkThread(std::size_t
stackSize)
{
_threadParkingContextHandle = boost::context::make_fcontext(_threadParkingContextStack->Bottom(),
_threadParkingContextStack->Size(), ParkingContextStartup);
boost::thread::attributes
attrs;
attrs.set_stack_size(stackSize);
_thread = new
boost::thread(attrs,
[=]
{
boost::context::jump_fcontext(&_threadContextHandle,
_threadParkingContextHandle, reinterpret_cast<intptr_t>(this));
if
(_threadInParkingContext)
{
_method();
}
});
}
static
void
ParkingContextStartup(intptr_t
instance)
{
HijackedThread*
hijackedThread = reinterpret_cast<HijackedThread*>(instance);
hijackedThread->ParkThread();
// Wind
up the context stack from the thread
boost::context::jump_fcontext(&(hijackedThread->_threadParkingContextHandle),
hijackedThread->_threadContextHandle, 0);
}
void
ParkThread()
{
boost::mutex::scoped_lock
lock(_mutex);
_threadInParkingContext = true;
_monitor.notify_all();
while
(!_releaseParkingThread)
{
_monitor.wait(lock);
}
_threadInParkingContext = false;
}
void
WaitForThreadToPark()
{
boost::mutex::scoped_lock
lock(_mutex);
while
(!_threadInParkingContext)
{
_monitor.wait(lock);
}
}
void
UnParkThread()
{
boost::mutex::scoped_lock
lock(_mutex);
_releaseParkingThread = true;
_monitor.notify_all();
}
void
ReleaseParkingThread()
{
UnParkThread();
_thread->join();
}
private:
boost::mutex
_mutex;
boost::condition_variable
_monitor;
boost::thread*
_thread;
boost::context::fcontext_t&
_threadContextHandle;
boost::context::fcontext_t
_threadParkingContextHandle;
std::unique_ptr<ContextStack>
_threadParkingContextStack;
std::function<void()>
_method;
bool
_threadInParkingContext;
bool
_releaseParkingThread;
};
// FIXME: Only with shared_ptr?
Why not with move constructor?
class
Context
: public
std::enable_shared_from_this<Context>
{
public:
~Context()
{
UnwindStack();
}
static
std::shared_ptr<Context>
CreateNew(std::size_t
stackSize)
{
auto
newContext = std::shared_ptr<Context>(new
Context(stackSize));
return
newContext;
}
static
std::shared_ptr<Context>
CreateFromCurrentStack()
{
auto
newContext = std::shared_ptr<Context>(new
Context());
return
newContext;
}
void
SetName(const
std::string&
name)
{
_name = name;
}
void
SetMethod(std::function<void(Context*)>
method)
{
if
(!_hijackedThread)
{
throw
ContextChainException("Cannot
set a Startup method for a thread");
}
if
(_hijackedThread->IsMethodSet())
{
throw
ContextChainException("Startup
method can be set only once");
}
_hijackedThread->SetMethod([=]
{
method(this);
_methodFinished
= true;
//
When the method is done, then yield back
Return();
});
}
void
SwitchSafe(const
std::shared_ptr<Context>&
newContext)
{
if
(_unwinding)
return;
if
(_hijackedThread && !(_hijackedThread->IsMethodSet()))
{
throw
ContextChainException("No
startup method defined");
}
// The
destination context shall be not involved in any switching chain
// otherwise
the way back to the thread stack will be messed up
if
(newContext->_returnContext)
{
throw
ContextChainException("Recursion
in context is not allowed");
}
// In
the newContext the method must be not finished, otherwise it makes no
// sense
to switch to it
if
(newContext->_methodFinished)
{
throw
ContextFinishedException("Context
method has already finished");
}
Switch(newContext);
}
void
Switch(const
std::shared_ptr<Context>&
newContext)
{
if
(_unwinding)
return;
newContext->_returnContext
= shared_from_this();
boost::context::jump_fcontext(&_contextHandle,
newContext->_contextHandle,
0);
}
void
ReturnSafe()
{
if
(_unwinding)
return;
// There
must be a place to return
if
(!_returnContext)
{
throw
ContextChainException("No
place to return");
}
Return();
}
void
Return()
{
if
(_unwinding)
return;
auto
returnContextHandle = _returnContext->_contextHandle;
_returnContext.reset();
boost::context::jump_fcontext(&_contextHandle,
returnContextHandle, 0);
}
protected:
Context()
: _hijackedThread(nullptr)
, _contextHandle(nullptr)
, _returnContext(nullptr)
, _methodFinished(false)
, _unwinding(false)
{
}
Context(std::size_t
stackSize)
: _hijackedThread(nullptr)
, _contextHandle(nullptr)
, _returnContext(nullptr)
, _methodFinished(false)
, _unwinding(false)
{
_hijackedThread.reset(new
HijackedThread(stackSize,
_contextHandle));
}
void
UnwindStack()
{
_unwinding = true;
// Let
the hijacked thread unwind the context
_hijackedThread.reset();
}
protected:
std::unique_ptr<HijackedThread>
_hijackedThread;
boost::context::fcontext_t
_contextHandle;
std::shared_ptr<Context>
_returnContext;
bool
_methodFinished;
bool
_unwinding;
std::string
_name;
};
From:
Oliver Kowalke <oliver.kowalke@gmail.com>
To:
boost-users <boost-users@lists.boost.org>,
Date:
14.03.2015 10:12
Subject:
Re: [Boost-users]
[context] Crash in case of exception from a context
Sent by:
"Boost-users"
<boost-users-bounces@lists.boost.org>
2015-03-14 6:37 GMT+01:00 Nathaniel Fries <nfries88@gmail.com>:
The solution is actually pretty simple - push the current
SEH list's head onto the stack before the context switch, and restore it
when resuming execution, with each context initializing its own list with
UnhandledExceptionFilter during either construction or first run (like
a new thread would). But maybe such a fix is out of scope for the context
library?l
boost.context already installs SEH structures on the stack
- the unit-tests of boost.context already check throwing/catching exceptions
->
http://www.boost.org/development/tests/master/developer/context.html
http://www.boost.org/development/tests/develop/developer/context.html_______________________________________________
Boost-users mailing list
Boost-users@lists.boost.org
http://lists.boost.org/mailman/listinfo.cgi/boost-users