* [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
@ 2026-09-13 19:38 Mikko Rantalainen
2026-09-13 19:38 ` [RFC PATCH 1/1] fs: don't return EINTR from close() Mikko Rantalainen
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Mikko Rantalainen @ 2026-09-13 19:38 UTC (permalink / raw)
To: linux-fsdevel
Cc: linux-api, linux-kernel, brauner, viro, jack, alx, dalias,
Mikko Rantalainen
This is an RFC because it deliberately changes a long-established raw
syscall ABI. Jan Kara raised userspace-regression concerns when this was
discussed in 2025:
https://lore.kernel.org/linux-fsdevel/ddqmhjc2rpzk2jjvunbt3l3eukcn4xzkocqzdg3j4msihdhzko@fizekvxndg2d/
while musl and Android bionic already normalize this result to success
in libc.
The Linux implementation of close() will always close the file descriptor
given as argument, except for the invalid file descriptor which will
return -EBADF.
However, currently Linux kernel will return EINTR in some cases for
close(). There is no good way for caller to recover from this case using
the original fd. Whatever action was actually interrupted cannot be
resumed or retried through this fd, because the fd has already been
consumed. Even worse, EINTR conventionally invites retrying an operation,
but retrying close() is unsafe: the same file descriptor number may
already refer to another file opened by another thread by the time
close() returns EINTR.
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.
This patch is about implementing the second option to be compatible with
both POSIX.1-2024 and real-world applications.
Since close() on Linux first relinquishes the file descriptor and only
then performs ->flush() work, interruption of that later work cannot be
recovered through the original fd. No matter how important that
close-time work was, ownership of the fd has already been irrevocably
relinquished.
For regular files, applications requiring durability already need an
explicit synchronization operation such as fsync() or fdatasync() before
relinquishing the fd. This proposal does not suppress meaningful
delayed-I/O errors such as EIO, ENOSPC, or EDQUOT; it only changes
interruption results whose conventional recovery action (retrying the
operation) is unsafe for close().
Historical discussions about this subject:
- https://inbox.sourceware.org/libc-alpha/efaffc5a404cf104f225c26dbc96e0001cede8f9.1747399542.git.alx@kernel.org/T/
- https://sourceware.org/pipermail/libc-alpha/2025-May/166675.html
- https://lkml.rescloud.iu.edu/hypermail/linux/kernel/2205.3/06731.html
- https://lwn.net/Articles/576478/
- https://yarchive.net/comp/linux/must_check.html
- https://sourceware.org/pipermail/libc-alpha/2025-May/166907.html
- https://sourceware.org/pipermail/libc-alpha/2025-May/166722.html
POSIX.1-2024 also allows EINPROGRESS after the descriptor has been closed.
I considered using that result, but it appears less useful than success
for Linux. It would preserve diagnostic information about interrupted
close-time work, but there is no operation the caller can perform on the
original fd to resume or complete that work. It would therefore turn an
irrevocably completed ownership transfer into an apparent failure without
providing a recovery path. Returning success avoids that ambiguity and
still leaves genuinely useful delayed-I/O errors such as EIO, ENOSPC, and
EDQUOT untouched.
This also matches the direction taken by musl, which initially used
EINPROGRESS for this case and later changed to success because existing
applications were prone to interpret EINPROGRESS as a failure and could
incorrectly infer that the fd was still open.
Automatically replacing EINTR with success does change the *observable*
raw syscall ABI for applications that distinguish EINTR from successful
close(). For applications that already treat the descriptor as consumed,
this changes control flow to the normal successful-close path. I would be
particularly interested in concrete examples where distinguishing EINTR
provides useful recovery semantics, given that the original fd has
already been consumed and cannot be used to resume the interrupted
close-time work.
Mikko Rantalainen (1):
fs: don't return EINTR from close()
fs/open.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH 1/1] fs: don't return EINTR from close()
2026-09-13 19:38 [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR Mikko Rantalainen
@ 2026-09-13 19:38 ` Mikko Rantalainen
2026-09-13 20:53 ` [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR Rich Felker
2026-09-13 22:42 ` Matthew Wilcox
2 siblings, 0 replies; 5+ messages in thread
From: Mikko Rantalainen @ 2026-09-13 19:38 UTC (permalink / raw)
To: linux-fsdevel
Cc: linux-api, linux-kernel, brauner, viro, jack, alx, dalias,
Mikko Rantalainen
close() removes the file descriptor from the descriptor table before
calling filp_flush(). Consequently, once file_close_fd() succeeds,
the descriptor is closed regardless of the result returned by
filp_flush() and the same fd number may immediately be reused.
Without this patch an interruptible ->flush() can nevertheless cause
close() to return EINTR, either directly or after an internal -ERESTART*
error is translated to EINTR.
This is particularly problematic for close(). EINTR conventionally
indicates an interrupted operation that may need to be retried, but
retrying close() is unsafe: another thread may already have reused the
descriptor number, causing the retry to close an unrelated file.
There is also no recovery operation the caller can perform through the
original descriptor, since it has already been removed from the
descriptor table.
Treat interruption after descriptor removal as successful close instead.
Continue to report other errors from ->flush(), such as delayed I/O
errors.
This also makes this case compatible with POSIX.1-2024. POSIX permits
an interrupted close() that has closed the descriptor to return success,
whereas if close() reports EINTR, POSIX requires the descriptor to
remain open.
musl and Android bionic already normalize EINTR from Linux close() to
success in userspace, providing substantial deployed precedent for this
behavior.
Signed-off-by: Mikko Rantalainen <mikko.rantalainen@peda.net>
---
fs/open.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/fs/open.c b/fs/open.c
index 408925d7bd0b..81a43b6c5b6b 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -1513,12 +1513,17 @@ SYSCALL_DEFINE1(close, unsigned int, fd)
if (likely(retval == 0))
return 0;
- /* can't restart close syscall because file table entry was cleared */
- if (retval == -ERESTARTSYS ||
+ /*
+ * The file descriptor has already been closed, so an interrupted
+ * close cannot be restarted safely. Do not report EINTR after the
+ * descriptor has been detached.
+ */
+ if (retval == -EINTR ||
+ retval == -ERESTARTSYS ||
retval == -ERESTARTNOINTR ||
retval == -ERESTARTNOHAND ||
retval == -ERESTART_RESTARTBLOCK)
- retval = -EINTR;
+ retval = 0;
return retval;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
2026-09-13 19:38 [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR Mikko Rantalainen
2026-09-13 19:38 ` [RFC PATCH 1/1] fs: don't return EINTR from close() Mikko Rantalainen
@ 2026-09-13 20:53 ` Rich Felker
2026-09-13 21:27 ` Alejandro Colomar
2026-09-13 22:42 ` Matthew Wilcox
2 siblings, 1 reply; 5+ messages in thread
From: Rich Felker @ 2026-09-13 20:53 UTC (permalink / raw)
To: Mikko Rantalainen
Cc: linux-fsdevel, linux-api, linux-kernel, brauner, viro, jack, alx
On Sun, Sep 13, 2026 at 10:38:14PM +0300, Mikko Rantalainen wrote:
> This is an RFC because it deliberately changes a long-established raw
> syscall ABI. Jan Kara raised userspace-regression concerns when this was
> discussed in 2025:
>
> https://lore.kernel.org/linux-fsdevel/ddqmhjc2rpzk2jjvunbt3l3eukcn4xzkocqzdg3j4msihdhzko@fizekvxndg2d/
>
> while musl and Android bionic already normalize this result to success
> in libc.
>
> The Linux implementation of close() will always close the file descriptor
> given as argument, except for the invalid file descriptor which will
> return -EBADF.
>
> However, currently Linux kernel will return EINTR in some cases for
> close(). There is no good way for caller to recover from this case using
> the original fd. Whatever action was actually interrupted cannot be
> resumed or retried through this fd, because the fd has already been
> consumed. Even worse, EINTR conventionally invites retrying an operation,
> but retrying close() is unsafe: the same file descriptor number may
> already refer to another file opened by another thread by the time
> close() returns EINTR.
>
> 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.
>
> This patch is about implementing the second option to be compatible with
> both POSIX.1-2024 and real-world applications.
>
> Since close() on Linux first relinquishes the file descriptor and only
> then performs ->flush() work, interruption of that later work cannot be
> recovered through the original fd. No matter how important that
> close-time work was, ownership of the fd has already been irrevocably
> relinquished.
>
> For regular files, applications requiring durability already need an
> explicit synchronization operation such as fsync() or fdatasync() before
> relinquishing the fd. This proposal does not suppress meaningful
> delayed-I/O errors such as EIO, ENOSPC, or EDQUOT; it only changes
> interruption results whose conventional recovery action (retrying the
> operation) is unsafe for close().
>
> Historical discussions about this subject:
>
> - https://inbox.sourceware.org/libc-alpha/efaffc5a404cf104f225c26dbc96e0001cede8f9.1747399542.git.alx@kernel.org/T/
>
> - https://sourceware.org/pipermail/libc-alpha/2025-May/166675.html
>
> - https://lkml.rescloud.iu.edu/hypermail/linux/kernel/2205.3/06731.html
>
> - https://lwn.net/Articles/576478/
>
> - https://yarchive.net/comp/linux/must_check.html
>
> - https://sourceware.org/pipermail/libc-alpha/2025-May/166907.html
>
> - https://sourceware.org/pipermail/libc-alpha/2025-May/166722.html
>
>
> POSIX.1-2024 also allows EINPROGRESS after the descriptor has been closed.
> I considered using that result, but it appears less useful than success
> for Linux. It would preserve diagnostic information about interrupted
> close-time work, but there is no operation the caller can perform on the
> original fd to resume or complete that work. It would therefore turn an
> irrevocably completed ownership transfer into an apparent failure without
> providing a recovery path. Returning success avoids that ambiguity and
> still leaves genuinely useful delayed-I/O errors such as EIO, ENOSPC, and
> EDQUOT untouched.
>
> This also matches the direction taken by musl, which initially used
> EINPROGRESS for this case and later changed to success because existing
> applications were prone to interpret EINPROGRESS as a failure and could
> incorrectly infer that the fd was still open.
>
> Automatically replacing EINTR with success does change the *observable*
> raw syscall ABI for applications that distinguish EINTR from successful
> close(). For applications that already treat the descriptor as consumed,
> this changes control flow to the normal successful-close path. I would be
> particularly interested in concrete examples where distinguishing EINTR
> provides useful recovery semantics, given that the original fd has
> already been consumed and cannot be used to resume the interrupted
> close-time work.
>
> Mikko Rantalainen (1):
> fs: don't return EINTR from close()
>
> fs/open.c | 11 ++++++++---
> 1 file changed, 8 insertions(+), 3 deletions(-)
>
> --
> 2.43.0
Hi! I'm one of the first people who pressed this issue while tracking
down the POSIX model for how side effects are supposed to work with
respect to EINTR and how that relates to thread cancellation, and how
glibc was getting all this stuff wrong, back around 2011-2012.
I don't think there is serious concern about userspace regressions
making this change. It would not be changing the meaning of any
existing result code or adding a new error condition applications need
to be aware of (like the EINPROGRESS mess).
But I'm also not sure how helpful the change would be. It's already
possible to patch this up in userspace, and as you noted, we already
do that in musl and so does Bionic. So the main practical effect of
this change would be just forcing the right behavior on glibc systems
even when glibc doesn't want to fix it. Maybe that's a good idea? I'm
not sure. I think it would be best to have everyone on the same page
that this should be fixed, with both glibc fixing it so it's right on
old-kernel/new-glibc, and the kernel fixing it so it's right on
new-kernel/old-glibc. That would also avoid hard feelings from a
unilateral action perceived as dictatorial.
Rich
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
2026-09-13 20:53 ` [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR Rich Felker
@ 2026-09-13 21:27 ` Alejandro Colomar
0 siblings, 0 replies; 5+ messages in thread
From: Alejandro Colomar @ 2026-09-13 21:27 UTC (permalink / raw)
To: Rich Felker
Cc: Mikko Rantalainen, linux-fsdevel, linux-api, linux-kernel,
brauner, viro, jack
[-- Attachment #1: Type: text/plain, Size: 6008 bytes --]
> Date: 2026-09-13 16:53:58-0400
> From: Rich Felker <dalias@libc.org>
>
> On Sun, Sep 13, 2026 at 10:38:14PM +0300, Mikko Rantalainen wrote:
> > This is an RFC because it deliberately changes a long-established raw
> > syscall ABI. Jan Kara raised userspace-regression concerns when this was
> > discussed in 2025:
> >
> > https://lore.kernel.org/linux-fsdevel/ddqmhjc2rpzk2jjvunbt3l3eukcn4xzkocqzdg3j4msihdhzko@fizekvxndg2d/
> >
> > while musl and Android bionic already normalize this result to success
> > in libc.
> >
> > The Linux implementation of close() will always close the file descriptor
> > given as argument, except for the invalid file descriptor which will
> > return -EBADF.
> >
> > However, currently Linux kernel will return EINTR in some cases for
> > close(). There is no good way for caller to recover from this case using
> > the original fd. Whatever action was actually interrupted cannot be
> > resumed or retried through this fd, because the fd has already been
> > consumed. Even worse, EINTR conventionally invites retrying an operation,
> > but retrying close() is unsafe: the same file descriptor number may
> > already refer to another file opened by another thread by the time
> > close() returns EINTR.
> >
> > 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.
> >
> > This patch is about implementing the second option to be compatible with
> > both POSIX.1-2024 and real-world applications.
> >
> > Since close() on Linux first relinquishes the file descriptor and only
> > then performs ->flush() work, interruption of that later work cannot be
> > recovered through the original fd. No matter how important that
> > close-time work was, ownership of the fd has already been irrevocably
> > relinquished.
> >
> > For regular files, applications requiring durability already need an
> > explicit synchronization operation such as fsync() or fdatasync() before
> > relinquishing the fd. This proposal does not suppress meaningful
> > delayed-I/O errors such as EIO, ENOSPC, or EDQUOT; it only changes
> > interruption results whose conventional recovery action (retrying the
> > operation) is unsafe for close().
> >
> > Historical discussions about this subject:
> >
> > - https://inbox.sourceware.org/libc-alpha/efaffc5a404cf104f225c26dbc96e0001cede8f9.1747399542.git.alx@kernel.org/T/
> >
> > - https://sourceware.org/pipermail/libc-alpha/2025-May/166675.html
> >
> > - https://lkml.rescloud.iu.edu/hypermail/linux/kernel/2205.3/06731.html
> >
> > - https://lwn.net/Articles/576478/
> >
> > - https://yarchive.net/comp/linux/must_check.html
> >
> > - https://sourceware.org/pipermail/libc-alpha/2025-May/166907.html
> >
> > - https://sourceware.org/pipermail/libc-alpha/2025-May/166722.html
> >
> >
> > POSIX.1-2024 also allows EINPROGRESS after the descriptor has been closed.
> > I considered using that result, but it appears less useful than success
> > for Linux. It would preserve diagnostic information about interrupted
> > close-time work, but there is no operation the caller can perform on the
> > original fd to resume or complete that work. It would therefore turn an
> > irrevocably completed ownership transfer into an apparent failure without
> > providing a recovery path. Returning success avoids that ambiguity and
> > still leaves genuinely useful delayed-I/O errors such as EIO, ENOSPC, and
> > EDQUOT untouched.
> >
> > This also matches the direction taken by musl, which initially used
> > EINPROGRESS for this case and later changed to success because existing
> > applications were prone to interpret EINPROGRESS as a failure and could
> > incorrectly infer that the fd was still open.
> >
> > Automatically replacing EINTR with success does change the *observable*
> > raw syscall ABI for applications that distinguish EINTR from successful
> > close(). For applications that already treat the descriptor as consumed,
> > this changes control flow to the normal successful-close path. I would be
> > particularly interested in concrete examples where distinguishing EINTR
> > provides useful recovery semantics, given that the original fd has
> > already been consumed and cannot be used to resume the interrupted
> > close-time work.
> >
> > Mikko Rantalainen (1):
> > fs: don't return EINTR from close()
> >
> > fs/open.c | 11 ++++++++---
> > 1 file changed, 8 insertions(+), 3 deletions(-)
> >
> > --
> > 2.43.0
>
> Hi! I'm one of the first people who pressed this issue while tracking
> down the POSIX model for how side effects are supposed to work with
> respect to EINTR and how that relates to thread cancellation, and how
> glibc was getting all this stuff wrong, back around 2011-2012.
>
> I don't think there is serious concern about userspace regressions
> making this change. It would not be changing the meaning of any
> existing result code or adding a new error condition applications need
> to be aware of (like the EINPROGRESS mess).
>
> But I'm also not sure how helpful the change would be. It's already
> possible to patch this up in userspace, and as you noted, we already
> do that in musl and so does Bionic. So the main practical effect of
> this change would be just forcing the right behavior on glibc systems
> even when glibc doesn't want to fix it. Maybe that's a good idea? I'm
> not sure. I think it would be best to have everyone on the same page
> that this should be fixed, with both glibc fixing it so it's right on
> old-kernel/new-glibc, and the kernel fixing it so it's right on
> new-kernel/old-glibc. That would also avoid hard feelings from a
> unilateral action perceived as dictatorial.
Acked-by: Alejandro Colomar <alx@kernel.org>
>
> Rich
--
<https://www.alejandro-colomar.es>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
2026-09-13 19:38 [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR Mikko Rantalainen
2026-09-13 19:38 ` [RFC PATCH 1/1] fs: don't return EINTR from close() Mikko Rantalainen
2026-09-13 20:53 ` [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR Rich Felker
@ 2026-09-13 22:42 ` Matthew Wilcox
2 siblings, 0 replies; 5+ messages in thread
From: Matthew Wilcox @ 2026-09-13 22:42 UTC (permalink / raw)
To: Mikko Rantalainen
Cc: linux-fsdevel, linux-api, linux-kernel, brauner, viro, jack, alx,
dalias
On Sun, Sep 13, 2026 at 10:38:14PM +0300, Mikko Rantalainen wrote:
> However, currently Linux kernel will return EINTR in some cases for
> close(). There is no good way for caller to recover from this case using
> the original fd. Whatever action was actually interrupted cannot be
> resumed or retried through this fd, because the fd has already been
> consumed. Even worse, EINTR conventionally invites retrying an operation,
> but retrying close() is unsafe: the same file descriptor number may
> already refer to another file opened by another thread by the time
> close() returns EINTR.
>
> 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". We don't want SIGWINCH or SIGALRM interrupting close();
that's just dumb.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-13 22:42 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-13 19:38 [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR Mikko Rantalainen
2026-09-13 19:38 ` [RFC PATCH 1/1] fs: don't return EINTR from close() Mikko Rantalainen
2026-09-13 20:53 ` [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR Rich Felker
2026-09-13 21:27 ` Alejandro Colomar
2026-09-13 22:42 ` Matthew Wilcox
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.