Skip to content
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

Unload MsQuic after checking for QUIC support to free resources. #74749

Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Microsoft.Quic;
Expand Down Expand Up @@ -47,7 +48,8 @@ private MsQuicApi(QUIC_API_TABLE* apiTable)
}
}

internal static MsQuicApi Api { get; } = null!;
internal static Lazy<MsQuicApi> _api = new Lazy<MsQuicApi>(AllocateMsQuicApi);
Copy link
Member

Choose a reason for hiding this comment

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

private readonly?

internal static MsQuicApi Api => _api.Value;

internal static bool IsQuicSupported { get; }

Expand All @@ -58,29 +60,21 @@ private MsQuicApi(QUIC_API_TABLE* apiTable)

static MsQuicApi()
{
IntPtr msQuicHandle;
if (!NativeLibrary.TryLoad($"{Interop.Libraries.MsQuic}.{MsQuicVersion.Major}", typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle) &&
!NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle))
if (!TryLoadMsQuic(out IntPtr msQuicHandle))
{
return;
}

try
{
if (!NativeLibrary.TryGetExport(msQuicHandle, "MsQuicOpenVersion", out IntPtr msQuicOpenVersionAddress))
{
return;
}

QUIC_API_TABLE* apiTable = null;
delegate* unmanaged[Cdecl]<uint, QUIC_API_TABLE**, int> msQuicOpenVersion = (delegate* unmanaged[Cdecl]<uint, QUIC_API_TABLE**, int>)msQuicOpenVersionAddress;
if (StatusFailed(msQuicOpenVersion((uint)MsQuicVersion.Major, &apiTable)))
if (!TryOpenMsQuic(msQuicHandle, out QUIC_API_TABLE* apiTable))
{
return;
}

try
{
// check version
Copy link
Member

Choose a reason for hiding this comment

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

Uber nitty nit 😄

Suggested change
// check version
// Check version

int arraySize = 4;
uint* libVersion = stackalloc uint[arraySize];
uint size = (uint)arraySize * sizeof(uint);
Expand All @@ -99,7 +93,7 @@ static MsQuicApi()
return;
}

// Assume SChannel is being used on windows and query for the actual provider from the library
// Assume SChannel is being used on windows and query for the actual provider from the library if querying is supported
QUIC_TLS_PROVIDER provider = OperatingSystem.IsWindows() ? QUIC_TLS_PROVIDER.SCHANNEL : QUIC_TLS_PROVIDER.OPENSSL;
size = sizeof(QUIC_TLS_PROVIDER);
apiTable->GetParam(null, QUIC_PARAM_GLOBAL_TLS_PROVIDER, &size, &provider);
Expand All @@ -122,18 +116,14 @@ static MsQuicApi()
Tls13ClientMayBeDisabled = IsTls13Disabled(isServer: false);
}

Api = new MsQuicApi(apiTable);
IsQuicSupported = true;
}
finally
{
if (!IsQuicSupported && NativeLibrary.TryGetExport(msQuicHandle, "MsQuicClose", out IntPtr msQuicClose))
{
// Gracefully close the API table
((delegate* unmanaged[Cdecl]<QUIC_API_TABLE*, void>)msQuicClose)(apiTable);
}
// Gracefully close the API table to free resources. The API table will be allocated lazily again if needed
bool closed = TryCloseMsQuic(msQuicHandle, apiTable);
Debug.Assert(closed, "Failed to close MsQuic");
}

}
finally
{
Expand All @@ -144,6 +134,53 @@ static MsQuicApi()
}
}

internal static MsQuicApi AllocateMsQuicApi()
{
Debug.Assert(IsQuicSupported);

if (TryLoadMsQuic(out IntPtr msQuicHandle) &&
TryOpenMsQuic(msQuicHandle, out QUIC_API_TABLE* apiTable))
{
return new MsQuicApi(apiTable);
}

throw new Exception("Failed to create MsQuicApi instance");
}

internal static bool TryLoadMsQuic(out IntPtr msQuicHandle) =>
Copy link
Member

Choose a reason for hiding this comment

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

Should those be private?

NativeLibrary.TryLoad($"{Interop.Libraries.MsQuic}.{MsQuicVersion.Major}", typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle) ||
NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle);

internal static bool TryOpenMsQuic(IntPtr msQuicHandle, out QUIC_API_TABLE* apiTable)
Copy link
Member Author

Choose a reason for hiding this comment

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

We could alternatively use [LibraryImport] for MsQuicOpenVersion and MsQuicClose, it might clean up the code a bit at the cost of not being able to unload the lib from the process I think (if we care about that)

Copy link
Member

Choose a reason for hiding this comment

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

They already have imports in:

internal static extern int MsQuicOpenVersion([NativeTypeName("uint32_t")] uint Version, [NativeTypeName("const void **")] void** QuicApi);

I'm not an expert on this, but we control which version of libmsquic we're loading in TryLoadMsQuic and we do an attempt to load it that would not fail if the lib is not present. I'm not sure we can achieve the same with the import. Relevant history: #49973

Anyway, we might at least use nameof(MsQuicOpenVersion) from the generated interop. Though we lived with the string until now, so it's certainly not critical.

{
apiTable = null;
if (!NativeLibrary.TryGetExport(msQuicHandle, "MsQuicOpenVersion", out IntPtr msQuicOpenVersionAddress))
{
return false;
}

QUIC_API_TABLE* table = null;
delegate* unmanaged[Cdecl]<uint, QUIC_API_TABLE**, int> msQuicOpenVersion = (delegate* unmanaged[Cdecl]<uint, QUIC_API_TABLE**, int>)msQuicOpenVersionAddress;
if (StatusFailed(msQuicOpenVersion((uint)MsQuicVersion.Major, &table)))
{
return false;
}

apiTable = table;
return true;
}

internal static bool TryCloseMsQuic(IntPtr msQuicHandle, QUIC_API_TABLE* apiTable)
{
if (NativeLibrary.TryGetExport(msQuicHandle, "MsQuicClose", out IntPtr msQuicClose))
{
((delegate* unmanaged[Cdecl]<QUIC_API_TABLE*, void>)msQuicClose)(apiTable);
return true;
}

return false;
}

private static bool IsWindowsVersionSupported() => OperatingSystem.IsWindowsVersionAtLeast(MinWindowsVersion.Major,
MinWindowsVersion.Minor, MinWindowsVersion.Build, MinWindowsVersion.Revision);

Expand Down