how does pselect blocks signal using signal mask in network programming

1.7k views Asked by At

I am currently studying the concepts of network programming in which I came across one of the functions pselect() which resolves the issue of select ie. with select(), there is a chance of problem which is that between the test of intr_flag and the call to select, if the signal occurs, it will be lost if select blocks forever.

if (intr_flag)
 handle_intr(); /* handle the signal */
if ( (nready = select( ... )) < 0) {
 if (errno == EINTR) {
 if (intr_flag)
 handle_intr();
 }

However, it says that With pselect, we can now code this example reliably as

sigset_t newmask, oldmask, zeromask;
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
sigprocmask(SIG_BLOCK, &newmask, &oldmask); /* block SIGINT */
if (intr_flag)    //here
 handle_intr(); /* handle the signal */ 
if ( (nready = pselect ( ... , &zeromask)) < 0) {
 if (errno == EINTR) {      //here
 if (intr_flag)
 handle_intr ();
 }
 ...
}

The explanation that it gives for the code to be reliable is that - Before testing the intr_flag variable, we block SIGINT. When pselect is called, it replaces the signal mask of the process with an empty set (i.e., zeromask) and then checks the descriptors, possibly going to sleep. But when pselect returns, the signal mask of the process is reset to its value before pselect was called (i.e., SIGINT is blocked).

But in the code with pselect mentioned above, we block the signals then how can we check the error EINTR? Since the pselect blocks all the signals then when Interrupt occurs it should block that from disrupting or being delivered to the process. It is only when pselect returns then the signal can be delivered.

According to the lines in front of which comment here is mentioned, INTERRUPT signal can still happen before pselect is called or in-between the first check and pselect or when pselect is called contradicting the purpose of blocking the Interrupt and any other signals and thus should lead to race condition as with select is there.

Please anyone explain how this is possible as I am new to these concepts.

1

There are 1 answers

0
AudioBubble On BEST ANSWER

Well, the main idea is that doing ready = pselect(nfds, &readfds, &writefds, &exceptfds, timeout, &sigmask); is equivalent to performing the following actions atomically:

sigset_t sigsaved;

sigprocmask(SIG_SETMASK, &sigmask, &sigsaved);
/* NB: NOTE-1 */
ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
sigprocmask(SIG_SETMASK, &sigsaved, NULL);

To make use of this we first block, say, SIGINT:

sigset_t emptyset, blockset;

sigemptyset(&blockset);
sigaddset(&blockset, SIGINT);
sigprocmask(SIG_BLOCK, &blockset, NULL);

At that point we can't receive SIGINT and, therefore, we can't process it. But we don't need it until we enter pselect(). What we want to do after we've blocked SIGINT is to setup a proper signal handler.

Say, we have a flag static volatile int intr_flag = 0; declared outside our main code and we define a handler called handler() which simply does intr_flag = 1;. So, we set it up as a handler:

sa.sa_handler = handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);

Then we configure readfs (declaration not show here), initialise an empty signal set and call pselect():

sigemptyset(&emptyset);
ready = pselect(nfds, &readfds, NULL, NULL, NULL, &emptyset);

So, I'll outline this one more time - we don't receive SIGINT until we call pselect(). We don't need it. As soon as we enter pselect(), signal SIGINT will be unblocked (because of an empty signal set provided to pselect()), and pselect() can be interrupted by SIGINT. At the point when pselect() returns, we again will not receive any further SIGINT, but if SIGINT has occurred during pselect(), then we will detect this as per errno being EINTR and if we check intr_flag, we'll find it to be 1. We will understand that we need to behave accordingly. So, it's pretty clear that signal handler may do its job as soon as the signal is unblocked, and the latter happens within pselect() call itself.

The most important detail here is that if we don't have a special pselect() call implemented in atomic way, then we have to do the steps around /* NB: NOTE-1 */ label in the snippet above ourselves when using a usual select(). And, as it will not be atomic, we will have a chance that SIGINT will be delivered to us in-between the two actions - where /* NB: NOTE-1 */ hint is located, i.e. after we unblock the signal from being delivered and before select() is entered. At that point the signal indeed will be lost. Here is why we need a pselect() call instead.

All in all, atomicity of pselect() is the explanation for its use. If you are not quite familiar with such a concept, you may refer to the article on Wikipedia or to special books on computer science topics.

Also, I'll furnish my answer with the link to an article on LWN which is far more exhaustive.