Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit df34a44

Browse files
committed
Merge pull request #2082 from stephentoub/improve_mmf_reliability
Improve reliability of System.IO.MemoryMappedFiles on Unix
2 parents baed3e7 + 95fad11 commit df34a44

File tree

7 files changed

+84
-93
lines changed

7 files changed

+84
-93
lines changed

src/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.Unix.cs

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,6 @@ namespace Microsoft.Win32.SafeHandles
1313
{
1414
public sealed partial class SafeMemoryMappedFileHandle : SafeHandle
1515
{
16-
/// <summary>Indicates where the FileHandle came from, which then controls if/how it should be cleaned up.</summary>
17-
internal enum FileStreamSource
18-
{
19-
Provided,
20-
ManufacturedFile,
21-
ManufacturedSharedMemory,
22-
}
23-
2416
/// <summary>Counter used to produce a unique handle value.</summary>
2517
private static long s_counter = 0;
2618

@@ -31,15 +23,8 @@ internal enum FileStreamSource
3123
/// </summary>
3224
internal readonly FileStream _fileStream;
3325

34-
/// <summary>Indication as to where the file stream came from, if it exists.</summary>
35-
internal readonly FileStreamSource _fileStreamSource;
36-
37-
/// <summary>
38-
/// The name of the map, currently used to give internal names to anonymous,
39-
/// memory-backed maps. This will be null if the map is file-backed or in
40-
/// some corner cases of memory-backed maps, e.g. read-only.
41-
/// </summary>
42-
internal readonly string _mapName;
26+
/// <summary>Whether this SafeHandle owns the _fileStream and should Dispose it when disposed.</summary>
27+
internal readonly bool _ownsFileStream;
4328

4429
/// <summary>The inheritability of the memory-mapped file.</summary>
4530
internal readonly HandleInheritability _inheritability;
@@ -54,24 +39,23 @@ internal enum FileStreamSource
5439
internal readonly long _capacity;
5540

5641
/// <summary>Initializes the memory-mapped file handle.</summary>
57-
/// <param name="mapName">The name of the map; may be null.</param>
5842
/// <param name="fileStream">The underlying file stream; may be null.</param>
59-
/// <param name="fileStreamSource">The source of the file stream.</param>
43+
/// <param name="ownsFileStream">Whether this SafeHandle is responsible for Disposing the fileStream.</param>
6044
/// <param name="inheritability">The inheritability of the memory-mapped file.</param>
6145
/// <param name="access">The access for the memory-mapped file.</param>
6246
/// <param name="options">The options for the memory-mapped file.</param>
6347
/// <param name="capacity">The capacity of the memory-mapped file.</param>
6448
internal SafeMemoryMappedFileHandle(
65-
string mapName,
66-
FileStream fileStream, FileStreamSource fileStreamSource, HandleInheritability inheritability,
49+
FileStream fileStream, bool ownsFileStream, HandleInheritability inheritability,
6750
MemoryMappedFileAccess access, MemoryMappedFileOptions options,
6851
long capacity)
6952
: base(new IntPtr(-1), ownsHandle: true)
7053
{
54+
Debug.Assert(!ownsFileStream || fileStream != null, "We can only own a FileStream we're actually given.");
55+
7156
// Store the arguments. We'll actually open the map when the view is created.
72-
_mapName = mapName;
7357
_fileStream = fileStream;
74-
_fileStreamSource = fileStreamSource;
58+
_ownsFileStream = ownsFileStream;
7559
_inheritability = inheritability;
7660
_access = access;
7761
_options = options;
@@ -84,26 +68,17 @@ internal SafeMemoryMappedFileHandle(
8468

8569
protected override void Dispose(bool disposing)
8670
{
87-
if (disposing && _fileStream != null && _fileStreamSource != FileStreamSource.Provided)
71+
if (disposing && _ownsFileStream)
8872
{
89-
// Clean up the file if we created it
73+
// Clean up the file descriptor (either for a file on disk or a shared memory object) if we created it
9074
_fileStream.Dispose();
9175
}
9276
base.Dispose(disposing);
9377
}
9478

9579
protected override unsafe bool ReleaseHandle()
9680
{
97-
if (_fileStreamSource == FileStreamSource.ManufacturedSharedMemory)
98-
{
99-
Debug.Assert(_mapName != null);
100-
Debug.Assert(_fileStream != null);
101-
return Interop.libc.shm_unlink(_mapName) == 0;
102-
}
103-
104-
// For _fileHandleSource == File, there's nothing to clean up, as it's either the caller's responsibility
105-
// or it was created as DeleteOnClose (if it was a temporary backing store).
106-
81+
// Nothing to clean up. We unlinked immediately after creating the backing store.
10782
return true;
10883
}
10984

src/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,6 @@
122122
<Compile Include="$(CommonPath)\Interop\Unix\Interop.IOErrors.cs">
123123
<Link>Common\Interop\Unix\Interop.IOErrors.cs</Link>
124124
</Compile>
125-
<Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.ftruncate.cs">
126-
<Link>Common\Interop\Unix\Interop.ftruncate.cs</Link>
127-
</Compile>
128125
<Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.gnu_get_libc_version.cs">
129126
<Link>Common\Interop\Unix\Interop.gnu_get_libc_version.cs</Link>
130127
</Compile>
@@ -156,8 +153,11 @@
156153
</ItemGroup>
157154
<!-- Linux -->
158155
<ItemGroup Condition="'$(TargetsLinux)' == 'true'">
159-
<Compile Include="System\IO\MemoryMappedFiles\MemoryMappedFile.Linux.cs" />
160-
<Compile Include="System\IO\MemoryMappedFiles\MemoryMappedView.Linux.cs" />
156+
<Compile Include="System\IO\MemoryMappedFiles\MemoryMappedFile.Unix.BackingObject_Memory.cs" />
157+
<Compile Include="System\IO\MemoryMappedFiles\MemoryMappedView.Unix.MADV_DONTFORK.cs" />
158+
<Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.ftruncate.cs">
159+
<Link>Common\Interop\Unix\Interop.ftruncate.cs</Link>
160+
</Compile>
161161
<Compile Include="$(CommonPath)\Interop\Linux\Interop.Errors.cs">
162162
<Link>Common\Interop\Linux\Interop.Errors.cs</Link>
163163
</Compile>
@@ -185,7 +185,10 @@
185185
</ItemGroup>
186186
<!-- OSX -->
187187
<ItemGroup Condition="'$(TargetsOSX)' == 'true'">
188-
<Compile Include="System\IO\MemoryMappedFiles\MemoryMappedFile.OSX.cs" />
188+
<Compile Include="System\IO\MemoryMappedFiles\MemoryMappedFile.Unix.BackingObject_File.cs" />
189+
<Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.unlink.cs">
190+
<Link>Common\Interop\Unix\Interop.unlink.cs</Link>
191+
</Compile>
189192
<Compile Include="$(CommonPath)\Interop\OSX\Interop.Errors.cs">
190193
<Link>Common\Interop\OSX\Interop.Errors.cs</Link>
191194
</Compile>
@@ -204,9 +207,6 @@
204207
<Compile Include="$(CommonPath)\Interop\OSX\libc\Interop.SysConfNames.cs">
205208
<Link>Common\Interop\OSX\Interop.SysConfNames.cs</Link>
206209
</Compile>
207-
<Compile Include="$(CommonPath)\Interop\OSX\libsystem_kernel\Interop.shm_open.cs">
208-
<Link>Common\Interop\OSX\Interop.shm_open.cs</Link>
209-
</Compile>
210210
</ItemGroup>
211211
<!-- Resource files -->
212212
<ItemGroup>

src/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.OSX.cs

Lines changed: 0 additions & 30 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Microsoft.Win32.SafeHandles;
5+
6+
namespace System.IO.MemoryMappedFiles
7+
{
8+
public partial class MemoryMappedFile
9+
{
10+
private static FileStream CreateSharedBackingObject(Interop.libc.MemoryMappedProtections protections, long capacity)
11+
{
12+
string path = TmpPathPrefix + Guid.NewGuid().ToString("N") + ".tmp";
13+
14+
FileAccess access =
15+
(protections & (Interop.libc.MemoryMappedProtections.PROT_READ | Interop.libc.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.ReadWrite :
16+
(protections & (Interop.libc.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.Write :
17+
FileAccess.Read;
18+
19+
// Create the backing file, then immediately unlink it so that it'll be cleaned up when no longer in use.
20+
// Then enlarge it to the requested capacity.
21+
const int DefaultBufferSize = 0x1000;
22+
var fs = new FileStream(path, FileMode.CreateNew, TranslateProtectionsToFileAccess(protections), FileShare.ReadWrite, DefaultBufferSize);
23+
Interop.CheckIo(Interop.libc.unlink(path));
24+
fs.SetLength(capacity);
25+
return fs;
26+
}
27+
28+
// -----------------------------
29+
// ---- PAL layer ends here ----
30+
// -----------------------------
31+
32+
private static readonly string TmpPathPrefix = Path.Combine(Path.GetTempPath(), MemoryMapObjectFilePrefix);
33+
}
34+
}

src/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Linux.cs renamed to src/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.BackingObject_Memory.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@ namespace System.IO.MemoryMappedFiles
88
public partial class MemoryMappedFile
99
{
1010
private static FileStream CreateSharedBackingObject(
11-
Interop.libc.MemoryMappedProtections protections, long capacity,
12-
out string mapName, out SafeMemoryMappedFileHandle.FileStreamSource fileStreamSource)
11+
Interop.libc.MemoryMappedProtections protections, long capacity)
1312
{
1413
// The POSIX shared memory object name must begin with '/'. After that we just want something short and unique.
15-
mapName = "/" + MemoryMapObjectFilePrefix + Guid.NewGuid().ToString("N");
16-
fileStreamSource = SafeMemoryMappedFileHandle.FileStreamSource.ManufacturedSharedMemory;
14+
string mapName = "/" + MemoryMapObjectFilePrefix + Guid.NewGuid().ToString("N");
1715

1816
// Determine the flags to use when creating the shared memory object
1917
Interop.libc.OpenFlags flags = (protections & Interop.libc.MemoryMappedProtections.PROT_WRITE) != 0 ?
@@ -30,15 +28,24 @@ private static FileStream CreateSharedBackingObject(
3028
if ((protections & Interop.libc.MemoryMappedProtections.PROT_EXEC) != 0)
3129
perms |= Interop.libc.Permissions.S_IXUSR;
3230

33-
// Create the shared memory object. Then enlarge it to the requested capacity.
31+
// Create the shared memory object.
3432
int fd;
3533
Interop.CheckIo(fd = Interop.libc.shm_open(mapName, flags, (int)perms), mapName);
3634
SafeFileHandle fileHandle = new SafeFileHandle((IntPtr)fd, ownsHandle: true);
3735

38-
// Wrap the handle in a stream and return it.
39-
var fs = new FileStream(fileHandle, TranslateProtectionsToFileAccess(protections));
40-
fs.SetLength(capacity);
41-
return fs;
36+
// Unlink the shared memory object immediatley so that it'll go away once all handles
37+
// to it are closed (as with opened then unlinked files, it'll remain usable via
38+
// the open handles even though it's unlinked and can't be opened anew via its name).
39+
Interop.CheckIo(Interop.libc.shm_unlink(mapName));
40+
41+
// Give it the right capacity. We do this directly with ftruncate rather
42+
// than via FileStream.SetLength after the FileStream is created because, on some systems,
43+
// lseek fails on shared memory objects, causing the FileStream to think it's unseekable,
44+
// causing it to preemptively throw from SetLength.
45+
Interop.CheckIo(Interop.libc.ftruncate(fd, capacity));
46+
47+
// Wrap the file descriptor in a stream and return it.
48+
return new FileStream(fileHandle, TranslateProtectionsToFileAccess(protections));
4249
}
4350
}
4451
}

src/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,19 @@ private static unsafe SafeMemoryMappedFileHandle CreateCore(
2121
{
2222
if (mapName != null)
2323
{
24-
// TODO: We currently do not support named maps. We could possibly support
25-
// named maps in the future by using shm_open / shm_unlink, as we do for
26-
// giving internal names to anonymous maps. Issues to work through will include
27-
// dealing with permissions, passing information from the creator of the
28-
// map to another opener of it, etc.
24+
// Named maps are not supported in our Unix implementation. We could support named maps on Linux using
25+
// shared memory segments (shmget/shmat/shmdt/shmctl/etc.), but that doesn't work on OSX by default due
26+
// to very low default limits on OSX for the size of such objects; it also doesn't support behaviors
27+
// like copy-on-write or the ability to control handle inheritability, and reliably cleaning them up
28+
// relies on some non-conforming behaviors around shared memory IDs remaining valid even after they've
29+
// been marked for deletion (IPC_RMID). We could also support named maps using the current implementation
30+
// by not unlinking after creating the backing store, but then the backing stores would remain around
31+
// and accessible even after process exit, with no good way to appropriately clean them up.
32+
// (File-backed maps may still be used for cross-process communication.)
2933
throw CreateNamedMapsNotSupportedException();
3034
}
3135

32-
var fileStreamSource = SafeMemoryMappedFileHandle.FileStreamSource.Provided;
36+
bool ownsFileStream = false;
3337
if (fileStream != null)
3438
{
3539
// This map is backed by a file. Make sure the file's size is increased to be
@@ -50,15 +54,16 @@ private static unsafe SafeMemoryMappedFileHandle CreateCore(
5054
// of an object that has read-only permissions, but we also don't need to worry about sharing
5155
// views over a read-only, anonymous, memory-backed map, because the data will never change, so all views
5256
// will always see zero and can't change that. In that case, we just use the built-in anonymous support of
53-
// the map by leaving fileHandle as null.
57+
// the map by leaving fileStream as null.
5458
Interop.libc.MemoryMappedProtections protections = MemoryMappedView.GetProtections(access, forVerification: false);
5559
if ((protections & Interop.libc.MemoryMappedProtections.PROT_WRITE) != 0 && capacity > 0)
5660
{
57-
fileStream = CreateSharedBackingObject(protections, capacity, out mapName, out fileStreamSource);
61+
ownsFileStream = true;
62+
fileStream = CreateSharedBackingObject(protections, capacity);
5863
}
5964
}
6065

61-
return new SafeMemoryMappedFileHandle(mapName, fileStream, fileStreamSource, inheritability, access, options, capacity);
66+
return new SafeMemoryMappedFileHandle(fileStream, ownsFileStream, inheritability, access, options, capacity);
6267
}
6368

6469
/// <summary>
@@ -109,7 +114,7 @@ private static Exception CreateNamedMapsNotSupportedException()
109114
return new PlatformNotSupportedException(SR.PlatformNotSupported_NamedMaps);
110115
}
111116

112-
private const string MemoryMapObjectFilePrefix = "CoreFX_MMF_";
117+
private const string MemoryMapObjectFilePrefix = "corefx_mmf_";
113118

114119
private static FileAccess TranslateProtectionsToFileAccess(Interop.libc.MemoryMappedProtections protections)
115120
{

0 commit comments

Comments
 (0)