Skip to content

[Breaking change]: SafeFileHandle.IsAsync now accurately reflects non-blocking state on Unix #52886

@adamsitnik

Description

@adamsitnik

Description

In .NET 11, SafeFileHandle.IsAsync on Unix no longer unconditionally returns true for regular files opened with FileOptions.Asynchronous. It now accurately reflects whether the underlying file descriptor is in non-blocking mode (O_NONBLOCK is set), which is possible only for pipes and sockets. The same applies to FileStream.IsAsync.

Related PR: dotnet/runtime#125220

Version

.NET 11 Preview 3

Previous behavior

SafeFileHandle.IsAsync always returned true on Unix for regular files opened with FileOptions.Asynchronous, even when the underlying file descriptor had no O_NONBLOCK set (non-blocking/async mode).

// On Unix, IsAsync was true for regular files opened with FileOptions.Asynchronous, regardless of the actual fd state
using SafeFileHandle handle = File.OpenHandle("myfile.txt", options: FileOptions.Asynchronous);
Console.WriteLine(handle.IsAsync);

New behavior

SafeFileHandle.IsAsync on Unix now returns true when the underlying file descriptor is in non-blocking mode (O_NONBLOCK is set), and false otherwise.

// On Unix, IsAsync now reflects the actual non-blocking state of the fd
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 on Unix opened with FileOptions.Asynchronous, IsAsync correctly returns false because regular file I/O on Unix is inherently synchronous at the kernel level.

Type of breaking change

  • Behavioral change: Existing binaries might behave differently at run time.

Reason for change

The previous behavior was incorrect and misleading. SafeFileHandle.IsAsync reported false on Unix even for handles that were genuinely non-blocking, causing APIs and user code that relied on this property to make incorrect decisions. Accurate IsAsync reporting was a prerequisite for the new SafeFileHandle.CreateAnonymousPipe API to correctly expose per-end async semantics on Unix (also introduced in dotnet/runtime#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 false on Unix, that assumption no longer holds for non-blocking file descriptors. Update any logic that unconditionally expects IsAsync == false on non-Windows.

  • If you wrap a non-blocking SafeFileHandle in a FileStream (for example, one created via the new SafeFileHandle.CreateAnonymousPipe with asyncRead: true or asyncWrite: true), FileStream.IsAsync may 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 ArgumentException to be thrown for non-async streams, note that this exception is no longer thrown on non-Windows. Guard any such expectation with OperatingSystem.IsWindows().

Feature area

Core .NET libraries

Affected APIs

  • Microsoft.Win32.SafeHandles.SafeFileHandle.IsAsync
  • System.IO.FileStream.IsAsync
  • System.Net.Sockets.SendPacketsElement.SendPacketsElement(System.IO.FileStream, System.Int64, System.Int32) (behavioral change on non-Windows)
  • System.Net.Sockets.SendPacketsElement.SendPacketsElement(System.IO.FileStream, System.Int64, System.Int32, System.Boolean) (behavioral change on non-Windows)

Associated WorkItem - 568521

Metadata

Metadata

Labels

📌 seQUESTeredIdentifies that an issue has been imported into Quest.breaking-changeIndicates a .NET Core breaking change

Type

No type
No fields configured for issues without a type.

Projects

Status

👀 In review

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions