diff --git a/src/libraries/System.IO.MemoryMappedFiles/ref/System.IO.MemoryMappedFiles.cs b/src/libraries/System.IO.MemoryMappedFiles/ref/System.IO.MemoryMappedFiles.cs index 2182c4a6d5b5a..c5b786f48acdf 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/ref/System.IO.MemoryMappedFiles.cs +++ b/src/libraries/System.IO.MemoryMappedFiles/ref/System.IO.MemoryMappedFiles.cs @@ -25,6 +25,7 @@ public partial class MemoryMappedFile : System.IDisposable internal MemoryMappedFile() { } public Microsoft.Win32.SafeHandles.SafeMemoryMappedFileHandle SafeMemoryMappedFileHandle { get { throw null; } } public static System.IO.MemoryMappedFiles.MemoryMappedFile CreateFromFile(System.IO.FileStream fileStream, string? mapName, long capacity, System.IO.MemoryMappedFiles.MemoryMappedFileAccess access, System.IO.HandleInheritability inheritability, bool leaveOpen) { throw null; } + public static System.IO.MemoryMappedFiles.MemoryMappedFile CreateFromFile(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, string? mapName, long capacity, System.IO.MemoryMappedFiles.MemoryMappedFileAccess access, System.IO.HandleInheritability inheritability, bool leaveOpen) { throw null; } public static System.IO.MemoryMappedFiles.MemoryMappedFile CreateFromFile(string path) { throw null; } public static System.IO.MemoryMappedFiles.MemoryMappedFile CreateFromFile(string path, System.IO.FileMode mode) { throw null; } public static System.IO.MemoryMappedFiles.MemoryMappedFile CreateFromFile(string path, System.IO.FileMode mode, string? mapName) { throw null; } diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.cs b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.cs index 57dc662749335..49b33555f120d 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.cs +++ b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.cs @@ -107,22 +107,7 @@ public static MemoryMappedFile CreateFromFile(string path, FileMode mode, string MemoryMappedFileAccess access) { ArgumentNullException.ThrowIfNull(path); - - if (mapName != null && mapName.Length == 0) - { - throw new ArgumentException(SR.Argument_MapNameEmptyString); - } - - if (capacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_PositiveOrDefaultCapacityRequired); - } - - if (access < MemoryMappedFileAccess.ReadWrite || - access > MemoryMappedFileAccess.ReadWriteExecute) - { - throw new ArgumentOutOfRangeException(nameof(access)); - } + ValidateCreateFile(mapName, capacity, access); if (mode == FileMode.Append) { @@ -132,10 +117,6 @@ public static MemoryMappedFile CreateFromFile(string path, FileMode mode, string { throw new ArgumentException(SR.Argument_NewMMFTruncateModeNotAllowed, nameof(mode)); } - if (access == MemoryMappedFileAccess.Write) - { - throw new ArgumentException(SR.Argument_NewMMFWriteAccessNotAllowed, nameof(access)); - } bool existed = mode switch { @@ -186,37 +167,84 @@ public static MemoryMappedFile CreateFromFile(string path, FileMode mode, string return new MemoryMappedFile(handle, fileHandle, false); } - public static MemoryMappedFile CreateFromFile(FileStream fileStream, string? mapName, long capacity, + /// + /// Creates a memory-mapped file from an existing file using a , + /// and the specified access mode, name, inheritability, and capacity. + /// + /// The to the existing file. Caller is + /// responsible for disposing when is (otherwise, + /// automatically disposed by the ). + /// A name to assign to the memory-mapped file, or for a + /// that you do not intend to share across processes. + /// The maximum size, in bytes, to allocate to the memory-mapped file. + /// Specify 0 to set the capacity to the size of the file. + /// One of the enumeration values that specifies the type of access allowed + /// to the memory-mapped file. + /// This parameter can't be set to + /// One of the enumeration values that specifies whether a handle + /// to the memory-mapped file can be inherited by a child process. The default is . + /// A value that indicates whether to close the source file handle when + /// the is disposed. + /// A memory-mapped file that has the specified characteristics. + /// + /// is or an empty string. + /// -or- + /// and the length of the file are zero. + /// -or- + /// is set to , which is not allowed. + /// -or- + /// is set to and is larger than the length of the file. + /// + /// is . + /// + /// is less than zero. + /// -or- + /// is less than the file size. + /// -or- + /// is not a valid enumeration value. + /// -or- + /// is not a valid enumeration value. + /// + public static MemoryMappedFile CreateFromFile(SafeFileHandle fileHandle, string? mapName, long capacity, MemoryMappedFileAccess access, HandleInheritability inheritability, bool leaveOpen) { - ArgumentNullException.ThrowIfNull(fileStream); + ArgumentNullException.ThrowIfNull(fileHandle); + ValidateCreateFile(mapName, capacity, access); - if (mapName != null && mapName.Length == 0) + long fileSize = RandomAccess.GetLength(fileHandle); + if (capacity == 0 && fileSize == 0) { - throw new ArgumentException(SR.Argument_MapNameEmptyString); + throw new ArgumentException(SR.Argument_EmptyFile); } - if (capacity < 0) + if (inheritability < HandleInheritability.None || inheritability > HandleInheritability.Inheritable) { - throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_PositiveOrDefaultCapacityRequired); + throw new ArgumentOutOfRangeException(nameof(inheritability)); } - long fileSize = fileStream.Length; - if (capacity == 0 && fileSize == 0) + if (capacity == DefaultSize) { - throw new ArgumentException(SR.Argument_EmptyFile); + capacity = fileSize; } - if (access < MemoryMappedFileAccess.ReadWrite || - access > MemoryMappedFileAccess.ReadWriteExecute) - { - throw new ArgumentOutOfRangeException(nameof(access)); - } + SafeMemoryMappedFileHandle handle = CreateCore(fileHandle, mapName, inheritability, + access, MemoryMappedFileOptions.None, capacity, fileSize); - if (access == MemoryMappedFileAccess.Write) + return new MemoryMappedFile(handle, fileHandle, leaveOpen); + } + + public static MemoryMappedFile CreateFromFile(FileStream fileStream, string? mapName, long capacity, + MemoryMappedFileAccess access, + HandleInheritability inheritability, bool leaveOpen) + { + ArgumentNullException.ThrowIfNull(fileStream); + ValidateCreateFile(mapName, capacity, access); + + long fileSize = fileStream.Length; + if (capacity == 0 && fileSize == 0) { - throw new ArgumentException(SR.Argument_NewMMFWriteAccessNotAllowed, nameof(access)); + throw new ArgumentException(SR.Argument_EmptyFile); } if (inheritability < HandleInheritability.None || inheritability > HandleInheritability.Inheritable) @@ -483,5 +511,29 @@ private static void CleanupFile(SafeFileHandle fileHandle, bool existed, string File.Delete(path); } } + + private static void ValidateCreateFile(string? mapName, long capacity, MemoryMappedFileAccess access) + { + if (mapName != null && mapName.Length == 0) + { + throw new ArgumentException(SR.Argument_MapNameEmptyString); + } + + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_PositiveOrDefaultCapacityRequired); + } + + if (access < MemoryMappedFileAccess.ReadWrite || + access > MemoryMappedFileAccess.ReadWriteExecute) + { + throw new ArgumentOutOfRangeException(nameof(access)); + } + + if (access == MemoryMappedFileAccess.Write) + { + throw new ArgumentException(SR.Argument_NewMMFWriteAccessNotAllowed, nameof(access)); + } + } } } diff --git a/src/libraries/System.IO.MemoryMappedFiles/tests/MemoryMappedFile.CreateFromFile.Tests.cs b/src/libraries/System.IO.MemoryMappedFiles/tests/MemoryMappedFile.CreateFromFile.Tests.cs index 8877936ed1287..894eaf4bc2b6d 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/tests/MemoryMappedFile.CreateFromFile.Tests.cs +++ b/src/libraries/System.IO.MemoryMappedFiles/tests/MemoryMappedFile.CreateFromFile.Tests.cs @@ -36,7 +36,17 @@ public void InvalidArguments_Path() public void InvalidArguments_FileStream() { // null is an invalid stream - AssertExtensions.Throws("fileStream", () => MemoryMappedFile.CreateFromFile(null, CreateUniqueMapName(), 4096, MemoryMappedFileAccess.Read, HandleInheritability.None, true)); + AssertExtensions.Throws("fileStream", () => MemoryMappedFile.CreateFromFile((FileStream)null, CreateUniqueMapName(), 4096, MemoryMappedFileAccess.Read, HandleInheritability.None, true)); + } + + /// + /// Tests invalid arguments to the CreateFromFile fileHandle parameter + /// + [Fact] + public void InvalidArguments_SafeFileHandle() + { + // null is an invalid handle + AssertExtensions.Throws("fileHandle", () => MemoryMappedFile.CreateFromFile((SafeFileHandle)null, CreateUniqueMapName(), 4096, MemoryMappedFileAccess.Read, HandleInheritability.None, true)); } /// @@ -103,6 +113,18 @@ public void InvalidArguments_Access() // Write-only access is not allowed AssertExtensions.Throws("access", () => MemoryMappedFile.CreateFromFile(fs, CreateUniqueMapName(), 4096, MemoryMappedFileAccess.Write, HandleInheritability.None, true)); } + + // Test the same things, but with a SafeFileHandle + using (TempFile file = new TempFile(GetTestFilePath())) + using (SafeFileHandle fileHandle = File.OpenHandle(file.Path, FileMode.Open, FileAccess.ReadWrite)) + { + // Out of range values with a fileHandle + AssertExtensions.Throws("access", () => MemoryMappedFile.CreateFromFile(fileHandle, CreateUniqueMapName(), 4096, (MemoryMappedFileAccess)(-2), HandleInheritability.None, true)); + AssertExtensions.Throws("access", () => MemoryMappedFile.CreateFromFile(fileHandle, CreateUniqueMapName(), 4096, (MemoryMappedFileAccess)(42), HandleInheritability.None, true)); + + // Write-only access is not allowed + AssertExtensions.Throws("access", () => MemoryMappedFile.CreateFromFile(fileHandle, CreateUniqueMapName(), 4096, MemoryMappedFileAccess.Write, HandleInheritability.None, true)); + } } /// @@ -115,7 +137,7 @@ public void InvalidArguments_Access() [InlineData(FileAccess.ReadWrite, MemoryMappedFileAccess.CopyOnWrite)] [InlineData(FileAccess.Read, MemoryMappedFileAccess.Read)] [InlineData(FileAccess.Read, MemoryMappedFileAccess.CopyOnWrite)] - public void FileAccessAndMapAccessCombinations_Valid(FileAccess fileAccess, MemoryMappedFileAccess mmfAccess) + public void FileAccessAndMapAccessCombinationsWithFileStream_Valid(FileAccess fileAccess, MemoryMappedFileAccess mmfAccess) { const int Capacity = 4096; using (TempFile file = new TempFile(GetTestFilePath(), Capacity)) @@ -126,6 +148,27 @@ public void FileAccessAndMapAccessCombinations_Valid(FileAccess fileAccess, Memo } } + /// + /// Tests various values of FileAccess used to construct a SafeFileHandle and MemoryMappedFileAccess used + /// to construct a map over that file handle. The combinations should all be valid. + /// + [Theory] + [InlineData(FileAccess.ReadWrite, MemoryMappedFileAccess.Read)] + [InlineData(FileAccess.ReadWrite, MemoryMappedFileAccess.ReadWrite)] + [InlineData(FileAccess.ReadWrite, MemoryMappedFileAccess.CopyOnWrite)] + [InlineData(FileAccess.Read, MemoryMappedFileAccess.Read)] + [InlineData(FileAccess.Read, MemoryMappedFileAccess.CopyOnWrite)] + public void FileAccessAndMapAccessCombinationsWithSafeFileHandle_Valid(FileAccess fileAccess, MemoryMappedFileAccess mmfAccess) + { + const int Capacity = 4096; + using (TempFile file = new TempFile(GetTestFilePath(), Capacity)) + using (SafeFileHandle fileHandle = File.OpenHandle(file.Path, FileMode.Open, fileAccess)) + using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fileHandle, null, Capacity, mmfAccess, HandleInheritability.None, true)) + { + ValidateMemoryMappedFile(mmf, Capacity, mmfAccess); + } + } + /// /// Tests various values of FileAccess used to construct a FileStream and MemoryMappedFileAccess used /// to construct a map over that stream on Windows. The combinations should all be invalid, resulting in exception. @@ -197,6 +240,11 @@ public void InvalidArguments_MapName() { AssertExtensions.Throws(null, () => MemoryMappedFile.CreateFromFile(fs, string.Empty, 4096, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, true)); } + + using (SafeFileHandle fileHandle = File.OpenHandle(file.Path, FileMode.Open, FileAccess.ReadWrite)) + { + AssertExtensions.Throws(null, () => MemoryMappedFile.CreateFromFile(fileHandle, string.Empty, 4096, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, true)); + } } } @@ -269,6 +317,35 @@ public void InvalidArguments_Capacity() } } + /// + /// Tests invalid arguments to the CreateFromFile capacity parameter + /// when used with SafeFileHandle parameter. + /// + [Fact] + public void InvalidArguments_CapacityWithFileHandle() + { + using (TempFile file = new TempFile(GetTestFilePath())) + { + using (SafeFileHandle fileHandle = File.OpenHandle(file.Path, FileMode.Open, FileAccess.ReadWrite)) + { + // Out of range values for capacity + Assert.Throws(() => MemoryMappedFile.CreateFromFile(fileHandle, null, -1, MemoryMappedFileAccess.Read, HandleInheritability.None, true)); + + // Default (0) capacity with an empty file + AssertExtensions.Throws(null, () => MemoryMappedFile.CreateFromFile(fileHandle, null, 0, MemoryMappedFileAccess.Read, HandleInheritability.None, true)); + AssertExtensions.Throws(null, () => MemoryMappedFile.CreateFromFile(fileHandle, CreateUniqueMapName(), 0, MemoryMappedFileAccess.Read, HandleInheritability.None, true)); + + // Larger capacity than the underlying file, but read-only such that we can't expand the file + RandomAccess.SetLength(fileHandle, 4096); + AssertExtensions.Throws(null, () => MemoryMappedFile.CreateFromFile(fileHandle, null, 8192, MemoryMappedFileAccess.Read, HandleInheritability.None, true)); + AssertExtensions.Throws(null, () => MemoryMappedFile.CreateFromFile(fileHandle, CreateUniqueMapName(), 8192, MemoryMappedFileAccess.Read, HandleInheritability.None, true)); + + // Capacity can't be less than the file size (for such cases a view can be created with the smaller size) + AssertExtensions.Throws("capacity", () => MemoryMappedFile.CreateFromFile(fileHandle, null, 1, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, true)); + } + } + } + /// /// Tests invalid arguments to the CreateFromFile inheritability parameter. /// @@ -279,10 +356,17 @@ public void InvalidArguments_Inheritability(HandleInheritability inheritability) { // Out of range values for inheritability using (TempFile file = new TempFile(GetTestFilePath())) - using (FileStream fs = File.Open(file.Path, FileMode.Open)) { - AssertExtensions.Throws("inheritability", () => MemoryMappedFile.CreateFromFile(fs, CreateUniqueMapName(), 4096, MemoryMappedFileAccess.ReadWrite, inheritability, true)); - } + using (FileStream fs = File.Open(file.Path, FileMode.Open)) + { + AssertExtensions.Throws("inheritability", () => MemoryMappedFile.CreateFromFile(fs, CreateUniqueMapName(), 4096, MemoryMappedFileAccess.ReadWrite, inheritability, true)); + } + + using (SafeFileHandle fileHandle = File.OpenHandle(file.Path, FileMode.Open, FileAccess.ReadWrite)) + { + AssertExtensions.Throws("inheritability", () => MemoryMappedFile.CreateFromFile(fileHandle, CreateUniqueMapName(), 4096, MemoryMappedFileAccess.ReadWrite, inheritability, true)); + } + } } /// @@ -448,11 +532,44 @@ public static IEnumerable MemberData_ValidNameCapacityCombinationsWith } } + /// + /// Test various combinations of arguments to CreateFromFile that accepts a SafeFileHandle. + /// + [Theory] + [MemberData(nameof(ValidArgumentCombinationsWithStreamOrHandle), + new string[] { null, "CreateUniqueMapName()" }, + new long[] { 1, 256, -1 /*pagesize*/, 10000 }, + new MemoryMappedFileAccess[] { MemoryMappedFileAccess.Read, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileAccess.CopyOnWrite }, + new HandleInheritability[] { HandleInheritability.None, HandleInheritability.Inheritable }, + new bool[] { false, true })] + [ActiveIssue("https://github.com/dotnet/runtime/issues/51375", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] + public void ValidArgumentCombinationsWithHandle( + string mapName, long capacity, MemoryMappedFileAccess access, HandleInheritability inheritability, bool leaveOpen) + { + // Create a file of the right size, then create the map for it. + using (TempFile file = new TempFile(GetTestFilePath(), capacity)) + using (SafeFileHandle fileHandle = File.OpenHandle(file.Path, FileMode.Open, FileAccess.ReadWrite)) + using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fileHandle, mapName, capacity, access, inheritability, leaveOpen)) + { + ValidateMemoryMappedFile(mmf, capacity, access, inheritability); + } + + // Start with an empty file and let the map grow it to the right size. This requires write access. + if (IsWritable(access)) + { + using (SafeFileHandle fileHandle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.ReadWrite)) + using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fileHandle, mapName, capacity, access, inheritability, leaveOpen)) + { + ValidateMemoryMappedFile(mmf, capacity, access, inheritability); + } + } + } + /// /// Test various combinations of arguments to CreateFromFile that accepts a FileStream. /// [Theory] - [MemberData(nameof(MemberData_ValidArgumentCombinationsWithStream), + [MemberData(nameof(ValidArgumentCombinationsWithStreamOrHandle), new string[] { null, "CreateUniqueMapName()" }, new long[] { 1, 256, -1 /*pagesize*/, 10000 }, new MemoryMappedFileAccess[] { MemoryMappedFileAccess.Read, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileAccess.CopyOnWrite }, @@ -482,10 +599,10 @@ public void ValidArgumentCombinationsWithStream( } /// - /// Provides input data to the ValidArgumentCombinationsWithStream tests, yielding the full matrix - /// of combinations of input values provided, except for those that are known to be unsupported - /// (e.g. non-null map names on Unix), and with appropriate values substituted in for placeholders - /// listed in the MemberData attribute (e.g. actual system page size instead of -1). + /// Provides input data to the ValidArgumentCombinationsWithStream or ValidArgumentCombinationsWithHandle + /// tests, yielding the full matrix of combinations of input values provided, except for those that are + /// known to be unsupported (e.g. non-null map names on Unix), and with appropriate values substituted + /// in for placeholders listed in the MemberData attribute (e.g. actual system page size instead of -1). /// /// /// The names to yield. @@ -498,7 +615,7 @@ public void ValidArgumentCombinationsWithStream( /// /// The inheritabilities to yield. /// The leaveOpen values to yield. - public static IEnumerable MemberData_ValidArgumentCombinationsWithStream( + public static IEnumerable ValidArgumentCombinationsWithStreamOrHandle( string[] mapNames, long[] capacities, MemoryMappedFileAccess[] accesses, HandleInheritability[] inheritabilities, bool[] leaveOpens) { foreach (string tmpMapName in mapNames) @@ -552,6 +669,14 @@ public void DefaultCapacityIsFileLength() { ValidateMemoryMappedFile(mmf, DesiredCapacity); } + + // With file handle + using (TempFile file = new TempFile(GetTestFilePath(), DesiredCapacity)) + using (SafeFileHandle fileHandle = File.OpenHandle(file.Path, FileMode.Open, FileAccess.ReadWrite)) + using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fileHandle, null, DefaultCapacity, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, true)) + { + ValidateMemoryMappedFile(mmf, DesiredCapacity); + } } /// @@ -711,7 +836,7 @@ public void WriteToReadOnlyFile(MemoryMappedFileAccess access, bool shouldSuccee [Theory] [InlineData(true)] [InlineData(false)] - public void LeaveOpenRespected_Basic(bool leaveOpen) + public void LeaveOpenRespectedWithFileStream_Basic(bool leaveOpen) { const int Capacity = 4096; @@ -730,6 +855,31 @@ public void LeaveOpenRespected_Basic(bool leaveOpen) } } + /// + /// Test to ensure that leaveOpen is appropriately respected, either leaving the SafeFileHandle open + /// or closing it on disposal. + /// + [Theory] + [InlineData(true)] + [InlineData(false)] + public void LeaveOpenRespectedWithSafeFileHandle_Basic(bool leaveOpen) + { + const int Capacity = 4096; + + using (TempFile file = new TempFile(GetTestFilePath())) + using (SafeFileHandle fileHandle = File.OpenHandle(file.Path, FileMode.Open, FileAccess.ReadWrite)) + { + // Handle should still be open + Assert.False(fileHandle.IsClosed); + + // Create and close the map + MemoryMappedFile.CreateFromFile(fileHandle, null, Capacity, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, leaveOpen).Dispose(); + + // The handle should now be open if leaveOpen + Assert.NotEqual(leaveOpen, fileHandle.IsClosed); + } + } + /// /// Test to ensure that leaveOpen is appropriately respected, either leaving the FileStream open /// or closing it on disposal. @@ -738,7 +888,7 @@ public void LeaveOpenRespected_Basic(bool leaveOpen) [InlineData(true)] [InlineData(false)] [ActiveIssue("https://github.com/dotnet/runtime/issues/83197", TestPlatforms.Browser)] - public void LeaveOpenRespected_OutstandingViews(bool leaveOpen) + public void LeaveOpenRespectedWithFileStream_OutstandingViews(bool leaveOpen) { const int Capacity = 4096; using (TempFile file = new TempFile(GetTestFilePath())) @@ -764,6 +914,39 @@ public void LeaveOpenRespected_OutstandingViews(bool leaveOpen) } } + /// + /// Test to ensure that leaveOpen is appropriately respected, either leaving the SafeFileHandle open + /// or closing it on disposal. + /// + [Theory] + [InlineData(true)] + [InlineData(false)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/83197", TestPlatforms.Browser)] + public void LeaveOpenRespectedWithSafeFileHandle_OutstandingViews(bool leaveOpen) + { + const int Capacity = 4096; + using (TempFile file = new TempFile(GetTestFilePath())) + using (SafeFileHandle fileHandle = File.OpenHandle(file.Path, FileMode.Open, FileAccess.ReadWrite)) + { + // Handle should still be open + Assert.False(fileHandle.IsClosed); + + // Create the map, create each of the views, then close the map + using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fileHandle, null, Capacity, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, leaveOpen)) + using (MemoryMappedViewAccessor acc = mmf.CreateViewAccessor(0, Capacity)) + using (MemoryMappedViewStream s = mmf.CreateViewStream(0, Capacity)) + { + // Explicitly close the map. The handle should now be open iff leaveOpen. + mmf.Dispose(); + Assert.NotEqual(leaveOpen, fileHandle.IsClosed); + + // But the views should still be usable. + ValidateMemoryMappedViewAccessor(acc, Capacity, MemoryMappedFileAccess.ReadWrite); + ValidateMemoryMappedViewStream(s, Capacity, MemoryMappedFileAccess.ReadWrite); + } + } + } + /// /// Test to validate we can create multiple maps from the same FileStream. /// @@ -801,6 +984,43 @@ public void MultipleMapsForTheSameFileStream() } } + /// + /// Test to validate we can create multiple maps from the same SafeFileHandle. + /// + [Fact] + [SkipOnPlatform(TestPlatforms.Browser, "the emscripten implementation doesn't share data")] + public void MultipleMapsForTheSameSafeFileHandle() + { + const int Capacity = 4096; + using (TempFile file = new TempFile(GetTestFilePath(), Capacity)) + using (SafeFileHandle fileHandle = File.OpenHandle(file.Path, FileMode.Open, FileAccess.ReadWrite)) + using (MemoryMappedFile mmf1 = MemoryMappedFile.CreateFromFile(fileHandle, null, Capacity, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, true)) + using (MemoryMappedFile mmf2 = MemoryMappedFile.CreateFromFile(fileHandle, null, Capacity, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, true)) + using (MemoryMappedViewAccessor acc1 = mmf1.CreateViewAccessor()) + using (MemoryMappedViewAccessor acc2 = mmf2.CreateViewAccessor()) + { + // The capacity of the two maps should be equal + Assert.Equal(acc1.Capacity, acc2.Capacity); + + var rand = new Random(); + for (int i = 1; i <= 10; i++) + { + // Write a value to one map, then read it from the other, + // ping-ponging between the two. + int pos = rand.Next((int)acc1.Capacity - 1); + MemoryMappedViewAccessor reader = acc1, writer = acc2; + if (i % 2 == 0) + { + reader = acc2; + writer = acc1; + } + writer.Write(pos, (byte)i); + writer.Flush(); + Assert.Equal(i, reader.ReadByte(pos)); + } + } + } + /// /// Test to verify that the map's size increases the underlying file size if the map's capacity is larger. /// @@ -964,6 +1184,23 @@ public void MapHandleMatchesFileStreamHandle() } } + /// + /// Test to verify that the MemoryMappedFile has the same underlying handle as the SafeFileHandle it's created from + /// + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Fact] + public void MapHandleMatchesSafeFileHandle() + { + using (SafeFileHandle fileHandle = File.OpenHandle(GetTestFilePath(), FileMode.OpenOrCreate, FileAccess.ReadWrite)) + { + using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fileHandle, null, 4096, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, false)) + { + SafeMemoryMappedFileHandle handle = mmf.SafeMemoryMappedFileHandle; + Assert.Equal(fileHandle.DangerousGetHandle(), handle.DangerousGetHandle()); + } + } + } + [Theory] [InlineData(0)] [InlineData(1)]