-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FileStream.ReadAsync() with isAsync: true is throwing IOException #54143
Comments
Tagging subscribers to this area: @dotnet/area-system-io Issue DetailsDescriptionFileStreams opened with The function used to get a FileStream: private void InitializeFileStream(string filePath, bool exclusive, int readSize, int writeSize)
{
FileAccess access = 0;
if (readSize > 0)
access |= FileAccess.Read;
if (writeSize > 0)
access |= FileAccess.Write;
FileShare share = exclusive ? FileShare.None : (FileShare)access;
FileStream = new FileStream(filePath, FileMode.Open, access, share, Math.Max(readSize, writeSize), true);
} Run loop of the task: private async Task ReadPoll()
{
while (_run)
{
int bytesReturned = await FileStream!.ReadAsync(_readBuffer);
OnInput(_readBuffer.Slice(0, bytesReturned));
}
} Configuration
Regression?I'm not sure... Other information
The source repo is currently private, but if more information or testing is needed from my side then I'll do it willingly.
|
Hi @X9VoiD Are you sure that you have provided the right example and stack trace? In the source code I can see a call to await FileStream!.ReadAsync(_readBuffer); While in the stack trace I can see a call to System.IO.FileStream.Read(Span`1 buffer) The method that should be invoked:
|
My bad, here's the correct stack trace:
Also updated the stack trace in issue. |
@X9VoiD Could you please try to run your example with the following env var enabled: set DOTNET_SYSTEM_IO_USENET5COMPATFILESTREAM=1 And see if it works? Which Windows version are you using? Is the file a local file or a file located on a remote machine? What is the file system? |
If that's the exact stack trace you saw in both cases, then the environment variable didn't properly configure things. Can you share the exact stack trace you got with the environment variable set? |
Does it work for you with .NET 5 or any other version of .NET besides .NET 6? |
@X9VoiD could you please run the following app by providing the path as first argument? <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Issue54143
{
class Program
{
static void Main(string[] args)
{
Test(args[0]);
Test(Path.GetFullPath(args[0]));
}
private static unsafe void Test(string path)
{
Console.WriteLine($"Path: '{path}'");
using (SafeFileHandle handle = CreateFile(path, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, (int)FileOptions.Asynchronous, IntPtr.Zero))
{
Console.WriteLine($"IsInvalid: {handle.IsInvalid}");
Console.WriteLine($"GetFileType(): {GetFileType(handle)}");
FILE_STANDARD_INFO info;
if (!GetFileInformationByHandleEx(handle, 1 /* FILE_STANDARD_INFO */, &info, (uint)sizeof(FILE_STANDARD_INFO)))
{
Console.WriteLine($"Marshal.GetLastWin32Error(): {Marshal.GetLastWin32Error()}");
}
else
{
Console.WriteLine($"EndOfFile: {info.EndOfFile}");
}
}
}
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
private static extern unsafe SafeFileHandle CreateFile(
string lpFileName,
FileAccess dwDesiredAccess,
FileShare dwShareMode,
IntPtr lpSecurityAttributes,
FileMode dwCreationDisposition,
int dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int GetFileType(SafeHandle handle);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
internal static extern unsafe bool GetFileInformationByHandleEx(SafeFileHandle hFile, int FileInformationClass, void* lpFileInformation, uint dwBufferSize);
internal enum BOOL : int
{
FALSE = 0,
TRUE = 1,
}
internal struct FILE_STANDARD_INFO
{
internal long AllocationSize;
internal long EndOfFile;
internal uint NumberOfLinks;
internal BOOL DeletePending;
internal BOOL Directory;
}
}
} dotnet run -- "thePathGoesHere" |
|
@X9VoiD big thanks for providing all the information. Looking at the results you have provided we have most probably never supported this scenario ( Is there any chance that you could tell me which sys-calls do you use exactly to get such a path? Or help me to create a repro? This is what I got so far: using System;
using System.Runtime.InteropServices;
using System.Text;
namespace DeviceIds
{
class Program
{
static void Main(string[] args)
{
const int DIGCF_PRESENT = 0x2;
const int DIGCF_ALLCLASSES = 0x4;
IntPtr deviceInfoSet = SetupDiGetClassDevs(IntPtr.Zero, "HID", IntPtr.Zero, DIGCF_ALLCLASSES | DIGCF_PRESENT);
try
{
SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA();
deviceInfoData.cbSize = (uint)Marshal.SizeOf(deviceInfoData);
int deviceIndex = 0;
while (SetupDiEnumDeviceInfo(deviceInfoSet, deviceIndex++, ref deviceInfoData))
{
StringBuilder deviceInstanceId = new StringBuilder(1024);
if (SetupDiGetDeviceInstanceId(deviceInfoSet, ref deviceInfoData, deviceInstanceId, deviceInstanceId.Capacity, out int requiredSize))
{
Console.WriteLine(deviceInstanceId.ToString());
}
}
}
finally
{
SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
}
[StructLayout(LayoutKind.Sequential)]
struct SP_DEVICE_INTERFACE_DATA
{
public int cbSize;
public Guid interfaceClassGuid;
public int flags;
private UIntPtr reserved;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SP_DEVINFO_DATA
{
public uint cbSize;
public Guid ClassGuid;
public uint DevInst;
public IntPtr Reserved;
}
[DllImport("setupapi.dll", CharSet = CharSet.Auto)]
static extern IntPtr SetupDiGetClassDevs(IntPtr ClassGuid, [MarshalAs(UnmanagedType.LPTStr)] string Enumerator, IntPtr hwndParent, int Flags);
[DllImport("setupapi.dll", SetLastError = true)]
public static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
[DllImport("setupapi.dll", SetLastError = true)]
internal static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, int MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool SetupDiGetDeviceInstanceId(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, StringBuilder DeviceInstanceId, int DeviceInstanceIdSize, out int RequiredSize);
}
} Which gives me a list of device IDs:
But I don't know how to get a path to a file like yours. |
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
namespace DeviceIds
{
class Program
{
static void Main(string[] args)
{
const int DIGCF_PRESENT = 0x2;
const int DIGCF_DEVICEINTERFACE = 0x10;
const int ERROR_NO_MORE_ITEMS = 259;
HidD_GetHidGuid(out Guid HidGuid);
IntPtr deviceInfoSet = SetupDiGetClassDevs(in HidGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
try
{
SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA();
deviceInfoData.cbSize = (uint)Marshal.SizeOf(deviceInfoData);
uint deviceIndex = 0;
while (SetupDiEnumDeviceInfo(deviceInfoSet, deviceIndex++, ref deviceInfoData))
{
if (Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS)
break;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA();
deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData);
if (!SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, in HidGuid, deviceIndex, ref deviceInterfaceData))
{
// throw new Win32Exception("Failed to get device interface data");
}
SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = new SP_DEVICE_INTERFACE_DETAIL_DATA();
deviceInterfaceDetailData.cbSize = IntPtr.Size == 8 ? 8 : 6;
uint size = (uint)Marshal.SizeOf(deviceInterfaceDetailData);
if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, ref deviceInterfaceDetailData, size, ref size, IntPtr.Zero))
{
// throw new Exception("Failed to get device interface detail");
}
Console.WriteLine(deviceInterfaceDetailData.DevicePath);
}
}
finally
{
SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
}
[StructLayout(LayoutKind.Sequential)]
struct SP_DEVICE_INTERFACE_DATA
{
public int cbSize;
public Guid interfaceClassGuid;
public int flags;
private UIntPtr reserved;
}
[StructLayout(LayoutKind.Sequential)]
struct SP_DEVINFO_DATA
{
public uint cbSize;
public Guid ClassGuid;
public uint DevInst;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
public int cbSize;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string DevicePath;
}
[DllImport("setupapi.dll", CharSet = CharSet.Auto)]
static extern IntPtr SetupDiGetClassDevs(IntPtr ClassGuid, [MarshalAs(UnmanagedType.LPTStr)] string Enumerator, IntPtr hwndParent, int Flags);
[DllImport("setupapi.dll", CharSet = CharSet.Auto)]
static extern IntPtr SetupDiGetClassDevs(in Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, int Flags);
[DllImport("setupapi.dll", SetLastError = true)]
static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
[DllImport("setupapi.dll", SetLastError = true)]
static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool SetupDiGetDeviceInstanceId(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, StringBuilder DeviceInstanceId, int DeviceInstanceIdSize, out int RequiredSize);
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool SetupDiEnumDeviceInterfaces(IntPtr hDevInfo, IntPtr devInfo, in Guid interfaceClassGuid, uint memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr hDevInfo, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData, uint deviceInterfaceDetailDataSize, ref uint requiredSize, IntPtr deviceInfoData);
[DllImport("hid.dll", SetLastError = true)]
static extern void HidD_GetHidGuid(out Guid Guid);
}
} This gives me:
Which should be a valid path. Some of these file paths though are exclusively opened by Windows, for example, all of the paths ending in |
@X9VoiD big thanks for providing the repro! I was able to confirm that it was not a regression, because it was throwing the same exception for .NET 5: The good news is that it works with latest .NET 6 preview as it got fixed by #53669 Some explanation: Now if Lines 224 to 238 in 7833828
Line 30 in 7833828
And because of that we don't access the problematic Lines 47 to 55 in 7833828
I am going to add a test that ensures that it keeps working. @X9VoiD in theory it should work with the Preview 6 bits but it should definitely work with latest bits. The links come from https://github.com/dotnet/installer |
Description
FileStreams opened with
isAsync: true
fails to perform asynchronous read operations. Not specifyingisAsync: true
runs completely fine.The function used to get a FileStream:
Run loop of the task:
Configuration
Regression?
I'm not sure...
Other information
The source repo is currently private, but if more information or testing is needed from my side then I'll do it willingly.
The text was updated successfully, but these errors were encountered: