Expose underlying unix file descriptor in SafeMemoryMappedFileHandle (#53538)
authorDanny Friar <dannyfriar@hotmail.co.uk>
Sat, 26 Jun 2021 19:29:30 +0000 (20:29 +0100)
committerGitHub <noreply@github.com>
Sat, 26 Jun 2021 19:29:30 +0000 (15:29 -0400)
* expose unix file descriptor safe memory map

* store handle after DangerousAddRef

* add test verifying handle matches filestream handle

* Update src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.Unix.cs

Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Update src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.Unix.cs

Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Update src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.Unix.cs

Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Update src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.Unix.cs

Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Update src/libraries/System.IO.MemoryMappedFiles/tests/MemoryMappedFile.CreateFromFile.Tests.cs

Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Dispose MemoryMappedFile in MapHandleMatchesFileStreamHandle test

* Update src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.Unix.cs

Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Update MemoryMappedFile.CreateFromFile.Tests.cs

* Move mmf in test to using block

* Fix test

Co-authored-by: Danny Friar <danny@Dannys-MacBook-Pro.local>
Co-authored-by: Stephen Toub <stoub@microsoft.com>
src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.Unix.cs
src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedView.Unix.cs
src/libraries/System.IO.MemoryMappedFiles/tests/MemoryMappedFile.CreateFromFile.Tests.cs

index 32e76bd..3428b01 100644 (file)
@@ -11,15 +11,14 @@ namespace Microsoft.Win32.SafeHandles
 {
     public sealed partial class SafeMemoryMappedFileHandle : SafeHandleZeroOrMinusOneIsInvalid
     {
-        /// <summary>Counter used to produce a unique handle value.</summary>
-        private static long s_counter;
-
         /// <summary>
         /// The underlying FileStream.  May be null.  We hold onto the stream rather than just
         /// onto the underlying handle to ensure that logic associated with disposing the stream
         /// (e.g. deleting the file for DeleteOnClose) happens at the appropriate time.
         /// </summary>
-        internal readonly FileStream? _fileStream;
+        private readonly FileStream? _fileStream;
+        /// <summary>The FileStream's handle, cached to avoid repeated accesses to FileStream.SafeFileHandle that could, in theory, change.</summary>
+        internal SafeFileHandle? _fileStreamHandle;
 
         /// <summary>Whether this SafeHandle owns the _fileStream and should Dispose it when disposed.</summary>
         internal readonly bool _ownsFileStream;
@@ -59,9 +58,22 @@ namespace Microsoft.Win32.SafeHandles
             _options = options;
             _capacity = capacity;
 
-            // Fake a unique int handle value > 0.
-            int nextHandleValue = (int)((Interlocked.Increment(ref s_counter) % (int.MaxValue - 1)) + 1);
-            SetHandle(new IntPtr(nextHandleValue));
+            IntPtr handlePtr;
+
+            if (fileStream != null)
+            {
+                bool ignored = false;
+                SafeFileHandle handle = fileStream.SafeFileHandle;
+                handle.DangerousAddRef(ref ignored);
+                _fileStreamHandle = handle;
+                handlePtr = handle.DangerousGetHandle();
+            }
+            else
+            {
+                handlePtr = IntPtr.MaxValue;
+            }
+
+            SetHandle(handlePtr);
         }
 
         protected override void Dispose(bool disposing)
@@ -74,7 +86,17 @@ namespace Microsoft.Win32.SafeHandles
             base.Dispose(disposing);
         }
 
-        protected override bool ReleaseHandle() => true; // Nothing to clean up.  We unlinked immediately after creating the backing store.
+        protected override bool ReleaseHandle()
+        {
+            if (_fileStreamHandle != null)
+            {
+                SetHandle((IntPtr) (-1));
+                _fileStreamHandle.DangerousRelease();
+                _fileStreamHandle = null;
+            }
+
+            return true;
+        }
 
         public override bool IsInvalid => (long)handle <= 0;
     }
index b5e5a54..917058a 100644 (file)
@@ -56,10 +56,10 @@ namespace System.IO.MemoryMappedFiles
             // If we have a file handle, get the file descriptor from it.  If the handle is null,
             // we'll use an anonymous backing store for the map.
             SafeFileHandle fd;
-            if (memMappedFileHandle._fileStream != null)
+            if (memMappedFileHandle._fileStreamHandle != null)
             {
                 // Get the file descriptor from the SafeFileHandle
-                fd = memMappedFileHandle._fileStream.SafeFileHandle;
+                fd = memMappedFileHandle._fileStreamHandle;
                 Debug.Assert(!fd.IsInvalid);
             }
             else
index 58cc8b3..824f9b9 100644 (file)
@@ -951,5 +951,22 @@ namespace System.IO.MemoryMappedFiles.Tests
             // ENODEV Operation not supported by device.
             catch (IOException ex) when (ex.HResult == 19) { };
         }
+
+        /// <summary>
+        /// Test to verify that the MemoryMappedFile has the same underlying handle as the FileStream it's created from
+        /// </summary>
+        [PlatformSpecific(TestPlatforms.AnyUnix)]
+        [Fact]
+        public void MapHandleMatchesFileStreamHandle()
+        {
+            using (FileStream fs = File.OpenWrite(GetTestFilePath()))
+            {
+                using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fs, null, 4096, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, false))
+                {
+                    SafeMemoryMappedFileHandle handle = mmf.SafeMemoryMappedFileHandle;
+                    Assert.Equal(fs.SafeFileHandle.DangerousGetHandle(), handle.DangerousGetHandle());
+                }
+            }
+        }
     }
 }