Reputation: 11
I am learning how to use pselect. I took an example code which worked fine and modified it to call the same code from a thread which is spawned from main and it does not work (pselect remains blocked forever)
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
/* Flag that tells the daemon to exit. */
static volatile int exit_request = 0;
/* Signal handler. */
static void hdl (int sig)
{
exit_request = 1;
printf("sig=%d\n", sig);
}
/* Accept client on listening socket lfd and close the connection
* immediatelly. */
static void handle_client (int lfd)
{
int sock = accept (lfd, NULL, 0);
if (sock < 0) {
perror ("accept");
exit (1);
}
puts ("accepted client");
close (sock);
}
void *mythread(void *arg __attribute__ ((unused)))
{
int lfd;
struct sockaddr_in myaddr;
int yes = 1;
sigset_t mask;
sigset_t orig_mask;
struct sigaction act;
memset (&act, 0, sizeof(act));
act.sa_handler = hdl;
/* This server should shut down on SIGUSR1. */
if (sigaction(SIGUSR1, &act, 0)) {
perror ("sigaction");
return NULL;
}
sigemptyset (&mask);
sigaddset (&mask, SIGUSR1);
if (pthread_sigmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
perror ("pthread_sigmask");
return NULL;
}
lfd = socket (AF_INET, SOCK_STREAM, 0);
if (lfd < 0) {
perror ("socket");
return NULL;
}
if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR,
&yes, sizeof(int)) == -1) {
perror ("setsockopt");
return NULL;
}
memset (&myaddr, 0, sizeof(myaddr));
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = INADDR_ANY;
myaddr.sin_port = htons (10000);
if (bind(lfd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) {
perror ("bind");
return NULL;
}
if (listen(lfd, 5) < 0) {
perror ("listen");
return NULL;
}
while (!exit_request) {
fd_set fds;
int res;
/* BANG! we can get SIGUSR1 at this point, but it will be
* delivered while we are in pselect(), because now
* we block SIGUSR1.
*/
FD_ZERO (&fds);
FD_SET (lfd, &fds);
res = pselect (lfd + 1, &fds, NULL, NULL, NULL, &orig_mask);
if (res < 0 && errno != EINTR) {
perror ("select");
return NULL;
}
else if (exit_request) {
puts ("exited");
break;
}
else if (res == 0)
continue;
if (FD_ISSET(lfd, &fds)) {
handle_client (lfd);
}
}
return NULL;
}
int main (int argc, char *argv[])
{
void * res;
pthread_t mythr_h;
pthread_create(&mythr_h, (pthread_attr_t *)NULL, mythread, NULL);
pthread_join(mythr_h, &res);
return 0;
}
strong text
After sending SIGUSR1 to this program I see that it remains blocked in the pselect call. When the code in mythread function is moved back into main and not spawning any thread from main, it works perfectly.
Upvotes: 0
Views: 1165
Reputation: 1529
The signal is delivered to the main thread, other than the thread blocking on the pselect()
call. If there are multiple threads that have the signal unblocked, the signal can be delivered to any one of the threads.
Since you didn't specify your platform, first I'm quoting from the POSIX standard (System Interfaces volume, 2.4.1 Signal Generation and Delivery).
Signals generated for the process shall be delivered to exactly one of those threads within the process which is in a call to a sigwait() function selecting that signal or has not blocked delivery of the signal.
You can also see similar statements in Linux manpage signal(7)
.
A process-directed signal may be delivered to any one of the threads that does not currently have the signal blocked. If more than one of the threads has the signal unblocked, then the kernel chooses an arbitrary thread to which to deliver the signal.
And FreeBSD manpage sigaction(2)
.
For signals directed at the process, if the signal is not currently blocked by all threads then it is delivered to one thread that does not have it blocked (the selection of which is unspecified).
So what you can do is to block SIGUSR1
for all the threads in the process except for the one that calls pselect()
. Luckily when a new thread is created, it inherits the signal mask from its creator.
From the same POSIX section above,
The signal mask for a thread shall be initialized from that of its parent or creating thread....
Linux pthread_sigmask(3)
,
A new thread inherits a copy of its creator's signal mask.
FreeBSD sigaction(2)
,
The signal mask for a thread is initialized from that of its parent (normally empty).
You can make the following changes to your code. In main()
, before creating any threads, block SIGUSR1
. In the thread, pass a signal mask that has SIGUSR1
unblocked into pselect()
.
--- old.c Mon Mar 21 22:48:52 2016
+++ new.c Mon Mar 21 22:53:54 2016
@@ -56,14 +56,14 @@
return NULL;
}
- sigemptyset (&mask);
- sigaddset (&mask, SIGUSR1);
-
- if (pthread_sigmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
+ sigemptyset(&orig_mask);
+ if (pthread_sigmask(SIG_BLOCK, NULL, &orig_mask) < 0) {
perror ("pthread_sigmask");
return NULL;
}
+ sigdelset(&orig_mask, SIGUSR1);
+
lfd = socket (AF_INET, SOCK_STREAM, 0);
if (lfd < 0) {
perror ("socket");
@@ -126,6 +126,15 @@
{
void * res;
pthread_t mythr_h;
+ sigset_t mask;
+
+ sigemptyset (&mask);
+ sigaddset (&mask, SIGUSR1);
+
+ if (pthread_sigmask(SIG_BLOCK, &mask, NULL) != 0) {
+ return 1;
+ }
+
pthread_create(&mythr_h, (pthread_attr_t *)NULL, mythread, NULL);
pthread_join(mythr_h, &res);
return 0;
Last thing is off topic. printf()
is not an async-signal-safe
function, so should not be called in the signal handler.
Upvotes: 1
Reputation: 73121
After sending SIGUSR1 to this program I see that it remains blocked in the pselect call. When the code in mythread function is moved back into main and not spawning any thread from main, it works perfectly.
That's to be expected -- there is no guarantee that a signal will be delivered to the "right" thread, since there is no well-defined notion of what the "right" thread would be.
Signals and multithreading don't mix particularly well, but if you want to do it, I suggest getting rid of the exit_request flag (note: the volatile keyword isn't sufficient to work reliably in multithreaded scenarios anyway), and instead create a connected pair of file descriptors (by calling either the pipe() function or the socketpair() function). All your signal handler function (hdl()) needs to do is write a byte into one of the two file descriptors. Have your thread include the other file descriptor in its read-socket-set (fds) so that when the byte is written that will cause pselect() to return and then your subsequent call to FD_ISSET(theSecondFileDescriptorOfThePair, &fds) will return true, which is how your thread will know it's time to exit now.
Upvotes: 1