-
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
File preallocationSize: align Windows and Unix behavior. #59338
Conversation
This aligns Windows and Unix behavior of preallocationSize for the intended use-case of specifing the size of a file that will be written. For this use-case, the expected FileAccess is Write, and the file should be a new one (FileMode.Create*) or a truncated file (FileMode.Truncate). Specifing a preallocationSize for other modes, or non-writable files throws ArgumentException. The opened file will have a length of zero, and is ready to be written to by the user. If the requested size cannot be allocated, an IOException is thrown. When the OS/filesystem does not support pre-allocating, preallocationSize is ignored.
Tagging subscribers to this area: @dotnet/area-system-io Issue DetailsThis aligns Windows and Unix behavior of preallocationSize for the For this use-case, the expected FileAccess is Write, and the file should be The opened file will have a length of zero, and is ready to be written to by the user. If the requested size cannot be allocated, an IOException is thrown. When the OS/filesystem does not support pre-allocating, preallocationSize is ignored. @stephentoub @danmoseley @jozkee @carlossanlop ptal. cc @adamsitnik
|
I've only run this on my Linux machine. I'll have to make some updates based on CI. |
src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FILE_ALLOCATION_INFO.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
Outdated
Show resolved
Hide resolved
@danmoseley, @jeffhandley, we're currently in a poor state with regards to this preallocationSize thing. main contains a breaking change from release/6.0, and neither has the behavior we should be shipping. The goal of this PR is to get us back to something that makes sense and is consistent across platforms, but for that to be successful we need to be willing to take this to release/6.0. For reference, this is about the preallocationSize argument added to FileStream in .NET 6. If you end up writing fewer bytes than you preallocated, in release/6.0 Windows will truncate the file to the number of bytes actually written whereas Linux will end up with a file padded with zeros at the end to meet the preallocatedSize. In main, both Windows and Linux will end up with said zeros at the end. This PR tries to bring it to a state where preallocatedSize is just a (non-observable) hint purely for performance, such that the resulting file size isn't dependent on the preallocatedSize, and the latter is just there to help the system optimize. |
Thanks for the helpful background and explanation, @stephentoub. The behavior of this just being a hint instead of a fixed file size (let alone the consistency) sounds intuitive to me. This has my support for acceptance into RC2. |
|
||
if (mode != FileMode.Create && | ||
mode != FileMode.CreateNew && | ||
mode != FileMode.Truncate) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to leave out Truncate
here as I think it requires some special attention in case the allocation fails to ensure the file remains but doesn't take up space.
For Create
and CreateNew
we'll delete the file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is missing the case of OpenOrCreate
when the file is seekable and its length is zero, was that made on purpose?
Is there any other case that this PR is switching to "not support" and it was previously supported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is missing the case of OpenOrCreate when the file is seekable and its length is zero, was that made on purpose?
Yes, the behavior changes depending on the length of the file, which the user typically won't know.
Leaving out OpenOrCreate
is more deterministic.
Is there any other case that this PR is switching to "not support" and it was previously supported?
The files must to be opened with write file access and a mode that creates.
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs
Show resolved
Hide resolved
|
||
if (mode != FileMode.Create && | ||
mode != FileMode.CreateNew && | ||
mode != FileMode.Truncate) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is missing the case of OpenOrCreate
when the file is seekable and its length is zero, was that made on purpose?
Is there any other case that this PR is switching to "not support" and it was previously supported?
@stephentoub @jozkee I've addressed your feedback and CI is passing, can you take another look? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tmds I still need to take a look at the tests but otherwise, LGTM.
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
Show resolved
Hide resolved
Co-authored-by: David Cantú <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My only concern with the proposed change here is that it is dropping support of various FileMode
s to only support Create
and CreateNew
, and it is switching to throw exceptions instead of just "ignoring the hint". I think is not a big deal since we can change in the future and it wouldn't be a breaking change to do so.
Other than my comments related to tests, LGTM.
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs
Show resolved
Hide resolved
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs
Show resolved
Hide resolved
{ | ||
protected override string GetExpectedParamName(string paramName) => paramName; | ||
|
||
protected override FileStream CreateFileStream(string path, FileMode mode) | ||
{ | ||
FileAccess access = mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite; | ||
return new FileStream(File.OpenHandle(path, mode, access, preallocationSize: PreallocationSize), access); | ||
return new FileStream(File.OpenHandle(path, mode, access), access); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was the verdict on testing coverage with all of these tests being updated to not pass in a preallocation size?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Coverage is good now.
I refactored tests to follow the pattern that was already used by adding an overload of CreateFileStream
to the base class FileStream_ctor_options
that accepts a preallocationSize
and add the preallocationSize
related tests in this class. The derived classes use it from different public APIs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, Tom.
CI failures are not related. This is good to merge. |
/backport to release/6.0 |
Started backporting to release/6.0: https://github.com/dotnet/runtime/actions/runs/1266655869 |
@jozkee backporting to release/6.0 failed, the patch most likely resulted in conflicts: $ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: File preallocationSize: align Windows and Unix behavior.
Using index info to reconstruct a base tree...
M src/libraries/Native/Unix/System.Native/entrypoints.c
M src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Windows.cs
M src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.cs
M src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj
M src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
M src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
M src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
M src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
M src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs
Falling back to patching base and 3-way merge...
Auto-merging src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs
CONFLICT (content): Merge conflict in src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs
Auto-merging src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
Auto-merging src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
CONFLICT (content): Merge conflict in src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
Auto-merging src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
CONFLICT (content): Merge conflict in src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Auto-merging src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
CONFLICT (content): Merge conflict in src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
Auto-merging src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj
Auto-merging src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.cs
CONFLICT (content): Merge conflict in src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.cs
Auto-merging src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Windows.cs
CONFLICT (content): Merge conflict in src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Windows.cs
CONFLICT (add/add): Merge conflict in src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Unix.cs
Auto-merging src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Unix.cs
Auto-merging src/libraries/Native/Unix/System.Native/entrypoints.c
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0001 File preallocationSize: align Windows and Unix behavior.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128 Please backport manually! |
* File preallocationSize: align Windows and Unix behavior. This aligns Windows and Unix behavior of preallocationSize for the intended use-case of specifing the size of a file that will be written. For this use-case, the expected FileAccess is Write, and the file should be a new one (FileMode.Create*) or a truncated file (FileMode.Truncate). Specifing a preallocationSize for other modes, or non-writable files throws ArgumentException. The opened file will have a length of zero, and is ready to be written to by the user. If the requested size cannot be allocated, an IOException is thrown. When the OS/filesystem does not support pre-allocating, preallocationSize is ignored. * fix pal_io preprocessor checks * pal_io more fixes * ctor_options_as.Windows.cs: fix compilation * Update tests * tests: use preallocationSize from all public APIs * pal_io: add back FreeBSD, fix OSX * tests: check allocated is zero when preallocation is not supported. * Only throw for not enough space errors * Fix compilation * Add some more tests * Fix ExtendedPathsAreSupported test * Apply suggestions from code review Co-authored-by: David Cantú <[email protected]> * Update System.Private.CoreLib Strings.resx * PR feedback * Remove GetPathToNonExistingFile * Fix compilation * Skip checking allocated size on mobile platforms. Co-authored-by: David Cantú <[email protected]>
…vior. (#59532) * Align preallocationSize behavior (#58726) Co-authored-by: Stephen Toub <[email protected]> * File preallocationSize: align Windows and Unix behavior. (#59338) * File preallocationSize: align Windows and Unix behavior. This aligns Windows and Unix behavior of preallocationSize for the intended use-case of specifing the size of a file that will be written. For this use-case, the expected FileAccess is Write, and the file should be a new one (FileMode.Create*) or a truncated file (FileMode.Truncate). Specifing a preallocationSize for other modes, or non-writable files throws ArgumentException. The opened file will have a length of zero, and is ready to be written to by the user. If the requested size cannot be allocated, an IOException is thrown. When the OS/filesystem does not support pre-allocating, preallocationSize is ignored. * fix pal_io preprocessor checks * pal_io more fixes * ctor_options_as.Windows.cs: fix compilation * Update tests * tests: use preallocationSize from all public APIs * pal_io: add back FreeBSD, fix OSX * tests: check allocated is zero when preallocation is not supported. * Only throw for not enough space errors * Fix compilation * Add some more tests * Fix ExtendedPathsAreSupported test * Apply suggestions from code review Co-authored-by: David Cantú <[email protected]> * Update System.Private.CoreLib Strings.resx * PR feedback * Remove GetPathToNonExistingFile * Fix compilation * Skip checking allocated size on mobile platforms. Co-authored-by: David Cantú <[email protected]> * Fix unused fileDescriptor * void fd when unused * Address feedback Co-authored-by: Adam Sitnik <[email protected]> Co-authored-by: Stephen Toub <[email protected]> Co-authored-by: Tom Deseyn <[email protected]>
This aligns Windows and Unix behavior of preallocationSize for the
intended use-case of specifing the size of a file that will be written.
For this use-case, the expected FileAccess is Write, and the file should be
a new one (FileMode.Create*) or a truncated file (FileMode.Truncate).
Specifing a preallocationSize for other modes, or non-writable files throws ArgumentException.
The opened file will have a length of zero, and is ready to be written to by the user.
If the requested size cannot be allocated, an IOException is thrown.
When the OS/filesystem does not support pre-allocating, preallocationSize is ignored.
@stephentoub @danmoseley @jozkee @carlossanlop ptal.
cc @adamsitnik
edit: relates to #59078