summaryrefslogtreecommitdiff
path: root/src/System.Private.CoreLib/shared/System/Runtime/MemoryFailPoint.Windows.cs
blob: 1e59a4ae5b9db00673bc3b31f9015cd8a0e206f7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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();
                }
            }
        }
    }
}