I am working on a project involving signal-driven I/O which may end up using a signal handler created with sigaction(). My concern is that the handler might be called multiple times. In other words, it will be processing message A, when it gets interrupted by message B and start processing B, possibly causing a problem. I have seen some code on the web that uses sigprocmask to avoid this, but it appears wrong to me. For example:
void handle_signal(int sig_num)
{
sigset_t mask_set; /* used to set a signal masking set. */
sigset_t old_set; /* used to store the old mask set. */
/* re-set the signal handler again to catch_int, for next time */
signal(SIGINT, catch_int);
/* mask any further signals while we're inside the handler. */
sigfillset(&mask_set);
sigprocmask(SIG_SETMASK, &mask_set, &old_set);
.... (content handling code here) ....
/* restore the old signal mask */{{/COMMENT_FONT}*/
sigprocmask(SIG_SETMASK, &old_set, NULL);
}
This is obviously wrong because sigprocmask is not atomic with the signal. In other words, there is a window of time between when the signal handler is called and when sigprocmask is called, and the signal handler could be called a second or third time in that window, creating a race condition.
My options:
(1) Use a semaphore inside of the handler to queue any redundant calls of the handler.
(2) Write the handler to be re-entrant, so it is fine for it to be called multiple times concurrently.
(3) Some other solution?
If I go for option (2) above, can I consider a socket read queue to be thread safe? For example, let's say a socket handler gets called twice. Instance A starts reading from the socket, then it is interrupted, and Instance B starts finishes reading the packet. Will this cause A to just find the queue empty and finish up or will I be at risk for some kind of error?
If you're using sigaction to setup the signal handler then the signal that caused the handler to be triggered will by default already be blocked inside the handler.
In your code then the blocking of all signals and then the restore of the old mask is about blocking all other signals, the original signal (that triggered the handler) will be blocked until you return from the handler (or you specifically unblock it).
With sigaction you can avoid doing this block and restore in the handler by setting the sa_mask field of the struct sigaction, which is the set of signals blocked in the handler.
Further, your use of signal to "re-set" the handler is a little confusing, you call your function handle_signal then you re-set to catch_int (assuming this handler is actually handling SIGINT...
The older signal API did used to reset the signal handler back to the default each time the handler was triggered. However, by default sigaction does not do this, so you shouldn't need to "re-set" the signal handler if you're using the sigaction API. I personally would avoid mixing calls to signal and sigaction in the same program, I'd choose one and stick to it.
In conclusion, I think your concerns about sigprocmask not being atomic are unnecessary as the signal in question is already blocked, your mixed use of signal and sigaction worries me more.
Related
Signal mask is thread-specific, which means blocking a signal doesn't prevent it from being delivered to another thread where this signal isn't blocked. When execution enters into a handler function (assuming no SA_NODEFER) "current" signal becomes blocked.
Does it mean it will be blocked for all threads in current process or it is possible for next such signal to be delivered to another thread (while it is still being handled in first thread)?
Reading man sigaction:
sa_mask specifies a mask of signals which should be blocked (i.e., added to the signal mask of the thread in which the signal handler is invoked) during execution of the signal handler. In addition, the signal which triggered the handler will be blocked, unless the SA_NODEFER flag is used.
This sounds like the masking of the currently handled signal only affects the handling thread, so other threads may handle further signals.
I think it is typical for a multithreaded system to block all signals for all threads except one (or several) dedicated signal handling threads (e.g. one that is polling on a signalfd). That way you never have to worry about signals landing on some unpredictable thread.
I have this code where I use sigaddset and sigaction. However if I comment segaddset the result is the same
struct sigaction act;
act.sa_handler = process_alarm;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
//sigaddset(&act.sa_mask, SIGINT);
sigaction(SIGALRM, &act, NULL);
for(;;)
{
alarm(3);
pause();
}
Why do I need to use it?
You are doing 2 things here:
Populating a sigset_t. A sigset_t is just a collection of values for signals, and are used in various system calls. You can:
Create an empty sigset_t (sigemptyset()
Add a signal to the set (sigaddset())
Remove a signal to the set (sigdelset())
etc...
Setting the signal mask for the signal handler. You do that by manipulating the sigset_t sa_mask member of the struct sigaction when you set up a signal handler in with a call to sigaction().
The signal mask of a signal handler means that while the signal handler is executing, the signals that are in the mask will be blocked - i.e. those signals will not be handled as long as they are blocked. When the signal handler are finished, the signals in will be unblocked.
A signal that is blocked isn't "lost", it will be handled when that particular signal is unblocked again.
The sigaddset(&act.sa_mask, SIGINT); means the the SIGINT signal cannot occur while the code for the SIGALRM handler is running.
On the other hand, if you comment out sigaddset(&act.sa_mask, SIGINT);, you're left with just an empty list of signals created with sigemptyset(&act.sa_mask);. So any signals that occur while the SIGALRM handler function is running might preempt that handler and execute the signal handler for that other signal.
For a SIGINT, you would normally not notice any difference with manual testing - it's unlikely you manage to hit CTRL-C exactly when your handler for SIGALRM is running, and your SIGALRM handler probably runs quickly enough that you would not notice if the SIGINT was slightly delayed.
Signals sets are manipulated via sigset_t type. Several operations are available for signals sets:
create an empty set S via sigemptyset, S=∅
add a signal s to a set S via sigaddset, S=S∪{s}
remove a signal s from a set via sigdelset, S=S\{s}
create the set of all possible signals via sigfillset.
test is a signal s is in a given set S via sigismember, s∈S?
Such a set is used at different places: setting a new process signal mask, blocking set during signal handling, requesting the set of pending signals, etc.
If you want to catch different signals, it may appears that some catching functions must not be interrupted with others, so you can add a set of signals to be blocked during the delivery of a given signal. You actually decided (when uncommented) to block SIGINT during SIGALRM delivery. So you can only observe this by sending a SIGINT during the execution of the handler; which is difficult to realize.
A example where it can be important ?
Suppose that the handler of SIGUSR1 modify a given data structure and that the handler for SIGUSR2 uses that same data structure. It is very important to make both handlers not concurrent, one can be run after the other but you probably don't want to be interrupted by one during the delivery of the other. Your code is self-concurrent, say that even in the case of only one thread, signals can lead you to concurrency.
sigaddset is used for adding the corresponding signal mask to that sigset_t variable.
In sigaction, it is not required. You can use that when you are using the sigprocmask which is for blocking the signal which we mentioning in that variable.
In a single threaded program, does a race condition is possible in a signal handler?
void signal_handler(...)
{
static int i = 0;
i = i + 10 * 10;
}
Imagine that two very close signals are thrown, so close that they enter the function at the same time.
I can't find informations about how recent Linux OS handle this. I just know that both signals are correctly handled but I don't know how. Does race conditions are possible ?
Any helps appreciated, thanks!
There is no race condition in the sense that you mean (between two signals). Multiple signals of the same signal are not delivered simultaneously. Unless precautions are taken, multiple signals for different signal numbers may be delivered simultaneously, as described in torek's answer.
Whenever you involve variables of static duration (or global variables), your function may no longer reentrant. This is typically not important for the signal handler function itself. However, if it calls some other function that accesses global or static data, then that function will see an access pattern that is similar to two threads racing through a critical section. That is, your program is calling such a function to do its normal processing, but the signal arrives in the middle of that function, and then your signal handler calls into that same function. The global/static variables may be in an inconsistent state, and might cause your program to have non-deterministic behavior.
POSIX defines a set of APIs that are safe to be called from within a signal handler. Your code should take similar precautions when you plan to let your signal handler call functions that you implement.
Single threaded means only one app touching the static at a time yes? If there are 2 apps, there are 2 statics and no race condition.
If this is an interrupt handler and i += 100 is not atomic (which it may be depending on the platform/CPU), then it would race.
One additional, important note: if you're using "reliable signals" (POSIX sigaction with the corresponding sa_mask field), you get control over how signals behave in a single-thread-single-process situation.
Consider the case of single process P1, with a signal-handler like the one you show above. Suppose that you are catching signal SIGUSR1 and having that enter the function signal_handler. While you are inside signal_handler, some other process P2 sends another SIGUSR1 to P1 (e.g., via kill). This signal is "blocked" (temporarily) via sa_mask until signal_handler returns in P1. This is true even if you don't set any bits in sa_mask (as long as you don't set SA_NODEFER in sa_flags, see below).
But, suppose you've also decided to catch SIGUSR2 with function signal_handler. Suppose that P2 also sends a SIGUSR2. In this case, the SIGUSR2 is (or may be) caught, starting another instance of signal_handler running, this time on behalf of the SIGUSR2 signal.
You can prevent this by making sure that when SIGUSR1 is being handled, SIGUSR2 is temporarily blocked as well. In general you'd probably want to make SIGUSR1 blocked while SIGUSR2 is being handled. To do this, set both corresponding bits in sa_mask:
struct sigaction sa;
memset(&sa, 0, sizeof sa);
sa.sa_flags = SA_RESTART | SA_SIGINFO; /* (decide for yourself which flags) */
sigaddset(&sa.sa_mask, SIGUSR1);
sigaddset(&sa.sa_mask, SIGUSR2);
sa.sa_sigaction = signal_handler;
error = sigaction(SIGUSR1, &sa, NULL);
if (error) ... handle error ...
error = sigaction(SIGUSR2, &sa, NULL);
if (error) ... handle error ...
The two sigaddset calls make sure that both SIGUSR1 and SIGUSR2 are held-off (blocked, temporarily) for the duration of the function.
If you're only catching one signal, there is no need for this extra complexity, because as long as SA_NODEFER is not set, the OS automatically adds whatever signal triggered entry to your signal handler to the "currently blocked signals" set at entry.
(Note that the OS's automatic blocking and unblocking of signals at entry and exit to your signal handler is done with sigprocmask, using SIG_BLOCK and SIG_SETMASK—not SIG_UNBLOCK—with the mask for the SIG_SETMASK at exit set by saving the previous mask filled in via SIG_BLOCK. Well, it's normally done inside kernel code, rather than actually calling sigprocmask, but the effect is the same, just more efficient.)
The sa_mask field of struct sigaction specifies signals that are blocked during handler invocation. Those signals are added to the process block mask just before the handler is invoked and removed right after handler completion. What if sa_mask and the process signal mask overlap? Will those signals that are masked both by sa_mask and the process signal mask be removed from the process signal mask?
When a signal handler returns, the signal mask that was in effect before the signal was handled gets restored atomically as part of the return process. This will happen unless you jump out of the signal handler with longjmp or siglongjmp, in which case it's up to you whether you want to manually save and restore the signal mask.
As an interesting aside, if you use the SA_SIGINFO flag to setup a three-argument-form signal handler, the ucontext_t pointed to by the third argument contains a sigset_t uc_sigmask member reflecting the saved signal mask. Further, I'm not sure whether this usage is sanctioned by POSIX, but on all real-world systems I know of, you can actually modify uc_sigmask before returning from the signal handler to set a different signal mask (as opposed to restoring the original one) when the signal handler returns. This can be used, for example, if you want to re-raise the signal you just handled but leave it blocked when returning, so that it actually gets handled later on when the signal is again unblocked by the interrupted code or when sigwaitinfo or similar is called.
Will those signals that are masked both by sa_mask and the process signal mask be removed from the process signal mask?
No. The original signal mask is restored, ie. what was blocked before would be blocked after.
I've got a situation where a signal handler needs to, under certain conditions, return without unmasking itself, i.e. after returning the signal should remain blocked in the thread's signal mask. Jumping out of the signal handler with longjmp will not work because I need to return to the exact point that was interrupted like a normal signal handler return. Is there any conformant way to do this, short of using the ucontext_t which was removed from the standards? I cannot change signal handlers or dispositions; all effects must be local to the thread.
The purpose of this code has to do with some atomic operations and potential for a race condition or deadlock. Basically the potentially interrupted code looks like:
atomic_write(&thread_local_flag, 1);
atomic_dec(&global_counter);
If the flag has been set and the counter decremented, all is well and the signal handler has nothing to do, but the signal could possibly arrive between the two instructions. In this case, the signal handler wants to immediately return and let the decrement proceed, but the process is being bombarded with signals (intended to be received by all threads for an arcane synchronization purpose) and there's a possibility it could loop forever (or at least for unbounded time) processing signals while other threads never receive their signals.
If I could leave the signal blocked when the signal handler returns, there would be no problem.
Hmm, the best I've found so far.. This seems to work and does not depend on any ucontext functions, just the structure which was not removed. In the signal handler:
if (thread_local_flag) {
sigaddset(&((ucontext_t *)ctx)->uc_sigmask, sig);
return;
}
Here sig and ctx are the first and third argument to the SA_SIGINFO-type signal handler, respectively.
Any thoughts on whether this is correct usage or a horrible hack (or both)?