From: Rodrigo Kumpera Date: Fri, 4 Nov 2016 21:59:47 +0000 (-0700) Subject: Merge pull request #3796 from ntherning/windows-backend-for-MemoryMappedFile X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=commitdiff_plain;h=7bd7446abcbe2c93be78fd97221dcddaeb92d653;hp=711f82801d60d817565dac0ac5f300a29fe5b7a0;p=mono.git Merge pull request #3796 from ntherning/windows-backend-for-MemoryMappedFile MemoryMappedFile support on Windows --- diff --git a/mcs/class/System.Core/Microsoft.Win32.SafeHandles/SafeMemoryMappedViewHandle.cs b/mcs/class/System.Core/Microsoft.Win32.SafeHandles/SafeMemoryMappedViewHandle.cs index f165f3f6279..e48161a358a 100644 --- a/mcs/class/System.Core/Microsoft.Win32.SafeHandles/SafeMemoryMappedViewHandle.cs +++ b/mcs/class/System.Core/Microsoft.Win32.SafeHandles/SafeMemoryMappedViewHandle.cs @@ -44,6 +44,10 @@ namespace Microsoft.Win32.SafeHandles Initialize ((ulong)size); } + internal void Flush () { + MemoryMapImpl.Flush (this.mmap_handle); + } + protected override bool ReleaseHandle () { if (this.handle != (IntPtr) (-1)) return MemoryMapImpl.Unmap (this.mmap_handle); diff --git a/mcs/class/System.Core/System.IO.MemoryMappedFiles/MemoryMappedFile.cs b/mcs/class/System.Core/System.IO.MemoryMappedFiles/MemoryMappedFile.cs index fbc55fd73b5..7a3cce164f6 100644 --- a/mcs/class/System.Core/System.IO.MemoryMappedFiles/MemoryMappedFile.cs +++ b/mcs/class/System.Core/System.IO.MemoryMappedFiles/MemoryMappedFile.cs @@ -70,7 +70,7 @@ namespace System.IO.MemoryMappedFiles case 1: return new ArgumentException ("A positive capacity must be specified for a Memory Mapped File backed by an empty file."); case 2: - return new ArgumentOutOfRangeException ("The capacity may not be smaller than the file size."); + return new ArgumentOutOfRangeException ("capacity", "The capacity may not be smaller than the file size."); case 3: return new FileNotFoundException (path); case 4: @@ -85,6 +85,10 @@ namespace System.IO.MemoryMappedFiles return new ArgumentException ("Invalid FileMode value."); case 9: return new IOException ("Could not map file"); + case 10: + return new UnauthorizedAccessException ("Access to the path is denied."); + case 11: + return new ArgumentOutOfRangeException ("capacity", "The capacity cannot be greater than the size of the system's logical address space."); default: return new IOException ("Failed with unknown error code " + error); } @@ -140,7 +144,7 @@ namespace System.IO.MemoryMappedFiles if (mode == FileMode.Append) throw new ArgumentException ("mode"); - IntPtr handle = MemoryMapImpl.OpenFile (path, mode, null, out capacity, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.DelayAllocatePages); + IntPtr handle = MemoryMapImpl.OpenFile (path, mode, null, out capacity, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None); return new MemoryMappedFile () { handle = handle, @@ -172,7 +176,7 @@ namespace System.IO.MemoryMappedFiles if (capacity < 0) throw new ArgumentOutOfRangeException ("capacity"); - IntPtr handle = MemoryMapImpl.OpenFile (path, mode, mapName, out capacity, access, MemoryMappedFileOptions.DelayAllocatePages); + IntPtr handle = MemoryMapImpl.OpenFile (path, mode, mapName, out capacity, access, MemoryMappedFileOptions.None); return new MemoryMappedFile () { handle = handle, @@ -193,7 +197,7 @@ namespace System.IO.MemoryMappedFiles if ((!MonoUtil.IsUnix && capacity == 0 && fileStream.Length == 0) || (capacity > fileStream.Length)) throw new ArgumentException ("capacity"); - IntPtr handle = MemoryMapImpl.OpenHandle (fileStream.SafeFileHandle.DangerousGetHandle (), mapName, out capacity, access, MemoryMappedFileOptions.DelayAllocatePages); + IntPtr handle = MemoryMapImpl.OpenHandle (fileStream.SafeFileHandle.DangerousGetHandle (), mapName, out capacity, access, MemoryMappedFileOptions.None); MemoryMapImpl.ConfigureHandleInheritability (handle, inheritability); @@ -220,7 +224,7 @@ namespace System.IO.MemoryMappedFiles if ((!MonoUtil.IsUnix && capacity == 0 && fileStream.Length == 0) || (capacity > fileStream.Length)) throw new ArgumentException ("capacity"); - IntPtr handle = MemoryMapImpl.OpenHandle (fileStream.SafeFileHandle.DangerousGetHandle (), mapName, out capacity, access, MemoryMappedFileOptions.DelayAllocatePages); + IntPtr handle = MemoryMapImpl.OpenHandle (fileStream.SafeFileHandle.DangerousGetHandle (), mapName, out capacity, access, MemoryMappedFileOptions.None); MemoryMapImpl.ConfigureHandleInheritability (handle, inheritability); @@ -258,13 +262,13 @@ namespace System.IO.MemoryMappedFiles [MonoLimitation ("Named mappings scope is process local")] public static MemoryMappedFile CreateNew (string mapName, long capacity) { - return CreateNew (mapName, capacity, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.DelayAllocatePages, null, HandleInheritability.None); + return CreateNew (mapName, capacity, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, null, HandleInheritability.None); } [MonoLimitation ("Named mappings scope is process local")] public static MemoryMappedFile CreateNew (string mapName, long capacity, MemoryMappedFileAccess access) { - return CreateNew (mapName, capacity, access, MemoryMappedFileOptions.DelayAllocatePages, null, HandleInheritability.None); + return CreateNew (mapName, capacity, access, MemoryMappedFileOptions.None, null, HandleInheritability.None); } [MonoLimitation ("Named mappings scope is process local; options is ignored")] @@ -290,7 +294,7 @@ namespace System.IO.MemoryMappedFiles [MonoLimitation ("Named mappings scope is process local")] public static MemoryMappedFile CreateOrOpen (string mapName, long capacity, MemoryMappedFileAccess access) { - return CreateOrOpen (mapName, capacity, access, MemoryMappedFileOptions.DelayAllocatePages, null, HandleInheritability.None); + return CreateOrOpen (mapName, capacity, access, MemoryMappedFileOptions.None, null, HandleInheritability.None); } [MonoLimitation ("Named mappings scope is process local")] diff --git a/mcs/class/System.Core/System.IO.MemoryMappedFiles/MemoryMappedView.cs b/mcs/class/System.Core/System.IO.MemoryMappedFiles/MemoryMappedView.cs index 9362f00b162..b0be2e4f188 100644 --- a/mcs/class/System.Core/System.IO.MemoryMappedFiles/MemoryMappedView.cs +++ b/mcs/class/System.Core/System.IO.MemoryMappedFiles/MemoryMappedView.cs @@ -92,7 +92,7 @@ namespace System.IO.MemoryMappedFiles public void Flush (IntPtr capacity) { - MemoryMapImpl.Flush (m_viewHandle.DangerousGetHandle ()); + m_viewHandle.Flush (); } protected virtual void Dispose (bool disposing) diff --git a/mcs/class/System.Core/Test/System.IO.MemoryMappedFiles/MemoryMappedFileTest.cs b/mcs/class/System.Core/Test/System.IO.MemoryMappedFiles/MemoryMappedFileTest.cs index 27191c37ed2..9c273b84a59 100644 --- a/mcs/class/System.Core/Test/System.IO.MemoryMappedFiles/MemoryMappedFileTest.cs +++ b/mcs/class/System.Core/Test/System.IO.MemoryMappedFiles/MemoryMappedFileTest.cs @@ -56,16 +56,28 @@ namespace MonoTests.System.IO.MemoryMappedFiles { return "test-" + named_index++; } - + static string baseTempDir = Path.Combine (Path.GetTempPath (), typeof (MemoryMappedFileTest).FullName); static string tempDir = Path.Combine (Path.GetTempPath (), typeof (MemoryMappedFileTest).FullName); string fname; - [SetUp] - protected void SetUp () { - if (Directory.Exists (tempDir)) - Directory.Delete (tempDir, true); + [TestFixtureSetUp] + public void FixtureSetUp () + { + try { + // Try to cleanup from any previous NUnit run. + Directory.Delete (baseTempDir, true); + } catch (Exception) { + } + } + [SetUp] + public void SetUp () + { + int i = 0; + do { + tempDir = Path.Combine (baseTempDir, (++i).ToString()); + } while (Directory.Exists (tempDir)); Directory.CreateDirectory (tempDir); fname = Path.Combine (tempDir, "basic.txt"); @@ -77,9 +89,14 @@ namespace MonoTests.System.IO.MemoryMappedFiles { } [TearDown] - protected void TearDown () { - if (Directory.Exists (tempDir)) + public void TearDown () + { + try { + // This throws an exception under MS.NET and Mono on Windows, + // since the directory contains open files. Directory.Delete (tempDir, true); + } catch (Exception) { + } } [Test] @@ -102,26 +119,16 @@ namespace MonoTests.System.IO.MemoryMappedFiles { public void CreateNew () { // This must succeed - MemoryMappedFile.CreateNew (Path.Combine (tempDir, "createNew.test"), 8192); - } - - [Test] - [ExpectedException (typeof (IOException))] - public void CreateNew_OnExistingFile () - { - // This must succeed - MemoryMappedFile.CreateNew (Path.Combine (tempDir, "createNew.test"), 8192); - - // This should fail, the file exists - MemoryMappedFile.CreateNew (Path.Combine (tempDir, "createNew.test"), 8192); + MemoryMappedFile.CreateNew (MkNamedMapping (), 8192); } // Call this twice, it should always work [Test] public void CreateOrOpen_Multiple () { - MemoryMappedFile.CreateOrOpen (Path.Combine (tempDir, "createOrOpen.test"), 8192); - MemoryMappedFile.CreateOrOpen (Path.Combine (tempDir, "createOrOpen.test"), 8192); + var name = MkNamedMapping (); + MemoryMappedFile.CreateOrOpen (name, 8192); + MemoryMappedFile.CreateOrOpen (name, 8192); } [Test] @@ -134,7 +141,7 @@ namespace MonoTests.System.IO.MemoryMappedFiles { // We are requesting fewer bytes to map. MemoryMappedFile.CreateFromFile (f, FileMode.Open, "myMap", 4192); } - + [Test] public void CreateFromFile_Null () { AssertThrows (delegate () { @@ -252,6 +259,9 @@ namespace MonoTests.System.IO.MemoryMappedFiles { [Test] public void NamedMappingToInvalidFile () { + if (Environment.OSVersion.Platform != PlatformID.Unix) { + Assert.Ignore ("Backslashes in mapping names are disallowed on .NET and Mono on Windows"); + } var fileName = Path.Combine (tempDir, "temp_file_123"); if (File.Exists (fileName)) File.Delete (fileName); @@ -352,7 +362,7 @@ namespace MonoTests.System.IO.MemoryMappedFiles { } [Test] - [ExpectedException(typeof(IOException))] + [ExpectedException(typeof(UnauthorizedAccessException))] public void CreateViewStreamWithOffsetPastFileEnd () { string f = Path.Combine (tempDir, "8192-file"); @@ -365,7 +375,7 @@ namespace MonoTests.System.IO.MemoryMappedFiles { } [Test] - [ExpectedException(typeof(IOException))] + [ExpectedException(typeof(UnauthorizedAccessException))] public void CreateViewStreamWithOffsetPastFileEnd2 () { string f = Path.Combine (tempDir, "8192-file"); @@ -394,7 +404,7 @@ namespace MonoTests.System.IO.MemoryMappedFiles { MemoryMappedViewStream stream = mappedFile.CreateViewStream (pageSize * 2, 0, MemoryMappedFileAccess.ReadWrite); #if !MONOTOUCH - Assert.AreEqual (stream.Capacity, Environment.SystemPageSize); + Assert.AreEqual (Environment.SystemPageSize, stream.Capacity); #endif stream.Write (new byte [pageSize], 0, pageSize); } @@ -407,9 +417,39 @@ namespace MonoTests.System.IO.MemoryMappedFiles { File.WriteAllBytes (f, new byte [size]); FileStream file = File.OpenRead (f); - MemoryMappedFile.CreateFromFile (file, null, size, MemoryMappedFileAccess.ReadExecute, null, 0, false); + MemoryMappedFile.CreateFromFile (file, null, size, MemoryMappedFileAccess.Read, null, 0, false); } - } -} + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void CreateNewLargerThanLogicalAddressSpace () + { + if (IntPtr.Size != 4) { + Assert.Ignore ("Only applies to 32-bit systems"); + } + MemoryMappedFile.CreateNew (MkNamedMapping (), (long) uint.MaxValue + 1); + } + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void CreateOrOpenLargerThanLogicalAddressSpace () + { + if (IntPtr.Size != 4) { + Assert.Ignore ("Only applies to 32-bit systems"); + } + MemoryMappedFile.CreateOrOpen (MkNamedMapping (), (long) uint.MaxValue + 1); + } + + [Test] + public void NamedMappingWithDelayAllocatePages () + { + var name = MkNamedMapping (); + using (var m0 = MemoryMappedFile.CreateNew(name, 4096, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.DelayAllocatePages, HandleInheritability.None)) { + using (MemoryMappedViewAccessor v0 = m0.CreateViewAccessor ()) { + Assert.AreEqual (0, v0.ReadInt32 (0)); + } + } + } + + } +} diff --git a/mono/metadata/file-mmap-posix.c b/mono/metadata/file-mmap-posix.c index 84e3b4aa68d..4b9ce74499e 100644 --- a/mono/metadata/file-mmap-posix.c +++ b/mono/metadata/file-mmap-posix.c @@ -63,7 +63,9 @@ enum { COULD_NOT_OPEN, CAPACITY_MUST_BE_POSITIVE, INVALID_FILE_MODE, - COULD_NOT_MAP_MEMORY + COULD_NOT_MAP_MEMORY, + ACCESS_DENIED, + CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE }; enum { @@ -300,10 +302,16 @@ static void* open_memory_map (const char *c_mapName, int mode, gint64 *capacity, int access, int options, int *ioerror) { MmapHandle *handle; - if (*capacity <= 1) { + if (*capacity <= 0) { *ioerror = CAPACITY_MUST_BE_POSITIVE; return NULL; } +#if SIZEOF_VOID_P == 4 + if (*capacity > UINT32_MAX) { + *ioerror = CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE; + return NULL; + } +#endif if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) { *ioerror = INVALID_FILE_MODE; @@ -499,8 +507,11 @@ mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mma struct stat buf = { 0 }; fstat (fh->fd, &buf); //FIXME error handling + *mmap_handle = NULL; + *base_address = NULL; + if (offset > buf.st_size || ((eff_size + offset) > buf.st_size && !is_special_zero_size_file (&buf))) - goto error; + return ACCESS_DENIED; /** * We use the file size if one of the following conditions is true: * -input size is zero @@ -522,9 +533,6 @@ mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mma return 0; } -error: - *mmap_handle = NULL; - *base_address = NULL; return COULD_NOT_MAP_MEMORY; } diff --git a/mono/metadata/file-mmap-windows.c b/mono/metadata/file-mmap-windows.c index dba37472c34..b0fd5d9b896 100644 --- a/mono/metadata/file-mmap-windows.c +++ b/mono/metadata/file-mmap-windows.c @@ -1,71 +1,413 @@ /* - * file-mmap-posix.c: File mmap internal calls + * file-mmap-windows.c: MemoryMappedFile internal calls for Windows * - * Author: - * Rodrigo Kumpera - * - * Copyright 2014 Xamarin Inc (http://www.xamarin.com) + * Copyright 2016 Microsoft * Licensed under the MIT license. See LICENSE file in the project root for full license information. */ +/* + * The code in this file has been inspired by the CoreFX MemoryMappedFile Windows implementation contained in the files + * + * https://github.com/dotnet/corefx/blob/master/src/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Windows.cs + * https://github.com/dotnet/corefx/blob/master/src/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedView.Windows.cs + */ + #include #ifdef HOST_WIN32 #include -#include -#include - -#include #include -void * -mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *error) +// These control the retry behaviour when lock violation errors occur during Flush: +#define MAX_FLUSH_WAITS 15 // must be <=30 +#define MAX_FLUSH_RETIRES_PER_WAIT 20 + +typedef struct { + void *address; + size_t length; +} MmapInstance; + +enum { + BAD_CAPACITY_FOR_FILE_BACKED = 1, + CAPACITY_SMALLER_THAN_FILE_SIZE, + FILE_NOT_FOUND, + FILE_ALREADY_EXISTS, + PATH_TOO_LONG, + COULD_NOT_OPEN, + CAPACITY_MUST_BE_POSITIVE, + INVALID_FILE_MODE, + COULD_NOT_MAP_MEMORY, + ACCESS_DENIED, + CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE +}; + +enum { + FILE_MODE_CREATE_NEW = 1, + FILE_MODE_CREATE = 2, + FILE_MODE_OPEN = 3, + FILE_MODE_OPEN_OR_CREATE = 4, + FILE_MODE_TRUNCATE = 5, + FILE_MODE_APPEND = 6, +}; + +enum { + MMAP_FILE_ACCESS_READ_WRITE = 0, + MMAP_FILE_ACCESS_READ = 1, + MMAP_FILE_ACCESS_WRITE = 2, + MMAP_FILE_ACCESS_COPY_ON_WRITE = 3, + MMAP_FILE_ACCESS_READ_EXECUTE = 4, + MMAP_FILE_ACCESS_READ_WRITE_EXECUTE = 5, +}; + +static DWORD get_page_access (int access) { - g_error ("No windows backend"); - return NULL; + switch (access) { + case MMAP_FILE_ACCESS_READ: + return PAGE_READONLY; + case MMAP_FILE_ACCESS_READ_WRITE: + return PAGE_READWRITE; + case MMAP_FILE_ACCESS_COPY_ON_WRITE: + return PAGE_WRITECOPY; + case MMAP_FILE_ACCESS_READ_EXECUTE: + return PAGE_EXECUTE_READ; + case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE: + return PAGE_EXECUTE_READWRITE; + default: + g_error ("unknown MemoryMappedFileAccess %d", access); + } } -void * -mono_mmap_open_handle (void *handle, MonoString *mapName, gint64 *capacity, int access, int options, int *error) +static DWORD get_file_access (int access) { - g_error ("No windows backend"); - return NULL; + switch (access) { + case MMAP_FILE_ACCESS_READ: + case MMAP_FILE_ACCESS_READ_EXECUTE: + return GENERIC_READ; + case MMAP_FILE_ACCESS_READ_WRITE: + case MMAP_FILE_ACCESS_COPY_ON_WRITE: + case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE: + return GENERIC_READ | GENERIC_WRITE; + case MMAP_FILE_ACCESS_WRITE: + return GENERIC_WRITE; + default: + g_error ("unknown MemoryMappedFileAccess %d", access); + } } -void -mono_mmap_close (void *mmap_handle) +static int get_file_map_access (int access) { - g_error ("No windows backend"); + switch (access) { + case MMAP_FILE_ACCESS_READ: + return FILE_MAP_READ; + case MMAP_FILE_ACCESS_WRITE: + return FILE_MAP_WRITE; + case MMAP_FILE_ACCESS_READ_WRITE: + return FILE_MAP_READ | FILE_MAP_WRITE; + case MMAP_FILE_ACCESS_COPY_ON_WRITE: + return FILE_MAP_COPY; + case MMAP_FILE_ACCESS_READ_EXECUTE: + return FILE_MAP_EXECUTE | FILE_MAP_READ; + case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE: + return FILE_MAP_EXECUTE | FILE_MAP_READ | FILE_MAP_WRITE; + default: + g_error ("unknown MemoryMappedFileAccess %d", access); + } } -void -mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability) +static int convert_win32_error (int error, int def) { - g_error ("No windows backend"); + switch (error) { + case ERROR_FILE_NOT_FOUND: + return FILE_NOT_FOUND; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + return FILE_ALREADY_EXISTS; + case ERROR_ACCESS_DENIED: + return ACCESS_DENIED; + } + return def; } -void -mono_mmap_flush (void *mmap_handle) +static void *open_handle (void *handle, MonoString *mapName, int mode, gint64 *capacity, int access, int options, int *error) { - g_error ("No windows backend"); + g_assert (handle != NULL); + + wchar_t *w_mapName = NULL; + HANDLE result = NULL; + + if (handle == INVALID_HANDLE_VALUE) { + if (*capacity <= 0) { + *error = CAPACITY_MUST_BE_POSITIVE; + return NULL; + } +#if SIZEOF_VOID_P == 4 + if (*capacity > UINT32_MAX) { + *error = CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE; + return NULL; + } +#endif + if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) { + *error = INVALID_FILE_MODE; + return NULL; + } + } else { + FILE_STANDARD_INFO info; + if (!GetFileInformationByHandleEx ((HANDLE) handle, FileStandardInfo, &info, sizeof (FILE_STANDARD_INFO))) { + *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN); + return NULL; + } + if (*capacity == 0) { + if (info.EndOfFile.QuadPart == 0) { + *error = CAPACITY_SMALLER_THAN_FILE_SIZE; + return NULL; + } + } else if (*capacity < info.EndOfFile.QuadPart) { + *error = CAPACITY_SMALLER_THAN_FILE_SIZE; + return NULL; + } + } + + w_mapName = mapName ? mono_string_to_utf16 (mapName) : NULL; + + if (mode == FILE_MODE_CREATE_NEW || handle != INVALID_HANDLE_VALUE) { + result = CreateFileMappingW ((HANDLE)handle, NULL, get_page_access (access) | options, (DWORD)(((guint64)*capacity) >> 32), (DWORD)*capacity, w_mapName); + if (result && GetLastError () == ERROR_ALREADY_EXISTS) { + CloseHandle (result); + result = NULL; + *error = FILE_ALREADY_EXISTS; + } else if (!result && GetLastError () != NO_ERROR) { + *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN); + } + } else if (mode == FILE_MODE_OPEN || mode == FILE_MODE_OPEN_OR_CREATE && access == MMAP_FILE_ACCESS_WRITE) { + result = OpenFileMappingW (get_file_map_access (access), FALSE, w_mapName); + if (!result) { + if (mode == FILE_MODE_OPEN_OR_CREATE && GetLastError () == ERROR_FILE_NOT_FOUND) { + *error = INVALID_FILE_MODE; + } else { + *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN); + } + } + } else if (mode == FILE_MODE_OPEN_OR_CREATE) { + + // This replicates how CoreFX does MemoryMappedFile.CreateOrOpen (). + + /// Try to open the file if it exists -- this requires a bit more work. Loop until we can + /// either create or open a memory mapped file up to a timeout. CreateFileMapping may fail + /// if the file exists and we have non-null security attributes, in which case we need to + /// use OpenFileMapping. But, there exists a race condition because the memory mapped file + /// may have closed between the two calls -- hence the loop. + /// + /// The retry/timeout logic increases the wait time each pass through the loop and times + /// out in approximately 1.4 minutes. If after retrying, a MMF handle still hasn't been opened, + /// throw an InvalidOperationException. + + guint32 waitRetries = 14; //((2^13)-1)*10ms == approximately 1.4mins + guint32 waitSleep = 0; + + while (waitRetries > 0) { + result = CreateFileMappingW ((HANDLE)handle, NULL, get_page_access (access) | options, (DWORD)(((guint64)*capacity) >> 32), (DWORD)*capacity, w_mapName); + if (result) + break; + if (GetLastError() != ERROR_ACCESS_DENIED) { + *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN); + break; + } + result = OpenFileMappingW (get_file_map_access (access), FALSE, w_mapName); + if (result) + break; + if (GetLastError () != ERROR_FILE_NOT_FOUND) { + *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN); + break; + } + // increase wait time + --waitRetries; + if (waitSleep == 0) { + waitSleep = 10; + } else { + mono_thread_info_sleep (waitSleep, NULL); + waitSleep *= 2; + } + } + + if (!result) { + *error = COULD_NOT_OPEN; + } + } + + if (w_mapName) + g_free (w_mapName); + return result; } +void *mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *error) +{ + g_assert (path != NULL || mapName != NULL); + + wchar_t *w_path = NULL; + HANDLE hFile = INVALID_HANDLE_VALUE; + HANDLE result = NULL; + gboolean delete_on_error = FALSE; + if (path) { + w_path = mono_string_to_utf16 (path); + WIN32_FILE_ATTRIBUTE_DATA file_attrs; + gboolean existed = GetFileAttributesExW (w_path, GetFileExInfoStandard, &file_attrs); + if (!existed && mode == FILE_MODE_CREATE_NEW && *capacity == 0) { + *error = CAPACITY_SMALLER_THAN_FILE_SIZE; + goto done; + } + hFile = CreateFileW (w_path, get_file_access (access), FILE_SHARE_READ, NULL, mode, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN); + goto done; + } + delete_on_error = !existed; + } -int -mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address) + result = open_handle (hFile, mapName, mode, capacity, access, options, error); + +done: + if (!result && delete_on_error) + DeleteFileW (w_path); + if (w_path) + g_free (w_path); + + return result; +} + +void *mono_mmap_open_handle (void *handle, MonoString *mapName, gint64 *capacity, int access, int options, int *error) { - g_error ("No windows backend"); + g_assert (handle != NULL); + + return open_handle (handle, mapName, FILE_MODE_OPEN, capacity, access, options, error); +} + +void mono_mmap_close (void *mmap_handle) +{ + g_assert (mmap_handle); + CloseHandle ((HANDLE) mmap_handle); +} + +void mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability) +{ + g_assert (mmap_handle); + if (!SetHandleInformation ((HANDLE) mmap_handle, HANDLE_FLAG_INHERIT, inheritability ? HANDLE_FLAG_INHERIT : 0)) { + g_error ("mono_mmap_configure_inheritability: SetHandleInformation failed with error %d!", GetLastError ()); + } +} + +void mono_mmap_flush (void *mmap_handle) +{ + g_assert (mmap_handle); + MmapInstance *h = (MmapInstance *)mmap_handle; + + if (FlushViewOfFile (h->address, h->length)) + return; + + // This replicates how CoreFX does MemoryMappedView.Flush (). + + // It is a known issue within the NTFS transaction log system that + // causes FlushViewOfFile to intermittently fail with ERROR_LOCK_VIOLATION + // As a workaround, we catch this particular error and retry the flush operation + // a few milliseconds later. If it does not work, we give it a few more tries with + // increasing intervals. Eventually, however, we need to give up. In ad-hoc tests + // this strategy successfully flushed the view after no more than 3 retries. + + if (GetLastError () != ERROR_LOCK_VIOLATION) + // TODO: Propagate error to caller + return; + + for (int w = 0; w < MAX_FLUSH_WAITS; w++) { + int pause = (1 << w); // MaxFlushRetries should never be over 30 + mono_thread_info_sleep (pause, NULL); + + for (int r = 0; r < MAX_FLUSH_RETIRES_PER_WAIT; r++) { + if (FlushViewOfFile (h->address, h->length)) + return; + + if (GetLastError () != ERROR_LOCK_VIOLATION) + // TODO: Propagate error to caller + return; + + mono_thread_info_yield (); + } + } + + // We got to here, so there was no success: + // TODO: Propagate error to caller +} + +int mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address) +{ + static DWORD allocationGranularity = 0; + if (allocationGranularity == 0) { + SYSTEM_INFO info; + GetSystemInfo (&info); + allocationGranularity = info.dwAllocationGranularity; + } + + gint64 extraMemNeeded = offset % allocationGranularity; + guint64 newOffset = offset - extraMemNeeded; + gint64 nativeSize = (*size != 0) ? *size + extraMemNeeded : 0; + +#if SIZEOF_VOID_P == 4 + if (nativeSize > UINT32_MAX) + return CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE; +#endif + + void *address = MapViewOfFile ((HANDLE) handle, get_file_map_access (access), (DWORD) (newOffset >> 32), (DWORD) newOffset, (SIZE_T) nativeSize); + if (!address) + return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY); + + // Query the view for its size and allocation type + MEMORY_BASIC_INFORMATION viewInfo; + VirtualQuery (address, &viewInfo, sizeof (MEMORY_BASIC_INFORMATION)); + guint64 viewSize = (guint64) viewInfo.RegionSize; + + // Allocate the pages if we were using the MemoryMappedFileOptions.DelayAllocatePages option + // OR check if the allocated view size is smaller than the expected native size + // If multiple overlapping views are created over the file mapping object, the pages in a given region + // could have different attributes(MEM_RESERVE OR MEM_COMMIT) as MapViewOfFile preserves coherence between + // views created on a mapping object backed by same file. + // In which case, the viewSize will be smaller than nativeSize required and viewState could be MEM_COMMIT + // but more pages may need to be committed in the region. + // This is because, VirtualQuery function(that internally invokes VirtualQueryEx function) returns the attributes + // and size of the region of pages with matching attributes starting from base address. + // VirtualQueryEx: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366907(v=vs.85).aspx + if (((viewInfo.State & MEM_RESERVE) != 0) || viewSize < (guint64) nativeSize) { + void *tempAddress = VirtualAlloc (address, nativeSize != 0 ? nativeSize : viewSize, MEM_COMMIT, get_page_access (access)); + if (!tempAddress) { + return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY); + } + // again query the view for its new size + VirtualQuery (address, &viewInfo, sizeof (MEMORY_BASIC_INFORMATION)); + viewSize = (guint64) viewInfo.RegionSize; + } + + if (*size == 0) + *size = viewSize - extraMemNeeded; + + MmapInstance *h = g_malloc0 (sizeof (MmapInstance)); + h->address = address; + h->length = *size + extraMemNeeded; + *mmap_handle = h; + *base_address = (char*) address + (offset - newOffset); + return 0; } -gboolean -mono_mmap_unmap (void *mmap_handle) +gboolean mono_mmap_unmap (void *mmap_handle) { - g_error ("No windows backend"); - return TRUE; + g_assert (mmap_handle); + + MmapInstance *h = (MmapInstance *) mmap_handle; + + gboolean result = UnmapViewOfFile (h->address); + + g_free (h); + return result; } #endif diff --git a/mono/utils/mono-codeman.c b/mono/utils/mono-codeman.c index 56948bdd901..f29a67ca096 100644 --- a/mono/utils/mono-codeman.c +++ b/mono/utils/mono-codeman.c @@ -329,7 +329,7 @@ new_codechunk (CodeChunk *last, int dynamic, int size) { int minsize, flags = CODE_FLAG_MMAP; int chunk_size, bsize = 0; - int pagesize; + int pagesize, valloc_granule; CodeChunk *chunk; void *ptr; @@ -338,12 +338,13 @@ new_codechunk (CodeChunk *last, int dynamic, int size) #endif pagesize = mono_pagesize (); + valloc_granule = mono_valloc_granule (); if (dynamic) { chunk_size = size; flags = CODE_FLAG_MALLOC; } else { - minsize = pagesize * MIN_PAGES; + minsize = MAX (pagesize * MIN_PAGES, valloc_granule); if (size < minsize) chunk_size = minsize; else { @@ -353,8 +354,8 @@ new_codechunk (CodeChunk *last, int dynamic, int size) size += MIN_ALIGN - 1; size &= ~(MIN_ALIGN - 1); chunk_size = size; - chunk_size += pagesize - 1; - chunk_size &= ~ (pagesize - 1); + chunk_size += valloc_granule - 1; + chunk_size &= ~ (valloc_granule - 1); } } #ifdef BIND_ROOM @@ -370,8 +371,8 @@ new_codechunk (CodeChunk *last, int dynamic, int size) if (chunk_size - size < bsize) { chunk_size = size + bsize; if (!dynamic) { - chunk_size += pagesize - 1; - chunk_size &= ~ (pagesize - 1); + chunk_size += valloc_granule - 1; + chunk_size &= ~ (valloc_granule - 1); } } #endif diff --git a/mono/utils/mono-mmap-windows.c b/mono/utils/mono-mmap-windows.c index 2c161c6c4ab..d0b6072bece 100644 --- a/mono/utils/mono-mmap-windows.c +++ b/mono/utils/mono-mmap-windows.c @@ -27,10 +27,22 @@ mono_pagesize (void) if (saved_pagesize) return saved_pagesize; GetSystemInfo (&info); - saved_pagesize = info.dwAllocationGranularity; + saved_pagesize = info.dwPageSize; return saved_pagesize; } +int +mono_valloc_granule (void) +{ + SYSTEM_INFO info; + static int saved_valloc_granule = 0; + if (saved_valloc_granule) + return saved_valloc_granule; + GetSystemInfo (&info); + saved_valloc_granule = info.dwAllocationGranularity; + return saved_valloc_granule; +} + int mono_mmap_win_prot_from_flags (int flags) { diff --git a/mono/utils/mono-mmap.c b/mono/utils/mono-mmap.c index 91fe5a12def..10a190ff9b0 100644 --- a/mono/utils/mono-mmap.c +++ b/mono/utils/mono-mmap.c @@ -155,6 +155,12 @@ mono_pagesize (void) return saved_pagesize; } +int +mono_valloc_granule (void) +{ + return mono_pagesize (); +} + static int prot_from_flags (int flags) { @@ -364,6 +370,12 @@ mono_pagesize (void) return 4096; } +int +mono_valloc_granule (void) +{ + return mono_pagesize (); +} + void* mono_valloc (void *addr, size_t length, int flags, MonoMemAccountType type) { diff --git a/mono/utils/mono-mmap.h b/mono/utils/mono-mmap.h index d441751c802..1b3b1d63af9 100644 --- a/mono/utils/mono-mmap.h +++ b/mono/utils/mono-mmap.h @@ -49,6 +49,7 @@ MONO_API int mono_file_map_fd (MonoFileMap *fmap); MONO_API int mono_file_map_close (MonoFileMap *fmap); MONO_API int mono_pagesize (void); +MONO_API int mono_valloc_granule (void); MONO_API void* mono_valloc (void *addr, size_t length, int flags, MonoMemAccountType type); MONO_API void* mono_valloc_aligned (size_t length, size_t alignment, int flags, MonoMemAccountType type); MONO_API int mono_vfree (void *addr, size_t length, MonoMemAccountType type);