mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [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; 15+ 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] 15+ 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; 15+ 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	[flat|nested] 15+ 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-14  6:36   ` Mikko Rantalainen
  2026-09-13 22:42 ` Matthew Wilcox
  2 siblings, 2 replies; 15+ 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] 15+ 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
  2026-09-14  6:36   ` Mikko Rantalainen
  1 sibling, 0 replies; 15+ 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] 15+ 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
  2026-09-13 23:51   ` Rich Felker
  2026-09-14  9:07   ` Mikko Rantalainen
  2 siblings, 2 replies; 15+ 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] 15+ messages in thread

* Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
  2026-09-13 22:42 ` Matthew Wilcox
@ 2026-09-13 23:51   ` Rich Felker
  2026-09-14  9:38     ` Mikko Rantalainen
  2026-09-14  9:07   ` Mikko Rantalainen
  1 sibling, 1 reply; 15+ messages in thread
From: Rich Felker @ 2026-09-13 23:51 UTC (permalink / raw)
  To: Matthew Wilcox
  Cc: Mikko Rantalainen, linux-fsdevel, linux-api, linux-kernel,
	brauner, viro, jack, alx

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:
> > 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".

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.

Rich

^ permalink raw reply	[flat|nested] 15+ 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
@ 2026-09-14  6:36   ` Mikko Rantalainen
  2026-09-14  9:44     ` Alejandro Colomar
  1 sibling, 1 reply; 15+ messages in thread
From: Mikko Rantalainen @ 2026-09-14  6:36 UTC (permalink / raw)
  To: Rich Felker
  Cc: linux-fsdevel, linux-api, linux-kernel, brauner, viro, jack, alx

Rich Felker (2026-09-13 23:53 Europe/Helsinki):
> 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.
>>
>> This patch is about implementing the second option to be compatible with
>> both POSIX.1-2024 and real-world applications.
> ...
> 
> 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.

I think there are two important questions:


(1) What is the caller truly expected to do for EINTR? They cannot
retry which would be the typical response to EINTR.

(2) If caller cannot retry because file was already closed, can caller
have any valuable information from EINTR return instead of success?
I think it's pretty safe assumption that success from close() would
be handled correctly by the caller.


I'm currently thinking that the answer to (1) is "???" and answer to
(2) is best you can do is to log "Some unknown action interrupted
some unknown background process while closing file XYZ, pretending
everything went well." because anything else would assume some
specific implementation of close() on Linux.

As I see it, the fact that avoiding EINTR return value when the file
is already closed fixing POSIX.1-2024 compatibility and glibc is just
a bonus for a change that makes sense otherwise, too.

-- 
Mikko


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
  2026-09-13 22:42 ` Matthew Wilcox
  2026-09-13 23:51   ` Rich Felker
@ 2026-09-14  9:07   ` Mikko Rantalainen
  2026-09-14 12:40     ` Mikko Rantalainen
  1 sibling, 1 reply; 15+ messages in thread
From: Mikko Rantalainen @ 2026-09-14  9:07 UTC (permalink / raw)
  To: Matthew Wilcox
  Cc: linux-fsdevel, linux-api, linux-kernel, brauner, viro, jack, alx, dalias

Matthew Wilcox (2026-09-14 01:42 Europe/Helsinki):
> 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.

Am I reading this correctly as a proposed VFS invariant: ->flush() must
not return an interruption result which could become visible as EINTR
to a surviving userspace caller of close()? A fatal signal is the
exception because the task will die before observing the return value.

That seems like a reasonable invariant, but I couldn't find it
documented anywhere. Documentation/filesystems/vfs.rst currently says
only:

    flush
        called by the close(2) system call to flush a file

without specifying allowed return values or signal semantics.

I had originally approached this from the userspace contract. close(2)
guarantees relinquishing the descriptor, but does not provide a general
synchronization guarantee. In particular, successful close() does not
mean regular-file data has reached storage. Applications which require
that guarantee need an explicit synchronization operation such as
fsync() or fdatasync() before close().

The close(2) documentation does say that later close-time operations,
including flushing data to a filesystem or device, *can* report errors.
But I don't read it as guaranteeing that all such work completes or that
all outstanding errors are discovered before close() returns.

That's why I was thinking EINTR should simply be converted to success
by close() implementation.

So I think the important distinction is:

1. userspace is not generally promised completion of arbitrary
   close-time work; but

2. if a subsystem deliberately performs synchronous work in ->flush(),
   the kernel may nevertheless require that work to complete for the
   subsystem's own semantics.

If (2) is the intended VFS rule, then I agree that converting EINTR to
success in close() would hide a bug rather than fix it. The bug would
be an ->flush() implementation allowing an ordinary signal to abandon
work which it intended to perform synchronously.

Would it make sense to document that invariant explicitly, e.g. that
->flush() must not return -EINTR or -ERESTART* due to an ordinary
non-fatal signal?

If those results should be considered implementation bugs, perhaps a
useful diagnostic would also be something along these lines:

---
retval = filp_flush(file, current->files);

WARN_ONCE(retval == -EINTR ||
          retval == -ERESTARTSYS ||
          retval == -ERESTARTNOINTR ||
          retval == -ERESTARTNOHAND ||
          retval == -ERESTART_RESTARTBLOCK,
          "close: ->flush %ps returned interrupt error %d\n",
          file->f_op->flush, retval);
---

That would leave the existing userspace ABI unchanged while making
remaining offending implementations easier to find and fix.

I also considered retrying filp_flush() inside close(), but I don't
think that can be done generically. ->flush() is not documented as
safe to restart from the beginning after partial execution, and
an interruptible wait could immediately encounter the same
still-pending signal again. So fixing the interruptibility at the
offending wait seems safer if the above invariant is indeed
the intended one.

-- 
Mikko


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
  2026-09-13 23:51   ` Rich Felker
@ 2026-09-14  9:38     ` Mikko Rantalainen
  2026-09-16 23:16       ` Andy Lutomirski
  0 siblings, 1 reply; 15+ messages in thread
From: Mikko Rantalainen @ 2026-09-14  9:38 UTC (permalink / raw)
  To: Rich Felker, Matthew Wilcox
  Cc: linux-fsdevel, linux-api, linux-kernel, brauner, viro, jack, alx

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


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
  2026-09-14  6:36   ` Mikko Rantalainen
@ 2026-09-14  9:44     ` Alejandro Colomar
  0 siblings, 0 replies; 15+ messages in thread
From: Alejandro Colomar @ 2026-09-14  9:44 UTC (permalink / raw)
  To: Mikko Rantalainen
  Cc: Rich Felker, linux-fsdevel, linux-api, linux-kernel, brauner, viro, jack

[-- Attachment #1: Type: text/plain, Size: 3190 bytes --]

Hi Mikko,

> Date: 2026-09-14 09:36:58+0300
> From: Mikko Rantalainen <mikko.rantalainen@peda.net>
>
> Rich Felker (2026-09-13 23:53 Europe/Helsinki):
> > 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.
> >>
> >> This patch is about implementing the second option to be compatible with
> >> both POSIX.1-2024 and real-world applications.
> > ...
> > 
> > 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.
> 
> I think there are two important questions:
> 
> 
> (1) What is the caller truly expected to do for EINTR? They cannot
> retry which would be the typical response to EINTR.
> 
> (2) If caller cannot retry because file was already closed, can caller
> have any valuable information from EINTR return instead of success?
> I think it's pretty safe assumption that success from close() would
> be handled correctly by the caller.

There's one thing a caller might do:

Let's say I have a program doing a backup, and close(2) reports EINTR.
I might decide that I don't trust any of what happened, and start the
backup again from scratch.

Of course, I should have sync(2)ed before calling close(2).  So this
should be a non-issue in the first place.

This is the one thing that makes me think it might be better to keep
close(2) broken: it would be a reminder that people should flush
everything before calling close(2) if they care about their files.


Have a lovely day!
Alex

> 
> 
> I'm currently thinking that the answer to (1) is "???" and answer to
> (2) is best you can do is to log "Some unknown action interrupted
> some unknown background process while closing file XYZ, pretending
> everything went well." because anything else would assume some
> specific implementation of close() on Linux.
> 
> As I see it, the fact that avoiding EINTR return value when the file
> is already closed fixing POSIX.1-2024 compatibility and glibc is just
> a bonus for a change that makes sense otherwise, too.
> 
> -- 
> Mikko
> 

-- 
<https://www.alejandro-colomar.es>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
  2026-09-14  9:07   ` Mikko Rantalainen
@ 2026-09-14 12:40     ` Mikko Rantalainen
  0 siblings, 0 replies; 15+ messages in thread
From: Mikko Rantalainen @ 2026-09-14 12:40 UTC (permalink / raw)
  To: Matthew Wilcox
  Cc: linux-fsdevel, linux-api, linux-kernel, brauner, viro, jack, alx, dalias

Mikko Rantalainen (2026-09-14 12:07 Europe/Helsinki):
> ---
> retval = filp_flush(file, current->files);
> 
> WARN_ONCE(retval == -EINTR ||
>           retval == -ERESTARTSYS ||
>           retval == -ERESTARTNOINTR ||
>           retval == -ERESTARTNOHAND ||
>           retval == -ERESTART_RESTARTBLOCK,
>           "close: ->flush %ps returned interrupt error %d\n",
>           file->f_op->flush, retval);
> ---
> 
> That would leave the existing userspace ABI unchanged while making
> remaining offending implementations easier to find and fix.
> 
> I also considered retrying filp_flush() inside close(), but I don't
> think that can be done generically. ->flush() is not documented as
> safe to restart from the beginning after partial execution, and
> an interruptible wait could immediately encounter the same
> still-pending signal again. So fixing the interruptibility at the
> offending wait seems safer if the above invariant is indeed
> the intended one.

Another thing I noticed is that there are already several paths where
the kernel calls filp_close() and intentionally ignores its return value.

For example, close_files() does:

    filp_close(file, files);

without checking the result. The same is true for do_close_on_exec(),
and close_range() explicitly says:

    Currently, errors to close a given file descriptor are ignored.

So I don't think a ->flush() implementation can rely on returning EINTR
and having somebody retry the interrupted operation. There are valid
close paths where nobody will ever see that return value, even when the
process itself continues running.

This seems to strengthen Matthew's point: if some work performed by
->flush() is required for correctness, that work has to tolerate these
close paths without depending on userspace retry. Returning an
interruption result cannot be the recovery mechanism.

I'm therefore leaning towards treating an observable -EINTR/-ERESTART*
from ->flush() as suspicious in general, rather than just special-casing
the close(2) syscall. The fatal-signal case is harmless because the task
will not observe the result, but close-on-exec and close_range() show
that unobserved filp_close() errors are already part of normal operation
as well.

That also makes me think documenting the intended ->flush() contract
would be useful: if required close-time work must not depend on the
caller retrying filp_close(), that seems like an important invariant for
implementations to know.

What guarantees must file_operations::flush provide when its caller may
have no way to act on its return value?

In any case, I'm now thinking that returning EINTR for close() is a bug
when file descriptor is already freed. I think the only question is how
it should be solved. I initially thought it should just be mapped to
success. Maybe it should be logged as subsystem bug *and* mapped to
success for userspace instead?

-- 
Mikko

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
  2026-09-14  9:38     ` Mikko Rantalainen
@ 2026-09-16 23:16       ` Andy Lutomirski
  2026-09-17 10:37         ` Mikko Rantalainen
  0 siblings, 1 reply; 15+ messages in thread
From: Andy Lutomirski @ 2026-09-16 23:16 UTC (permalink / raw)
  To: Mikko Rantalainen
  Cc: Rich Felker, Matthew Wilcox, linux-fsdevel, linux-api,
	linux-kernel, brauner, viro, jack, alx

On Mon, Sep 14, 2026 at 2:44 AM Mikko Rantalainen
<mikko.rantalainen@peda.net> wrote:

> 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().

I realize this would be complex and maybe a can of worms, but maybe we
should have a new improved syscall here.  close() is indeed deeply
problematic.

On the one hand, we have the actual task of closing an fd in the sense
of removing it from the table.  This should really be doable without
blocking or without side effects (except possibly for zapping
old-style POSIX locks -- it doesn't really make sense to be able to
close all fds to a file while still keeping it locked, especially
since we report the pid of the lock-holding process).

On the other hand, close has actual *meaningful* effects, many of
which you've mentioned in your email.  IMO it would be really nice to
be able to explicitly *do* those effects separately from closing the
fd, maybe even asynchronously via io_uring.

Would it be so bad to have a new operation to do (possibly with
moderately fine control) the close work and another one to just
release fd- and process-associated locks and drop the fd?

(Hmm, for OFD locks, it really does make sense to unlock when the OFD
goes away.  For old-style locks, maybe we want a way to release an fd
without dropping the lock if it's not the last reference to the
underlying file in the process.  But maybe specifying and implementing
that would be a nightmare.)

--Andy

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
  2026-09-16 23:16       ` Andy Lutomirski
@ 2026-09-17 10:37         ` Mikko Rantalainen
  2026-09-17 17:03           ` Andy Lutomirski
  0 siblings, 1 reply; 15+ messages in thread
From: Mikko Rantalainen @ 2026-09-17 10:37 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: Rich Felker, Matthew Wilcox, linux-fsdevel, linux-api,
	linux-kernel, brauner, viro, jack, alx

On 9/17/26 02:16, Andy Lutomirski wrote:
> On Mon, Sep 14, 2026 at 2:44 AM Mikko Rantalainen
> <mikko.rantalainen@peda.net> wrote:
> 
>> 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().
> 
> I realize this would be complex and maybe a can of worms, but maybe we
> should have a new improved syscall here.  close() is indeed deeply
> problematic.
> 
> On the one hand, we have the actual task of closing an fd in the sense
> of removing it from the table.  This should really be doable without
> blocking or without side effects (except possibly for zapping
> old-style POSIX locks -- it doesn't really make sense to be able to
> close all fds to a file while still keeping it locked, especially
> since we report the pid of the lock-holding process).
> 
> On the other hand, close has actual *meaningful* effects, many of
> which you've mentioned in your email.  IMO it would be really nice to
> be able to explicitly *do* those effects separately from closing the
> fd, maybe even asynchronously via io_uring.
> 
> Would it be so bad to have a new operation to do (possibly with
> moderately fine control) the close work and another one to just
> release fd- and process-associated locks and drop the fd?

I think introducing yet another syscall would be bad replacement
for the *inability to decide the exact semantics* we want to use for
the current syscalls, especially close().

It's pretty clear that close() is poorly defined right now. The only
thing that we know for sure is that it releases the file descriptor
and in case of Linux, this happens for sure unless EBADF is returned.

It doesn't matter if you get EIO, EINTR or some other more or less
important sounding error code, the file descriptor will be closed
anyway. It's never possible to workaround the issue using the file
descriptor passed to close() after getting an error because the
file descriptor is not usable no matter the error code.

So all these extra errors happen because of practically undocumented
side-effects which may or may not be optional (that is, even POSIX
defines flushing to maybe happen with some unspecified timeout) and
the close() may return before the flush is complete even though it
apparently should be somewhat synchronous according to POSIX.

However, an implementation doing nothing (logically using zero
length timeout for the best-effort part to maximize performance)
would still appear to be POSIX compatible without *ever* doing
anything else but releasing the file descriptor.

That said, I would assume that for real-world compatibility,
close() must be able to report at least ENOSPC and EIO because
of existing userland code. Otherwise existing programs failing
to call fsync() or fdatasync() could cause silent data loss
too often in practice.

I still think that close() should never emit any error code that
even suggests that the caller should retry. This is because there
is no way to ever retry the close() call on Linux because
the file descriptor is always released.

Returning error from close() makes about same amount of sense
as returning an error code from free().

As a result, close() should internally convert any error codes
suggesting a retry requirement such as EINTR, ERESTART* into
either success or EIO depending on how the caller is expected
to handle the case.

And even then, considering that kernel-internal close_files(),
do_close_on_exec() and close_range() all call filp_close()
without ever checking the return value, any implementation
of filp_close() (basically ->flush()) returning error
that suggests that retry would be *required* is probably buggy.

So even the internal interface filp_close() is poorly defined.
What the caller is expected to do if EINTR, ERESTART* is
returned? Currently in-kernel clients do nothing and userland
programs receive EINTR without any real documentation what is
supposed to happen next. The best we have is a suggestion
to *maybe* log it somewhere and pretend it was success or
failure basically randomly because the correct intepretation
is never defined anywhere.

The only thing we know for sure on Linux, is that returned
EINTR does not allow restart attempt which would be the
correct action to take according to latest POSIX spec.

That's why I think the only sensible thing is to convert
EINTR into success if file descriptor is always released as
in current kernel implementation.

We don't have EMAYBESOMEWHATFAILED which would better describe
the current implementation causing EINTR.

If any kernel driver or subsystem is returning EINTR, ERESTART*
for filp_close(), there's practically no way *any caller* would
ever retry. Therefore any code emitting those errors for
filp_close() is highly probably a bug and would probably need
to be changed to either zero or EIO or some other error code
that actually makes sense for a call where nobody is going to
retry, ever.

If we could decide on actual semantics of close() first, then
we can tell if the "rewind tape synchronously on close()" is
a bug or feature.

I'd prefer style where close() would always release the file
handle and do nothing else. And fsync(fildes) should be
defined to handle the side-effects that historically were
*sometimes* caused by call to close().

I see no problem using fsync(fildes) on tape device to mean
complete all the writing and rewind the tape. If that results in
error, the client can retry as many time as needed. And close()
would just signal, I'm done trying / I accept the results.

Then close() would *always* be successful if you pass a valid
file descriptor to it. This would make a lot of sense for
a syscall that's supposed to release resources.

The POSIX style may result in state where file descriptor can
never be relased if the kernel keeps returning EINTR. That might
be even worse than the current Linux implementation which closes
the file even in case of EINTR, even if the returned value
does not make sense considering the file descriptor was actually
closed, as requested.

As a result, EINTR should never be returned from close() on Linux
because there is no sensible way to proceed after that, other
than pretend it was "success but spelled weirdly".


Here's my suggestion for improved semantics for close():

   close(int fildes)

     Close the file descriptor fildes, or return EBADF if
     given fildes didn't refer to any open file.

     The return value *may* be EIO or ENOSPC if the kernel
     immediately knows that writing the buffer will fail.
     The file descriptor is closed even in this case.

     Otherwise, 0 (success) is returned.

     If calling code needs to know if EIO or ENOSPC happens
     before all the data has been stored in permanent storage,
     the calling code must use fsync() or fdatasync()
     with the file descriptor before calling close().


(I would love to also include "closing the file descriptor
immediately releases all locks acquired via the file descriptor"
but I'm not sure if that's compatible with the current implementation.)

As a result, close() would then *never* return anything else but
success (zero), EIO or NOSPC. And both EIO and NOSPC would be
returned immediately (if status is known) before waiting even
a millisecond for any kind of flush synchronously.

Any other error from internal kernel interfaces would need to
be translated into one of the above status codes.

As far as I can tell this new semantics would be compatible with
POSIX definition and highly probably with existing userspace
code, too. Racy client code that doesn't call fsync or fdatasync
would still be racy; the race might become more visible, which
could be positive or negative depending on if the author of
the client code were then better aware about the race.

And a shell script using a tape drive could just add
`sync /path/to/tape/device` before the user rips the tape out,
if needed to workaround "close() no longer rewinds the tape"
in random binary. Kernel could still start rewinding the
tape on close() but close() would immediately return instead
of waiting for the rewind be complete and the user could
wait for the process to complete using `sync`.

-- 
Mikko


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
  2026-09-17 10:37         ` Mikko Rantalainen
@ 2026-09-17 17:03           ` Andy Lutomirski
  2026-09-17 17:48             ` Rich Felker
  0 siblings, 1 reply; 15+ messages in thread
From: Andy Lutomirski @ 2026-09-17 17:03 UTC (permalink / raw)
  To: Mikko Rantalainen
  Cc: Rich Felker, Matthew Wilcox, linux-fsdevel, linux-api,
	linux-kernel, brauner, viro, jack, alx

On Thu, Sep 17, 2026 at 3:37 AM Mikko Rantalainen
<mikko.rantalainen@peda.net> wrote:
>
> On 9/17/26 02:16, Andy Lutomirski wrote:
> > On Mon, Sep 14, 2026 at 2:44 AM Mikko Rantalainen
> > <mikko.rantalainen@peda.net> wrote:
> >
> >> 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().
> >
> > I realize this would be complex and maybe a can of worms, but maybe we
> > should have a new improved syscall here.  close() is indeed deeply
> > problematic.
> >
> > On the one hand, we have the actual task of closing an fd in the sense
> > of removing it from the table.  This should really be doable without
> > blocking or without side effects (except possibly for zapping
> > old-style POSIX locks -- it doesn't really make sense to be able to
> > close all fds to a file while still keeping it locked, especially
> > since we report the pid of the lock-holding process).
> >
> > On the other hand, close has actual *meaningful* effects, many of
> > which you've mentioned in your email.  IMO it would be really nice to
> > be able to explicitly *do* those effects separately from closing the
> > fd, maybe even asynchronously via io_uring.
> >
> > Would it be so bad to have a new operation to do (possibly with
> > moderately fine control) the close work and another one to just
> > release fd- and process-associated locks and drop the fd?
>
> I think introducing yet another syscall would be bad replacement
> for the *inability to decide the exact semantics* we want to use for
> the current syscalls, especially close().
>
> It's pretty clear that close() is poorly defined right now. The only
> thing that we know for sure is that it releases the file descriptor
> and in case of Linux, this happens for sure unless EBADF is returned.

I think I mostly agree with your analysis except that I really don't
like the ENOSPC.  The kernel actually has the ability to commit to
having space for pending writes on an fd *without syncing*, then I
think that capability should be expose to userspace.  fsync is a
pretty poor alternative for code that just wants to make sure that, in
the absence of a bug, crash or physical failure, the data won't be
lost.

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 0/1] close(): stop exposing non-retryable EINTR
  2026-09-17 17:03           ` Andy Lutomirski
@ 2026-09-17 17:48             ` Rich Felker
  0 siblings, 0 replies; 15+ messages in thread
From: Rich Felker @ 2026-09-17 17:48 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: Mikko Rantalainen, Matthew Wilcox, linux-fsdevel, linux-api,
	linux-kernel, brauner, viro, jack, alx

On Thu, Sep 17, 2026 at 10:03:36AM -0700, Andy Lutomirski wrote:
> On Thu, Sep 17, 2026 at 3:37 AM Mikko Rantalainen
> <mikko.rantalainen@peda.net> wrote:
> >
> > On 9/17/26 02:16, Andy Lutomirski wrote:
> > > On Mon, Sep 14, 2026 at 2:44 AM Mikko Rantalainen
> > > <mikko.rantalainen@peda.net> wrote:
> > >
> > >> 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().
> > >
> > > I realize this would be complex and maybe a can of worms, but maybe we
> > > should have a new improved syscall here.  close() is indeed deeply
> > > problematic.
> > >
> > > On the one hand, we have the actual task of closing an fd in the sense
> > > of removing it from the table.  This should really be doable without
> > > blocking or without side effects (except possibly for zapping
> > > old-style POSIX locks -- it doesn't really make sense to be able to
> > > close all fds to a file while still keeping it locked, especially
> > > since we report the pid of the lock-holding process).
> > >
> > > On the other hand, close has actual *meaningful* effects, many of
> > > which you've mentioned in your email.  IMO it would be really nice to
> > > be able to explicitly *do* those effects separately from closing the
> > > fd, maybe even asynchronously via io_uring.
> > >
> > > Would it be so bad to have a new operation to do (possibly with
> > > moderately fine control) the close work and another one to just
> > > release fd- and process-associated locks and drop the fd?
> >
> > I think introducing yet another syscall would be bad replacement
> > for the *inability to decide the exact semantics* we want to use for
> > the current syscalls, especially close().
> >
> > It's pretty clear that close() is poorly defined right now. The only
> > thing that we know for sure is that it releases the file descriptor
> > and in case of Linux, this happens for sure unless EBADF is returned.
> 
> I think I mostly agree with your analysis except that I really don't
> like the ENOSPC.  The kernel actually has the ability to commit to
> having space for pending writes on an fd *without syncing*, then I
> think that capability should be expose to userspace.  fsync is a
> pretty poor alternative for code that just wants to make sure that, in
> the absence of a bug, crash or physical failure, the data won't be
> lost.

Yes, if close() can give ENOSPC, that's indicative of a bug in
write(). I think the problem is everyone likes their bad NFS
implementations cutting corners to be fast...

Rich

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2026-09-17 17:48 UTC | newest]

Thread overview: 15+ 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-14  6:36   ` Mikko Rantalainen
2026-09-14  9:44     ` Alejandro Colomar
2026-09-13 22:42 ` Matthew Wilcox
2026-09-13 23:51   ` Rich Felker
2026-09-14  9:38     ` Mikko Rantalainen
2026-09-16 23:16       ` Andy Lutomirski
2026-09-17 10:37         ` Mikko Rantalainen
2026-09-17 17:03           ` Andy Lutomirski
2026-09-17 17:48             ` Rich Felker
2026-09-14  9:07   ` Mikko Rantalainen
2026-09-14 12:40     ` Mikko Rantalainen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®