Skip to content

Commit

Permalink
Extend FileStreamOptions with BufferSize, allow to specify 0 to disab…
Browse files Browse the repository at this point in the history
…le the buffering (#52928)

* extend FileStreamOptions with BufferSize

* allow to disable the buffering by setting bufferSize to 0

* Apply suggestions from code review

Co-authored-by: David Cantú <dacantu@microsoft.com>
  • Loading branch information
adamsitnik and Jozkee committed May 19, 2021
1 parent 9ac15f7 commit 82289b2
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public async Task NoDataIsLostWhenWritingToFile(ReadWriteMode mode)
filePath = stream.Name;

// the following buffer fits into internal FileStream buffer
byte[] small = Enumerable.Repeat(byte.MinValue, BufferSize - 1).ToArray();
byte[] small = Enumerable.Repeat(byte.MinValue, Math.Max(1, BufferSize - 1)).ToArray();
// the following buffer does not fit into internal FileStream buffer
byte[] big = Enumerable.Repeat(byte.MaxValue, BufferSize + 1).ToArray();
// in this test we are selecting a random buffer and write it to file
Expand Down Expand Up @@ -175,7 +175,7 @@ public async Task WriteByteFlushesTheBufferWhenItBecomesFull()
stream.WriteByte(0);
writtenBytes.Add(0);

byte[] bytes = new byte[BufferSize - 1];
byte[] bytes = new byte[Math.Max(0, BufferSize - 1)];
stream.Write(bytes.AsSpan());
writtenBytes.AddRange(bytes);

Expand All @@ -191,7 +191,12 @@ public async Task WriteByteFlushesTheBufferWhenItBecomesFull()
public class UnbufferedSyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests
{
protected override FileOptions Options => FileOptions.None;

#if RELEASE // since buffering can be now disabled by setting the buffer size to 0 or 1, let's test 0 in one config and 1 in the other
protected override int BufferSize => 0;
#else
protected override int BufferSize => 1;
#endif
}

public class BufferedSyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests
Expand All @@ -205,7 +210,12 @@ public class BufferedSyncFileStreamStandaloneConformanceTests : FileStreamStanda
public class UnbufferedAsyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests
{
protected override FileOptions Options => FileOptions.Asynchronous;

#if RELEASE
protected override int BufferSize => 0;
#else
protected override int BufferSize => 1;
#endif
}

[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ protected override FileStream CreateFileStream(string path, FileMode mode, FileA
PreallocationSize = PreallocationSize
});

protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
=> new FileStream(path,
new FileStreamOptions
{
Mode = mode,
Access = access,
Share = share,
BufferSize = bufferSize,
Options = options,
PreallocationSize = PreallocationSize
});

protected FileStreamOptions GetOptions(FileMode mode, FileAccess access, FileShare share, FileOptions options, long preAllocationSize)
=> new FileStreamOptions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ public void NegativeBufferSizeThrows()
}

[Fact]
public void ZeroBufferSizeThrows()
public void ZeroBufferSizeDoesNotThrow()
{
// Unfortunate pre-existing behavior of FileStream, we should look into enabling this sometime.
AssertExtensions.Throws<ArgumentOutOfRangeException>("bufferSize", () => CreateFileStream(GetTestFilePath(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 0));
using (CreateFileStream(GetTestFilePath(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 0))
{
}
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ public FileStream(string path, FileMode mode, FileAccess access, FileShare share
/// -or-
/// <paramref name="path" /> refers to a non-file device, such as <c>CON:</c>, <c>COM1:</c>, <c>LPT1:</c>, etc. in an NTFS environment.</exception>
/// <exception cref="T:System.NotSupportedException"><paramref name="path" /> refers to a non-file device, such as <c>CON:</c>, <c>COM1:</c>, <c>LPT1:</c>, etc. in a non-NTFS environment.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException"><see cref="System.IO.FileStreamOptions.PreallocationSize" /> is negative.
/// <exception cref="T:System.ArgumentOutOfRangeException"><see cref="System.IO.FileStreamOptions.BufferSize" /> is negative.
/// -or-
/// <see cref="System.IO.FileStreamOptions.PreallocationSize" /> is negative.
/// -or-
/// <see cref="System.IO.FileStreamOptions.Mode" />, <see cref="System.IO.FileStreamOptions.Access" />, or <see cref="System.IO.FileStreamOptions.Share" /> contain an invalid value.</exception>
/// <exception cref="T:System.IO.FileNotFoundException">The file cannot be found, such as when <see cref="System.IO.FileStreamOptions.Mode" /> is <see langword="FileMode.Truncate" /> or <see langword="FileMode.Open" />, and the file specified by <paramref name="path" /> does not exist. The file must already exist in these modes.</exception>
Expand All @@ -167,7 +169,7 @@ public FileStream(string path, FileMode mode, FileAccess access, FileShare share
/// <see cref="F:System.IO.FileOptions.Encrypted" /> is specified for <see cref="System.IO.FileStreamOptions.Options" /> , but file encryption is not supported on the current platform.</exception>
/// <exception cref="T:System.IO.PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. </exception>
public FileStream(string path, FileStreamOptions options)
: this(path, options.Mode, options.Access, options.Share, DefaultBufferSize, options.Options, options.PreallocationSize)
: this(path, options.Mode, options.Access, options.Share, options.BufferSize, options.Options, options.PreallocationSize)
{
}

Expand Down Expand Up @@ -209,7 +211,7 @@ private FileStream(string path, FileMode mode, FileAccess access, FileShare shar
{
throw new ArgumentOutOfRangeException(nameof(options), SR.ArgumentOutOfRange_Enum);
}
else if (bufferSize <= 0)
else if (bufferSize < 0)
{
throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,10 @@ public sealed class FileStreamOptions
/// In other cases (including the default 0 value), it's ignored.
/// </summary>
public long PreallocationSize { get; set; }
/// <summary>
/// The size of the buffer used by <see cref="FileStream" /> for buffering. The default buffer size is 4096.
/// 0 or 1 means that buffering should be disabled. Negative values are not allowed.
/// </summary>
public int BufferSize { get; set; } = FileStream.DefaultBufferSize;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ internal static partial class FileStreamHelpers
{
// in the future we are most probably going to introduce more strategies (io_uring etc)
private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, FileAccess access, FileShare share, int bufferSize, bool isAsync)
=> new Net5CompatFileStreamStrategy(handle, access, bufferSize, isAsync);
=> new Net5CompatFileStreamStrategy(handle, access, bufferSize == 0 ? 1 : bufferSize, isAsync);

private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
=> new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize, options, preallocationSize);
=> new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize == 0 ? 1 : bufferSize, options, preallocationSize);

internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, File
{
if (UseNet5CompatStrategy)
{
return new Net5CompatFileStreamStrategy(handle, access, bufferSize, isAsync);
// The .NET 5 Compat strategy does not support bufferSize == 0.
// To minimize the risk of introducing bugs to it, we just pass 1 to disable the buffering.
return new Net5CompatFileStreamStrategy(handle, access, bufferSize == 0 ? 1 : bufferSize, isAsync);
}

WindowsFileStreamStrategy strategy = isAsync
Expand All @@ -46,7 +48,7 @@ private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode,
{
if (UseNet5CompatStrategy)
{
return new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize, options, preallocationSize);
return new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize == 0 ? 1 : bufferSize, options, preallocationSize);
}

WindowsFileStreamStrategy strategy = (options & FileOptions.Asynchronous) != 0
Expand All @@ -57,7 +59,7 @@ private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode,
}

internal static FileStreamStrategy EnableBufferingIfNeeded(WindowsFileStreamStrategy strategy, int bufferSize)
=> bufferSize == 1 ? strategy : new BufferedFileStreamStrategy(strategy, bufferSize);
=> bufferSize > 1 ? new BufferedFileStreamStrategy(strategy, bufferSize) : strategy;

internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
=> CreateFileOpenHandle(path, mode, access, share, options, preallocationSize);
Expand Down
1 change: 1 addition & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7307,6 +7307,7 @@ public sealed class FileStreamOptions
public System.IO.FileShare Share { get; set; }
public System.IO.FileOptions Options { get; set; }
public long PreallocationSize { get; set; }
public int BufferSize { get; set; }
}
public partial class FileStream : System.IO.Stream
{
Expand Down

0 comments on commit 82289b2

Please sign in to comment.