3

I'm currently researching using sigprocmask to block certain signals (in this case, SIGALRM and SIGCHLD) when a critical segment of code is executing. Both of the signal handlers associated with these signals will access and modify a central data structure, so it is crucial that I prevent them from accessing it while the main process is working on it.

At the moment, my plan would be to simply disable these signals at the start of the critical section of code, and then re-enable them at the end.

void criticalFunction(void) {
    // disable signals with sigprocmask
    // critical code
    // enable signals with sigprocmask
}

However, the signal handler's for the signals which will be blocked also call criticalFunction. What will happen when they call the sigprocmask function and enable blocking on their own signals? Will they stall or keep executing? (Or some third condition..)

The only note I was able to find about this is the following:

If sigprocmask() is called in a signal handler, returning from the handler may undo the work of sigprocmask() by restoring the original pending signal mask. (http://www.mkssoftware.com/docs/man3/sigprocmask.3.asp)

(This is a follow-up question to my previous question: Signal handler accessing queue data structure (race condition?))

Community
  • 1
  • 1
BSchlinker
  • 3,401
  • 11
  • 51
  • 82

2 Answers2

4

Keep in mind that the default behavior inside a signal-handler is to block the signal that is being handled. Also when making function calls inside of signal-handlers, you want to only call signal-safe functions. With that said, sigprocmask() is a signal-safe function, and if you are using it to block the same signal that is being blocked by the signal handler it's being called inside, then really nothing is going to happen ... you're going to remain with the same signal-mask that you currently have. The only difference is that inside the signal-handler, only the signals for either SIGALRM or SIGCHLD are guaranteed to be blocked (it will depend on which signal-handler you are in), where-as when you call sigprocmask() to block those specific signals, both signals will be blocked after the call.

The thing to watch out for is the second part of your code in criticalFunction when you try to call sigprocmask() to enable the signals that are currently blocked in the signal mask. What this could create is a situation where you end up with a level of re-entrancy in the calls to your signal handler. In other words enabling the signal for the signal-handler you're in can mean that before you exit from the current signal handler, another SIGALRM or SIGCHLD is caught, and you'll re-enter the signal-handler again to handle this newly caught signal. As long as you're enabling the signals after any critical-section updates, then I think you should be fine with this re-entrant situation, but just to play it safe, you may only want to enable the signals in criticalFunction at the very end of criticalFunction, not somewhere in the middle, and when you return from criticalFunction, don't do anything that would not be async-safe ... you have to assume that the any code after the return of the second sigprocmask() may not be executing in-order (i.e., it might be executing after a second signal was caught and its signal-handler was run).

You would only need to be concerned about "stalling" if you tried to call something from the exec family, or something of that nature inside of your signal-handler. What would happen is that the newly overlaid process would inherit the signal-mask from the current process, so if the current process had blocked certain signals, then they would also be blocked in the new process. Thus if the new process was assuming the signals were unblocked, your signal-handler in the new process would never run.

BTW, one warning: don't mix signals and threads! You mention "main process" in your question ... I hope that doesn't mean you're attempting to mix signals and threads. If so, that requires a very specific idiom, otherwise you will create all sorts of havoc.

Jason
  • 31,834
  • 7
  • 59
  • 78
  • 2
    You can avoid the re-entrancy problem by using `sigprocmask()` to restore the previous signal mask at the end of `criticalFunction()`, instead of blindly unblocking the signals you care about (ie. at the start of `criticalFunction()` you would do `sigprocmask(SIG_BLOCK, &critical_sigs, &old_sigs);` and at the end you would do `sigprocmask(SIG_SETMASK, &old_sigs, NULL);`. – caf Nov 17 '11 at 02:10
  • I am not using threads in this system -- my use of signals is intended to avoid using threads. Sorry for the poor choice of words. – BSchlinker Nov 17 '11 at 05:00
  • @Jason, would it be considered safe to modify lock-free data structures from a signal handler? sigprocmask() was mentioned as a possible method to resolve my issue within my previous (linked) question. However, from your link it looks like modifying a queue could still result in corruption. I imagine I could use a lock-free queue instead.. but not sure if that puts to rest all issues. – BSchlinker Nov 17 '11 at 09:49
  • @BSchlinker : Enqueuing and dequeuing objects from the lock-free queue should be fine *if* you are using an atomic operation like an atomic compare-exchange. Otherwise, if this is a single producer/consumer queue, then the only way you will be asynchronous-safe is if the signal-handler is either the producer or the consumer, and the non-signal-handler code is the opposite. Finally, while enqueuing and dequeueing may be safe, the rest of your code cannot make assumptions about the state of the queue, since your code can be interrupted at any point, which would also change the queue state. – Jason Nov 17 '11 at 15:30
2

Your design is wrong when you say that "signal handlers [...] call criticalFunction." Signal handlers should never do any serious amount of work. They're not made for that.

The only thing you should reasonably do from within a signal handler is to modify a variable of type sigatomic_t. This is usually used to set a flag, and the remainder of your code (e.g. the main loop) just has to check periodically if any flags have been set.

I think it is in fact undefined behaviour if a signal handler does anything other than that. Update: From man 2 signal: "See signal(7) for a list of the async-signal-safe functions that can be safely called from inside a signal handler."

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • POSIX defines the behavior of doing stuff in signal handlers, and there's quite a lot you can do. It's only non-POSIX, plain C environments where you're unable to do anything in signal handlers, and in such environments there are no useful signals anyway and nothing useful for a signal handler to do... – R.. GitHub STOP HELPING ICE Nov 17 '11 at 05:47
  • 1
    @R..: Yes, indeed, all the "async-signal-safe" functions are OK. In particular, you can change the signal mask and other signal-related things inside the signal handler. But as far as general user code is concerned, that is best left out of the signal handler. Otherwise you'd have to prove that everything you do is async-signal-safe, and that's potentially non-local and very hard to get right. – Kerrek SB Nov 17 '11 at 13:28
  • If you know that an async-signal-unsafe function was not interrupted by the signal handler, you're free to call **any** function from the signal handler. If you carefully mask and unmask signals, it's quite easy to ensure this, even in multi-threaded code. – R.. GitHub STOP HELPING ICE Nov 18 '11 at 02:28
  • @R..: Maybe you should post that as an answer! Seems like you need to put several things together in the right order; that's well worth a post! – Kerrek SB Nov 18 '11 at 02:30