Skip to content

Use MemoryMappedFileAccess.Read when searching a memory mapped file #67386

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 16, 2022

Conversation

0xced
Copy link
Contributor

@0xced 0xced commented Mar 31, 2022

Since we're only interested in searching for a pattern inside a memory mapped file, explicitly specify MemoryMappedFileAccess.Read (instead of the default MemoryMappedFileAccess.ReadWrite) in order to avoid IO exceptions on Windows.

Since we're only interested in searching for a pattern inside a memory mapped file, explicitly specify `MemoryMappedFileAccess.Read` (instead of the default `MemoryMappedFileAccess.ReadWrite`) in order to avoid IO exceptions on Windows.
@ghost ghost added area-HostModel Microsoft.NET.HostModel issues community-contribution Indicates that the PR has been added by a community member labels Mar 31, 2022
@ghost
Copy link

ghost commented Mar 31, 2022

Tagging subscribers to this area: @vitek-karas, @agocke
See info in area-owners.md if you want to be subscribed.

Issue Details

Since we're only interested in searching for a pattern inside a memory mapped file, explicitly specify MemoryMappedFileAccess.Read (instead of the default MemoryMappedFileAccess.ReadWrite) in order to avoid IO exceptions on Windows.

Author: 0xced
Assignees: -
Labels:

area-HostModel, community-contribution

Milestone: -

@vitek-karas vitek-karas requested a review from VSadov April 4, 2022 09:03
@vitek-karas
Copy link
Member

@0xced Do you have an example of the IO failure you refer to?

@VSadov Could you please review the change?

@0xced
Copy link
Contributor Author

0xced commented Apr 4, 2022

Do you have an example of the IO failure you refer to?

Yes, here are reproduction steps.

SingleFileIsBundle.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <DebugType>embedded</DebugType>
    <PublishSingleFile>true</PublishSingleFile>
    <SelfContained>false</SelfContained>
    <UseCurrentRuntimeIdentifier>true</UseCurrentRuntimeIdentifier>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.HostModel" Version="3.1.16" />
  </ItemGroup>

</Project>

Program.cs

using System;
using Microsoft.NET.HostModel.AppHost;

try
{
    Console.WriteLine($"HostWriter.IsBundle({Environment.ProcessPath})");
    var isBundle = HostWriter.IsBundle(Environment.ProcessPath, out var bundleHeaderOffset);
    Console.WriteLine($"isBundle: {isBundle}");
    Console.WriteLine($"bundleHeaderOffset: {bundleHeaderOffset}");
}
catch (Exception exception)
{
    Console.Error.WriteLine(exception);
}

On Windows, run the following commands:

dotnet publish -c Release
.\bin\Release\net6.0\win-x64\publish\SingleFileIsBundle.exe

After 50+ seconds (500 × 100ms retries) the following error occurs:

System.IO.IOException: The process cannot access the file '[…]\SingleFileIsBundle\bin\Release\net6.0\win-x64\publish\SingleFileIsBundle.exe' because it is being used by another process.
   at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
   at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
   at System.IO.MemoryMappedFiles.MemoryMappedFile.CreateFromFile(String path, FileMode mode, String mapName, Int64 capacity, MemoryMappedFileAccess access)
   at Microsoft.NET.HostModel.AppHost.HostWriter.<>c__DisplayClass4_0.<IsBundle>g__FindBundleHeader|0()
   at Microsoft.NET.HostModel.RetryUtil.RetryOnIOError(Action func)
   at Microsoft.NET.HostModel.AppHost.HostWriter.IsBundle(String appHostFilePath, Int64& bundleHeaderOffset)
   at Program.<Main>$(String[] args) in […]\SingleFileIsBundle\Program.cs:line 7

On macOS, running the following commands works fine.

dotnet publish -c Release
./bin/Release/net6.0/osx-x64/publish/SingleFileIsBundle

This produces the following result:

HostWriter.IsBundle([…]/SingleFileIsBundle/bin/Release/net6.0/osx-x64/publish/SingleFileIsBundle)
isBundle: True
bundleHeaderOffset: 165456

@vitek-karas
Copy link
Member

Thanks.

Side note: I'm curious about the scenario where you're trying to determine if the current app is single-file or not? What do you need the information for?
Also note that the Microsoft.NET.HostModel package is not intended as a public API, it's only intended use is from the SDK.

@0xced
Copy link
Contributor Author

0xced commented Apr 4, 2022

Side note: I'm curious about the scenario where you're trying to determine if the current app is single-file or not? What do you need the information for?

I want to get the default DependencyContext of a single-file application. This is currently not supported but can be achieved once the location (offset + size) of the bundled deps.json file within the app host is known. I'm currently working on it in the context of serilog/serilog-settings-configuration#304.

Also note that the Microsoft.NET.HostModel package is not intended as a public API, it's only intended use is from the SDK.

That's good to know! I think this should be mentioned in the NuGet package description, currently it only says Abstractions for modifying .net core host-binaries. This could easily lead people to think it's fine for public API consumption.

0xced added a commit to 0xced/serilog-settings-configuration that referenced this pull request Apr 4, 2022
As [stated by Vitek Karas][1]:
> Also note that the `Microsoft.NET.HostModel` package is not intended as a public API, it's only intended use is from the SDK.

[1]: dotnet/runtime#67386 (comment)
0xced added a commit to 0xced/serilog-settings-configuration that referenced this pull request Apr 4, 2022
As [stated by Vitek Karas][1]:
> Also note that the `Microsoft.NET.HostModel` package is not intended as a public API, it's only intended use is from the SDK.

[1]: dotnet/runtime#67386 (comment)
0xced added a commit to 0xced/serilog-settings-configuration that referenced this pull request Apr 4, 2022
As [stated by Vitek Karas][1]:
> Also note that the `Microsoft.NET.HostModel` package is not intended as a public API, it's only intended use is from the SDK.

[1]: dotnet/runtime#67386 (comment)
@VSadov
Copy link
Member

VSadov commented Apr 4, 2022

If we want to avoid file sharing issues, I am not sure that this change is sufficient.
The default file sharing mode for MemoryMappedFile.CreateFromFile is FileShare.None, so in other places a different pattern is used -

fileStream = new FileStream(assemblyFile, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1, useAsync: false);

@agocke
Copy link
Member

agocke commented Jun 6, 2022

@VSadov How do we know which APIs will be used for the bundle?

@danmoseley
Copy link
Member

Looking at old PR's ... is this change a reasonable cleanup, even if not necessarily a complete fix for anything?

@VSadov
Copy link
Member

VSadov commented Sep 14, 2022

@danmoseley - the idea of not locking the file for other readers, since we only read, makes sense.
The problem is however not in the access mode, that the PR tries to fix, but in the sharing mode. Confusingly, these are different things.

Even if we creating a readonly mapping, the underlying API maps with FileShare.None and noone else will be able to read. A different API needs to be used to achieve the desired result.

@danmoseley
Copy link
Member

@VSadov which underlying API? MMF opens with FileShare.Read:

SafeFileHandle fileHandle = File.OpenHandle(path, mode, GetFileAccess(access), FileShare.Read, FileOptions.None);

This succeeds:

using (var memoryMappedFile = MemoryMappedFile.CreateFromFile(@"c:\proj\testmmf", FileMode.Open, null, 0, MemoryMappedFileAccess.Read))
using (var memoryMappedFile2 = MemoryMappedFile.CreateFromFile(@"c:\proj\testmmf", FileMode.Open, null, 0, MemoryMappedFileAccess.Read))
{
    Console.WriteLine("success");
    Console.Read();
}

@VSadov
Copy link
Member

VSadov commented Sep 16, 2022

@danmoseley - looks like this was fixed quite a while ago in e0205a6

@VSadov
Copy link
Member

VSadov commented Sep 16, 2022

we may want to undo use of patterns like

fileStream = new FileStream(assemblyFile, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1, useAsync: false);

Copy link
Member

@VSadov VSadov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, Thanks!!

@VSadov VSadov merged commit 9e128de into dotnet:main Sep 16, 2022
@0xced 0xced deleted the AppHost-SearchRead branch September 16, 2022 05:52
@ghost ghost locked as resolved and limited conversation to collaborators Oct 16, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-HostModel Microsoft.NET.HostModel issues community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants