From: Thomas Maier-Komor on
Hi,

I am getting inconsistent behaviors on different systems with the
program below. Now, I am wondering if the behavior of this program is
triggering unspecified behavior according to SUS.

Can anybody comment, weather closing a file-descriptor should unblock a
thread blocked in read(2), when a signal handler uses close(2) on the
same file descriptor.

To trigger the behavior in question compile and run the program, and
simply signal SIGINT by pressing CTRL-C.

The behavior I am seeing is as follows:
- Linux: continues reading on the file descriptor, and the file
descriptor isn't even closed. I.e. read will return valid data, if one
hits enter after pressing CTRL-C

- FreeBSD and Solaris: read(2) returns -1 and sets errno to EBADF

I would have suspected the behavior seen by FreeBSD and Solaris. But is
that expectation valid, or am I triggering unspecified behavior.

Thanks,
Thomas



#define _REENTRANT 1
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>


void sig_handler(int sig)
{
write(STDOUT_FILENO,"sig_handler\n",12);
close(STDIN_FILENO);
}

void *reader(void *ignored)
{
char buf[4096];
char msg[4096];
int err;

buf[0] = 0;
write(STDOUT_FILENO,"reader started\n",16);
err = read(STDIN_FILENO,buf,sizeof(buf));
if (err == -1)
snprintf(msg,sizeof(msg),"errno = %d, errstr =
%s\n",errno,strer
else
snprintf(msg,sizeof(msg),"read = %d, buf = %s\n",err,buf);
write(STDOUT_FILENO,msg,strlen(msg));
return 0;
}

int main(void)
{
pthread_t thr;
int err;
struct sigaction sig;

sig.sa_handler = sig_handler;
err = sigemptyset(&sig.sa_mask);
assert(err == 0);
err = sigaddset(&sig.sa_mask,SIGINT);
assert(err == 0);
sig.sa_flags = SA_RESTART;
err = sigaction(SIGINT,&sig,0);
assert(err == 0);
printf("creating thread...\n");
err = pthread_create(&thr,0,reader,0);
assert(err == 0);
printf("sleeping...\n");
sleep(60);
pthread_join(thr,0);
printf("done...\n");
return 0;
}
From: Xavier Roche on
Thomas Maier-Komor a �crit :
> I would have suspected the behavior seen by FreeBSD and Solaris. But is
> that expectation valid, or am I triggering unspecified behavior.

The behaviour of file descriptors is nothing but clear regarding
multithreading in POSIX:
<http://www.opengroup.org/onlinepubs/000095399/functions/close.html>
<http://www.opengroup.org/onlinepubs/000095399/functions/read.html>

Linux /seems/ to handle some kind of refcount on the file descriptor
level, hence (AFAICS) closing stdin in one thread won't do anything but
decreasing the count from 2 to 1 (the other thread is still blocked in a
read operation). These are only guesses, I don't know if the underlying
kernel syscall actually handles such refcount or not, but the behaviour
may be consistent with this hypothesis.

[ The same question is also interesting for interrupting an accept()
system call, with a close() call ]

If the goal is "just" to interrupt a read operation, you can, of course,
slightly change your handler code:

- sig.sa_flags = SA_RESTART;
+ sig.sa_flags = 0;

The handled signal (SIGINT) will not trigger a retry within interrupted
system calls anymore, which lets you handle manually the EINTR error
within the reader() function (you may want to retry the call unless some
king of mutexed flag was set to TRUE in your signal handler, because the
system call may be interrupted by someone else). [
pthread_kill()/pthread_sigmask() can also be used to tune the thread(s)
to interrupt. ]
From: Thomas Maier-Komor on
On 27.12.09 14:51, Xavier Roche wrote:
> Thomas Maier-Komor a �crit :
>> I would have suspected the behavior seen by FreeBSD and Solaris. But
>> is that expectation valid, or am I triggering unspecified behavior.
>
> The behaviour of file descriptors is nothing but clear regarding
> multithreading in POSIX:
> <http://www.opengroup.org/onlinepubs/000095399/functions/close.html>
> <http://www.opengroup.org/onlinepubs/000095399/functions/read.html>
>
> Linux /seems/ to handle some kind of refcount on the file descriptor
> level, hence (AFAICS) closing stdin in one thread won't do anything but
> decreasing the count from 2 to 1 (the other thread is still blocked in a
> read operation). These are only guesses, I don't know if the underlying
> kernel syscall actually handles such refcount or not, but the behaviour
> may be consistent with this hypothesis.
>
> [ The same question is also interesting for interrupting an accept()
> system call, with a close() call ]
>
> If the goal is "just" to interrupt a read operation, you can, of course,
> slightly change your handler code:
>
> - sig.sa_flags = SA_RESTART;
> + sig.sa_flags = 0;
>
> The handled signal (SIGINT) will not trigger a retry within interrupted
> system calls anymore, which lets you handle manually the EINTR error
> within the reader() function (you may want to retry the call unless some
> king of mutexed flag was set to TRUE in your signal handler, because the
> system call may be interrupted by someone else). [
> pthread_kill()/pthread_sigmask() can also be used to tune the thread(s)
> to interrupt. ]


Hi Xavier,

what it seems to be doing is obvious. My question was really whether the
standard says anything about this.

BTW: changing sa_flags to 0 doesn't even give the behavior you expect. I
suspected read to return with EINTR, too - but of course that isn't
guaranteed, because EINTR will only turn up in the thread that handles
the signal.

In case of Linux, changing sa_flags to 0 has no impact what so ever -
i.e. the interrupt seems to be handled in the thread that is blocked in
sleep(). So this would really require binding SIGINT to the reader
thread, which is pretty restrictve..

Cheers,
Thomas
From: Xavier Roche on
Thomas Maier-Komor a �crit :
> what it seems to be doing is obvious. My question was really whether the
> standard says anything about this.

The standard (see the opengroup pages in my message) does not seem to
say anything, unfortunately.

> In case of Linux, changing sa_flags to 0 has no impact what so ever

[ Yep, because kill() (and friends) seem to send the signal to the first
thread. A quick solution is to mirror the signal to the correct thread
if necessary:

static pthread_t thr;
void sig_handler(int sig)
{
if (sig != SIGINT)
return;
if (pthread_self() == thr)
return;
write(STDOUT_FILENO,"sig_handler\n",12);
pthread_kill(thr, sig);
}
]
From: J de Boyne Pollard on
TMK> My question was really whether the
TMK> standard says anything about this.

And your answer is "Yes". Read what the standard says about non-
cancelled I/O requests completing as if the close() didn't occur until
the end of the I/O request.