Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Core / System / IO / MemoryMappedFiles / MemoryMappedView.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 /*============================================================
7 **
8 ** Class:   MemoryMappedView
9 **
10 ** Purpose: Internal class representing MemoryMappedFile view
11 **
12 ** Date:  February 7, 2007 
13 **
14 ===========================================================*/
15
16 using System.Diagnostics;
17 using System.Runtime.InteropServices;
18 using System.Runtime.CompilerServices;
19 using System.Security;
20 using System.Threading;
21 using Microsoft.Win32;
22 using Microsoft.Win32.SafeHandles;
23
24 namespace System.IO.MemoryMappedFiles {
25
26     internal class MemoryMappedView : IDisposable {
27
28         private SafeMemoryMappedViewHandle m_viewHandle;
29         private Int64 m_pointerOffset;
30         private Int64 m_size;
31         private MemoryMappedFileAccess m_access;
32
33         // These control the retry behaviour when lock violation errors occur during Flush:
34         private const Int32 MaxFlushWaits = 15;  // must be <=30
35         private const Int32 MaxFlushRetriesPerWait = 20; 
36
37         [System.Security.SecurityCritical]
38         private unsafe MemoryMappedView(SafeMemoryMappedViewHandle viewHandle, Int64 pointerOffset, 
39                                             Int64 size, MemoryMappedFileAccess access) {
40
41             m_viewHandle = viewHandle;
42             m_pointerOffset = pointerOffset;
43             m_size = size;
44             m_access = access;
45         }
46
47         internal SafeMemoryMappedViewHandle ViewHandle {
48             [System.Security.SecurityCritical]
49             get {
50                 return m_viewHandle;
51             }
52         }
53
54         internal Int64 PointerOffset {
55             get {
56                 return m_pointerOffset;
57             }
58         }
59
60         internal Int64 Size {
61             get {
62                 return m_size;
63             }
64         }
65
66         internal MemoryMappedFileAccess Access {
67             get {
68                 return m_access;
69             }
70         }
71
72         // Callers must demand unmanaged code first
73         [System.Security.SecurityCritical]
74         internal unsafe static MemoryMappedView CreateView(SafeMemoryMappedFileHandle memMappedFileHandle,
75                                             MemoryMappedFileAccess access, Int64 offset, Int64 size) {
76
77             // MapViewOfFile can only create views that start at a multiple of the system memory allocation 
78             // granularity. We decided to hide this restriction form the user by creating larger views than the
79             // user requested and hiding the parts that the user did not request.  extraMemNeeded is the amount of
80             // extra memory we allocate before the start of the requested view. MapViewOfFile will also round the 
81             // capacity of the view to the nearest multiple of the system page size.  Once again, we hide this 
82             // from the user by preventing them from writing to any memory that they did not request.
83             ulong extraMemNeeded = (ulong)offset % (ulong)MemoryMappedFile.GetSystemPageAllocationGranularity();
84
85             // newOffset takes into account the fact that we have some extra memory allocated before the requested view
86             ulong newOffset = (ulong)offset - extraMemNeeded;
87             Debug.Assert(newOffset >= 0, "newOffset = (offset - extraMemNeeded) < 0");
88
89             // determine size to pass to MapViewOfFile
90             ulong nativeSize;
91             if (size != MemoryMappedFile.DefaultSize) {
92                 nativeSize = (ulong)size + (ulong)extraMemNeeded;
93             }
94             else {
95                 nativeSize = 0;
96             }
97
98             if (IntPtr.Size == 4 && nativeSize > UInt32.MaxValue) {
99                 throw new ArgumentOutOfRangeException("size", SR.GetString(SR.ArgumentOutOfRange_CapacityLargerThanLogicalAddressSpaceNotAllowed));
100             }
101
102             // if request is >= than total virtual, then MapViewOfFile will fail with meaningless error message 
103             // "the parameter is incorrect"; this provides better error message in advance
104             UnsafeNativeMethods.MEMORYSTATUSEX memStatus = new UnsafeNativeMethods.MEMORYSTATUSEX();
105             bool result = UnsafeNativeMethods.GlobalMemoryStatusEx(ref memStatus);
106             ulong totalVirtual = memStatus.ullTotalVirtual; 
107             if (nativeSize >= totalVirtual) {
108                 throw new IOException(SR.GetString(SR.IO_NotEnoughMemory));
109             }
110
111             // split the Int64 into two ints
112             uint offsetLow = (uint)(newOffset & 0x00000000FFFFFFFFL);
113             uint offsetHigh = (uint)(newOffset >> 32);
114
115             // create the view
116             SafeMemoryMappedViewHandle viewHandle = UnsafeNativeMethods.MapViewOfFile(memMappedFileHandle, 
117                     MemoryMappedFile.GetFileMapAccess(access), offsetHigh, offsetLow, new UIntPtr(nativeSize));
118             if (viewHandle.IsInvalid) {
119                 __Error.WinIOError(Marshal.GetLastWin32Error(), String.Empty);
120             }
121
122             // Query the view for its size and allocation type
123             UnsafeNativeMethods.MEMORY_BASIC_INFORMATION viewInfo = new UnsafeNativeMethods.MEMORY_BASIC_INFORMATION();
124             UnsafeNativeMethods.VirtualQuery(viewHandle, ref viewInfo, (IntPtr)Marshal.SizeOf(viewInfo)); 
125             ulong viewSize = (ulong)viewInfo.RegionSize;
126             
127
128             // Allocate the pages if we were using the MemoryMappedFileOptions.DelayAllocatePages option
129             // OR check if the allocated view size is smaller than the expected native size
130             // If multiple overlapping views are created over the file mapping object, the pages in a given region
131             // could have different attributes(MEM_RESERVE OR MEM_COMMIT) as MapViewOfFile preserves coherence between 
132             // views created on a mapping object backed by same file.
133             // In which case, the viewSize will be smaller than nativeSize required and viewState could be MEM_COMMIT 
134             // but more pages may need to be committed in the region.
135             // This is because, VirtualQuery function(that internally invokes VirtualQueryEx function) returns the attributes 
136             // and size of the region of pages with matching attributes starting from base address.
137             // VirtualQueryEx: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366907(v=vs.85).aspx
138             if (((viewInfo.State & UnsafeNativeMethods.MEM_RESERVE) != 0) || (viewSize < nativeSize)) {
139                 ulong allocSize = (nativeSize == 0) ? viewSize : nativeSize;
140                 IntPtr tempHandle = UnsafeNativeMethods.VirtualAlloc(viewHandle, (UIntPtr)allocSize, UnsafeNativeMethods.MEM_COMMIT, 
141                                                         MemoryMappedFile.GetPageAccess(access));
142                 int lastError = Marshal.GetLastWin32Error();
143                 // The following is commented out for backward compatibility.
144                 // Previously releases failed to check for this error so introducing this check
145                 // could cause new/different exceptions in existing code paths.
146                 // if (tempHandle == IntPtr.Zero) {
147                 //     __Error.WinIOError(lastError, String.Empty);
148                 // }
149                 
150                 // again query the view for its new size
151                 viewInfo = new UnsafeNativeMethods.MEMORY_BASIC_INFORMATION();
152                 UnsafeNativeMethods.VirtualQuery(viewHandle, ref viewInfo, (IntPtr)Marshal.SizeOf(viewInfo)); 
153                 viewSize = (ulong)viewInfo.RegionSize;
154             }
155
156             // if the user specified DefaultSize as the size, we need to get the actual size
157             if (size == MemoryMappedFile.DefaultSize) {
158                 size = (Int64)(viewSize - extraMemNeeded);
159             }
160             else {
161                 Debug.Assert(viewSize >= (ulong)size, "viewSize < size");
162             }
163
164             viewHandle.Initialize((ulong)size + extraMemNeeded);
165             MemoryMappedView mmv = new MemoryMappedView(viewHandle, (long)extraMemNeeded, size, access);
166             return mmv;
167
168         }
169
170         // Flushes the changes such that they are in sync with the FileStream bits (ones obtained
171         // with the win32 ReadFile and WriteFile functions).  Need to call FileStream's Flush to 
172         // flush to the disk.
173         // NOTE: This will flush all bytes before and after the view up until an offset that is a multiple
174         //       of SystemPageSize.
175         [System.Security.SecurityCritical]
176         public void Flush(IntPtr capacity) {
177
178             if (m_viewHandle != null) {
179
180                 unsafe {
181                     byte* firstPagePtr = null;
182                     RuntimeHelpers.PrepareConstrainedRegions();
183                     try {
184                         m_viewHandle.AcquirePointer(ref firstPagePtr);
185
186                         bool success = UnsafeNativeMethods.FlushViewOfFile(firstPagePtr, capacity);
187                         if (success)
188                             return; // This will visit the finally block.
189
190                         // It is a known issue within the NTFS transaction log system that
191                         // causes FlushViewOfFile to intermittently fail with ERROR_LOCK_VIOLATION
192                         // [http://bugcheck/bugs/Windows8Bugs/152862].
193                         // As a workaround, we catch this particular error and retry the flush operation 
194                         // a few milliseconds later. If it does not work, we give it a few more tries with
195                         // increasing intervals. Eventually, however, we need to give up. In ad-hoc tests
196                         // this strategy successfully flushed the view after no more than 3 retries.
197
198                         Int32 error = Marshal.GetLastWin32Error();
199                         bool canRetry = (!success && error == UnsafeNativeMethods.ERROR_LOCK_VIOLATION);
200
201                         for (Int32 w = 0; canRetry && w < MaxFlushWaits; w++) {
202
203                             Int32 pause = (1 << w);  // MaxFlushRetries should never be over 30
204                             Thread.Sleep(pause);
205
206                             for (Int32 r = 0; canRetry && r < MaxFlushRetriesPerWait; r++) {
207
208                                 success = UnsafeNativeMethods.FlushViewOfFile(firstPagePtr, capacity);
209                                 if (success)
210                                     return; // This will visit the finally block.
211
212                                 Thread.Sleep(0);
213
214                                 error = Marshal.GetLastWin32Error();
215                                 canRetry = (error == UnsafeNativeMethods.ERROR_LOCK_VIOLATION);
216                             }
217                         }
218
219                         // We got too here, so there was no success:
220                         __Error.WinIOError(error, String.Empty);                        
221                     }
222                     finally {
223                         if (firstPagePtr != null) {
224                             m_viewHandle.ReleasePointer();
225                         }
226                     }
227                 }
228
229             }
230         }
231                 
232
233         [System.Security.SecurityCritical]
234         protected virtual void Dispose(bool disposing) {
235
236             if (m_viewHandle != null && !m_viewHandle.IsClosed) {
237                 m_viewHandle.Dispose();
238             }
239         }
240
241         [System.Security.SecurityCritical]
242         public void Dispose() {
243
244             Dispose(true);
245             GC.SuppressFinalize(this);
246         }
247
248         internal bool IsClosed {
249             [SecuritySafeCritical]
250             get {
251                 return (m_viewHandle == null || m_viewHandle.IsClosed);
252             }
253         }
254
255     }
256
257 }