diff options
Diffstat (limited to 'src/System.Private.CoreLib')
13 files changed, 317 insertions, 171 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); |