Skip to content

Commit

Permalink
Speed up DirLoader.DirLoader on Windows.
Browse files Browse the repository at this point in the history
New FileHelper.TryOpenFileRead that doesn't throw if the file doesn't exist.

Also used it in a couple other spots.
  • Loading branch information
PJB3005 committed Jul 15, 2023
1 parent 177ca6b commit d967bc9
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 14 deletions.
4 changes: 2 additions & 2 deletions Robust.Server/ServerStatus/StatusHost.Acz.Sources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
6 changes: 2 additions & 4 deletions Robust.Shared/ContentPack/AssemblyTypeChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -895,12 +895,10 @@ public Resolver(AssemblyTypeChecker parent, string[] diskLoadPaths, ResPath[] re
{
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)
Expand Down
11 changes: 3 additions & 8 deletions Robust.Shared/ContentPack/DirLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,11 @@ public void Mount()
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)
Expand Down
70 changes: 70 additions & 0 deletions Robust.Shared/Utility/FileHelper.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit d967bc9

Please sign in to comment.