Description
On Linux, FileStream.Flush(flushToDisk: true) never reports a failed fsync. In .NET 10, SystemNative_FSync assigns the result of the comparison, not the result of the syscall:
while ((result =
#if defined(TARGET_OSX) && HAVE_F_FULLFSYNC
fcntl(fileDescriptor, F_FULLFSYNC)
#else
fsync(fileDescriptor)
#endif
< 0) && errno == EINTR);
return result;
When fsync fails, result is 1. The managed caller checks < 0, so the error is dropped. The #else branch is the Linux path, so this is not specific to macOS.
#124725 fixed this in main. Its title, and the issue behind it (#124722), are about macOS and SMB, and it was not backported. release/10.0 and the v10.0.12 tag still have the code above.
This is a regression from .NET 5. In v5.0.0 the code was while ((result = fsync(ToFileDescriptor(fd))) < 0 && errno == EINTR);. Every release from v6.0.0 has the form above.
Why it matters
On NFS and other network file systems, the client caches writes. Errors from the server, such as ENOSPC, EDQUOT or EIO, are often reported only by fsync or close. close(2) recommends calling fsync first to catch them. On Unix, .NET also ignores close() errors (#111807). So Flush(flushToDisk: true) is the only API that could report these errors, and today it does not.
We hit this while building a storage service on .NET 10 that writes to customer NFS exports:
- A 64 MiB NFS export served by Linux knfsd, mounted with NFSv4.2, v4.1 and v3.
- We wrote 80 MiB through
FileStream, then called Flush(flushToDisk: true).
strace showed fsync(31) = -1 ENOSPC.
Flush(true) returned normally, and the file on the server was truncated.
- A P/Invoke
fsync on the same handle returned errno 28.
Reproduction Steps
This needs no NFS. strace injects the failure.
using System.IO;
Console.WriteLine($".NET {Environment.Version}");
using var fs = new FileStream("/tmp/repro.bin", FileMode.Create, FileAccess.Write);
fs.Write(new byte[4096]);
try { fs.Flush(flushToDisk: true); Console.WriteLine("RESULT: Flush(flushToDisk: true) returned without an exception"); }
catch (IOException e) { Console.WriteLine($"RESULT: IOException: {e.Message}"); }
dotnet build repro.cs -o out
strace -f -qq -e trace=fsync -e inject=fsync:error=EIO ./out/repro
Results on Linux x64, in Docker:
| Image |
Runtime |
strace |
Result |
mcr.microsoft.com/dotnet/sdk:10.0 |
10.0.12 |
fsync(31) = -1 EIO (INJECTED) |
no exception |
mcr.microsoft.com/dotnet/sdk:11.0-preview |
11.0.0 |
fsync(25) = -1 EIO (INJECTED) |
IOException: Input/output error : '/tmp/repro.bin' |
Expected behavior
Flush(flushToDisk: true) throws IOException when fsync fails, as it does on .NET 5 and .NET 11.
Actual behavior
No exception. The data loss is silent.
Regression?
Yes. It works in .NET 5 and fails in every release from .NET 6 to .NET 10.12.
Known Workarounds
Call fsync through your own P/Invoke on SafeFileHandle, and check errno before disposing the stream.
Configuration
- .NET 10.0.12 and 10.0.9, from the
mcr.microsoft.com/dotnet/sdk:10.0 image.
- Linux x64: Docker Desktop with WSL2 kernel 6.6.87.
- The source of
release/10.0 (as of 2026-10-02) and the v10.0.12 tag still contain the old code.
Other information
Please consider backporting #124725 to release/10.0. For Linux, the fix is a single line:
while ((result = fsync(fileDescriptor)) < 0 && errno == EINTR);
The macOS part of #124725 adds a fallback from F_FULLFSYNC to fsync. Servicing can take it or leave it out.
Related: #111807. That issue tracks close() errors being ignored on Unix, which is the same class of silent data loss on NFS.
FYI @stephentoub @adamsitnik, as the people involved in #124725 and #111807.
Description
On Linux,
FileStream.Flush(flushToDisk: true)never reports a failedfsync. In .NET 10,SystemNative_FSyncassigns the result of the comparison, not the result of the syscall:When
fsyncfails,resultis1. The managed caller checks< 0, so the error is dropped. The#elsebranch is the Linux path, so this is not specific to macOS.#124725 fixed this in
main. Its title, and the issue behind it (#124722), are about macOS and SMB, and it was not backported.release/10.0and thev10.0.12tag still have the code above.This is a regression from .NET 5. In
v5.0.0the code waswhile ((result = fsync(ToFileDescriptor(fd))) < 0 && errno == EINTR);. Every release fromv6.0.0has the form above.Why it matters
On NFS and other network file systems, the client caches writes. Errors from the server, such as ENOSPC, EDQUOT or EIO, are often reported only by
fsyncorclose.close(2)recommends callingfsyncfirst to catch them. On Unix, .NET also ignoresclose()errors (#111807). SoFlush(flushToDisk: true)is the only API that could report these errors, and today it does not.We hit this while building a storage service on .NET 10 that writes to customer NFS exports:
FileStream, then calledFlush(flushToDisk: true).straceshowedfsync(31) = -1 ENOSPC.Flush(true)returned normally, and the file on the server was truncated.fsyncon the same handle returned errno 28.Reproduction Steps
This needs no NFS.
straceinjects the failure.Results on Linux x64, in Docker:
mcr.microsoft.com/dotnet/sdk:10.0fsync(31) = -1 EIO (INJECTED)mcr.microsoft.com/dotnet/sdk:11.0-previewfsync(25) = -1 EIO (INJECTED)IOException: Input/output error : '/tmp/repro.bin'Expected behavior
Flush(flushToDisk: true)throwsIOExceptionwhenfsyncfails, as it does on .NET 5 and .NET 11.Actual behavior
No exception. The data loss is silent.
Regression?
Yes. It works in .NET 5 and fails in every release from .NET 6 to .NET 10.12.
Known Workarounds
Call
fsyncthrough your own P/Invoke onSafeFileHandle, and checkerrnobefore disposing the stream.Configuration
mcr.microsoft.com/dotnet/sdk:10.0image.release/10.0(as of 2026-10-02) and thev10.0.12tag still contain the old code.Other information
Please consider backporting #124725 to
release/10.0. For Linux, the fix is a single line:The macOS part of #124725 adds a fallback from
F_FULLFSYNCtofsync. Servicing can take it or leave it out.Related: #111807. That issue tracks
close()errors being ignored on Unix, which is the same class of silent data loss on NFS.FYI @stephentoub @adamsitnik, as the people involved in #124725 and #111807.