How do you prevent interruptions in a C++ function?

236 Views Asked by At

I'm running a long computation in Linux, and every now and then I save partial results. I'd like to prevent interruptions in the function that saves partial results: for example, if a user presses CTRL-C, I want the function to finish and then the program to stop running).

For example, in the following code, I want that each "Saving..." is followed by a "Done".

#include <iostream>

void save_result(int i) {
  disable_interruptions();  // WHAT SHOULD I PUT HERE?

  std::cout << "Saving...";
  /* open some files, edit them depending on i, etc. */
  std::cout << "Done\n";

  enable_interruptions();   // WHAT SHOULD I PUT HERE?
}

int main() {
  while(true) {
    int i = 1;  /* some long computation */
    save_result(i);
  }
}

What I tried:

  • Use some properties of file atomicity. Unfortunately, I have to change many files in various places, and I cannot do all the updates at the same time.
  • Copy the files, edit the copy, move back atomically to the original place. The files are huge, and it will take too much time.
2

There are 2 best solutions below

3
Michele Borassi On

Thanks to Homer512 for pointing me in the right direction. We can capture the actions that stop the program using a signal handler, and re-raise the signal after we finished our update. Code:

#include <atomic>
#include <iostream>
#include <signal.h>

// The signal that we received.
// We use a global variable for simplicity, in reality it's better
// to wrap it in a class. We use an atomic in case multiple signals
// come at the same time (not sure if it's really needed). 
std::atomic_int received_signal = NSIG;

void SignalHandler(int signal) {
  received_signal = signal;
}

void save_result(int i) {
  // From now on, instead of killing the program, these actions
  // will trigger our signal handler.
  signal(SIGINT, SignalHandler);   // CTRL-C
  signal(SIGHUP, SignalHandler);   // Close terminal
  signal(SIGQUIT, SignalHandler);  // CTRL-/

  std::cout << "Saving...";
  /* open some files, edit them depending on i, etc. */
  std::cout << "Done\n";

  // Reset these actions to default behavior.
  signal(SIGINT, SIG_DFL);
  signal(SIGHUP, SIG_DFL);
  signal(SIGQUIT, SIG_DFL);

  if (received_signal != NSIG) {
    raise(received_signal);
  }
}

Other links:

0
Brian Bi On

Instead of catching the signal and re-raising it, the ideal solution is actually to mask out the signal, which will cause the kernel to queue it up. Then, as soon as you unmask, the signal will be delivered. Here's an example:

#include <signal.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <iostream>
#include <string>

int main() {
    std::cout << "Masking out all signals\n";
    sigset_t oldset, newset;
    sigfillset(&newset);
    if (sigprocmask(SIG_SETMASK, &newset, &oldset) < 0) {
        perror("sigprocmask");
        exit(1);
    }
    std::cout << "Press enter to continue\n";
    std::string s;
    std::getline(std::cin, s);
    std::cout << "Unmasking..." << std::endl;
    sigprocmask(SIG_SETMASK, &oldset, NULL);
}

In a multithreaded program, use pthread_sigmask. When a signal is directed at the process as a whole (as is the case with a SIGINT generated by the terminal, for example) it can be handled by one of the threads that does not have that signal masked out.