Search code examples
c++stdthread

How do I pause exception propagation until a separate std::thread completes?


My code is split into one part running in the main thread, and another part in a separate thread. The main thread waits on thread.join() at the end.

Now, if the main thread throws, my program terminates immediately, even if I try to catch the exception. I traced it to std::~thread, which is unhappy if the thread is still running (joinable).

In such a case, I want the exception to wait until the thread quits naturally, and then continue to propagate. How do I do that? Do I need to wrap std::thread with some sort of joining_thread, whose d'tor would join?

#include <iostream>
#include <thread>

class foo
{
public:
    foo(const char* name) : m_name(name) {}
    ~foo() { std::cout << "~foo - " << m_name << "\n"; }
private:
    std::string m_name;
};

void doInThread()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "doInThread\n";
}

int doit()
{
    foo fooBefore("main before thread");   // d'tor is never called
    auto thread = std::thread(doInThread);
    foo fooAfter("main after thread");

    // Do stuff, which might throw
    throw std::exception("Something happened in main");

    thread.join();
}

int main()
{
    try
    {
        doit();
    }
    catch (std::exception ex)
    {
        std::cout << "Exception: " << ex.what() << "\n";  // Never happens - program terminates beforehand
    }
}

Solution

  • Your program is aborted by calling std::terminate function.

    When exception is thrown, stack unwinding happens, which means all local variables created before calling throw are deleted. When dtor of thread is called, and the thread is in joinable state, std::terminate is invoked according to thread reference.

    Use RAII to create object whose dtor will join your thread.

    template<class F>
    struct Cleaner {
        Cleaner(F in) : f(in) {}
        ~Cleaner() { f(); }
    
        F f;
    };
    
    template<class F>
    Cleaner<F> makeCleaner(F f) {
        return Cleaner<F>(f);
    }
    
    int doit()
    {
        foo fooBefore("main before thread");
        auto thread = std::thread(doInThread);
        auto cleaner = makeCleaner([&thread](){ thread.join(); });
    
        foo fooAfter("main after thread");
    
        throw std::runtime_error("Something happened in main");
    
        // without throw statement, dtor of cleaner instance would be called here
    }
    

    Now while unwinding stack, destructors are being called as follows: fooAfter, cleaner (where you wait until thread is completion) and fooBefore.