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
}
}
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
.