Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR

From: Mikko Rantalainen

Date: Mon Sep 14 2026 - 05:48:48 EST


Rich Felker (2026-09-14 02:51 Europe/Helsinki):
> On Sun, Sep 13, 2026 at 11:42:23PM +0100, Matthew Wilcox wrote:
>> On Sun, Sep 13, 2026 at 10:38:14PM +0300, Mikko Rantalainen wrote:
>>> In addition, POSIX.1-2024 requires that if close() reports EINTR, the
>>> descriptor must remain open. It also explicitly permits an interrupted
>>> close() to return success after closing the descriptor.
>>
>> I think any filesystem / device driver / ... which returns -EINTR from
>> close() is broken. There is one exception though -- if the signal is
>> fatal. It's like read()/write() being killable; if the signal is fatal,
>> the task dies before it gets to see the errno. So it doesn't matter.
>>
>> So that's my preferred solution; track down the bad kernel code that's
>> doing things in close() that are "interruptible" and convert them to
>> "killable".
>
> The traditional things that admit EINTR in close are bad NFS
> implementations (which intentionally use interruptible instead of
> killable because network flaky) and tape drives (which at least
> historically performed rewinding as part of the close operation).
>
>> We don't want SIGWINCH or SIGALRM interrupting close();
>> that's just dumb.
>
> You don't want a slow close blocking delivery of SIGALRM either.
> Freezing the process during a long close is not the answer. The answer
> is just getting rid of long close and having it behave properly as a
> resource-handle-free-only operation and return immediately. But that's
> a more invasive change than just replacing -EINTR with 0 if/when it
> gets interrupted.


For ordinary filesystems where close-time work is not itself part of a
stronger synchronization contract, I think the answer should be fairly
clear: close() should not expose EINTR to userspace after consuming the
fd.

close() does not provide a durability guarantee. It *may* report
deferred I/O errors or errors discovered during close-time work, but
an application which requires synchronization has an explicit
interface such as fsync() or fdatasync() for that purpose.

So if such close-time work happens to be interrupted, I don't see what
useful recovery semantics exposing EINTR provides. The fd is already
gone and close() cannot safely be retried. Returning success seems more
useful than exposing an interruption which the caller cannot act on.

NFS and devices make the question more interesting, though. Some
implementations put meaningful state transitions into their close path.
Tape devices are an obvious historical example: depending on the
device/mode, close may write filemarks, flush tape buffers, rewind, etc.
Those operations are not necessarily replaceable by fsync() before
close().

But I think EINTR is problematic there too, for almost the opposite
reason. If the semantics require some close-time operation to complete,
then returning EINTR after the fd has already been removed leaves
userspace with no generic way to complete that operation:

close(fd) -> EINTR

- fd is already closed on Linux
- retrying close(fd) is unsafe
- the interrupted operation cannot be resumed through fd

That seems to leave a few possible designs:

1. The close-time operation is required. Then the kernel should complete
it despite non-fatal signals, or provide some explicit operation which
userspace can use to request/check completion.

2. The close-time operation is best-effort. Then interruption should not
turn an already completed descriptor release into EINTR; returning
success seems more appropriate.

3. POSIX.1-2024 also provides EINPROGRESS for the case where the fd has
been closed but close-time work is incomplete. That at least describes
the state correctly, although I still don't see what useful recovery
operation userspace has if the work can only be performed through the
now-consumed fd. In addition, the implementation in musl suggests
that this wouldn't be compatible with real-world programs.

What seems particularly difficult to justify is the current Linux result:

close(fd) -> EINTR

while the fd has nevertheless been irrevocably consumed. It has the
usual appearance of a retryable interruption without providing any safe
way to retry it.

So replacing -EINTR with 0 still looks attractive to me as the generic
close() behavior, even if removing all long synchronous close operations
would be a larger project.

It may also make sense independently to diagnose ->flush()
implementations which return an interruption result in cases where that
means required close-time work has been abandoned. I would be interested
in whether there is a good way to warn or trace those cases without
complaining about the fatal-signal case Matthew mentioned.

--
Mikko