I'm not sure I understand this issue very well, so I've written a small example program that demonstrates it:
#include <iostream>
#include <csignal>
#include <mutex>
#include <condition_variable>
#include <thread>
class Application {
std::mutex cvMutex;
std::condition_variable cv;
std::thread t2;
bool ready = false;
// I know I'm accessing this without a lock, please ignore that
bool shuttingDown = false;
public:
void mainThread() {
auto lock = std::unique_lock<std::mutex>(this->cvMutex);
while (!this->shuttingDown) {
if (!this->ready) {
std::cout << "Main thread waiting.\n" << std::flush;
this->cv.wait(lock, [this] () {return this->ready;});
}
// Do the thing
this->ready = false;
std::cout << "Main thread notification recieved.\n" << std::flush;
}
};
void notifyMainThread() {
std::cout << "Notifying main thread.\n" << std::flush;
this->cvMutex.lock();
this->ready = true;
this->cv.notify_all();
this->cvMutex.unlock();
std::cout << "Notified.\n" << std::flush;
};
void threadTwo() {
while(!this->shuttingDown) {
// Wait some seconds, then notify main thread
std::cout << "Thread two sleeping for some seconds.\n" << std::flush;
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "Thread two calling notifyMainThread().\n" << std::flush;
this->notifyMainThread();
}
std::cout << "Thread two exiting.\n" << std::flush;
};
void run() {
this->t2 = std::thread(&Application::threadTwo, this);
this->mainThread();
};
void shutdown() {
this->shuttingDown = true;
this->notifyMainThread();
std::cout << "Joining thread two.\n" << std::flush;
this->t2.join();
std::cout << "Thread two joined.\n" << std::flush;
// The following call causes the program to hang when triggered by a signal handler
exit(EXIT_SUCCESS);
}
};
auto app = Application();
int sigIntCount = 0;
int main(int argc, char *argv[])
{
std::signal(SIGINT, [](int signum) {
std::cout << "SIGINT recieved!\n" << std::flush;
sigIntCount++;
if (sigIntCount == 1) {
// First SIGINT recieved, attempt a clean shutdown
app.shutdown();
} else {
abort();
}
});
app.run();
return 0;
}
You can run the program online, here: https://onlinegdb.com/Bkjf-4RHP
The example above is a simple multithreaded application that consists of two threads. The main thread waits on a condition variable until a notification is received and this->ready
has been set to true
. The second thread simply updates this->ready
and notifies the main thread, periodically. And finally, the application handles SIGINT on the main thread, where it attempts to perform a clean shutdown.
The issue:
When a SIGINT is triggered (via Ctrl+C), the application does not exit, despite calling exit()
in Application::shutdown()
.
This is what I think is happening:
this->cv.wait(lock, [this] () {return this->ready;});
wait()
call is interrupted by the signal, which results in the signal handler being invoked.Application::shutdown()
, which subsequently calls exit()
. The call to exit()
hangs indefinitely because it's attempting some cleanup that cannot be achieved until the wait()
call resumes (I'm not sure about this).I'm really not sure about that last point, but this is why I think it's the case:
exit()
in Application::shutdown()
and let the main()
return, the program exits without issue.exit()
with abort()
, which does less in respect to cleanup, the program exits without issue (so this indicates that the cleanup process conducted by exit() is resulting in the freezing).The above is just an example of the issue I'm having. In my case, I need to call exit()
in shutdown()
, and shutdown()
needs to be invoked from the signal handler. So far, my options seem to be:
Application::shutdown()
from a different thread as to the one that owns the instance to Application
. I'd also need a way to pull the main thread out of the wait()
call, likely by adding some OR
condition to the predicate.exit()
with a call to abort()
. This would work but would result in the stack not being unwound (specifically, the Application
instance).Do I have any other options? Is there any way to properly interrupt a thread during a call to std::condition_variable::wait()
, and exit the program from within the interrupt handler?
As mentioned by Igor, you can't really do much in signal handlers. You may operate on lock-free atomic variables though, so you could modify the code to work on that.
I've added that and made a few other changes and commented on my suggested changes in the code:
#include <atomic>
#include <condition_variable>
#include <csignal>
#include <iostream>
#include <mutex>
#include <thread>
// Make sure the atomic type we'll operate on is lock-free.
static_assert(std::atomic<bool>::is_always_lock_free);
class Application {
std::mutex cvMutex;
std::condition_variable cv;
std::thread t2;
bool ready = false;
static std::atomic<bool> shuttingDown; // made it atomic
public:
void mainThread() {
std::unique_lock<std::mutex> lock(cvMutex);
while(!shuttingDown) {
// There is no need to check if(!ready) here since
// the condition in the cv.wait() lambda will be checked
// before it is going to wait, like this:
//
// while(!ready) cv.wait(lock);
std::cout << "Main thread waiting." << std::endl; // endl = newline + flush
cv.wait(lock, [this] { return ready; });
std::cout << "Main thread notification recieved." << std::endl;
// Do the thing
ready = false;
}
}
void notifyMainThread() {
{ // lock scope - don't do manual lock() / unlock()-ing
std::lock_guard<std::mutex> lock(cvMutex);
std::cout << "Notifying main thread." << std::endl;
ready = true;
}
cv.notify_all(); // no need to hold lock when notifying
}
void threadTwo() {
while(!shuttingDown) {
// Wait some seconds, then notify main thread
std::cout << "Thread two sleeping for some seconds." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "Thread two calling notifyMainThread()." << std::endl;
notifyMainThread();
}
std::cout << "Time to quit..." << std::endl;
notifyMainThread();
std::cout << "Thread two exiting." << std::endl;
}
void run() {
// Installing the signal handler as part of starting the application.
std::signal(SIGINT, [](int /* signum */) {
// if we have received the signal before, abort.
if(shuttingDown) abort();
// First SIGINT recieved, attempt a clean shutdown
shutdown();
});
t2 = std::thread(&Application::threadTwo, this);
mainThread();
// move join()ing out of the signal handler
std::cout << "Joining thread two." << std::endl;
t2.join();
std::cout << "Thread two joined." << std::endl;
}
// This is made static. All instances of Application
// will likely need to shutdown.
static void shutdown() { shuttingDown = true; }
};
std::atomic<bool> Application::shuttingDown = false;
int main() {
auto app = Application();
app.run();
}