Boost logo

Boost Users :

Subject: Re: [Boost-users] [context] Crash in case of exception from a context
From: Tamas.Ruszkai_at_[hidden]
Date: 2015-03-16 04:08:58


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_at_[hidden]>
To: boost-users <boost-users_at_[hidden]>,
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_at_[hidden]>

2015-03-14 6:37 GMT+01:00 Nathaniel Fries <nfries88_at_[hidden]>:
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_at_[hidden]
http://lists.boost.org/mailman/listinfo.cgi/boost-users



Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net