1

I have some code written in C (working on ubuntu 17):

void sig_stop(int sig_num) {
    /* Some cleanup that needs to be done */
}

void some_routine(const char *array[], const int length) {
    /* Initialization */
    signal(SIGTERM, sig_stop);

    while (true) {
        /* Some function */

        /* I have this sleep to minimize the load on the CPU 
            as I don't need to check the conditions here 
            all the time. */
        sleep(5);
    }
}

Whenever I include the 5 minute sleep (sleep(5)), it appears sig_stop isn't called. However, when I comment out the sleep(5), the sig_stop cleanup works just fine. Have I got something wrong with my understanding of how to catch SIGTERM?

If I can't use the sleep function, is there a better way to "sleep" the program" so that it only runs the loop every x minutes or in such a way that minimizes the CPU load?

Priya
  • 334
  • 3
  • 8
Athena
  • 320
  • 2
  • 12

1 Answers1

5

sleep() and signals

sleep() should not prevent the signal from being caught and the signal handler being executed. From the manpage for sleep() (emphasis mine):

sleep() causes the calling thread to sleep either until the number of real-time seconds specified in seconds have elapsed or until a signal arrives which is not ignored.

Take the following example ...

#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

static volatile sig_atomic_t flag = 0;

static void sig_stop(int signum) { flag = 1; }

int main(void) {
    int secs_remaining = 0;
    signal(SIGTERM, sig_stop);

    while (!flag) {
        printf("Sleeping at time %d\n", time(NULL));
        secs_remaining = sleep(5);
    }
    printf(
        "Flag raised. Exiting at time %d. sleep() was interrupted %d seconds "
        "early ...\n",
        time(NULL), secs_remaining);

    return 0;
}

Note that - in the case where it was interrupted by a signal - sleep() returns the number of seconds left to sleep. E.g., if it is interrupted 3 seconds early it will return 3. It will return 0 if it is not interrupted.

Compile as gcc -o test test.c and run. Then from another terminal run

pkill -15 test

You will see output similar to the following ...

Sleeping at time 1532273709
Flag raised. Exiting at time 1532273711. sleep() was interrupted 2 seconds early ...

By the way ... sleep(x) sleeps for x seconds - not minutes.

signal() vs sigaction()

Due to portability issues associated with signal(), it is often recommended to use sigaction() instead. The use of sigaction() would be something like the following.

int main(void) {
    struct sigaction sa;

    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = sig_stop;
    if (sigaction(SIGTERM, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }
    // Etc.
}

As you can see the usage of sigaction() is a little more verbose than that of signal(). Perhaps that's why people still sometimes use signal().

David Collins
  • 2,852
  • 9
  • 13
  • My apologies for the sleep mistake, stupid error. I can run your test.c perfectly and it behaves like you said (and makes a lot of sense). However when I attempt the same pkill -15 on my own program it presents the msg: "pkill: killing pid 45 failed: Operation not permitted", and with sudo prefix, it executes, but doesn't kill the program. – Athena Jul 22 '18 at 11:42
  • 1
    @Athena: SIGTERM will indeed usually terminate a program. However, when you use `signal()` to install a custom signal handler, the default behaviour is overriden. If you want to exit the program, you need to set a flag / variable in your signal handler and monitor that in your `while()` loop. – David Collins Jul 22 '18 at 11:56
  • After I changed my `while(true)` condition, to `while(!flag)`, as you recommended, it's working perfectly. – Athena Jul 22 '18 at 12:05
  • 1
    @Athena: Cool. Also ... I updated my answer with a mention of `sigaction()` (which is a recommended alternative to `signal()`.) – David Collins Jul 22 '18 at 12:06
  • 1
    @Athena Given `static int flag;`, `while (!flag)` will not necessarily "see" the change to `flag=1` in `static void sig_stop(int signum) { flag = 1; }`. Any compiler would be free to convert `while(!flag)...` to an infinite loop since `flag` starts with the value `0` and never changes value in `main`. `flag` needs to be `volatile` in this case - or better yet, `sig_atomic_t`. See https://port70.net/~nsz/c/c11/n1570.html#7.14 – Andrew Henle Jul 22 '18 at 13:23