From eeb5e0b6c1d97db6697364ed69535a5d24e8cb18 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 12 Nov 2021 17:14:43 +0100 Subject: [PATCH 1/9] switch from FileStream to RandomAccess --- .../System.Private.CoreLib/src/System/IO/File.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 29dff0c4d009f..b0a6f95579261 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -251,19 +251,18 @@ public static void WriteAllText(string path, string? contents, Encoding encoding public static byte[] ReadAllBytes(string path) { - // bufferSize == 1 used to avoid unnecessary buffer in FileStream - using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1, FileOptions.SequentialScan)) + using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, FileOptions.SequentialScan)) { long fileLength = 0; - if (fs.CanSeek && (fileLength = fs.Length) > int.MaxValue) + if (sfh.CanSeek && (fileLength = RandomAccess.GetFileLength(sfh)) > int.MaxValue) { throw new IOException(SR.IO_FileTooLong2GB); } if (fileLength == 0) { - // Some file systems (e.g. procfs on Linux) return 0 for length even when there's content; also there is non-seekable file stream. + // Some file systems (e.g. procfs on Linux) return 0 for length even when there's content; also theree are non-seekable files. // Thus we need to assume 0 doesn't mean empty. - return ReadAllBytesUnknownLength(fs); + return ReadAllBytesUnknownLength(sfh); } int index = 0; @@ -271,7 +270,7 @@ public static byte[] ReadAllBytes(string path) byte[] bytes = new byte[count]; while (count > 0) { - int n = fs.Read(bytes, index, count); + int n = RandomAccess.ReadAtOffset(sfh, bytes.AsSpan(index, count), index); if (n == 0) { ThrowHelper.ThrowEndOfFileException(); @@ -775,7 +774,7 @@ private static void Validate(string path, Encoding encoding) throw new ArgumentException(SR.Argument_EmptyPath, nameof(path)); } - private static byte[] ReadAllBytesUnknownLength(FileStream fs) + private static byte[] ReadAllBytesUnknownLength(SafeFileHandle sfh) { byte[]? rentedArray = null; Span buffer = stackalloc byte[512]; @@ -803,7 +802,7 @@ private static byte[] ReadAllBytesUnknownLength(FileStream fs) } Debug.Assert(bytesRead < buffer.Length); - int n = fs.Read(buffer.Slice(bytesRead)); + int n = RandomAccess.ReadAtOffset(sfh, buffer.Slice(bytesRead), bytesRead); if (n == 0) { return buffer.Slice(0, bytesRead).ToArray(); From 87aaf3dd5119ac3125b6468927f10e6326988d1a Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 12 Nov 2021 17:34:24 +0100 Subject: [PATCH 2/9] use Array.MaxLength as a limit for File.ReadAllBytes and fix an edge case bug for files: Array.MaxLength < Length < int.MaxValue --- .../tests/File/ReadWriteAllBytes.cs | 15 +++++++++++++++ .../System.Private.CoreLib/src/System/IO/File.cs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs index 679ff9b5b7986..39becd6d0d514 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs @@ -72,6 +72,21 @@ public void ReadFileOver2GB() Assert.Throws(() => File.ReadAllBytes(path)); } + [Fact] + [OuterLoop] + [ActiveIssue("https://github.com/dotnet/runtime/issues/45954", TestPlatforms.Browser)] + public void ReadFileOverMaxArrayLength() + { + string path = GetTestFilePath(); + using (FileStream fs = File.Create(path)) + { + fs.SetLength(Array.MaxLength + 1L); + } + + // File is too large for ReadAllBytes at once + Assert.Throws(() => File.ReadAllBytes(path)); + } + [Fact] public void Overwrite() { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index b0a6f95579261..67a52bda04ed4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -254,7 +254,7 @@ public static byte[] ReadAllBytes(string path) using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, FileOptions.SequentialScan)) { long fileLength = 0; - if (sfh.CanSeek && (fileLength = RandomAccess.GetFileLength(sfh)) > int.MaxValue) + if (sfh.CanSeek && (fileLength = RandomAccess.GetFileLength(sfh)) > Array.MaxLength) { throw new IOException(SR.IO_FileTooLong2GB); } From 2b6b22ba1c9d5e98e4027f10b8f3b509a5b25eb9 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 12 Nov 2021 18:01:41 +0100 Subject: [PATCH 3/9] apply same changes to the async version --- .../src/System/IO/File.cs | 50 +++++-------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 67a52bda04ed4..83db27dc8ba0c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -511,64 +511,39 @@ private static async Task InternalReadAllTextAsync(string path, Encoding return WriteToFileAsync(path, FileMode.Create, contents, encoding, cancellationToken); } - public static Task ReadAllBytesAsync(string path, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task ReadAllBytesAsync(string path, CancellationToken cancellationToken = default(CancellationToken)) { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } + cancellationToken.ThrowIfCancellationRequested(); - var fs = new FileStream( - path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1, // bufferSize == 1 used to avoid unnecessary buffer in FileStream - FileOptions.Asynchronous | FileOptions.SequentialScan); - - bool returningInternalTask = false; - try + using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, FileOptions.Asynchronous | FileOptions.SequentialScan)) { long fileLength = 0L; - if (fs.CanSeek && (fileLength = fs.Length) > int.MaxValue) + if (sfh.CanSeek && (fileLength = RandomAccess.GetFileLength(sfh)) > Array.MaxLength) { - var e = new IOException(SR.IO_FileTooLong2GB); - ExceptionDispatchInfo.SetCurrentStackTrace(e); - return Task.FromException(e); + throw new IOException(SR.IO_FileTooLong2GB); } - - returningInternalTask = true; - return fileLength > 0 ? - InternalReadAllBytesAsync(fs, (int)fileLength, cancellationToken) : - InternalReadAllBytesUnknownLengthAsync(fs, cancellationToken); - } - finally - { - if (!returningInternalTask) + if (fileLength == 0) { - fs.Dispose(); + return await InternalReadAllBytesUnknownLengthAsync(sfh, cancellationToken).ConfigureAwait(false); } - } - } - private static async Task InternalReadAllBytesAsync(FileStream fs, int count, CancellationToken cancellationToken) - { - using (fs) - { int index = 0; - byte[] bytes = new byte[count]; + byte[] bytes = new byte[fileLength]; do { - int n = await fs.ReadAsync(new Memory(bytes, index, count - index), cancellationToken).ConfigureAwait(false); + int n = await RandomAccess.ReadAtOffsetAsync(sfh, bytes.AsMemory(index), index, cancellationToken).ConfigureAwait(false); if (n == 0) { ThrowHelper.ThrowEndOfFileException(); } index += n; - } while (index < count); - + } while (index < fileLength); return bytes; } } - private static async Task InternalReadAllBytesUnknownLengthAsync(FileStream fs, CancellationToken cancellationToken) + private static async Task InternalReadAllBytesUnknownLengthAsync(SafeFileHandle sfh, CancellationToken cancellationToken) { byte[] rentedArray = ArrayPool.Shared.Rent(512); try @@ -594,7 +569,7 @@ private static async Task InternalReadAllBytesUnknownLengthAsync(FileStr } Debug.Assert(bytesRead < rentedArray.Length); - int n = await fs.ReadAsync(rentedArray.AsMemory(bytesRead), cancellationToken).ConfigureAwait(false); + int n = await RandomAccess.ReadAtOffsetAsync(sfh, rentedArray.AsMemory(bytesRead), bytesRead, cancellationToken).ConfigureAwait(false); if (n == 0) { return rentedArray.AsSpan(0, bytesRead).ToArray(); @@ -604,7 +579,6 @@ private static async Task InternalReadAllBytesUnknownLengthAsync(FileStr } finally { - fs.Dispose(); ArrayPool.Shared.Return(rentedArray); } } From 39c2621e8f158ae5f120652c89bc489f7e0cc330 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 12 Nov 2021 18:14:37 +0100 Subject: [PATCH 4/9] there is no gain of using FileOptions.SequentialScan, as it requires an additional sys call on Unix and every OS is able to recognize the disk access patterns anyway --- src/libraries/System.Private.CoreLib/src/System/IO/File.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 83db27dc8ba0c..f5809c59702ae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -251,7 +251,7 @@ public static void WriteAllText(string path, string? contents, Encoding encoding public static byte[] ReadAllBytes(string path) { - using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, FileOptions.SequentialScan)) + using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { long fileLength = 0; if (sfh.CanSeek && (fileLength = RandomAccess.GetFileLength(sfh)) > Array.MaxLength) @@ -515,7 +515,7 @@ private static async Task InternalReadAllTextAsync(string path, Encoding { cancellationToken.ThrowIfCancellationRequested(); - using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, FileOptions.Asynchronous | FileOptions.SequentialScan)) + using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, FileOptions.Asynchronous)) { long fileLength = 0L; if (sfh.CanSeek && (fileLength = RandomAccess.GetFileLength(sfh)) > Array.MaxLength) From 29b6b8df3c480a1578e502629d6b054ab271693d Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 15 Nov 2021 10:16:13 +0100 Subject: [PATCH 5/9] Update src/libraries/System.Private.CoreLib/src/System/IO/File.cs Co-authored-by: Dan Moseley --- src/libraries/System.Private.CoreLib/src/System/IO/File.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index f5809c59702ae..0b3b12a68f237 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -260,7 +260,7 @@ public static byte[] ReadAllBytes(string path) } if (fileLength == 0) { - // Some file systems (e.g. procfs on Linux) return 0 for length even when there's content; also theree are non-seekable files. + // Some file systems (e.g. procfs on Linux) return 0 for length even when there's content; also there are non-seekable files. // Thus we need to assume 0 doesn't mean empty. return ReadAllBytesUnknownLength(sfh); } From 99633470a872ab15900b8927232d99cefd730eca Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 15 Nov 2021 11:47:17 +0100 Subject: [PATCH 6/9] use SequentialScan on Windows just in case it helps for some configs --- .../System.Private.CoreLib/src/System/IO/File.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 0b3b12a68f237..e44e3cc65615d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -251,7 +251,9 @@ public static void WriteAllText(string path, string? contents, Encoding encoding public static byte[] ReadAllBytes(string path) { - using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + // SequentialScan is a perf hint that requires extra sys-call on non-Windows OSes. + FileOptions options = OperatingSystem.IsWindows() ? FileOptions.SequentialScan : FileOptions.None; + using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, options)) { long fileLength = 0; if (sfh.CanSeek && (fileLength = RandomAccess.GetFileLength(sfh)) > Array.MaxLength) @@ -515,7 +517,9 @@ private static async Task InternalReadAllTextAsync(string path, Encoding { cancellationToken.ThrowIfCancellationRequested(); - using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, FileOptions.Asynchronous)) + // SequentialScan is a perf hint that requires extra sys-call on non-Windows OSes. + FileOptions options = (OperatingSystem.IsWindows() ? FileOptions.SequentialScan : FileOptions.None) | FileOptions.Asynchronous; + using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, options)) { long fileLength = 0L; if (sfh.CanSeek && (fileLength = RandomAccess.GetFileLength(sfh)) > Array.MaxLength) From 9755a4575470f16dd7821725049f403c607cd19c Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 15 Nov 2021 11:51:25 +0100 Subject: [PATCH 7/9] use the previous pattern (the code is more complex but also more efficient for edge cases) --- .../src/System/IO/File.cs | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index e44e3cc65615d..7b078561a50ec 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -513,26 +513,35 @@ private static async Task InternalReadAllTextAsync(string path, Encoding return WriteToFileAsync(path, FileMode.Create, contents, encoding, cancellationToken); } - public static async Task ReadAllBytesAsync(string path, CancellationToken cancellationToken = default(CancellationToken)) + public static Task ReadAllBytesAsync(string path, CancellationToken cancellationToken = default(CancellationToken)) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } // SequentialScan is a perf hint that requires extra sys-call on non-Windows OSes. FileOptions options = (OperatingSystem.IsWindows() ? FileOptions.SequentialScan : FileOptions.None) | FileOptions.Asynchronous; - using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, options)) + SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, options); + + long fileLength = 0L; + if (sfh.CanSeek && (fileLength = RandomAccess.GetFileLength(sfh)) > Array.MaxLength) { - long fileLength = 0L; - if (sfh.CanSeek && (fileLength = RandomAccess.GetFileLength(sfh)) > Array.MaxLength) - { - throw new IOException(SR.IO_FileTooLong2GB); - } - if (fileLength == 0) - { - return await InternalReadAllBytesUnknownLengthAsync(sfh, cancellationToken).ConfigureAwait(false); - } + sfh.Dispose(); + return Task.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new IOException(SR.IO_FileTooLong2GB))); + } + + return fileLength > 0 ? + InternalReadAllBytesAsync(sfh, (int)fileLength, cancellationToken) : + InternalReadAllBytesUnknownLengthAsync(sfh, cancellationToken); + } + private static async Task InternalReadAllBytesAsync(SafeFileHandle sfh, int count, CancellationToken cancellationToken) + { + using (sfh) + { int index = 0; - byte[] bytes = new byte[fileLength]; + byte[] bytes = new byte[count]; do { int n = await RandomAccess.ReadAtOffsetAsync(sfh, bytes.AsMemory(index), index, cancellationToken).ConfigureAwait(false); @@ -542,7 +551,8 @@ private static async Task InternalReadAllTextAsync(string path, Encoding } index += n; - } while (index < fileLength); + } while (index < count); + return bytes; } } @@ -583,6 +593,7 @@ private static async Task InternalReadAllBytesUnknownLengthAsync(SafeFil } finally { + sfh.Dispose(); ArrayPool.Shared.Return(rentedArray); } } From 9fa81909f056cca7828f961d32a392a3301781e4 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 15 Nov 2021 12:16:22 +0100 Subject: [PATCH 8/9] improve test coverage --- .../tests/File/ReadWriteAllBytesAsync.cs | 15 +++++++++++++++ .../System.Private.CoreLib/src/System/IO/File.cs | 9 +++++++++ 2 files changed, 24 insertions(+) diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs index be562f15ca953..cb655576a7a8a 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs @@ -85,6 +85,21 @@ public Task ReadFileOver2GBAsync() return Assert.ThrowsAsync(async () => await File.ReadAllBytesAsync(path)); } + [Fact] + [OuterLoop] + [ActiveIssue("https://github.com/dotnet/runtime/issues/45954", TestPlatforms.Browser)] + public Task MaxArrayLengthAsync() + { + string path = GetTestFilePath(); + using (FileStream fs = File.Create(path)) + { + fs.SetLength(Array.MaxLength + 1L); + } + + // File is too large for ReadAllBytes at once + return Assert.ThrowsAsync(async () => await File.ReadAllBytesAsync(path)); + } + [Fact] public async Task OverwriteAsync() { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 7b078561a50ec..911655c411ffc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -260,6 +260,11 @@ public static byte[] ReadAllBytes(string path) { throw new IOException(SR.IO_FileTooLong2GB); } + +#if DEBUG + fileLength = 0; // improve the test coverage for ReadAllBytesUnknownLength +#endif + if (fileLength == 0) { // Some file systems (e.g. procfs on Linux) return 0 for length even when there's content; also there are non-seekable files. @@ -531,6 +536,10 @@ private static async Task InternalReadAllTextAsync(string path, Encoding return Task.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new IOException(SR.IO_FileTooLong2GB))); } +#if DEBUG + fileLength = 0; // improve the test coverage for InternalReadAllBytesUnknownLengthAsync +#endif + return fileLength > 0 ? InternalReadAllBytesAsync(sfh, (int)fileLength, cancellationToken) : InternalReadAllBytesUnknownLengthAsync(sfh, cancellationToken); From 25763904ee0f3dac6b9769b4c3479d4aee2f438d Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 15 Nov 2021 17:09:41 +0100 Subject: [PATCH 9/9] address code review feedback --- .../tests/File/ReadWriteAllBytesAsync.cs | 12 ++++++------ .../System.Private.CoreLib/src/System/IO/File.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs index cb655576a7a8a..6414ab2ad873c 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs @@ -73,7 +73,7 @@ public Task AlreadyCanceledAsync() [Fact] [OuterLoop] [ActiveIssue("https://github.com/dotnet/runtime/issues/45954", TestPlatforms.Browser)] - public Task ReadFileOver2GBAsync() + public async Task ReadFileOver2GBAsync() { string path = GetTestFilePath(); using (FileStream fs = File.Create(path)) @@ -81,14 +81,14 @@ public Task ReadFileOver2GBAsync() fs.SetLength(int.MaxValue + 1L); } - // File is too large for ReadAllBytes at once - return Assert.ThrowsAsync(async () => await File.ReadAllBytesAsync(path)); + // File is too large for ReadAllBytesAsync at once + await Assert.ThrowsAsync(async () => await File.ReadAllBytesAsync(path)); } [Fact] [OuterLoop] [ActiveIssue("https://github.com/dotnet/runtime/issues/45954", TestPlatforms.Browser)] - public Task MaxArrayLengthAsync() + public async Task ReadFileOverMaxArrayLengthAsync() { string path = GetTestFilePath(); using (FileStream fs = File.Create(path)) @@ -96,8 +96,8 @@ public Task MaxArrayLengthAsync() fs.SetLength(Array.MaxLength + 1L); } - // File is too large for ReadAllBytes at once - return Assert.ThrowsAsync(async () => await File.ReadAllBytesAsync(path)); + // File is too large for ReadAllBytesAsync at once + await Assert.ThrowsAsync(async () => await File.ReadAllBytesAsync(path)); } [Fact] diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 911655c411ffc..aa0bb62be57dc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -526,7 +526,7 @@ private static async Task InternalReadAllTextAsync(string path, Encoding } // SequentialScan is a perf hint that requires extra sys-call on non-Windows OSes. - FileOptions options = (OperatingSystem.IsWindows() ? FileOptions.SequentialScan : FileOptions.None) | FileOptions.Asynchronous; + FileOptions options = FileOptions.Asynchronous | (OperatingSystem.IsWindows() ? FileOptions.SequentialScan : FileOptions.None); SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, options); long fileLength = 0L;