2

I wanted a convenient way to restart a program. I thought I could just catch a signal (USR1 in the example) and call exec.

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

char* const* args;
void restart() {
    printf("restarting\n");
    execv(args[0], args);
}

int main(int argc, char* const argv[]) {
    printf("Starting\n");
    args = argv;
    signal(SIGUSR1, restart);
    raise(SIGUSR1); // could use pkill -SIGUSR1 file-name instead
    pause();
    printf("Terminated normally\n");
    return 0;
}

The above example kinda works. The output is

Starting
restarting
Starting

and then it hangs. Other signals can still be received.

I assume I'm just failing to clear the signal. The expected behavior is for program to keep restarting indefinitely.

alk
  • 69,737
  • 10
  • 105
  • 255
TAAPSogeking
  • 350
  • 4
  • 14
  • `printf()` is not guaranteed to be async-signal-safe. Do not call it from a signal handler. The list of safe functions is here: http://man7.org/linux/man-pages/man7/signal-safety.7.html – alk Mar 15 '20 at 09:52
  • Ever thought of using setjmp and longjmp? Setjmp to the start of the program and when you get the signal, longjmp to the start of the program. That way you will only ever have one process in memory instead of a new process every time it gets a SIGUSR1. – cup Mar 15 '20 at 10:24
  • @cup: perhaps, but that doesn't restart the program. In any case, it won't change OP's problem; the handler still won't fire a second time. – rici Mar 15 '20 at 13:04
  • `raise()` does not generate an asynchronous signal — but it is still not a good idea to use `printf()` in a signal handler. See: [How to avoid using `printf()` in a signal handler?](https://stackoverflow.com/a/16891799/15168) – Jonathan Leffler Mar 15 '20 at 14:19

3 Answers3

1

Linux's man 2 signal explains what happens when you set a signal handler with signal. There are two possibilities:

  1. The disposition of the signal is reset to SIG_DFL, and then the handler is called. To handle this signal again, you need to re-establish the signal handler. This would have worked as you expected.

Or:

  1. The signal is blocked, and then the handler is called. When the handler returns, the signal is unblocked. If the handler doesn't return, the signal is still blocked, which is what was happening to you.

So one works with your code and the other doesn't. But which of the two applies? There is no reliable way of knowing. Posix allows both possibilities and both possibilities exist on different platforms. For this reason, the Linux manpage recommends:

The only portable use of signal() is to set a signal's disposition to SIG_DFL or SIG_IGN... [D]o not use it for [the purpose of establishing a signal handler].

POSIX.1 solved the portability mess by specifying sigaction(2), whichprovides explicit control of the semantics when a signal handler is invoked; use that interface instead of signal().

That's good advice. When you change to using sigaction, you'll probably want to go for the first option above, which requires:

sa.sa_flags = SA_RESETHAND | SA_NODEFER

That also comes from the Linux manpage, which is well worth reading in full. (Of course, the sigaction manpage is even more relevant.)

Community
  • 1
  • 1
rici
  • 234,347
  • 28
  • 237
  • 341
  • #1 is not quite germane — each execution lifetime of the program handles one and only one instance of SIGUSR1, and then only after having specified a user-defined handler for disposition, so there's no worry about SIG_DFL behavior. – pilcrow Mar 15 '20 at 14:25
  • @pilcrow: if OP's platform choose option 1, it would be fine. But it didn't, and the only portable way to ensure that behaviour is to use `sigaction`. I quoted the two behaviours to emphasize that `signal` cannot be used portably, which I believe is the key point here. Once you decide to use `sigaction`, the solution is simple. – rici Mar 15 '20 at 14:44
  • @pilcrow: having said that, if you don't specify `SA_RESETHAND`, you leave yourself open to the handler being entered recursively by a deluge of signals from a different process. So it's not totally irrelevant. – rici Mar 15 '20 at 14:47
  • Perhaps this is obvious, but `man sigaction` provides a complete example of how to use sigaction, and using the flags provided in this answer yielded a solution for me – TAAPSogeking Mar 15 '20 at 19:29
1

On your platform, SIGUSR1 is masked inside the signal handler, which is typical but not mandated behavior for signal (see, by contrast, SA_NODEFER and sigaction).

This mask is inherited across the execve, thus the SIGUSR1 is held pending in your second execution and never delivered.

Try something like this at the top of main(), and perhaps inside the handler, to see what's happening:

static int
is_blocked(int sig) {
  sigset_t ss;
  sigemptyset(&ss);
  (void)sigprocmask(SIG_BLOCK, NULL, &ss);
  return sigismember(&ss, sig);
}
pilcrow
  • 56,591
  • 13
  • 94
  • 135
1

Not sure why this works - replace signal with sigset

First, define __USER_XOPEN_EXTENDED otherwise sigset is undefined

#define __USE_XOPEN_EXTENDED
#include <signal.h>

Change signal to sigset

...
sigset(SIGUSR1, restart);
...

It then keeps on restarting until it is killed. Perhaps someone could explain why sigset works and signal doesn't.

cup
  • 7,589
  • 4
  • 19
  • 42
  • 1
    The obsolescent `sigset(signal, disposition)` function works because it is [specified](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html) to remove _signal_ from the calling thread's mask if _dispo_ is anything other than SIG_HOLD. So, the handler is invoked with USR1 blocked, the program is exec'd anew with USR1 still blocked, and then the call to `sigset` unblocks it to allow delivery to proceed. – pilcrow Mar 15 '20 at 20:01