diff options
20 files changed, 339 insertions, 250 deletions
diff --git a/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/System.Private.CoreLib.csproj index b7c5623f6e..3d2eda1b53 100644 --- a/src/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -260,7 +260,6 @@ <Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeHandle.cs" /> <Compile Include="$(BclSourcesRoot)\System\Runtime\Loader\AssemblyLoadContext.cs" /> <Compile Include="$(BclSourcesRoot)\System\Runtime\Loader\AssemblyDependencyResolver.cs" /> - <Compile Include="$(BclSourcesRoot)\System\Runtime\MemoryFailPoint.cs" /> <Compile Include="$(BclSourcesRoot)\System\Runtime\Serialization\FormatterServices.cs" /> <Compile Include="$(BclSourcesRoot)\System\Runtime\Versioning\CompatibilitySwitch.cs" /> <Compile Include="$(BclSourcesRoot)\System\RuntimeArgumentHandle.cs" /> diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GlobalMemoryStatusEx.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GlobalMemoryStatusEx.cs new file mode 100644 index 0000000000..6c56dba950 --- /dev/null +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GlobalMemoryStatusEx.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + unsafe internal static bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX buffer) + { + buffer.length = sizeof(MEMORYSTATUSEX); + return GlobalMemoryStatusExNative(ref buffer); + } + + [DllImport(Libraries.Kernel32, SetLastError = true, EntryPoint = "GlobalMemoryStatusEx")] + private static extern bool GlobalMemoryStatusExNative(ref MEMORYSTATUSEX buffer); + } +} diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MEMORYSTATUSEX.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MEMORYSTATUSEX.cs new file mode 100644 index 0000000000..45f57aa4d8 --- /dev/null +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MEMORYSTATUSEX.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + [StructLayout(LayoutKind.Sequential)] + internal struct MEMORYSTATUSEX + { + // The length field must be set to the size of this data structure. + internal int length; + internal int memoryLoad; + internal ulong totalPhys; + internal ulong availPhys; + internal ulong totalPageFile; + internal ulong availPageFile; + internal ulong totalVirtual; + internal ulong availVirtual; + internal ulong availExtendedVirtual; + } + } +} diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MEMORY_BASIC_INFORMATION.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MEMORY_BASIC_INFORMATION.cs new file mode 100644 index 0000000000..0744d53f66 --- /dev/null +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MEMORY_BASIC_INFORMATION.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct MEMORY_BASIC_INFORMATION + { + internal void* BaseAddress; + internal void* AllocationBase; + internal uint AllocationProtect; + internal UIntPtr RegionSize; + internal uint State; + internal uint Protect; + internal uint Type; + } + } +} diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.VirtualAlloc.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.VirtualAlloc.cs new file mode 100644 index 0000000000..18ba2a4940 --- /dev/null +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.VirtualAlloc.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + internal const int MEM_COMMIT = 0x1000; + internal const int MEM_RESERVE = 0x2000; + internal const int MEM_RELEASE = 0x8000; + internal const int MEM_FREE = 0x10000; + internal const int PAGE_READWRITE = 0x04; + + [DllImport(Libraries.Kernel32)] + internal static extern unsafe void* VirtualAlloc(void* address, UIntPtr numBytes, int commitOrReserve, int pageProtectionMode); + } +} diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.VirtualFree.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.VirtualFree.cs new file mode 100644 index 0000000000..dc3f42bc39 --- /dev/null +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.VirtualFree.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + [DllImport(Libraries.Kernel32)] + unsafe internal static extern bool VirtualFree(void* address, UIntPtr numBytes, int pageFreeMode); + } +} diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.VirtualQuery.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.VirtualQuery.cs new file mode 100644 index 0000000000..4cce807068 --- /dev/null +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.VirtualQuery.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + [DllImport(Libraries.Kernel32, SetLastError = true)] + unsafe internal static extern UIntPtr VirtualQuery(void* address, ref MEMORY_BASIC_INFORMATION buffer, UIntPtr sizeOfBuffer); + } +} diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems index faecf9bf34..4658c7fe67 100644 --- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems @@ -672,6 +672,7 @@ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\StreamingContext.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Versioning\NonVersionableAttribute.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Versioning\TargetFrameworkAttribute.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\MemoryFailPoint.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\SByte.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Security\AllowPartiallyTrustedCallersAttribute.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Security\CryptographicException.cs" /> @@ -926,11 +927,15 @@ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetFileType_SafeHandle.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetFullPathNameW.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetLongPathNameW.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetSystemInfo.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetTempFileNameW.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetTempPathW.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.Globalization.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GlobalMemoryStatusEx.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.LockFile.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.MAX_PATH.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.MEMORY_BASIC_INFORMATION.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.MEMORYSTATUSEX.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.MultiByteToWideChar.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.OutputDebugString.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.ReadFile_SafeHandle_IntPtr.cs" /> @@ -942,7 +947,11 @@ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SetEnvironmentVariable.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SetThreadErrorMode.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SetFilePointerEx.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SYSTEM_INFO.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.TimeZone.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.VirtualAlloc.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.VirtualFree.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.VirtualQuery.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.WideCharToMultiByte.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.WriteFile_SafeHandle_IntPtr.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.WriteFile_SafeHandle_NativeOverlapped.cs" /> @@ -975,6 +984,7 @@ <Compile Include="$(MSBuildThisFileDirectory)System\IO\PathHelper.Windows.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.Windows.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\IO\DisableMediaInsertionPrompt.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\MemoryFailPoint.Windows.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Security\SafeBSTRHandle.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.Windows.cs" /> </ItemGroup> @@ -1082,6 +1092,7 @@ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Unix.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\IO\Path.Unix.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.Unix.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\MemoryFailPoint.Unix.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.Unix.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.Unix.cs" /> </ItemGroup> diff --git a/src/System.Private.CoreLib/shared/System/Runtime/MemoryFailPoint.Unix.cs b/src/System.Private.CoreLib/shared/System/Runtime/MemoryFailPoint.Unix.cs new file mode 100644 index 0000000000..2f53052001 --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Runtime/MemoryFailPoint.Unix.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime +{ + public sealed partial class MemoryFailPoint + { + private static ulong GetTopOfMemory() + { + // These values are optimistic assumptions. In reality the value will + // often be lower. + return IntPtr.Size == 4 ? uint.MaxValue : ulong.MaxValue; + } + + private static bool CheckForAvailableMemory(out ulong availPageFile, out ulong totalAddressSpaceFree) + { + // TODO: Implement + availPageFile = 0; + totalAddressSpaceFree = 0; + return false; + } + + // Based on the shouldThrow parameter, this will throw an exception, or + // returns whether there is enough space. In all cases, we update + // our last known free address space, hopefully avoiding needing to + // probe again. + private static bool CheckForFreeAddressSpace(ulong size, bool shouldThrow) + { + // Unreachable until CheckForAvailableMemory is implemented + return false; + } + + // Allocate a specified number of bytes, commit them and free them. This should enlarge + // page file if necessary and possible. + private static void GrowPageFileIfNecessaryAndPossible(UIntPtr numBytes) + { + // Unreachable until CheckForAvailableMemory is implemented + } + } +} diff --git a/src/System.Private.CoreLib/shared/System/Runtime/MemoryFailPoint.Windows.cs b/src/System.Private.CoreLib/shared/System/Runtime/MemoryFailPoint.Windows.cs new file mode 100644 index 0000000000..1e59a4ae5b --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Runtime/MemoryFailPoint.Windows.cs @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Runtime.InteropServices; + +namespace System.Runtime +{ + public sealed partial class MemoryFailPoint + { + private static ulong GetTopOfMemory() + { + Interop.Kernel32.SYSTEM_INFO info = new Interop.Kernel32.SYSTEM_INFO(); + Interop.Kernel32.GetSystemInfo(out info); + return (ulong)info.lpMaximumApplicationAddress; + } + + private static bool CheckForAvailableMemory(out ulong availPageFile, out ulong totalAddressSpaceFree) + { + bool r; + Interop.Kernel32.MEMORYSTATUSEX memory = new Interop.Kernel32.MEMORYSTATUSEX(); + r = Interop.Kernel32.GlobalMemoryStatusEx(ref memory); + if (!r) + throw Win32Marshal.GetExceptionForLastWin32Error(); + availPageFile = memory.availPageFile; + totalAddressSpaceFree = memory.availVirtual; + // Console.WriteLine($"Memory gate: Mem load: {memory.memoryLoad}% Available memory (physical + page file): {(memory.availPageFile >> 20)} MB Total free address space: {memory.availVirtual >> 20} MB GC Heap: {(GC.GetTotalMemory(true) >> 20)} MB"); + return true; + } + + // Based on the shouldThrow parameter, this will throw an exception, or + // returns whether there is enough space. In all cases, we update + // our last known free address space, hopefully avoiding needing to + // probe again. + private static unsafe bool CheckForFreeAddressSpace(ulong size, bool shouldThrow) + { + // Start walking the address space at 0. VirtualAlloc may wrap + // around the address space. We don't need to find the exact + // pages that VirtualAlloc would return - we just need to + // know whether VirtualAlloc could succeed. + ulong freeSpaceAfterGCHeap = MemFreeAfterAddress(null, size); + + // Console.WriteLine($"MemoryFailPoint: Checked for free VA space. Found enough? {(freeSpaceAfterGCHeap >= size)} Asked for: {size} Found: {freeSpaceAfterGCHeap}"); + + // We may set these without taking a lock - I don't believe + // this will hurt, as long as we never increment this number in + // the Dispose method. If we do an extra bit of checking every + // once in a while, but we avoid taking a lock, we may win. + LastKnownFreeAddressSpace = (long)freeSpaceAfterGCHeap; + LastTimeCheckingAddressSpace = Environment.TickCount; + + if (freeSpaceAfterGCHeap < size && shouldThrow) + throw new InsufficientMemoryException(SR.InsufficientMemory_MemFailPoint_VAFrag); + return freeSpaceAfterGCHeap >= size; + } + + // Returns the amount of consecutive free memory available in a block + // of pages. If we didn't have enough address space, we still return + // a positive value < size, to help potentially avoid the overhead of + // this check if we use a MemoryFailPoint with a smaller size next. + private static unsafe ulong MemFreeAfterAddress(void* address, ulong size) + { + if (size >= s_topOfMemory) + return 0; + + ulong largestFreeRegion = 0; + Interop.Kernel32.MEMORY_BASIC_INFORMATION memInfo = new Interop.Kernel32.MEMORY_BASIC_INFORMATION(); + UIntPtr sizeOfMemInfo = (UIntPtr)sizeof(Interop.Kernel32.MEMORY_BASIC_INFORMATION); + + while (((ulong)address) + size < s_topOfMemory) + { + UIntPtr r = Interop.Kernel32.VirtualQuery(address, ref memInfo, sizeOfMemInfo); + if (r == UIntPtr.Zero) + throw Win32Marshal.GetExceptionForLastWin32Error(); + + ulong regionSize = memInfo.RegionSize.ToUInt64(); + if (memInfo.State == Interop.Kernel32.MEM_FREE) + { + if (regionSize >= size) + return regionSize; + else + largestFreeRegion = Math.Max(largestFreeRegion, regionSize); + } + address = (void*)((ulong)address + regionSize); + } + return largestFreeRegion; + } + + // Allocate a specified number of bytes, commit them and free them. This should enlarge + // page file if necessary and possible. + private static void GrowPageFileIfNecessaryAndPossible(UIntPtr numBytes) + { + unsafe + { +#if ENABLE_WINRT + void* pMemory = Interop.mincore.VirtualAllocFromApp(null, numBytes, Interop.Kernel32.MEM_COMMIT, Interop.Kernel32.PAGE_READWRITE); +#else + void* pMemory = Interop.Kernel32.VirtualAlloc(null, numBytes, Interop.Kernel32.MEM_COMMIT, Interop.Kernel32.PAGE_READWRITE); +#endif + if (pMemory != null) + { + bool r = Interop.Kernel32.VirtualFree(pMemory, UIntPtr.Zero, Interop.Kernel32.MEM_RELEASE); + if (!r) + throw Win32Marshal.GetExceptionForLastWin32Error(); + } + } + } + } +} diff --git a/src/System.Private.CoreLib/src/System/Runtime/MemoryFailPoint.cs b/src/System.Private.CoreLib/shared/System/Runtime/MemoryFailPoint.cs index a6d8ab4284..88e222f318 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/MemoryFailPoint.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/MemoryFailPoint.cs @@ -13,14 +13,10 @@ ** ===========================================================*/ -using System; using System.IO; -using Microsoft.Win32; -using System.Runtime.InteropServices; using System.Threading; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; -using System.Runtime.Versioning; using System.Diagnostics; /* @@ -74,7 +70,7 @@ using System.Diagnostics; namespace System.Runtime { - public sealed class MemoryFailPoint : CriticalFinalizerObject, IDisposable + public sealed partial class MemoryFailPoint : CriticalFinalizerObject, IDisposable { // Find the top section of user mode memory. Avoid the last 64K. // Windows reserves that block for the kernel, apparently, and doesn't @@ -82,7 +78,7 @@ namespace System.Runtime // chunks, we don't have to special case this. Also, we need to // deal with 32 bit machines in 3 GB mode. // Using Win32's GetSystemInfo should handle all this for us. - private static readonly ulong s_topOfMemory; + private static readonly ulong s_topOfMemory = GetTopOfMemory(); // Walking the address space is somewhat expensive, taking around half // a millisecond. Doing that per transaction limits us to a max of @@ -130,7 +126,7 @@ namespace System.Runtime // Note: This may become dynamically tunable in the future. // Also note that we can have different segment sizes for the normal vs. // large object heap. We currently use the max of the two. - private static readonly ulong s_GCSegmentSize; + private static readonly ulong s_GCSegmentSize = GC.GetSegmentSize(); // For multi-threaded workers, we want to ensure that if two workers // use a MemoryFailPoint at the same time, and they both succeed, that @@ -142,11 +138,6 @@ namespace System.Runtime private ulong _reservedMemory; // The size of this request (from user) private bool _mustSubtractReservation; // Did we add data to SharedStatics? - static MemoryFailPoint() - { - GetMemorySettings(out s_GCSegmentSize, out s_topOfMemory); - } - // We can remove this link demand in a future version - we will // have scenarios for this in partial trust in the future, but // we're doing this just to restrict this in case the code below @@ -156,7 +147,6 @@ namespace System.Runtime if (sizeInMegabytes <= 0) throw new ArgumentOutOfRangeException(nameof(sizeInMegabytes), SR.ArgumentOutOfRange_NeedNonNegNum); -#if !FEATURE_PAL // Remove this when CheckForAvailableMemory is able to provide legitimate estimates ulong size = ((ulong)sizeInMegabytes) << 20; _reservedMemory = size; @@ -191,12 +181,16 @@ namespace System.Runtime // would probably work, but do some thinking first.) for (int stage = 0; stage < 3; stage++) { - CheckForAvailableMemory(out availPageFile, out totalAddressSpaceFree); + if (!CheckForAvailableMemory(out availPageFile, out totalAddressSpaceFree)) + { + // _mustSubtractReservation == false + return; + } // If we have enough room, then skip some stages. // Note that multiple threads can still lead to a race condition for our free chunk // of address space, which can't be easily solved. - ulong reserved = (ulong)Volatile.Read(ref s_failPointReservedMemory); + ulong reserved = MemoryFailPointReservedMemory; ulong segPlusReserved = segmentSize + reserved; bool overflow = segPlusReserved < segmentSize || segPlusReserved < reserved; bool needPageFile = availPageFile < (requestedSizeRounded + reserved + LowMemoryFudgeFactor) || overflow; @@ -250,17 +244,7 @@ namespace System.Runtime // This shouldn't overflow due to the if clauses above. UIntPtr numBytes = new UIntPtr(segmentSize); - unsafe - { - void* pMemory = Win32Native.VirtualAlloc(null, numBytes, Win32Native.MEM_COMMIT, Win32Native.PAGE_READWRITE); - if (pMemory != null) - { - bool r = Win32Native.VirtualFree(pMemory, UIntPtr.Zero, Win32Native.MEM_RELEASE); - if (!r) - throw Win32Marshal.GetExceptionForLastWin32Error(); - } - } - + GrowPageFileIfNecessaryAndPossible(numBytes); continue; case 2: @@ -307,84 +291,10 @@ namespace System.Runtime RuntimeHelpers.PrepareConstrainedRegions(); - Interlocked.Add(ref s_failPointReservedMemory, (long)size); + AddMemoryFailPointReservation((long)size); _mustSubtractReservation = true; -#endif - } - - private static void CheckForAvailableMemory(out ulong availPageFile, out ulong totalAddressSpaceFree) - { - bool r; - Win32Native.MEMORYSTATUSEX memory = new Win32Native.MEMORYSTATUSEX(); - r = Win32Native.GlobalMemoryStatusEx(ref memory); - if (!r) - throw Win32Marshal.GetExceptionForLastWin32Error(); - availPageFile = memory.availPageFile; - totalAddressSpaceFree = memory.availVirtual; - // Console.WriteLine($"Memory gate: Mem load: {memory.memoryLoad}% Available memory (physical + page file): {(memory.availPageFile >> 20)} MB Total free address space: {memory.availVirtual >> 20} MB GC Heap: {(GC.GetTotalMemory(true) >> 20)} MB"); - } - - // Based on the shouldThrow parameter, this will throw an exception, or - // returns whether there is enough space. In all cases, we update - // our last known free address space, hopefully avoiding needing to - // probe again. - private static unsafe bool CheckForFreeAddressSpace(ulong size, bool shouldThrow) - { - // Start walking the address space at 0. VirtualAlloc may wrap - // around the address space. We don't need to find the exact - // pages that VirtualAlloc would return - we just need to - // know whether VirtualAlloc could succeed. - ulong freeSpaceAfterGCHeap = MemFreeAfterAddress(null, size); - - // Console.WriteLine($"MemoryFailPoint: Checked for free VA space. Found enough? {(freeSpaceAfterGCHeap >= size)} Asked for: {size} Found: {freeSpaceAfterGCHeap}"); - - // We may set these without taking a lock - I don't believe - // this will hurt, as long as we never increment this number in - // the Dispose method. If we do an extra bit of checking every - // once in a while, but we avoid taking a lock, we may win. - LastKnownFreeAddressSpace = (long)freeSpaceAfterGCHeap; - LastTimeCheckingAddressSpace = Environment.TickCount; - - if (freeSpaceAfterGCHeap < size && shouldThrow) - throw new InsufficientMemoryException(SR.InsufficientMemory_MemFailPoint_VAFrag); - return freeSpaceAfterGCHeap >= size; - } - - // Returns the amount of consecutive free memory available in a block - // of pages. If we didn't have enough address space, we still return - // a positive value < size, to help potentially avoid the overhead of - // this check if we use a MemoryFailPoint with a smaller size next. - private static unsafe ulong MemFreeAfterAddress(void* address, ulong size) - { - if (size >= s_topOfMemory) - return 0; - - ulong largestFreeRegion = 0; - Win32Native.MEMORY_BASIC_INFORMATION memInfo = new Win32Native.MEMORY_BASIC_INFORMATION(); - UIntPtr sizeOfMemInfo = (UIntPtr)Marshal.SizeOf(memInfo); - - while (((ulong)address) + size < s_topOfMemory) - { - UIntPtr r = Win32Native.VirtualQuery(address, ref memInfo, sizeOfMemInfo); - if (r == UIntPtr.Zero) - throw Win32Marshal.GetExceptionForLastWin32Error(); - - ulong regionSize = memInfo.RegionSize.ToUInt64(); - if (memInfo.State == Win32Native.MEM_FREE) - { - if (regionSize >= size) - return regionSize; - else - largestFreeRegion = Math.Max(largestFreeRegion, regionSize); - } - address = (void*)((ulong)address + regionSize); - } - return largestFreeRegion; } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void GetMemorySettings(out ulong maxGCSegmentSize, out ulong topOfMemory); - ~MemoryFailPoint() { Dispose(false); @@ -412,7 +322,7 @@ namespace System.Runtime { RuntimeHelpers.PrepareConstrainedRegions(); - Interlocked.Add(ref s_failPointReservedMemory, -(long)_reservedMemory); + AddMemoryFailPointReservation(-((long)_reservedMemory)); _mustSubtractReservation = false; } @@ -430,6 +340,21 @@ namespace System.Runtime */ } + internal static long AddMemoryFailPointReservation(long size) + { + // Size can legitimately be negative - see Dispose. + return Interlocked.Add(ref s_failPointReservedMemory, (long)size); + } + + internal static ulong MemoryFailPointReservedMemory + { + get + { + Debug.Assert(Volatile.Read(ref s_failPointReservedMemory) >= 0, "Process-wide MemoryFailPoint reserved memory was negative!"); + return (ulong)Volatile.Read(ref s_failPointReservedMemory); + } + } + #if DEBUG [Serializable] internal sealed class MemoryFailPointState diff --git a/src/System.Private.CoreLib/src/Microsoft/Win32/Win32Native.cs b/src/System.Private.CoreLib/src/Microsoft/Win32/Win32Native.cs index 9207035d6e..d2007d8681 100644 --- a/src/System.Private.CoreLib/src/Microsoft/Win32/Win32Native.cs +++ b/src/System.Private.CoreLib/src/Microsoft/Win32/Win32Native.cs @@ -90,19 +90,8 @@ namespace Microsoft.Win32 { using System; - using System.Security; - using System.Text; - using System.Configuration.Assemblies; using System.Runtime.InteropServices; - using System.Threading; using Microsoft.Win32.SafeHandles; - using System.Runtime.CompilerServices; - using System.Runtime.ConstrainedExecution; - using System.Runtime.Versioning; - - using BOOL = System.Int32; - using DWORD = System.UInt32; - using ULONG = System.UInt32; /** * Win32 encapsulation for System.Private.CoreLib. @@ -129,33 +118,6 @@ namespace Microsoft.Win32 internal byte wReserved; } - [StructLayout(LayoutKind.Sequential)] - internal struct MEMORYSTATUSEX - { - // The length field must be set to the size of this data structure. - internal int length; - internal int memoryLoad; - internal ulong totalPhys; - internal ulong availPhys; - internal ulong totalPageFile; - internal ulong availPageFile; - internal ulong totalVirtual; - internal ulong availVirtual; - internal ulong availExtendedVirtual; - } - - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct MEMORY_BASIC_INFORMATION - { - internal void* BaseAddress; - internal void* AllocationBase; - internal uint AllocationProtect; - internal UIntPtr RegionSize; - internal uint State; - internal uint Protect; - internal uint Type; - } - internal const string ADVAPI32 = "advapi32.dll"; [DllImport(Interop.Libraries.Kernel32, EntryPoint = "LocalAlloc")] @@ -164,26 +126,8 @@ namespace Microsoft.Win32 [DllImport(Interop.Libraries.Kernel32, SetLastError = true)] internal static extern IntPtr LocalFree(IntPtr handle); - internal static bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX buffer) - { - buffer.length = Marshal.SizeOf(typeof(MEMORYSTATUSEX)); - return GlobalMemoryStatusExNative(ref buffer); - } - - [DllImport(Interop.Libraries.Kernel32, SetLastError = true, EntryPoint = "GlobalMemoryStatusEx")] - private static extern bool GlobalMemoryStatusExNative([In, Out] ref MEMORYSTATUSEX buffer); - - [DllImport(Interop.Libraries.Kernel32, SetLastError = true)] - internal static extern unsafe UIntPtr VirtualQuery(void* address, ref MEMORY_BASIC_INFORMATION buffer, UIntPtr sizeOfBuffer); - - // VirtualAlloc should generally be avoided, but is needed in - // the MemoryFailPoint implementation (within a CER) to increase the - // size of the page file, ignoring any host memory allocators. - [DllImport(Interop.Libraries.Kernel32, SetLastError = true)] - internal static extern unsafe void* VirtualAlloc(void* address, UIntPtr numBytes, int commitOrReserve, int pageProtectionMode); - - [DllImport(Interop.Libraries.Kernel32, SetLastError = true)] - internal static extern unsafe bool VirtualFree(void* address, UIntPtr numBytes, int pageFreeMode); + [DllImport(Interop.Libraries.Kernel32)] + internal static extern IntPtr LocalReAlloc(IntPtr handle, IntPtr sizetcbBytes, int uFlags); [DllImport(Interop.Libraries.OleAut32, CharSet = CharSet.Unicode)] internal static extern IntPtr SysAllocStringLen(string src, int len); // BSTR @@ -213,13 +157,6 @@ namespace Microsoft.Win32 [DllImport(Interop.Libraries.Kernel32, SetLastError = true)] internal static extern IntPtr GetStdHandle(int nStdHandle); // param is NOT a handle, but it returns one! - internal const int PAGE_READWRITE = 0x04; - - internal const int MEM_COMMIT = 0x1000; - internal const int MEM_RESERVE = 0x2000; - internal const int MEM_RELEASE = 0x8000; - internal const int MEM_FREE = 0x10000; - [DllImport(Interop.Libraries.Kernel32)] internal static extern unsafe int WideCharToMultiByte(uint cp, uint flags, char* pwzSource, int cchSource, byte* pbDestBuffer, int cbDestBuffer, IntPtr null1, IntPtr null2); @@ -261,9 +198,6 @@ namespace Microsoft.Win32 [DllImport(Interop.Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] internal static extern uint ExpandEnvironmentStringsW(string lpSrc, ref char lpDst, uint nSize); - [DllImport(Interop.Libraries.Kernel32)] - internal static extern IntPtr LocalReAlloc(IntPtr handle, IntPtr sizetcbBytes, int uFlags); - [DllImport(Interop.Libraries.Kernel32, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool QueryUnbiasedInterruptTime(out ulong UnbiasedTime); diff --git a/src/System.Private.CoreLib/src/System/GC.cs b/src/System.Private.CoreLib/src/System/GC.cs index aac612a63a..838e8147f2 100644 --- a/src/System.Private.CoreLib/src/System/GC.cs +++ b/src/System.Private.CoreLib/src/System/GC.cs @@ -104,6 +104,9 @@ namespace System [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern bool IsServerGC(); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern ulong GetSegmentSize(); + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] private static extern void _AddMemoryPressure(ulong bytesAllocated); diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt index b3137d3e45..62acecf11e 100644 --- a/src/vm/CMakeLists.txt +++ b/src/vm/CMakeLists.txt @@ -298,7 +298,6 @@ set(VM_SOURCES_WKS comdatetime.cpp comdependenthandle.cpp comdynamic.cpp - commemoryfailpoint.cpp commodule.cpp compatibilityswitch.cpp comsynchronizable.cpp @@ -414,7 +413,6 @@ set(VM_HEADERS_WKS comdatetime.h comdependenthandle.h comdynamic.h - commemoryfailpoint.h commodule.h compatibilityswitch.h comsynchronizable.h diff --git a/src/vm/commemoryfailpoint.cpp b/src/vm/commemoryfailpoint.cpp deleted file mode 100644 index 2900409fb1..0000000000 --- a/src/vm/commemoryfailpoint.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*============================================================ -** -** Class: COMMemoryFailPoint -** -** -** Purpose: Native methods for System.Runtime.MemoryFailPoint. -** These are to implement memory gates to limit allocations -** when progress will likely result in an OOM. -** -===========================================================*/ -#include "common.h" - -#include "frames.h" -#include "commemoryfailpoint.h" - -// Need to know the maximum segment size for both the normal GC heap and the -// large object heap, as well as the top user-accessible address within the -// address space (ie, theoretically 2^31 - 1 on a 32 bit machine, but a tad -// lower in practice). This will help out with 32 bit machines running in -// 3 GB mode. -FCIMPL2(void, COMMemoryFailPoint::GetMemorySettings, UINT64* pMaxGCSegmentSize, UINT64* pTopOfMemory) -{ - FCALL_CONTRACT; - - IGCHeap * pGC = GCHeapUtilities::GetGCHeap(); - size_t segment_size = pGC->GetValidSegmentSize(false); - size_t large_segment_size = pGC->GetValidSegmentSize(true); - _ASSERTE(segment_size < SIZE_T_MAX && large_segment_size < SIZE_T_MAX); - if (segment_size > large_segment_size) - *pMaxGCSegmentSize = (UINT64) segment_size; - else - *pMaxGCSegmentSize = (UINT64) large_segment_size; - - // GetTopMemoryAddress returns a void*, which can't be cast - // directly to a UINT64 without causing an error from GCC. - void * topOfMem = GetTopMemoryAddress(); - *pTopOfMemory = (UINT64) (size_t) topOfMem; -} -FCIMPLEND diff --git a/src/vm/commemoryfailpoint.h b/src/vm/commemoryfailpoint.h deleted file mode 100644 index 902e5e36ed..0000000000 --- a/src/vm/commemoryfailpoint.h +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*============================================================ -** -** Class: COMMemoryFailPoint -** -** -** Purpose: Native methods for System.Runtime.MemoryFailPoint. -** These are to implement memory gates to limit allocations -** when progress will likely result in an OOM. -** -** -===========================================================*/ - -#ifndef _COMMEMORYFAILPOINT_H -#define _COMMEMORYFAILPOINT_H - -#include "fcall.h" - -class COMMemoryFailPoint -{ -public: - static FCDECL2(void, GetMemorySettings, UINT64* pMaxGCSegmentSize, UINT64* pTopOfMemory); -}; - -#endif // _COMMEMORYFAILPOINT_H diff --git a/src/vm/comutilnative.cpp b/src/vm/comutilnative.cpp index 7ca48ffc59..56408c5f70 100644 --- a/src/vm/comutilnative.cpp +++ b/src/vm/comutilnative.cpp @@ -1034,6 +1034,26 @@ FCIMPL1(int, GCInterface::GetGeneration, Object* objUNSAFE) } FCIMPLEND +/*================================GetSegmentSize========-======================= +**Action: Returns the maximum GC heap segment size +**Returns: The maximum segment size of either the normal heap or the large object heap, whichever is bigger +==============================================================================*/ +FCIMPL0(UINT64, GCInterface::GetSegmentSize) +{ + FCALL_CONTRACT; + + IGCHeap * pGC = GCHeapUtilities::GetGCHeap(); + size_t segment_size = pGC->GetValidSegmentSize(false); + size_t large_segment_size = pGC->GetValidSegmentSize(true); + _ASSERTE(segment_size < SIZE_T_MAX && large_segment_size < SIZE_T_MAX); + if (segment_size < large_segment_size) + segment_size = large_segment_size; + + FC_GC_POLL_RET(); + return (UINT64) segment_size; +} +FCIMPLEND + /*================================CollectionCount================================= **Action: Returns the number of collections for this generation since the begining of the life of the process **Returns: The collection count. diff --git a/src/vm/comutilnative.h b/src/vm/comutilnative.h index 7f51e23db2..2b825b497a 100644 --- a/src/vm/comutilnative.h +++ b/src/vm/comutilnative.h @@ -120,6 +120,7 @@ public: static FCDECL1(int, WaitForFullGCComplete, int millisecondsTimeout); static FCDECL1(int, GetGenerationWR, LPVOID handle); static FCDECL1(int, GetGeneration, Object* objUNSAFE); + static FCDECL0(UINT64, GetSegmentSize); static INT64 QCALLTYPE GetTotalMemory(); diff --git a/src/vm/ecalllist.h b/src/vm/ecalllist.h index 5fce2000eb..83dadce185 100644 --- a/src/vm/ecalllist.h +++ b/src/vm/ecalllist.h @@ -786,6 +786,7 @@ FCFuncStart(gGCInterfaceFuncs) QCFuncElement("_StartNoGCRegion", GCInterface::StartNoGCRegion) QCFuncElement("_EndNoGCRegion", GCInterface::EndNoGCRegion) FCFuncElement("IsServerGC", SystemNative::IsServerGC) + FCFuncElement("GetSegmentSize", GCInterface::GetSegmentSize) QCFuncElement("_AddMemoryPressure", GCInterface::_AddMemoryPressure) QCFuncElement("_RemoveMemoryPressure", GCInterface::_RemoveMemoryPressure) FCFuncElement("GetGeneration", GCInterface::GetGeneration) @@ -800,10 +801,6 @@ FCFuncStart(gGCInterfaceFuncs) FCFuncElement("_GetAllocatedBytesForCurrentThread", GCInterface::GetAllocatedBytesForCurrentThread) FCFuncEnd() -FCFuncStart(gMemoryFailPointFuncs) - FCFuncElement("GetMemorySettings", COMMemoryFailPoint::GetMemorySettings) -FCFuncEnd() - FCFuncStart(gInteropMarshalFuncs) FCFuncElement("GetLastWin32Error", MarshalNative::GetLastWin32Error) FCFuncElement("SetLastWin32Error", MarshalNative::SetLastWin32Error) @@ -1250,7 +1247,6 @@ FCClassElement("MathF", "System", gMathFFuncs) FCClassElement("Mda", "System", gMda) #endif FCClassElement("MdUtf8String", "System", gMdUtf8String) -FCClassElement("MemoryFailPoint", "System.Runtime", gMemoryFailPointFuncs) FCClassElement("MetadataImport", "System.Reflection", gMetaDataImport) FCClassElement("MissingMemberException", "System", gMissingMemberExceptionFuncs) #ifdef FEATURE_COMINTEROP diff --git a/src/vm/mscorlib.cpp b/src/vm/mscorlib.cpp index e69cfb8774..023a865e68 100644 --- a/src/vm/mscorlib.cpp +++ b/src/vm/mscorlib.cpp @@ -58,7 +58,6 @@ #include "reflectioninvocation.h" #include "managedmdimport.hpp" #include "synchronizationcontextnative.h" -#include "commemoryfailpoint.h" #include "typestring.h" #include "comdependenthandle.h" #include "weakreferencenative.h" |