2

I have a executable that starts up some resources, spins a bunch of worker threads and then waits in a loop for the done command. After the done command is received, it does a bunch of clean up and then exits.

void run() { /* more initialization code, creation of classes, start worker threads etc. */ while (!done) { // wait for done command and then exit while } /* clean up resources */ } int main(int argc, char * argv[]) { /* initialization code */ ... run() ... /* clean up code */ return 0; } 

However, when interrupted by a signal like Ctrl + c on Linux, it doesn't go through the cleanup sequence (neither in run() nor in main())

Now this tutorial shows you how to handle signals by having a function signalHandler for instance that is called on receiving a signal. However, the variables I need to clean up are not in the scope of that function. Also, it seems like duplicated code, since I already have all the clean up stuff in run() and main().

What is a good design approach here? If this was a class, I would just have all the clean up in the destructor, but for a executable with global scope functions like these, I am not sure what's the best design.

Thank you.

2 Answers 2

2

The signal doesn't terminate your program.

It would be nice though if your program took the hint and shutdown.

You have a done variable controlling your run loop. Perhaps you could elevate it to global scope, or introduce a special global flag just for monitoring if you've received a terminate signal. When your next loop happens it will exit naturally.

2
  • currently, the signal does in fact terminate the program, presumably from me not having defined a signal handler so maybe that's default behavior? Commented Nov 11, 2020 at 22:40
  • Pretty much. Except for SIGKILL, that is swiftly followed by the OS halting the program. My work blocks that tutorial for some reason but here is a good example of how this can be setup. Commented Nov 11, 2020 at 22:47
1

The cleanest approach is to use the signal handler to alter the path that execution takes. It doesn't necessarily need to be a termination point. (Although it probably should be for things like SIGSEGV.)

Graceful termination requires that you account for when exactly it's safe to redirect execution, and that requires knowing what the process is doing at the time.

For example, it appears that your program is already designed to clean up after itself before exiting. Would you want the cleanup code getting executed at some arbitrary line in run? (Remember that the signal can happen anywhere; even while cleanup is already in progress.)

Assuming not, you can use a thread_local flag that gets set by the signal handler and checked by the main routine anywhere it's convenient for it to exit.

For example:

  • Before and after blocking calls. These will be interrupted as long as the signal handler isn't SIG_IGN.
  • In the while predicate.
  • Prior to executing something that's going to take a while to complete or make permanent data changes that you want to cancel.

At each of these points, you can include a break to escape the loop and jump to the cleanup code. If the signal handler returns, the main thread will continue where it left off, with the flag set.

Another consideration is what should happen if the signal happens again during cleanup. For example, should quick repetitions of Ctrl+C bypass cleanup?

For most signals, it's good practice to unset the handler for that signal in the handler that's handling it, e.g., in case the handler handling SIGSEGV causes another SIGSEGV. But, if the signal indicates some sort of normal termination request (e.g., SIGINT), you can sometimes get away with not unsetting the handler.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.