Opened 8 years ago

Closed 8 years ago

#10093 closed Bugs (wontfix)

Exceptions "teleport" between coroutines when switching inside catch

Reported by: snaury@… Owned by: olli
Milestone: To Be Determined Component: context
Version: Boost 1.54.0 Severity: Problem
Keywords: Cc:

Description

Sorry for using coroutines v1 API, but the actual API doesn't matter as much as the result and the reason for it. Consider the following program:

#define BOOST_COROUTINES_V1
#include <boost/coroutine/all.hpp>
#include <exception>
#include <iostream>

typedef boost::coroutines::coroutine<void()> coro_t;

void mycoro(coro_t::caller_type& caller)
{
    std::cout << "calling caller..." << std::endl;
    caller();
    try {
        try {
            throw std::runtime_error("mycoro exception");
        } catch(const std::exception& e) {
            std::cout << "calling caller in the catch block..." << std::endl;
            caller();
            std::cout << "rethrowing mycoro exception..." << std::endl;
            throw;
        }
    } catch (const std::exception& e) {
        std::cout << "mycoro caught: " << e.what() << std::endl;
    }
    std::cout << "exiting mycoro..." << std::endl;
}

int main(int argc, char** argv)
{
    coro_t callee(mycoro);
    try {
        try {
            throw std::runtime_error("main exception");
        } catch(const std::exception& e) {
            std::cout << "calling callee in the catch block..." << std::endl;
            callee();
            std::cout << "rethrowing main exception..." << std::endl;
            throw;
        }
    } catch (const std::exception& e) {
        std::cout << "main caught: " << e.what() << std::endl;
    }
    std::cout << "calling callee one last time..." << std::endl;
    callee();
    std::cout << "exiting main..." << std::endl;
    return 0;
}

When compiled with gcc it causes the following output:

calling caller...
calling callee in the catch block...
calling caller in the catch block...
rethrowing main exception...
main caught: mycoro exception
calling callee one last time...
rethrowing mycoro exception...
mycoro caught: main exception
exiting mycoro...
exiting main...

Which shows that boost coroutines are unsafe in the presence of current exceptions, since in that case exceptions "teleport" between coroutines in weird ways. The problem here is that caught exceptions are saved in per-thread globals according to ABI, see: mentorembedded.github.io/cxx-abi/abi-eh.html (struct cxa_eh_globals).

It contains two very important fields, pointer to which can be obtained with cxx_get_globals() or with cxx_get_globals_fast() and check for null result (though the latter is not as reliable). Whenever coroutines switch they should save and restore those fields to properly support exceptions and avoid current exception bleeding between coroutines.

This ABI is supported by both gcc and clang, though I'm not sure since which version.

Change History (5)

comment:1 by olli, 8 years ago

Component: coroutinecontext

boost.context provides the 'jump' facility

comment:2 by snaury@…, 8 years ago

In my opinion boost.context is too low level for this, for example boost.context doesn't know anything about exceptions (and boost.coroutine does), besides calling C++ ABIs from assembler would be too tedious, and there's just nothing platform-dependant about it (more compiler or libc++ dependant). At the same time boost.coroutine has its own abstraction, coroutine_context, where coroutine_context::jump would be easy to modify: declare and call __cxx_get_globals() (similar to the way boost.log already does) and save/restore the exception information.

in reply to:  2 comment:3 by olli, 8 years ago

Replying to snaury@…:

for example boost.context doesn't know anything about exceptions

boost.context handles Windows' structured exception handling

comment:4 by olli, 8 years ago

  • 32bit MSVC works
  • 64bit MSVC crashes the app - no info regarding to this issue found in the INet
  • compiler using C++ Itanium ABI are problematic because cxaget_globals() does call malloc() store/restore of __cxa_eh_globals::caughtExceptions and __cxa_eh_globals::uncaughtExceptions per context we might end up with memory leaks (== closing a context without cleaning up its list of the active exceptions).

=> maybe the best solution would be a hint in the docu. not to call context inside a catch-block

comment:5 by olli, 8 years ago

Resolution: wontfix
Status: newclosed

It is not easy to solve because the different compilers handle it in a different way. even worse for some compilers (for instance MSVC) it is not know where currently catched exceptions are stored.

I've added a note in the doc not to re-throw exceptions from a catch-clause inside a coroutine/context.

Last edited 8 years ago by olli (previous) (diff)
Note: See TracTickets for help on using tickets.