3 * MemoryMappedFile internal calls for Windows
5 * Copyright 2016 Microsoft
6 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
10 * The code in this file has been inspired by the CoreFX MemoryMappedFile Windows implementation contained in the files
12 * https://github.com/dotnet/corefx/blob/master/src/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Windows.cs
13 * https://github.com/dotnet/corefx/blob/master/src/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedView.Windows.cs
18 #include <mono/utils/mono-compiler.h>
19 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) && defined(HOST_WIN32)
23 #include <mono/metadata/file-mmap.h>
25 // These control the retry behaviour when lock violation errors occur during Flush:
26 #define MAX_FLUSH_WAITS 15 // must be <=30
27 #define MAX_FLUSH_RETIRES_PER_WAIT 20
35 BAD_CAPACITY_FOR_FILE_BACKED
= 1,
36 CAPACITY_SMALLER_THAN_FILE_SIZE
,
41 CAPACITY_MUST_BE_POSITIVE
,
45 CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
49 FILE_MODE_CREATE_NEW
= 1,
52 FILE_MODE_OPEN_OR_CREATE
= 4,
53 FILE_MODE_TRUNCATE
= 5,
58 MMAP_FILE_ACCESS_READ_WRITE
= 0,
59 MMAP_FILE_ACCESS_READ
= 1,
60 MMAP_FILE_ACCESS_WRITE
= 2,
61 MMAP_FILE_ACCESS_COPY_ON_WRITE
= 3,
62 MMAP_FILE_ACCESS_READ_EXECUTE
= 4,
63 MMAP_FILE_ACCESS_READ_WRITE_EXECUTE
= 5,
66 static DWORD
get_page_access (int access
)
69 case MMAP_FILE_ACCESS_READ
:
71 case MMAP_FILE_ACCESS_READ_WRITE
:
72 return PAGE_READWRITE
;
73 case MMAP_FILE_ACCESS_COPY_ON_WRITE
:
74 return PAGE_WRITECOPY
;
75 case MMAP_FILE_ACCESS_READ_EXECUTE
:
76 return PAGE_EXECUTE_READ
;
77 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE
:
78 return PAGE_EXECUTE_READWRITE
;
80 g_error ("unknown MemoryMappedFileAccess %d", access
);
84 static DWORD
get_file_access (int access
)
87 case MMAP_FILE_ACCESS_READ
:
88 case MMAP_FILE_ACCESS_READ_EXECUTE
:
90 case MMAP_FILE_ACCESS_READ_WRITE
:
91 case MMAP_FILE_ACCESS_COPY_ON_WRITE
:
92 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE
:
93 return GENERIC_READ
| GENERIC_WRITE
;
94 case MMAP_FILE_ACCESS_WRITE
:
97 g_error ("unknown MemoryMappedFileAccess %d", access
);
101 static int get_file_map_access (int access
)
104 case MMAP_FILE_ACCESS_READ
:
105 return FILE_MAP_READ
;
106 case MMAP_FILE_ACCESS_WRITE
:
107 return FILE_MAP_WRITE
;
108 case MMAP_FILE_ACCESS_READ_WRITE
:
109 return FILE_MAP_READ
| FILE_MAP_WRITE
;
110 case MMAP_FILE_ACCESS_COPY_ON_WRITE
:
111 return FILE_MAP_COPY
;
112 case MMAP_FILE_ACCESS_READ_EXECUTE
:
113 return FILE_MAP_EXECUTE
| FILE_MAP_READ
;
114 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE
:
115 return FILE_MAP_EXECUTE
| FILE_MAP_READ
| FILE_MAP_WRITE
;
117 g_error ("unknown MemoryMappedFileAccess %d", access
);
121 static int convert_win32_error (int win32error
, int default_
)
123 switch (win32error
) {
124 case ERROR_FILE_NOT_FOUND
:
125 return FILE_NOT_FOUND
;
126 case ERROR_FILE_EXISTS
:
127 case ERROR_ALREADY_EXISTS
:
128 return FILE_ALREADY_EXISTS
;
129 case ERROR_ACCESS_DENIED
:
130 return ACCESS_DENIED
;
136 open_handle (void *handle
, const gunichar2
*mapName
, gint mapName_length
, int mode
, gint64
*capacity
, int access
, int options
, int *ioerror
, MonoError
*error
)
138 g_assert (handle
!= NULL
);
140 // INVALID_HANDLE_VALUE (-1) is valid, to make named shared memory,
141 // backed by physical memory / pagefile.
143 if (handle
== INVALID_HANDLE_VALUE
) {
144 if (*capacity
<= 0 && mode
!= FILE_MODE_OPEN
) {
145 *ioerror
= CAPACITY_MUST_BE_POSITIVE
;
148 #if SIZEOF_VOID_P == 4
149 if (*capacity
> UINT32_MAX
) {
150 *ioerror
= CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
;
154 if (!(mode
== FILE_MODE_CREATE_NEW
|| mode
== FILE_MODE_OPEN_OR_CREATE
|| mode
== FILE_MODE_OPEN
)) {
155 *ioerror
= INVALID_FILE_MODE
;
159 FILE_STANDARD_INFO info
;
160 gboolean getinfo_success
;
162 getinfo_success
= GetFileInformationByHandleEx (handle
, FileStandardInfo
, &info
, sizeof (FILE_STANDARD_INFO
));
164 if (!getinfo_success
) {
165 *ioerror
= convert_win32_error (GetLastError (), COULD_NOT_OPEN
);
168 if (*capacity
== 0) {
169 if (info
.EndOfFile
.QuadPart
== 0) {
170 *ioerror
= CAPACITY_SMALLER_THAN_FILE_SIZE
;
173 } else if (*capacity
< info
.EndOfFile
.QuadPart
) {
174 *ioerror
= CAPACITY_SMALLER_THAN_FILE_SIZE
;
179 HANDLE result
= NULL
;
181 if (mode
== FILE_MODE_CREATE_NEW
|| handle
!= INVALID_HANDLE_VALUE
) {
183 result
= CreateFileMappingW (handle
, NULL
, get_page_access (access
) | options
, (DWORD
)(((guint64
)*capacity
) >> 32), (DWORD
)*capacity
, mapName
);
185 if (result
&& GetLastError () == ERROR_ALREADY_EXISTS
) {
187 CloseHandle (result
);
190 *ioerror
= FILE_ALREADY_EXISTS
;
191 } else if (!result
&& GetLastError () != NO_ERROR
) {
192 *ioerror
= convert_win32_error (GetLastError (), COULD_NOT_OPEN
);
194 } else if (mode
== FILE_MODE_OPEN
|| mode
== FILE_MODE_OPEN_OR_CREATE
&& access
== MMAP_FILE_ACCESS_WRITE
) {
196 result
= OpenFileMappingW (get_file_map_access (access
), FALSE
, mapName
);
199 if (mode
== FILE_MODE_OPEN_OR_CREATE
&& GetLastError () == ERROR_FILE_NOT_FOUND
) {
200 *ioerror
= INVALID_FILE_MODE
;
202 *ioerror
= convert_win32_error (GetLastError (), COULD_NOT_OPEN
);
205 } else if (mode
== FILE_MODE_OPEN_OR_CREATE
) {
207 // This replicates how CoreFX does MemoryMappedFile.CreateOrOpen ().
209 /// Try to open the file if it exists -- this requires a bit more work. Loop until we can
210 /// either create or open a memory mapped file up to a timeout. CreateFileMapping may fail
211 /// if the file exists and we have non-null security attributes, in which case we need to
212 /// use OpenFileMapping. But, there exists a race condition because the memory mapped file
213 /// may have closed between the two calls -- hence the loop.
215 /// The retry/timeout logic increases the wait time each pass through the loop and times
216 /// out in approximately 1.4 minutes. If after retrying, a MMF handle still hasn't been opened,
217 /// throw an InvalidOperationException.
219 guint32 waitRetries
= 14; //((2^13)-1)*10ms == approximately 1.4mins
220 guint32 waitSleep
= 0;
222 while (waitRetries
> 0) {
224 result
= CreateFileMappingW (handle
, NULL
, get_page_access (access
) | options
, (DWORD
)(((guint64
)*capacity
) >> 32), (DWORD
)*capacity
, mapName
);
228 if (GetLastError() != ERROR_ACCESS_DENIED
) {
229 *ioerror
= convert_win32_error (GetLastError (), COULD_NOT_OPEN
);
233 result
= OpenFileMappingW (get_file_map_access (access
), FALSE
, mapName
);
237 if (GetLastError () != ERROR_FILE_NOT_FOUND
) {
238 *ioerror
= convert_win32_error (GetLastError (), COULD_NOT_OPEN
);
241 // increase wait time
243 if (waitSleep
== 0) {
246 mono_thread_info_sleep (waitSleep
, NULL
);
252 *ioerror
= COULD_NOT_OPEN
;
260 mono_mmap_open_file (const gunichar2
*path
, gint path_length
, int mode
, const gunichar2
*mapName
, gint mapName_length
, gint64
*capacity
, int access
, int options
, int *ioerror
, MonoError
*error
)
262 g_assert (path
!= NULL
|| mapName
!= NULL
);
264 HANDLE hFile
= INVALID_HANDLE_VALUE
;
265 HANDLE result
= NULL
;
266 gboolean delete_on_error
= FALSE
;
269 WIN32_FILE_ATTRIBUTE_DATA file_attrs
;
272 existed
= GetFileAttributesExW (path
, GetFileExInfoStandard
, &file_attrs
);
274 if (!existed
&& mode
== FILE_MODE_CREATE_NEW
&& *capacity
== 0) {
275 *ioerror
= CAPACITY_SMALLER_THAN_FILE_SIZE
;
279 hFile
= CreateFileW (path
, get_file_access (access
), FILE_SHARE_READ
, NULL
, mode
, FILE_ATTRIBUTE_NORMAL
, NULL
);
281 if (hFile
== INVALID_HANDLE_VALUE
) {
282 *ioerror
= convert_win32_error (GetLastError (), COULD_NOT_OPEN
);
285 delete_on_error
= !existed
;
287 // INVALID_HANDLE_VALUE (-1) is valid, to make named shared memory,
288 // backed by physical memory / pagefile.
291 result
= open_handle (hFile
, mapName
, mapName_length
, mode
, capacity
, access
, options
, ioerror
, error
);
295 if (hFile
!= INVALID_HANDLE_VALUE
)
297 if (!result
&& delete_on_error
)
305 mono_mmap_open_handle (void *handle
, const gunichar2
*mapName
, gint mapName_length
, gint64
*capacity
, int access
, int options
, int *ioerror
, MonoError
*error
)
307 g_assert (handle
!= NULL
);
309 return open_handle (handle
, mapName
, mapName_length
, FILE_MODE_OPEN
, capacity
, access
, options
, ioerror
, error
);
313 mono_mmap_close (void *mmap_handle
, MonoError
*error
)
315 g_assert (mmap_handle
);
317 CloseHandle (mmap_handle
);
322 mono_mmap_configure_inheritability (void *mmap_handle
, gint32 inheritability
, MonoError
*error
)
324 g_assert (mmap_handle
);
325 if (!SetHandleInformation (mmap_handle
, HANDLE_FLAG_INHERIT
, inheritability
? HANDLE_FLAG_INHERIT
: 0)) {
326 g_error ("mono_mmap_configure_inheritability: SetHandleInformation failed with error %d!", GetLastError ());
331 mono_mmap_flush (void *mmap_handle
, MonoError
*error
)
333 g_assert (mmap_handle
);
334 MmapInstance
*h
= (MmapInstance
*)mmap_handle
;
336 gboolean flush_success
;
338 flush_success
= FlushViewOfFile (h
->address
, h
->length
);
344 // This replicates how CoreFX does MemoryMappedView.Flush ().
346 // It is a known issue within the NTFS transaction log system that
347 // causes FlushViewOfFile to intermittently fail with ERROR_LOCK_VIOLATION
348 // As a workaround, we catch this particular error and retry the flush operation
349 // a few milliseconds later. If it does not work, we give it a few more tries with
350 // increasing intervals. Eventually, however, we need to give up. In ad-hoc tests
351 // this strategy successfully flushed the view after no more than 3 retries.
353 if (GetLastError () != ERROR_LOCK_VIOLATION
)
354 // TODO: Propagate error to caller
357 for (int w
= 0; w
< MAX_FLUSH_WAITS
; w
++) {
358 int pause
= (1 << w
); // MaxFlushRetries should never be over 30
359 mono_thread_info_sleep (pause
, NULL
);
361 for (int r
= 0; r
< MAX_FLUSH_RETIRES_PER_WAIT
; r
++) {
363 flush_success
= FlushViewOfFile (h
->address
, h
->length
);
368 if (GetLastError () != ERROR_LOCK_VIOLATION
)
369 // TODO: Propagate error to caller
372 mono_thread_info_yield ();
376 // We got to here, so there was no success:
377 // TODO: Propagate error to caller
381 mono_mmap_map (void *handle
, gint64 offset
, gint64
*size
, int access
, void **mmap_handle
, void **base_address
, MonoError
*error
)
383 static DWORD allocationGranularity
= 0;
384 if (allocationGranularity
== 0) {
386 GetSystemInfo (&info
);
387 allocationGranularity
= info
.dwAllocationGranularity
;
390 gint64 extraMemNeeded
= offset
% allocationGranularity
;
391 guint64 newOffset
= offset
- extraMemNeeded
;
392 gint64 nativeSize
= (*size
!= 0) ? *size
+ extraMemNeeded
: 0;
394 #if SIZEOF_VOID_P == 4
395 if (nativeSize
> UINT32_MAX
)
396 return CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
;
401 address
= MapViewOfFile (handle
, get_file_map_access (access
), (DWORD
) (newOffset
>> 32), (DWORD
) newOffset
, (SIZE_T
) nativeSize
);
404 return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY
);
406 // Query the view for its size and allocation type
407 MEMORY_BASIC_INFORMATION viewInfo
;
409 VirtualQuery (address
, &viewInfo
, sizeof (MEMORY_BASIC_INFORMATION
));
411 guint64 viewSize
= (guint64
) viewInfo
.RegionSize
;
413 // Allocate the pages if we were using the MemoryMappedFileOptions.DelayAllocatePages option
414 // OR check if the allocated view size is smaller than the expected native size
415 // If multiple overlapping views are created over the file mapping object, the pages in a given region
416 // could have different attributes(MEM_RESERVE OR MEM_COMMIT) as MapViewOfFile preserves coherence between
417 // views created on a mapping object backed by same file.
418 // In which case, the viewSize will be smaller than nativeSize required and viewState could be MEM_COMMIT
419 // but more pages may need to be committed in the region.
420 // This is because, VirtualQuery function(that internally invokes VirtualQueryEx function) returns the attributes
421 // and size of the region of pages with matching attributes starting from base address.
422 // VirtualQueryEx: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366907(v=vs.85).aspx
423 if (((viewInfo
.State
& MEM_RESERVE
) != 0) || viewSize
< (guint64
) nativeSize
) {
426 tempAddress
= VirtualAlloc (address
, nativeSize
!= 0 ? nativeSize
: viewSize
, MEM_COMMIT
, get_page_access (access
));
429 return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY
);
431 // again query the view for its new size
433 VirtualQuery (address
, &viewInfo
, sizeof (MEMORY_BASIC_INFORMATION
));
435 viewSize
= (guint64
) viewInfo
.RegionSize
;
439 *size
= viewSize
- extraMemNeeded
;
441 MmapInstance
*h
= g_malloc0 (sizeof (MmapInstance
));
442 h
->address
= address
;
443 h
->length
= *size
+ extraMemNeeded
;
445 *base_address
= (char*) address
+ (offset
- newOffset
);
451 mono_mmap_unmap (void *mmap_handle
, MonoError
*error
)
453 g_assert (mmap_handle
);
455 MmapInstance
*h
= (MmapInstance
*) mmap_handle
;
459 result
= UnmapViewOfFile (h
->address
);
468 MONO_EMPTY_SOURCE_FILE (file_mmap_windows
);