Commit a904af9
Improve EINTR handling for close in
When `close(fd)` returns an error and `errno == EINTR`, it depends on the exact operating system used whether `fd` has been closed or not.
This is bad for us: if we *do* retry `close(fd)` but `fd` *was closed*, then it might be that `fd` has already been given out again, and we will close a file descriptor which is still in use. If we *do not* retry `close(fd)` and `fd` was *not closed*, we will leak `fd`.
It turns out that this is a rather complicated issue. I will try to first give an overview, then explain what the new code does.
### Overview
[POSIX-2016](https://pubs.opengroup.org/onlinepubs/9699919799.2016edition/functions/close.html) and earlier explicitly said that it is unspecified whether `fd` is closed in case it returns -1 with `errno == EINTR` (the text uses `filedes` instead of `fd`):
> If `close()` is interrupted by a signal that is to be caught, it shall return
> -1 with `errno` set to `[EINTR]` and the state of `fildes` is unspecified.
As one might expect given this, this means that everything is a mess. Some operating systems left `fd` open, others closed it. I could figure out the following:
* [Linux closes `fd`](https://man7.org/linux/man-pages/man2/close.2.html).
* [HP-UX keeps `fd` open](https://www.unix.com/man_page/hpux/2/close)
* MacOS keeps it open (at least this is implied by https://crbug.com/269623#comment1).
A good overview of the situation before POSIX.1-2024 is given in https://crbug.com/269623#comment1.
Then came [POSIX.1-2024](https://pubs.opengroup.org/onlinepubs/9799919799/functions/close.html). This explicitly specifies that if close returns EINTR it *must* keep `fd` open:
> If `close()` \[with argument `filedes`\] is interrupted by a signal that is to be caught, then it is unspecified whether it returns -1 with `errno` set to `[EINTR]` and `fildes` remaining open, or returns -1 with `errno` set to `[EINPROGRESS]` and `fildes` being closed, or returns 0 to indicate successful completion.
This actually sounds good for the code before this CL: it specifies that the code here is correct! Unfortunately for this code, it seems it doesn't matter to us what POSIX specifes: the Linux maintainers very clearly do not intend to adhere to this. [`man 2 close`](https://man7.org/linux/man-pages/man2/close.2.html) explicitly states in the end that Linux is non-conforming and will not change. The [corresponding discussion](https://lore.kernel.org/all/fskxqmcszalz6dmoak6de4c7bxt4juvc5zrpboae4dqw4y6aih@lskezjrbnsws/) from 2025-05-15 in the mailing list also explicitly states that it is possible that Linux returns EINTR and has `fd` closed.
### Proposal
Given all this, we hence propose the following:
* if POSIX_CLOSE_RESTART is defined, we assume we have a POSIX.1-2024 compliant system. In this case, we simply use [posix_close](https://pubs.opengroup.org/onlinepubs/9799919799/functions/close.html#tag_17_81) which allows us to specify the behavior to what we want.
* Otherwise, we fallback to simply calling close once. In the end, this is the safer option than retrying, since this will only leak `fd` if something goes wrong. If we call close twice when we should not, we risk closing `fd` while it is still in use.
### More references:
Chromium: [scoped_file](https://source.chromium.org/chromium/chromium/src/+/main:base/files/scoped_file.cc;drc=4bfcab51c9e72a7bdc1abb6c436a8e6b93dc1689) and [hack for MacOS](https://source.chromium.org/chromium/chromium/src/+/main:base/mac/close_nocancel.cc;l=51;drc=068b7e346d819075983f831a853bdf1da287973c)
Riegeli: [fd_handle](https://github.com/google/riegeli/blob/e1b2fedf24735a1221f44cd01a6d0d79a522d5aa/riegeli/bytes/fd_handle.h#L367-L380)
PiperOrigin-RevId: 861056844zero_copy_stream_impl.h
1 parent a0b7794 commit a904af9
1 file changed
Lines changed: 19 additions & 15 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
12 | 15 | | |
13 | 16 | | |
14 | 17 | | |
| |||
47 | 50 | | |
48 | 51 | | |
49 | 52 | | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
57 | 67 | | |
58 | 68 | | |
59 | 69 | | |
| |||
101 | 111 | | |
102 | 112 | | |
103 | 113 | | |
104 | | - | |
105 | | - | |
106 | | - | |
107 | | - | |
| 114 | + | |
108 | 115 | | |
109 | 116 | | |
110 | 117 | | |
| |||
178 | 185 | | |
179 | 186 | | |
180 | 187 | | |
181 | | - | |
182 | | - | |
183 | | - | |
184 | | - | |
| 188 | + | |
185 | 189 | | |
186 | 190 | | |
187 | 191 | | |
| |||
0 commit comments