-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Minor File.ReadAllBytes* improvements #61519
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
Changes from all commits
eeb5e0b
87aaf3d
2b6b22b
39c2621
29b6b8d
9963347
9755a45
9fa8190
2576390
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -251,27 +251,33 @@ 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)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SequentialScan is a perf hint that requires extra sys-call on non-Windows OSes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FileOptions options = OperatingSystem.IsWindows() ? FileOptions.SequentialScan : FileOptions.None; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: maybe turn this into a static property, like |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using (SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, options)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| long fileLength = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fs.CanSeek && (fileLength = fs.Length) > int.MaxValue) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (sfh.CanSeek && (fileLength = RandomAccess.GetFileLength(sfh)) > Array.MaxLength) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new IOException(SR.IO_FileTooLong2GB); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #if DEBUG | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileLength = 0; // improve the test coverage for ReadAllBytesUnknownLength | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we instead just ensure we have tests that read files that return 0 for the length?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We have some tests, and you are the person who authored most of them: dotnet/corefx#28388 The problem is that they don't guarantee that all possible code paths are going to be executed (using stack-allocated array, renting an array from the pool, returning it and renting a larger array etc) as they deal with a virtual file system that seems to be exposing files only for reading. We could use pipes (#58434 introduced some tests for that ), but it would require more work and handling all edge cases. I know that setting
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Does it? It means, for example, that Debug.Asserts in the normal non-zero code paths are now rendered useless.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Which asserts do you mean? I can't see any in the code path for regular files that report length properly: runtime/src/libraries/System.Private.CoreLib/src/System/IO/File.cs Lines 548 to 567 in 2576390
runtime/src/libraries/System.Private.CoreLib/src/System/IO/File.cs Lines 275 to 289 in 2576390
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Any on any code paths used from here all the way down through the runtime. The change says that the 99.9% code path is never executed in debug.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
These paths (namely I do understand that it's not a clear win and a a trade off. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 there are non-seekable files. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Thus we need to assume 0 doesn't mean empty. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ReadAllBytesUnknownLength(fs); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ReadAllBytesUnknownLength(sfh); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int index = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int count = (int)fileLength; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -519,44 +525,35 @@ private static async Task<string> InternalReadAllTextAsync(string path, Encoding | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Task.FromCanceled<byte[]>(cancellationToken); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SequentialScan is a perf hint that requires extra sys-call on non-Windows OSes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FileOptions options = FileOptions.Asynchronous | (OperatingSystem.IsWindows() ? FileOptions.SequentialScan : FileOptions.None); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SafeFileHandle sfh = OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read, options); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool returningInternalTask = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| long fileLength = 0L; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (sfh.CanSeek && (fileLength = RandomAccess.GetFileLength(sfh)) > Array.MaxLength) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| long fileLength = 0L; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fs.CanSeek && (fileLength = fs.Length) > int.MaxValue) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var e = new IOException(SR.IO_FileTooLong2GB); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ExceptionDispatchInfo.SetCurrentStackTrace(e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Task.FromException<byte[]>(e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| returningInternalTask = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fileLength > 0 ? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| InternalReadAllBytesAsync(fs, (int)fileLength, cancellationToken) : | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| InternalReadAllBytesUnknownLengthAsync(fs, cancellationToken); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| finally | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!returningInternalTask) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs.Dispose(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sfh.Dispose(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Task.FromException<byte[]>(ExceptionDispatchInfo.SetCurrentStackTrace(new IOException(SR.IO_FileTooLong2GB))); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #if DEBUG | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileLength = 0; // improve the test coverage for InternalReadAllBytesUnknownLengthAsync | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question/comment as earlier. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fileLength > 0 ? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| InternalReadAllBytesAsync(sfh, (int)fileLength, cancellationToken) : | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| InternalReadAllBytesUnknownLengthAsync(sfh, cancellationToken); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static async Task<byte[]> InternalReadAllBytesAsync(FileStream fs, int count, CancellationToken cancellationToken) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static async Task<byte[]> InternalReadAllBytesAsync(SafeFileHandle sfh, int count, CancellationToken cancellationToken) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using (fs) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using (sfh) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int index = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] bytes = new byte[count]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int n = await fs.ReadAsync(new Memory<byte>(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(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -569,7 +566,7 @@ private static async Task<byte[]> InternalReadAllBytesAsync(FileStream fs, int c | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static async Task<byte[]> InternalReadAllBytesUnknownLengthAsync(FileStream fs, CancellationToken cancellationToken) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static async Task<byte[]> InternalReadAllBytesUnknownLengthAsync(SafeFileHandle sfh, CancellationToken cancellationToken) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] rentedArray = ArrayPool<byte>.Shared.Rent(512); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -595,7 +592,7 @@ private static async Task<byte[]> 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(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -605,7 +602,7 @@ private static async Task<byte[]> InternalReadAllBytesUnknownLengthAsync(FileStr | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| finally | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs.Dispose(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sfh.Dispose(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ArrayPool<byte>.Shared.Return(rentedArray); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -775,7 +772,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<byte> buffer = stackalloc byte[512]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -803,7 +800,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(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.