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

Fix C# native library loading in .NET 5 single-file apps #24744

Merged
merged 4 commits into from
Nov 30, 2020

Conversation

jtattermusch
Copy link
Contributor

Fixes #24266.

@jtattermusch
Copy link
Contributor Author

Once this is approved, I'll also create a backport PR.

@jtattermusch
Copy link
Contributor Author

Adhoc distribtests: https://g3c.corp.google.com/results/invocations/a25cde5a-bf1c-49cc-923e-315e96987e17 (including the newly added single file publish distribtest).

{
var assembly = typeof(NativeExtension).GetTypeInfo().Assembly;
#if NETSTANDARD
// Assembly.EscapedCodeBase does not exist under CoreCLR, but assemblies imported from a nuget package
// don't seem to be shadowed by DNX-based projects at all.
return assembly.Location;
var assemblyLocation = assembly.Location;
if (assemblyLocation != null)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, assembly.Location might be returning an empty string according to the docs:
https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file#api-incompatibility

I'll check which one is correct and update the code if needed.

@jtattermusch
Copy link
Contributor Author

@jtattermusch
Copy link
Contributor Author

@jkotas
Copy link

jkotas commented Nov 13, 2020

https://github.com/grpc/grpc/blob/master/src/csharp/Grpc.Core/Internal/UnmanagedLibrary.cs#L245 is another problem that needs to be fixed to make the single file scenario working. There is no libcoreclr.so on disk in the single file scenario.

@jtattermusch
Copy link
Contributor Author

https://github.com/grpc/grpc/blob/master/src/csharp/Grpc.Core/Internal/UnmanagedLibrary.cs#L245 is another problem that needs to be fixed to make the single file scenario working. There is no libcoreclr.so on disk in the single file scenario.

Ok, thanks for the tip. Do you have ideas on how to fix this problem?

@gitfool
Copy link
Contributor

gitfool commented Nov 13, 2020

@jtattermusch please add a distribtest for the IncludeNativeLibrariesForSelfExtract case. Hopefully @jkotas can advise on how to detect and/or handle that too?

@jkotas
Copy link

jkotas commented Nov 13, 2020

Do you have ideas on how to fix this problem?

The best way to fix this problem is to use Native.Load for .NET Core. This API deals with all probing paths, single file and platform differences, including IncludeNativeLibrariesForSelfExtract.

@jtattermusch
Copy link
Contributor Author

Native.Load

Thanks for the tip! Unfortunately Native.Load is only available on .NET Core 3+ (and at this point the newest we target is netstandard2.0)

@jtattermusch
Copy link
Contributor Author

@jtattermusch
Copy link
Contributor Author

The interop test failure is #24764.

@jtattermusch
Copy link
Contributor Author

jtattermusch commented Nov 16, 2020

As @jkotas has pointed out, I'm now getting the Unhandled exception. System.DllNotFoundException: Unable to load shared library 'libcoreclr.so' error.

https://source.cloud.google.com/results/invocations/9ccd7ae8-32c0-4ee9-b08a-e7848b2f84a4/targets/github%2Fgrpc/tests

[DllImport("libcoreclr.so")]

@jtattermusch
Copy link
Contributor Author

@yulin-liang I don't have a fully working fix at this point, so I think it's fine to cut the release branch without it. (If I come up with a fix, I can backport it)

@jkotas
Copy link

jkotas commented Nov 24, 2020

Unfortunately Native.Load is only available on .NET Core 3+ (and at this point the newest we target is netstandard2.0)

The fix for this should be to try to lookup these methods using reflection, and then use them instead of the hardcoded P/Invokes when these methods are available. Something like:

delegate bool TryLoadDelegate(string libraryName, System.Reflection.Assembly assembly, System.Runtime.InteropServices.DllImportSearchPath? searchPath, out IntPtr handle);
delegate bool TryGetExportDelegate(IntPtr handle, string name, out IntPtr address);

static TryLoadDelegate s_tryLoad;
static TryGetExportDelegate s_tryGetExport; 

...

Type nativeLibrary = Type.GetType("System.Runtime.InteropServices.NativeLibrary, System.Runtime.InteropServices, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: false);
if (nativeLibrary == null)
    return;

MethodInfo tryLoad = nativeLibrary.GetMethod("TryLoad", BindingFlags.Static | BindingFlags.Public, null,
    new Type[] { typeof(string), typeof(Assembly), typeof(Nullable<DllImportSearchPath>), typeof(IntPtr).MakeByRefType() }, null);
s_tryLoad = (TryLoadDelegate)tryLoad?.CreateDelegate(typeof(TryLoadDelegate));

MethodInfo tryGetExport = nativeLibrary.GetMethod("TryLoad", BindingFlags.Static | BindingFlags.Public, null,
    new Type[] { typeof(IntPtr), typeof(string), typeof(IntPtr).MakeByRefType() }, null);
s_tryGetExport = (TryGetExportDelegate)tryGetExport?.CreateDelegate(typeof(TryGetExportDelegate));

@jtattermusch
Copy link
Contributor Author

Alternative approach used by a library that faced a similar problem: gui-cs/Terminal.Gui@19955fa#diff-91d94d0e5b8dd01a182c51299d7e8093247fa636a9768574b7042bfd5df9a1deR261

@jtattermusch
Copy link
Contributor Author

I refactored the native library loading logic to use the default DllImport loading mechanism when on .NET Core.
It leads to some code simplification and also seems to handle the case with of .NET 5 single file published app well.
PTAL.

The adhoc distribtest run is here: https://g3c.corp.google.com/results/invocations/4da3122d-8972-48ff-ac92-d01b22705b99/log

@jtattermusch
Copy link
Contributor Author

Known failures: build_docker_go is also failing on master.

@jtattermusch
Copy link
Contributor Author

@jtattermusch
Copy link
Contributor Author

@jkotas your opinions on the approach I ended up taking (= rely on DllImport default loading logic) are welcome.

@apolcyn PTAL.

{
// TODO: allow customizing path to native extension (possibly through exposing a GrpcEnvironment property).
// See https://github.com/grpc/grpc/pull/7303 for one option.
var assemblyDirectory = Path.GetDirectoryName(GetAssemblyPath());
var assemblyDirectory = GetAssemblyDirectory();
Copy link

Choose a reason for hiding this comment

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

The changes in this method should not be needed with your new fix.

// The classes with the list of DllImport'd methods are code generated,
// so having more than just one doesn't really bother us.

// on Windows, the DllImport("grpc_csharp_ext.x64") doesn't work for some reason,
Copy link

Choose a reason for hiding this comment

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

This was bug dotnet/coreclr#17505 . It is fixed in .NET Core 3.1, but I assume you still want this to work on .NET Core 2.1.

@jkotas
Copy link

jkotas commented Nov 30, 2020

@jkotas your opinions on the approach I ended up taking

Looks fine to me.

@gitfool
Copy link
Contributor

gitfool commented Nov 30, 2020

@jtattermusch does your fix handle the IncludeNativeLibrariesForSelfExtract case too?

@jtattermusch
Copy link
Contributor Author

@jtattermusch does your fix handle the IncludeNativeLibrariesForSelfExtract case too?

I haven't tested that explicitly - would you mind trying it out and posting back the result?

The nightly build of the nugets should appear here soon:
https://packages.grpc.io/

Alternatively, here are nugets from my last manual build:
https://storage.cloud.google.com/grpc-testing-kokoro-prod/test_result_public/prod/grpc/core/master/linux/grpc_build_packages/14907/20201130-052042/github/grpc/artifacts/csharp_nugets_windows_dotnetcli.zip

I wanted to merge this PR ASAP because like this the fixes will still make it to Grpc.Core 2.34.0 release - we can address IncludeNativeLibrariesForSelfExtract as a follow up.

@gitfool
Copy link
Contributor

gitfool commented Dec 7, 2020

@jtattermusch I did a test with Grpc.Core version 2.34.0 and it works for IncludeNativeLibrariesForSelfExtract too! 😀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lang/C# priority/P1 release notes: yes Indicates if PR needs to be in release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

net5.0 'publish single file' makes LoadUnmanagedLibrary fail with ArgumentNullException
4 participants