2

I have my own thread class that is intended to help safely manage exceptions. It looks like this: (skipped other constructors and mutexes for simplicity)

class ExceptThread
    : public std::thread {
public:
    template<typename Func, typename... Args>
    ExceptThread(Func&& f, Args&&... args)
        : std::thread([] (Args&&... args) {
        try {
            return f(args...);
        } catch(...) {
            exc = std::current_exception();
        }
    }, args...) { }
    // skipped other constructors etc.
    //...    
    void check() { 
        if(exc) { 
            std::exception_ptr tmp = exc; 
            exc = nullptr; 
            std::rethrow_exception(tmp); 
        } 
    }

private:
    std::exception_ptr exc;
};

This class means to be used like:

ExceptThread et([] { std::this_thread::sleep_for(5s); throw std::runtime_error("Ugly exception"); });

try {
    while(/*...*/) {
        // main loop
        et.check();
    }
} catch(std::exception& e) {
    // do sth
}

Problem:

When thread throws exception it gets catched in catch(...) and saved to exc, everything is fine. But when execution goes further std::terminate is called just like exception wasn't caught. I also tried to pause the child thread (e.g Sleep(INFINITE)) after catching an exception but std::terminate() gets called while detaching the thread in std::thread::~thread() during stack unwinding in main thread. How can I prevent system of doing this?

Platform: MSVC

Similar: How can I propagate exceptions between threads?

mfkw1
  • 920
  • 6
  • 15
  • When a thread exits normally; it doesn't call std::terminate. This means that your exception handling is probably throwing... you probably want to investigate that in your debugger. – UKMonkey Mar 05 '18 at 20:43
  • You have a **data race** in your code, since you modify the `exc` member variable from new created thread and you read it from within the main thread via `check` member function. – Daniel Langr Mar 06 '18 at 07:48
  • I skipped synchronization in posted sample to keep it simple – mfkw1 Mar 06 '18 at 08:09

1 Answers1

1

You have to explicitly join a thread before destructing it, this is to help to prevent potential deadlocks/crashes when you forget to interrupt a thread before destroying it (in every implementation I've used this is stated in the std::terminate message).

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • Thanks, that was it. I'd never think you have to join thread that has already finished – mfkw1 Mar 05 '18 at 20:50
  • yep, caught me out too, I think boost::thread didn't have this requirement and porting from boost to std broke our code – Alan Birtles Mar 05 '18 at 20:52
  • @kubawal You don't have to `join` it, the other option is to `detach` it. You wrote in you answer that you _detached_ created threads, which was quite misleading, since you likely did not mean to call `detach`. – Daniel Langr Mar 06 '18 at 07:55
  • @Daniel Detaching it without getting the thread joined resulted in the same behaviour – mfkw1 Mar 06 '18 at 08:05
  • 1
    @DanielLangr detaching in this instance could be dangerous as if the thread is still running it will crash when it tries to access the deleted exc member. The requirement to join threads before destruction is there to protect you and shouldn't be worked around. I assume in most implementations joining a thread that is already complete is a cheap operation. – Alan Birtles Mar 06 '18 at 09:00