diff --git a/docs/core/compatibility/11.md b/docs/core/compatibility/11.md index 0fd9eb43a4d92..43df77939bb2d 100644 --- a/docs/core/compatibility/11.md +++ b/docs/core/compatibility/11.md @@ -29,6 +29,7 @@ See [Breaking changes in ASP.NET Core 10](/aspnet/core/breaking-changes/10/overv | [Environment.TickCount made consistent with Windows timeout behavior](core-libraries/11/environment-tickcount-windows-behavior.md) | Behavioral change | | [MemoryStream maximum capacity updated and exception behavior changed](core-libraries/11/memorystream-max-capacity.md) | Behavioral change | | [API obsoletions with non-default diagnostic IDs (.NET 11)](core-libraries/11/obsolete-apis.md) | Source incompatible | +| [SafeFileHandle.IsAsync and FileStream.IsAsync accurately reflect non-blocking state on Unix](core-libraries/11/safefilehandle-isasync-unix.md) | Behavioral change | | [TAR-reading APIs verify header checksums when reading](core-libraries/11/tar-checksum-validation.md) | Behavioral change | | [ZipArchive.CreateAsync eagerly loads ZIP archive entries](core-libraries/11/ziparchive-createasync-eager-load.md) | Behavioral change | diff --git a/docs/core/compatibility/core-libraries/11/safefilehandle-isasync-unix.md b/docs/core/compatibility/core-libraries/11/safefilehandle-isasync-unix.md new file mode 100644 index 0000000000000..96291a2aae680 --- /dev/null +++ b/docs/core/compatibility/core-libraries/11/safefilehandle-isasync-unix.md @@ -0,0 +1,77 @@ +--- +title: "Breaking change: SafeFileHandle.IsAsync and FileStream.IsAsync accurately reflect non-blocking state on Unix" +description: "Learn about the breaking change in .NET 11 where SafeFileHandle.IsAsync and FileStream.IsAsync on Unix now accurately reflect whether the underlying file descriptor is in non-blocking mode." +ms.date: 04/07/2026 +ai-usage: ai-assisted +--- + +# SafeFileHandle.IsAsync and FileStream.IsAsync accurately reflect non-blocking state on Unix + +On Unix, and now accurately reflect whether the underlying file descriptor has the `O_NONBLOCK` flag set. Previously, `IsAsync` unconditionally returned `true` for regular files opened with , even though regular file I/O on Unix is inherently synchronous at the kernel level. + +## Version introduced + +.NET 11 Preview 3 + +## Previous behavior + +Previously, `SafeFileHandle.IsAsync` on Unix returned `true` for regular files opened with `FileOptions.Asynchronous`, even though no `O_NONBLOCK` flag was set on the file descriptor (regular file I/O on Unix is inherently synchronous). For file descriptors that were genuinely non-blocking `IsAsync` incorrectly returned `false` on Unix. + +```csharp +using Microsoft.Win32.SafeHandles; + +// On Unix, IsAsync was true for regular files opened with FileOptions.Asynchronous, +// even though the file descriptor had no O_NONBLOCK set. +using SafeFileHandle handle = File.OpenHandle("myfile.txt", options: FileOptions.Asynchronous); +Console.WriteLine(handle.IsAsync); // true (misleading; no O_NONBLOCK on regular file) +``` + +On non-Windows platforms, constructing a `SendPacketsElement` with a non-async `FileStream` threw an `ArgumentException`. + +## New behavior + +Starting in .NET 11, `SafeFileHandle.IsAsync` on Unix returns `true` only when the underlying file descriptor has the `O_NONBLOCK` flag set, which is possible for pipes and sockets. For regular files, it returns `false`. + +```csharp +using Microsoft.Win32.SafeHandles; + +// On Unix, IsAsync now reflects the actual +// non-blocking state of the file descriptor. +SafeFileHandle.CreateAnonymousPipe( + out SafeFileHandle readHandle, + out SafeFileHandle writeHandle, + asyncRead: true, + asyncWrite: false); + +Console.WriteLine(readHandle.IsAsync); // true (O_NONBLOCK set on read end) +Console.WriteLine(writeHandle.IsAsync); // false (blocking write end) +``` + +For regular files opened with `FileOptions.Asynchronous`, `IsAsync` correctly returns `false` on Unix because regular file I/O is inherently synchronous at the kernel level. + +Additionally, on non-Windows platforms, constructing a `SendPacketsElement` with a `FileStream` no longer throws `ArgumentException` regardless of whether the stream is async. + +## Type of breaking change + +This change is a [behavioral change](../../categories.md#behavioral-change). + +## Reason for change + +The previous behavior was incorrect and misleading. `SafeFileHandle.IsAsync` reported `false` for file descriptors (such as pipes or sockets) that genuinely had `O_NONBLOCK` set. This caused APIs and user code that relied on this property to make incorrect decisions. Accurate `IsAsync` reporting was also a prerequisite for the new `SafeFileHandle.CreateAnonymousPipe` API to correctly expose per-end async semantics on Unix. For more information, see [dotnet/runtime#125220](https://github.com/dotnet/runtime/pull/125220). + +## Recommended action + +Review any code that checks `SafeFileHandle.IsAsync` or `FileStream.IsAsync` on Unix and takes action based on the result: + +- If your code assumed `IsAsync` was always `true` on Unix for files opened with `FileOptions.Asynchronous`, update it to account for the fact that regular file handles now return `false`. + +- If you wrap a non-blocking `SafeFileHandle` in a `FileStream` (for example, one created via `SafeFileHandle.CreateAnonymousPipe` with `asyncRead: true` or `asyncWrite: true`), `FileStream.IsAsync` might now return `true` where it previously returned `false`. Adjust downstream code accordingly. + +- If you construct `SendPacketsElement` with a `FileStream` on a non-Windows platform and previously expected an `ArgumentException` to be thrown for non-async streams, note that the exception is no longer thrown. Guard any such expectation with `OperatingSystem.IsWindows()`. + +## Affected APIs + +- +- +- +- diff --git a/docs/core/compatibility/toc.yml b/docs/core/compatibility/toc.yml index a4a5373dd4248..4604fd02e5602 100644 --- a/docs/core/compatibility/toc.yml +++ b/docs/core/compatibility/toc.yml @@ -20,6 +20,8 @@ items: href: core-libraries/11/memorystream-max-capacity.md - name: API obsoletions with non-default diagnostic IDs href: core-libraries/11/obsolete-apis.md + - name: SafeFileHandle.IsAsync and FileStream.IsAsync accurately reflect non-blocking state on Unix + href: core-libraries/11/safefilehandle-isasync-unix.md - name: TAR-reading APIs verify header checksums when reading href: core-libraries/11/tar-checksum-validation.md - name: ZipArchive.CreateAsync eagerly loads ZIP archive entries