How to clean up local data in SIGINT handler
Asked Answered
A

1

1

I need to execute clean up functions in SIGINT handler, but I can't pass local data to it. Here an example:

int main(int argc, char *argv[]) {

    struct database *db = db_cursor();
    sockfd_t master_socket = init_server();

    signal(SIGINT, sigint_handler);

    while (1) {
        // accepting connections
    }
}

void sigint_handler(int s) {
    destroy_db(db);
    shutdown(master_socket, SHUT_RDWR);
    close(master_socket);
    exit(EXIT_SUCCESS);
}

How can I implement such behaviour? I've tried make this variables are global, but I can't invoke this functions at compile time (error: initializer element is not a compile-time constant).

Arlenarlena answered 4/6, 2016 at 14:4 Comment(5)
You shouldn't do all of this logic in the handler itself. Just have it set a flag, and test for the flag in the main loop.Aileneaileron
Adding up to Oliver's comment: Type the flag sig_atomic_t.Coriecorilla
Now it works, but I have blocking calls like accept, and when I invoke SIGINT it doesn't stop immediately. Should I use multiplexing to address this issue?Arlenarlena
If accept() doesn't return, create this signal-handler using sigaction() instead of signal() and unset the SA_RESTART flag in the member sa_flags of the structure passed.Coriecorilla
You don't need to block on accept(): just set the listening socket to non-blocking, add it to the select()ed (or polled) set, and check the flag whenever select() returns -1/EINTR.Firehouse
C
7

Only a very limited number of functions is guaranteed to be async-signal-safe and therefore may be called from within a signal handler, exit() f.e. does not belong to them.

Take a different approach:

#include <signal.h>

volatile sig_atomic_t sigint_received = 0; /* volatile might be necessary depending on 
                                              the system/implementation in use. 
                                              (see "C11 draft standard n1570: 5.1.2.3")*/

void sigint_handler(int s) 
{
  sigint_received = 1;
}

int main(void) 
{
  signal(SIGINT, sigint_handler);

  while (!sigint_received) 
  {
    /* Do stuff. */
  }

  /* Clean up here. */
}

For more on this see


Note: To be maximal portable you want to use sigaction() instead of signal().

The code to replace signal() might look like this:

struct sigaction sa = 
{
  sigint_handler
  /*, SA_RESTART */ /* Set this by if blocking calls like read() or accept() 
                       should _not_ return on signal reception. */
};

if (-1 == sigaction(SIGINT, &sa, NULL))
{
  perror("sigaction() failed");
  exit(EXIT_FAILURE);
}

Documentation on sigaction():

Coriecorilla answered 4/6, 2016 at 14:19 Comment(6)
volatile sig_atomic_t.Kermit
No. Why do you think this is needed? @KermitCoriecorilla
The signal disposition is done via the OS to the process. No external devices is changing anything here.Coriecorilla
C11 draft standard n1570: 5.1.2.3 Program execution 5 When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects that are neither lock-free atomic objects nor of type volatile sig_atomic_t are unspecified, as is the state of the floating-point environment. The value of any object modified by the handler that is neither a lock-free atomic object nor of type volatile sig_atomic_t becomes indeterminate when the handler exits, as does the state of the floating-point environment if it is modified by the handler and not restored to its original state.Kermit
@Kermit Hm, fair enough, from the part of the standard quoted, it looks as if qualifying the sig_atomic_t as volatile is required, depending on the implementation. (Other parts of the Standard (7.14/2) aren't such explicit.)Coriecorilla
n1570 7.14.1.1/5 has similar provisions (with some exceptions), which are also found in the earlier C99 draft standard n1256.Kermit

© 2022 - 2024 — McMap. All rights reserved.