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>
20 #include <mono/utils/mono-error.h>
21 #include <mono/utils/mono-error-internals.h>
22 #include <mono/metadata/object-internals.h>
23 #include <mono/metadata/file-mmap.h>
24 #include <mono/utils/w32subset.h>
26 #if HAVE_API_SUPPORT_WIN32_FILE_MAPPING
27 // These control the retry behaviour when lock violation errors occur during Flush:
28 #define MAX_FLUSH_WAITS 15 // must be <=30
29 #define MAX_FLUSH_RETIRES_PER_WAIT 20
37 BAD_CAPACITY_FOR_FILE_BACKED
= 1,
38 CAPACITY_SMALLER_THAN_FILE_SIZE
,
43 CAPACITY_MUST_BE_POSITIVE
,
47 CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
51 FILE_MODE_CREATE_NEW
= 1,
54 FILE_MODE_OPEN_OR_CREATE
= 4,
55 FILE_MODE_TRUNCATE
= 5,
60 MMAP_FILE_ACCESS_READ_WRITE
= 0,
61 MMAP_FILE_ACCESS_READ
= 1,
62 MMAP_FILE_ACCESS_WRITE
= 2,
63 MMAP_FILE_ACCESS_COPY_ON_WRITE
= 3,
64 MMAP_FILE_ACCESS_READ_EXECUTE
= 4,
65 MMAP_FILE_ACCESS_READ_WRITE_EXECUTE
= 5,
68 static DWORD
get_page_access (int access
)
71 case MMAP_FILE_ACCESS_READ
:
73 case MMAP_FILE_ACCESS_READ_WRITE
:
74 return PAGE_READWRITE
;
75 case MMAP_FILE_ACCESS_COPY_ON_WRITE
:
76 return PAGE_WRITECOPY
;
77 case MMAP_FILE_ACCESS_READ_EXECUTE
:
78 return PAGE_EXECUTE_READ
;
79 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE
:
80 return PAGE_EXECUTE_READWRITE
;
82 g_error ("unknown MemoryMappedFileAccess %d", access
);
86 static DWORD
get_file_access (int access
)
89 case MMAP_FILE_ACCESS_READ
:
90 case MMAP_FILE_ACCESS_READ_EXECUTE
:
92 case MMAP_FILE_ACCESS_READ_WRITE
:
93 case MMAP_FILE_ACCESS_COPY_ON_WRITE
:
94 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE
:
95 return GENERIC_READ
| GENERIC_WRITE
;
96 case MMAP_FILE_ACCESS_WRITE
:
99 g_error ("unknown MemoryMappedFileAccess %d", access
);
103 static int get_file_map_access (int access
)
106 case MMAP_FILE_ACCESS_READ
:
107 return FILE_MAP_READ
;
108 case MMAP_FILE_ACCESS_WRITE
:
109 return FILE_MAP_WRITE
;
110 case MMAP_FILE_ACCESS_READ_WRITE
:
111 return FILE_MAP_READ
| FILE_MAP_WRITE
;
112 case MMAP_FILE_ACCESS_COPY_ON_WRITE
:
113 return FILE_MAP_COPY
;
114 case MMAP_FILE_ACCESS_READ_EXECUTE
:
115 return FILE_MAP_EXECUTE
| FILE_MAP_READ
;
116 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE
:
117 return FILE_MAP_EXECUTE
| FILE_MAP_READ
| FILE_MAP_WRITE
;
119 g_error ("unknown MemoryMappedFileAccess %d", access
);
123 static int convert_win32_error (int win32error
, int default_
)
125 switch (win32error
) {
126 case ERROR_FILE_NOT_FOUND
:
127 return FILE_NOT_FOUND
;
128 case ERROR_FILE_EXISTS
:
129 case ERROR_ALREADY_EXISTS
:
130 return FILE_ALREADY_EXISTS
;
131 case ERROR_ACCESS_DENIED
:
132 return ACCESS_DENIED
;
138 open_handle (void *handle
, const gunichar2
*mapName
, gint mapName_length
, int mode
, gint64
*capacity
, int access
, int options
, int *ioerror
, MonoError
*error
)
140 g_assert (handle
!= NULL
);
142 // INVALID_HANDLE_VALUE (-1) is valid, to make named shared memory,
143 // backed by physical memory / pagefile.
145 if (handle
== INVALID_HANDLE_VALUE
) {
146 if (*capacity
<= 0 && mode
!= FILE_MODE_OPEN
) {
147 *ioerror
= CAPACITY_MUST_BE_POSITIVE
;
150 #if SIZEOF_VOID_P == 4
151 if (*capacity
> UINT32_MAX
) {
152 *ioerror
= CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
;
156 if (!(mode
== FILE_MODE_CREATE_NEW
|| mode
== FILE_MODE_OPEN_OR_CREATE
|| mode
== FILE_MODE_OPEN
)) {
157 *ioerror
= INVALID_FILE_MODE
;
161 FILE_STANDARD_INFO info
;
162 gboolean getinfo_success
;
164 getinfo_success
= GetFileInformationByHandleEx (handle
, FileStandardInfo
, &info
, sizeof (FILE_STANDARD_INFO
));
166 if (!getinfo_success
) {
167 *ioerror
= convert_win32_error (GetLastError (), COULD_NOT_OPEN
);
170 if (*capacity
== 0) {
171 if (info
.EndOfFile
.QuadPart
== 0) {
172 *ioerror
= CAPACITY_SMALLER_THAN_FILE_SIZE
;
175 } else if (*capacity
< info
.EndOfFile
.QuadPart
) {
176 *ioerror
= CAPACITY_SMALLER_THAN_FILE_SIZE
;
181 HANDLE result
= NULL
;
183 if (mode
== FILE_MODE_CREATE_NEW
|| handle
!= INVALID_HANDLE_VALUE
) {
185 result
= CreateFileMappingW (handle
, NULL
, get_page_access (access
) | options
, (DWORD
)(((guint64
)*capacity
) >> 32), (DWORD
)*capacity
, mapName
);
187 if (result
&& GetLastError () == ERROR_ALREADY_EXISTS
) {
189 CloseHandle (result
);
192 *ioerror
= FILE_ALREADY_EXISTS
;
193 } else if (!result
&& GetLastError () != NO_ERROR
) {
194 *ioerror
= convert_win32_error (GetLastError (), COULD_NOT_OPEN
);
196 } else if (mode
== FILE_MODE_OPEN
|| mode
== FILE_MODE_OPEN_OR_CREATE
&& access
== MMAP_FILE_ACCESS_WRITE
) {
198 result
= OpenFileMappingW (get_file_map_access (access
), FALSE
, mapName
);
201 if (mode
== FILE_MODE_OPEN_OR_CREATE
&& GetLastError () == ERROR_FILE_NOT_FOUND
) {
202 *ioerror
= INVALID_FILE_MODE
;
204 *ioerror
= convert_win32_error (GetLastError (), COULD_NOT_OPEN
);
207 } else if (mode
== FILE_MODE_OPEN_OR_CREATE
) {
209 // This replicates how CoreFX does MemoryMappedFile.CreateOrOpen ().
211 /// Try to open the file if it exists -- this requires a bit more work. Loop until we can
212 /// either create or open a memory mapped file up to a timeout. CreateFileMapping may fail
213 /// if the file exists and we have non-null security attributes, in which case we need to
214 /// use OpenFileMapping. But, there exists a race condition because the memory mapped file
215 /// may have closed between the two calls -- hence the loop.
217 /// The retry/timeout logic increases the wait time each pass through the loop and times
218 /// out in approximately 1.4 minutes. If after retrying, a MMF handle still hasn't been opened,
219 /// throw an InvalidOperationException.
221 guint32 waitRetries
= 14; //((2^13)-1)*10ms == approximately 1.4mins
222 guint32 waitSleep
= 0;
224 while (waitRetries
> 0) {
226 result
= CreateFileMappingW (handle
, NULL
, get_page_access (access
) | options
, (DWORD
)(((guint64
)*capacity
) >> 32), (DWORD
)*capacity
, mapName
);
230 if (GetLastError() != ERROR_ACCESS_DENIED
) {
231 *ioerror
= convert_win32_error (GetLastError (), COULD_NOT_OPEN
);
235 result
= OpenFileMappingW (get_file_map_access (access
), FALSE
, mapName
);
239 if (GetLastError () != ERROR_FILE_NOT_FOUND
) {
240 *ioerror
= convert_win32_error (GetLastError (), COULD_NOT_OPEN
);
243 // increase wait time
245 if (waitSleep
== 0) {
248 mono_thread_info_sleep (waitSleep
, NULL
);
254 *ioerror
= COULD_NOT_OPEN
;
262 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
)
264 g_assert (path
!= NULL
|| mapName
!= NULL
);
266 HANDLE hFile
= INVALID_HANDLE_VALUE
;
267 HANDLE result
= NULL
;
268 gboolean delete_on_error
= FALSE
;
271 WIN32_FILE_ATTRIBUTE_DATA file_attrs
;
274 existed
= GetFileAttributesExW (path
, GetFileExInfoStandard
, &file_attrs
);
276 if (!existed
&& mode
== FILE_MODE_CREATE_NEW
&& *capacity
== 0) {
277 *ioerror
= CAPACITY_SMALLER_THAN_FILE_SIZE
;
281 hFile
= CreateFileW (path
, get_file_access (access
), FILE_SHARE_READ
, NULL
, mode
, FILE_ATTRIBUTE_NORMAL
, NULL
);
283 if (hFile
== INVALID_HANDLE_VALUE
) {
284 *ioerror
= convert_win32_error (GetLastError (), COULD_NOT_OPEN
);
287 delete_on_error
= !existed
;
289 // INVALID_HANDLE_VALUE (-1) is valid, to make named shared memory,
290 // backed by physical memory / pagefile.
293 result
= open_handle (hFile
, mapName
, mapName_length
, mode
, capacity
, access
, options
, ioerror
, error
);
297 if (hFile
!= INVALID_HANDLE_VALUE
)
299 if (!result
&& delete_on_error
)
307 mono_mmap_open_handle (void *handle
, const gunichar2
*mapName
, gint mapName_length
, gint64
*capacity
, int access
, int options
, int *ioerror
, MonoError
*error
)
309 g_assert (handle
!= NULL
);
311 return open_handle (handle
, mapName
, mapName_length
, FILE_MODE_OPEN
, capacity
, access
, options
, ioerror
, error
);
315 mono_mmap_close (void *mmap_handle
, MonoError
*error
)
317 g_assert (mmap_handle
);
319 CloseHandle (mmap_handle
);
324 mono_mmap_configure_inheritability (void *mmap_handle
, gint32 inheritability
, MonoError
*error
)
326 g_assert (mmap_handle
);
327 if (!SetHandleInformation (mmap_handle
, HANDLE_FLAG_INHERIT
, inheritability
? HANDLE_FLAG_INHERIT
: 0)) {
328 g_error ("mono_mmap_configure_inheritability: SetHandleInformation failed with error %d!", GetLastError ());
333 mono_mmap_flush (void *mmap_handle
, MonoError
*error
)
335 g_assert (mmap_handle
);
336 MmapInstance
*h
= (MmapInstance
*)mmap_handle
;
338 gboolean flush_success
;
340 flush_success
= FlushViewOfFile (h
->address
, h
->length
);
346 // This replicates how CoreFX does MemoryMappedView.Flush ().
348 // It is a known issue within the NTFS transaction log system that
349 // causes FlushViewOfFile to intermittently fail with ERROR_LOCK_VIOLATION
350 // As a workaround, we catch this particular error and retry the flush operation
351 // a few milliseconds later. If it does not work, we give it a few more tries with
352 // increasing intervals. Eventually, however, we need to give up. In ad-hoc tests
353 // this strategy successfully flushed the view after no more than 3 retries.
355 if (GetLastError () != ERROR_LOCK_VIOLATION
)
356 // TODO: Propagate error to caller
359 for (int w
= 0; w
< MAX_FLUSH_WAITS
; w
++) {
360 int pause
= (1 << w
); // MaxFlushRetries should never be over 30
361 mono_thread_info_sleep (pause
, NULL
);
363 for (int r
= 0; r
< MAX_FLUSH_RETIRES_PER_WAIT
; r
++) {
365 flush_success
= FlushViewOfFile (h
->address
, h
->length
);
370 if (GetLastError () != ERROR_LOCK_VIOLATION
)
371 // TODO: Propagate error to caller
374 mono_thread_info_yield ();
378 // We got to here, so there was no success:
379 // TODO: Propagate error to caller
383 mono_mmap_map (void *handle
, gint64 offset
, gint64
*size
, int access
, void **mmap_handle
, void **base_address
, MonoError
*error
)
385 static DWORD allocationGranularity
= 0;
386 if (allocationGranularity
== 0) {
388 GetSystemInfo (&info
);
389 allocationGranularity
= info
.dwAllocationGranularity
;
392 gint64 extraMemNeeded
= offset
% allocationGranularity
;
393 guint64 newOffset
= offset
- extraMemNeeded
;
394 gint64 nativeSize
= (*size
!= 0) ? *size
+ extraMemNeeded
: 0;
396 #if SIZEOF_VOID_P == 4
397 if (nativeSize
> UINT32_MAX
)
398 return CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
;
403 address
= MapViewOfFile (handle
, get_file_map_access (access
), (DWORD
) (newOffset
>> 32), (DWORD
) newOffset
, (SIZE_T
) nativeSize
);
406 return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY
);
408 // Query the view for its size and allocation type
409 MEMORY_BASIC_INFORMATION viewInfo
;
411 VirtualQuery (address
, &viewInfo
, sizeof (MEMORY_BASIC_INFORMATION
));
413 guint64 viewSize
= (guint64
) viewInfo
.RegionSize
;
415 // Allocate the pages if we were using the MemoryMappedFileOptions.DelayAllocatePages option
416 // OR check if the allocated view size is smaller than the expected native size
417 // If multiple overlapping views are created over the file mapping object, the pages in a given region
418 // could have different attributes(MEM_RESERVE OR MEM_COMMIT) as MapViewOfFile preserves coherence between
419 // views created on a mapping object backed by same file.
420 // In which case, the viewSize will be smaller than nativeSize required and viewState could be MEM_COMMIT
421 // but more pages may need to be committed in the region.
422 // This is because, VirtualQuery function(that internally invokes VirtualQueryEx function) returns the attributes
423 // and size of the region of pages with matching attributes starting from base address.
424 // VirtualQueryEx: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366907(v=vs.85).aspx
425 if (((viewInfo
.State
& MEM_RESERVE
) != 0) || viewSize
< (guint64
) nativeSize
) {
428 tempAddress
= VirtualAlloc (address
, nativeSize
!= 0 ? nativeSize
: viewSize
, MEM_COMMIT
, get_page_access (access
));
431 return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY
);
433 // again query the view for its new size
435 VirtualQuery (address
, &viewInfo
, sizeof (MEMORY_BASIC_INFORMATION
));
437 viewSize
= (guint64
) viewInfo
.RegionSize
;
441 *size
= viewSize
- extraMemNeeded
;
443 MmapInstance
*h
= g_malloc0 (sizeof (MmapInstance
));
444 h
->address
= address
;
445 h
->length
= *size
+ extraMemNeeded
;
447 *base_address
= (char*) address
+ (offset
- newOffset
);
453 mono_mmap_unmap (void *base_address
, MonoError
*error
)
455 g_assert (base_address
);
457 MmapInstance
*h
= (MmapInstance
*) base_address
;
461 result
= UnmapViewOfFile (h
->address
);
467 #elif !HAVE_EXTERN_DEFINED_WIN32_FILE_MAPPING
469 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
)
471 g_unsupported_api ("MapViewOfFile");
472 mono_error_set_not_supported (error
, G_UNSUPPORTED_API
, "MapViewOfFile");
473 SetLastError (ERROR_NOT_SUPPORTED
);
478 mono_mmap_open_handle (void *handle
, const gunichar2
*mapName
, gint mapName_length
, gint64
*capacity
, int access
, int options
, int *ioerror
, MonoError
*error
)
480 g_unsupported_api ("MapViewOfFile");
481 mono_error_set_not_supported (error
, G_UNSUPPORTED_API
, "MapViewOfFile");
482 SetLastError (ERROR_NOT_SUPPORTED
);
487 mono_mmap_close (void *mmap_handle
, MonoError
*error
)
489 g_unsupported_api ("MapViewOfFile");
490 mono_error_set_not_supported (error
, G_UNSUPPORTED_API
, "MapViewOfFile");
491 SetLastError (ERROR_NOT_SUPPORTED
);
496 mono_mmap_configure_inheritability (void *mmap_handle
, gint32 inheritability
, MonoError
*error
)
498 g_unsupported_api ("MapViewOfFile");
499 mono_error_set_not_supported (error
, G_UNSUPPORTED_API
, "MapViewOfFile");
500 SetLastError (ERROR_NOT_SUPPORTED
);
505 mono_mmap_flush (void *mmap_handle
, MonoError
*error
)
507 g_unsupported_api ("FlushViewOfFile");
508 mono_error_set_not_supported (error
, G_UNSUPPORTED_API
, "FlushViewOfFile");
509 SetLastError (ERROR_NOT_SUPPORTED
);
514 mono_mmap_map (void *handle
, gint64 offset
, gint64
*size
, int access
, void **mmap_handle
, void **base_address
, MonoError
*error
)
516 g_unsupported_api ("MapViewOfFile");
517 mono_error_set_not_supported (error
, G_UNSUPPORTED_API
, "MapViewOfFile");
518 SetLastError (ERROR_NOT_SUPPORTED
);
523 mono_mmap_unmap (void *base_address
, MonoError
*error
)
525 g_unsupported_api ("UnmapViewOfFile");
526 mono_error_set_not_supported (error
, G_UNSUPPORTED_API
, "UnmapViewOfFile");
527 SetLastError (ERROR_NOT_SUPPORTED
);
530 #endif /* HAVE_API_SUPPORT_WIN32_FILE_MAPPING */
531 #endif /* HOST_WIN32 */
533 MONO_EMPTY_SOURCE_FILE (file_mmap_windows
)