-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Add new option bag for FileStream ctor #52446
Comments
Tagging subscribers to this area: @carlossanlop Issue DetailsBackground and MotivationWe have recently approved one new As pointed by @carlossanlop and @stephentoub, we should consider adding options bag instead. An extra motivation for adding the option bag is a new feature request (#52321) for allowing to pass Proposed APInamespace System.IO
{
+ public readonly struct FileStreamOptions
+ {
+ public string Path { get; init; }
+ public FileMode Mode { get; init; }
+ public FileAccess Access { get; init; }
+ public FileShare Share { get; init; }
+ public Memory<byte>? Buffer { get; init; } // default value == null => use default buffer size and allocate the buffer (current behaviour)
+ public FileOptions Options { get; init; }
+ public long AllocationSize { get; init; }
+ }
public class FileStream : Stream
{
public FileStream(string path, FileMode mode)
public FileStream(string path, FileMode mode, FileAccess access)
public FileStream(string path, FileMode mode, FileAccess access, FileShare share)
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync)
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
+ public FileStream(FileStreamOptions options)
} Usage Examples// Opening file for Read:
var basic = new FileStreamOptions
{
Path = @"C:\FrameworkDesignGuidelines.pdf",
Mode = FileMode.Open,
Access = FileAccess.Read,
};
var read = new FileStream(basic);
// creating new file for async write with allocation size and buffer provided by the user
var advanced = new FileStreamOptions
{
Path = @"C:\ouput.txt",
Mode = FileMode.CreateNew,
Access = FileAccess.Write,
Share = FileShare.None,
AllocationSize = 1024 * 1024,
Options = FileOptions.Asynchronous | FileOptions.WriteThrough,
Buffer = ArrayPool<byte>.Shared.Rent(4096)
};
var write = new FileStream(advanced);
// To disable the buffering, users would have to pass a default or empty `Memory<byte>`:
var noBuffering = new FileStreamOptions
{
Buffer = default // Array.Empty<byte>() would also work
}; Alternative DesignsDon't let the user provide the buffer (to minimize risk of misuse), but instead provide namespace System.IO
{
+ public readonly struct FileStreamOptions
+ {
+ public string Path { get; init; }
+ public FileMode Mode { get; init; }
+ public FileAccess Access { get; init; }
+ public FileShare Share { get; init; }
+ public int BufferSize { get; init; } // the difference
+ public FileOptions Options { get; init; }
+ public long AllocationSize { get; init; }
+ }
public class FileStream : Stream
{
public FileStream(string path, FileMode mode)
public FileStream(string path, FileMode mode, FileAccess access)
public FileStream(string path, FileMode mode, FileAccess access, FileShare share)
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync)
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
+ public FileStream(FileStreamOptions options)
}
public enum FileOptions
{
WriteThrough,
None,
Encrypted,
DeleteOnClose,
SequentialScan,
RandomAccess,
Asynchronous,
+ PoolBuffer // new option
} RisksAllowing the users to pass the buffer creates the risk of misusing the buffer by the user:
|
How would you know to return the array pool buffer in the FileStream implementation with the original proposal or is that the job of the caller to do? |
It would be caller's responsibility. With the alternative design ( edit: I've edited the example to clarify that |
Do we have another other examples of APIs like that? |
Since |
|
And the more recent |
should this have blocking label to put it at the top of the api review queue? |
@ayende @nietras (who provided feedback about issues with buffer allocations) considering the two proposals:
Which would work best for your needs and why? |
I would rather have |
namespace System.IO
{
public sealed class FileStreamOptions
{
public FileStreamOptions();
public FileMode Mode { get; set; }
public FileAccess Access { get; set; }
public FileShare Share { get; set; }
public FileOptions Options { get; set; }
public long PreAllocationSize { get; set; }
}
public partial class FileStream : Stream
{
// Existing:
// public FileStream(string path, FileMode mode)
// public FileStream(string path, FileMode mode, FileAccess access)
// public FileStream(string path, FileMode mode, FileAccess access, FileShare share)
// public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
// public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync)
// public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
public FileStream(string path, FileStreamOptions options);
// Hiding obsoleting APIs
[EditorBrowsable(EditorBrowsableState.Never)]
public FileStream(IntPtr handle, FileAccess access);
[EditorBrowsable(EditorBrowsableState.Never)]
public FileStream(IntPtr handle, FileAccess access, bool ownsHandle);
[EditorBrowsable(EditorBrowsableState.Never)]
public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize);
[EditorBrowsable(EditorBrowsableState.Never)]
public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync);
}
} |
My 2c
|
use-after-free issues can be solved through delegation of buffer allocation. I see two options here: public class FileStreamOptions // not sealed
{
public int BufferSize { get; set; }
public virtual Memory<byte> AllocateBuffer() => new byte[BufferSize].AsMemory(); // default impl
} or public sealed class FileStreamOptions
{
public int BufferSize { get; set; }
public Func<int, Memory<byte>>? BufferAllocator { get; set; }
} |
There's a subtle difference in interpretation between pre-allocation and preallocation. With the hyphen means before an allocation event ("Pre-allocation we were running at 1200 RPS, but after we were up to 24k RPS."), without the hyphen means allocating in advance of need ("Utilizing a preallocation of 73 workers we were able to smooth out the latency spike induced from lunchtime web traffic.") Since this is "allocating in advance of need" there's no hyphen, so no second capital letter. This matches both the MS Style Guide (https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/p/pre) and Wiktionary's (uncited) definition/spelling (https://en.wiktionary.org/wiki/preallocate). (None of the American Heritage Dictionary, Merriam-Webster, or Oxford English Dictionary currently list "preallocate" as a word, for what it's worth.) Therefore: namespace System.IO
{
partial class FileStreamOptions
{
public long PreallocationSize { get; set; }
}
} Alternative names:
|
Deleted my previous post. I’d forgotten that useAsync was part of FileOptions enum. I do feel that prior to release, we will want to either have a way to pass in a buffer, or have BufferSize added to this class, since it would be really weird to make the ability to chose a different to be mutually exclusive with any new parameters added. |
I've added the proposal for specifying buffer or buffer size and a possibility to rent the array to #15088 (comment) |
Background and Motivation
We have recently approved one new
FileStream
constructor argument:long allocationSize
(#45946 (comment)) that got implemented, but has not been merged yet (#51111).As pointed by @carlossanlop and @stephentoub, we should consider adding options bag instead.
An extra motivation for adding the option bag is a new feature request (#52321) for allowing to pass
Memory<byte>
as a buffer. This would help us to solve #15088 and literally remove the last problematic allocation fromFileStream
.Proposed API
Usage Examples
Alternative Designs
Don't let the user provide the buffer (to minimize risk of misuse), but instead provide
bufferSize
and extendFileOptions
withPoolBuffer
:Risks
Allowing the users to pass the buffer creates the risk of misusing the buffer by the user:
ArrayPool
Memory<byte>
wraps when it's still being used byFileStream
The text was updated successfully, but these errors were encountered: