System call interrupted by a signal still has to be completed
Asked Answered
A

3

12

A lot of system calls like close( fd ) Can be interrupted by a signal. In this case usually -1 is returned and errno is set EINTR.

The question is what is the right thing to do? Say, I still want this fd to be closed.

What I can come up with is:

while( close( fd ) == -1 )
  if( errno != EINTR ) {
    ReportError();
    break;
  }

Can anybody suggest a better/more elegant/standard way to handle this situation?

UPDATE: As noticed by mux, SA_RESTART flag can be used when installing the signal handler. Can somebody tell me which functions are guaranteed to be restartable on all POSIX systems(not only Linux)?

Anorthic answered 13/11, 2012 at 6:38 Comment(3)
Although in general this might be the correct approach, for close() in particular, at least on Linux, it is not recommended. Please read here: lkml.org/lkml/2002/7/17/165Indescribable
I see. But I don't think it's guaranteed that the same happens on all UNIX systems. I need a portable solution.Anorthic
May be you can comment on the updated question? Would be great.Anorthic
M
9

Some system calls are restartable, which means the kernel will restart the call if interrupted, if the SA_RESTART flag is used when installing the signal handler, the signal(7) man page says:

If a blocked call to one of the following interfaces is interrupted by a signal handler, then the call will be automatically restarted after the signal handler returns if the SA_RESTART flag was used; otherwise the call will fail with the error EINTR:

It doesn't mention if close() is restartable, but these are:

read(2), readv(2), write(2), writev(2), ioctl(2), open(2),wait(2), wait3(2), wait4(2), waitid(2), and waitpid,accept(2), connect(2), recv(2), recvfrom(2), recvmsg(2), send(2), sendto(2), and sendmsg(2) flock(2) and fcntl(2) mq_receive(3), mq_timedreceive(3), mq_send(3), and mq_timedsend(3) sem_wait(3) and sem_timedwait(3) futex(2)

Note that those details, specifically the list of non-restartable calls, are Linux-specific

I posted a relevant question about which system calls are restartable and if it's specified by POSIX somewhere, it is specified by POSIX but it's optional, so you should check the list of non-restartable calls for your OS, if it's not there it should be restartable. This is my question: How to know if a Linux system call is restartable or not?

Update: Close is a special case it's not restartable and should not be retried in Linux, see this answer for more details: https://mcmap.net/q/742271/-how-to-know-if-a-linux-system-call-is-restartable-or-not

Manton answered 13/11, 2012 at 7:6 Comment(3)
You got this from man signal(7) on a Linux system? In case yes: this list is valid for Linux only. On the same man page it is stated: "The details vary across Unix systems"Indescribable
OK, can you point me to a list of functions guaranteed to be restartable by POSIX or something?Anorthic
close() must not be retried on every sane UNIX (HP-UX is the only common exception). Can we fix this answer, for the record?Fenella
K
2

Assuming you're after shorter code, you can try something like:

while (((rc = close (fd)) == -1) && (errno == EINTR));
if (rc == -1)
    complainBitterly (errno);

Assuming you're after more readable code in addition to shorter, just create a function:

int closeWithRetry (int fd);

and place your readable code in there. Then it doesn't really matter how long it is, it's still a one-liner where you call it, but you can make the function body itself very readable:

int closeWithRetry (int fd) {
    // Initial close attempt.

    int rc = close (fd);

    // As long as you failed with EINTR, keep trying.
    // Possibly with a limit (count or time-based).

    while ((rc == -1) && (errno == EINTR))
        rc = close (fd);

    // Once either success or non-retry failure, return error code.

    return rc;
}
Karlsruhe answered 13/11, 2012 at 6:43 Comment(0)
F
2

For the record: On essentially every UNIX, close() must not be retried if it returns EINTR. DO NOT put an EINTR retry-loop in place for close like you would for waitpid() or read(). See this page for more details: http://austingroupbugs.net/view.php?id=529 On linux, Solaris, BSD and others, retrying close() is incorrect. HP-UX is the only common(!) system I could find that requires this.

EINTR means something very different for read() and select() and waitpid() and so on than it does for close(). For most calls, you retry on EINTR because you asked for something to be done which blocks, and if you were interrupted that means it didn't happen, so you try again. For close(), the action you requested was for an entry to be removed from the fd table, which is instantaneous, without error, and will always happen no matter what close() returns.[*] The only reason close() blocks is that sometimes, for special semantics (like TCP linger), it can wait until I/O is done before returning. If close returns EINTR, that means that you asked it to wait but it couldn't. However, the fd was still closed; you just lost your chance to wait on it.

Conclusion: unless you know you can't receive signals, using close() for waiting is a very stupid thing to do. Use an application-level ACK (TCP) or an fsync (file I/O) to make sure any writes were completed before closing the fd.

[*] There is a caveat: if another thread of the process is inside a blocking syscall on the same fd, well, ... it depends.

Fenella answered 14/3, 2013 at 15:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.