From 72a4e94f84a1017dc8c780397dacd7781a82fd6d Mon Sep 17 00:00:00 2001 From: David Cantu Date: Mon, 15 Mar 2021 01:26:47 -0700 Subject: [PATCH] Cache GetFileInformationByHandleEx (Length) when FileShare does not allow other processes to modify it --- .../IO/AsyncWindowsFileStreamStrategy.cs | 1 + .../src/System/IO/FileStream.cs | 2 +- .../IO/SyncWindowsFileStreamStrategy.cs | 2 +- .../System/IO/WindowsFileStreamStrategy.cs | 26 ++++++++++++++++++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/AsyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/AsyncWindowsFileStreamStrategy.cs index baa0bae4eb2b0..7c7bcecd89423 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/AsyncWindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/AsyncWindowsFileStreamStrategy.cs @@ -271,6 +271,7 @@ private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory source, Cancella // touch the file pointer location at all. We will adjust it // ourselves, but only in memory. This isn't threadsafe. _filePosition += source.Length; + UpdateLengthOnChangePosition(); } // queue an async WriteFile operation and pass in a packed overlapped diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs index 6109cf014298c..35b3c6a5e8ba1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs @@ -12,7 +12,7 @@ namespace System.IO public class FileStream : Stream { internal const int DefaultBufferSize = 4096; - private const FileShare DefaultShare = FileShare.Read; + internal const FileShare DefaultShare = FileShare.Read; private const bool DefaultIsAsync = false; /// Caches whether Serialization Guard has been disabled for file writes diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/SyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/SyncWindowsFileStreamStrategy.cs index e722867e3b9cc..85900b82e2d83 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/SyncWindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/SyncWindowsFileStreamStrategy.cs @@ -159,7 +159,7 @@ private unsafe void WriteSpan(ReadOnlySpan source) } Debug.Assert(r >= 0, "FileStream's WriteCore is likely broken."); _filePosition += r; - return; + UpdateLengthOnChangePosition(); } private NativeOverlapped GetNativeOverlappedForCurrentPosition() diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/WindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/WindowsFileStreamStrategy.cs index 15c4581069df2..0badeb1cc74b6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/WindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/WindowsFileStreamStrategy.cs @@ -23,6 +23,8 @@ internal abstract class WindowsFileStreamStrategy : FileStreamStrategy /// Whether the file is opened for reading, writing, or both. private readonly FileAccess _access; + private readonly FileShare _share; + /// The path to the opened file. protected readonly string? _path; @@ -32,6 +34,7 @@ internal abstract class WindowsFileStreamStrategy : FileStreamStrategy private readonly bool _isPipe; // Whether to disable async buffering code. private long _appendStart; // When appending, prevent overwriting file. + private long? _length; internal WindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access) { @@ -40,6 +43,7 @@ internal WindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access) // Note: Cleaner to set the following fields in ValidateAndInitFromHandle, // but we can't as they're readonly. _access = access; + _share = FileStream.DefaultShare; // As the handle was passed in, we must set the handle field at the very end to // avoid the finalizer closing the handle when we throw errors. @@ -52,6 +56,7 @@ internal WindowsFileStreamStrategy(string path, FileMode mode, FileAccess access _path = fullPath; _access = access; + _share = share; _fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options); @@ -77,7 +82,25 @@ internal WindowsFileStreamStrategy(string path, FileMode mode, FileAccess access public sealed override bool CanWrite => !_fileHandle.IsClosed && (_access & FileAccess.Write) != 0; - public unsafe sealed override long Length => FileStreamHelpers.GetFileLength(_fileHandle, _path); + public unsafe sealed override long Length => _share > FileShare.Read ? + FileStreamHelpers.GetFileLength(_fileHandle, _path) : + _length ??= FileStreamHelpers.GetFileLength(_fileHandle, _path); + + protected void UpdateLengthOnChangePosition() + { + // Do not update the cached length if the file can be written somewhere else + // or if the length has not been queried. + if (_share > FileShare.Read || _length is null) + { + Debug.Assert(_length is null); + return; + } + + if (_filePosition > _length) + { + _length = _filePosition; + } + } /// Gets or sets the position within the current stream public override long Position @@ -256,6 +279,7 @@ protected unsafe void SetLengthCore(long value) Debug.Assert(value >= 0, "value >= 0"); FileStreamHelpers.SetLength(_fileHandle, _path, value); + _length = value; if (_filePosition > value) {