Speed up DirLoader.DirLoader on Windows.

New FileHelper.TryOpenFileRead that doesn't throw if the file doesn't exist.

Also used it in a couple other spots.
This commit is contained in:
Pieter-Jan Briers
2023-07-15 19:08:37 +02:00
parent 177ca6b627
commit d967bc9fdc
4 changed files with 77 additions and 14 deletions

View File

@@ -105,11 +105,11 @@ internal sealed partial class StatusHost
private Task<bool> SourceAczDictionaryViaFile(AssetPass pass, IPackageLogger logger)
{
var path = PathHelpers.ExecutableRelativeFile("Content.Client.zip");
if (!File.Exists(path))
if (!FileHelper.TryOpenFileRead(path, out var fileStream))
return Task.FromResult(false);
_aczSawmill.Info($"StatusHost found client zip: {path}");
using var zip = new ZipArchive(File.OpenRead(path), ZipArchiveMode.Read, leaveOpen: false);
using var zip = new ZipArchive(fileStream, ZipArchiveMode.Read, leaveOpen: false);
SourceAczDictionaryViaZipStream(zip, pass, logger);
return Task.FromResult(true);
}

View File

@@ -895,12 +895,10 @@ namespace Robust.Shared.ContentPack
{
var path = Path.Combine(diskLoadPath, dllName);
if (!File.Exists(path))
{
if (!FileHelper.TryOpenFileRead(path, out var fileStream))
continue;
}
return ModLoader.MakePEReader(File.OpenRead(path));
return ModLoader.MakePEReader(fileStream);
}
foreach (var resLoadPath in _resLoadPaths)

View File

@@ -46,16 +46,11 @@ namespace Robust.Shared.ContentPack
public bool TryGetFile(ResPath relPath, [NotNullWhen(true)] out Stream? stream)
{
var path = GetPath(relPath);
if (!File.Exists(path))
{
stream = null;
return false;
}
CheckPathCasing(relPath);
stream = File.OpenRead(path);
return true;
var ret = FileHelper.TryOpenFileRead(path, out var fStream);
stream = fStream;
return ret;
}
public bool FileExists(ResPath relPath)

View File

@@ -0,0 +1,70 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using TerraFX.Interop.Windows;
namespace Robust.Shared.Utility;
internal static class FileHelper
{
/// <summary>
/// Try to open a file for reading. If the file does not exist, the operation fails without exception.
/// </summary>
/// <remarks>
/// This API is not atomic and can thus be vulnerable to TOCTOU attacks. Don't use it if that's relevant.
/// </remarks>
/// <param name="path">The path to try to open.</param>
/// <param name="stream">The resulting file stream.</param>
/// <returns>True if the file existed and was opened.</returns>
public static bool TryOpenFileRead(string path, [NotNullWhen(true)] out FileStream? stream)
{
// On Windows, the separate File.Exists() call alone adds a ton of weight.
// The alternative however (opening the file and catching the error) is extremely slow because of .NET exceptions.
// So we manually call the windows API and make the file handle from that. Problem solved!
if (OperatingSystem.IsWindows())
return TryGetFileWindows(path, out stream);
if (!File.Exists(path))
{
stream = null;
return false;
}
stream = File.OpenRead(path);
return true;
}
private static unsafe bool TryGetFileWindows(string path, [NotNullWhen(true)] out FileStream? stream)
{
HANDLE file;
fixed (char* pPath = path)
{
file = Windows.CreateFileW(
(ushort*)pPath,
Windows.GENERIC_READ,
FILE.FILE_SHARE_READ,
null,
OPEN.OPEN_EXISTING,
FILE.FILE_ATTRIBUTE_NORMAL,
HANDLE.NULL);
}
if (file == HANDLE.INVALID_VALUE)
{
var lastError = Marshal.GetLastWin32Error();
if (lastError is ERROR.ERROR_FILE_NOT_FOUND or ERROR.ERROR_PATH_NOT_FOUND)
{
stream = null;
return false;
}
Marshal.ThrowExceptionForHR(Windows.HRESULT_FROM_WIN32(lastError));
}
var sf = new SafeFileHandle(file, ownsHandle: true);
stream = new FileStream(sf, FileAccess.Read);
return true;
}
}