From 738a30b4fbaa581a0ecb934d900fc54da6ae7305 Mon Sep 17 00:00:00 2001 From: Bob Donovan Date: Wed, 21 Oct 2026 14:52:26 -0400 Subject: [PATCH 1/2] Adding CoreCLR Added artifacts/CoreCLR/** to the list of files the Linux build job uploads (in .yamato/standalone-build-test.yml). --- .yamato/standalone-build-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.yamato/standalone-build-test.yml b/.yamato/standalone-build-test.yml index 633fdd87a77..785fe8adcc2 100644 --- a/.yamato/standalone-build-test.yml +++ b/.yamato/standalone-build-test.yml @@ -40,4 +40,5 @@ test_linux_standalone_{{ editor.version }}_{{ editor.extra_test }}: paths: - "artifacts/testPlayer*/**" - "artifacts/**/UnityPlayer.so" + - "artifacts/CoreCLR/**" {% endfor %} From 702acee493416eae34f71a7fb9c46db4f993ceff Mon Sep 17 00:00:00 2001 From: Bob Donovan Date: Fri, 23 Oct 2026 15:49:33 -0400 Subject: [PATCH 2/2] Fix gRPC failing to load libdl.so on Linux CoreCLR Grpc.Core 1.10 P/Invokes dlopen from the unversioned libdl.so on runtimes other than Mono/.NET Core, which only exists when glibc dev packages are installed. Redirect it to libdl.so.2 (or libc.so.6) via the load context's ResolvingUnmanagedDll event. No-op on Mono and IL2CPP. --- com.unity.ml-agents/CHANGELOG.md | 1 + .../Runtime/Communicator/GrpcNativeLoader.cs | 99 +++++++++++++++++++ .../Communicator/GrpcNativeLoader.cs.meta | 11 +++ .../Runtime/Communicator/RpcCommunicator.cs | 1 + 4 files changed, 112 insertions(+) create mode 100644 com.unity.ml-agents/Runtime/Communicator/GrpcNativeLoader.cs create mode 100644 com.unity.ml-agents/Runtime/Communicator/GrpcNativeLoader.cs.meta diff --git a/com.unity.ml-agents/CHANGELOG.md b/com.unity.ml-agents/CHANGELOG.md index d44e33a3229..cf5b8054a36 100755 --- a/com.unity.ml-agents/CHANGELOG.md +++ b/com.unity.ml-agents/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to - Make `BehaviorParametersEditor` create new sensors only when needed. (#6318, #6323) - Fixed a `NullReferenceException` thrown from the `TensorProxy` finalizer, which is reported as an unhandled exception on CoreCLR. (#6335) - Fix sensor management of CameraSensorComponent and RenderTextureSensorComponent (#6320, #6324) +- Fixed Linux players and editors on CoreCLR failing to connect to the trainer because gRPC could not load `libdl.so`. ## [4.1.0] - 2026-07-27 ### Major Changes diff --git a/com.unity.ml-agents/Runtime/Communicator/GrpcNativeLoader.cs b/com.unity.ml-agents/Runtime/Communicator/GrpcNativeLoader.cs new file mode 100644 index 00000000000..fc77c8793c2 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Communicator/GrpcNativeLoader.cs @@ -0,0 +1,99 @@ +#if UNITY_EDITOR || UNITY_STANDALONE +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using UnityEngine; + +namespace Unity.MLAgents +{ + /// + /// Works around Grpc.Core 1.10 failing to load its native extension on Linux under CoreCLR. + /// + /// Grpc.Core only special-cases Mono and ".NET Core"; on any other runtime (e.g. Unity's CoreCLR) + /// it P/Invokes dlopen/dlsym from "libdl.so". That unversioned name only exists when glibc dev + /// packages are installed, so loading fails on most machines (only "libdl.so.2" is present). + /// We hook the load context's ResolvingUnmanagedDll event, which is only raised after the default + /// lookup fails, and redirect "libdl.so" to "libdl.so.2" (or libc, which exports dlopen on glibc 2.34+). + /// + /// Everything is done via reflection so this compiles against all API profiles and is a no-op on + /// Mono and IL2CPP, where these types don't exist. + /// + internal static class GrpcNativeLoader + { + static readonly string[] k_LibdlCandidates = { "libdl.so.2", "libc.so.6" }; + + static bool s_Installed; + static MethodInfo s_TryLoad; + + internal static void EnsureInstalled() + { + if (s_Installed) + { + return; + } + s_Installed = true; + + if (Application.platform != RuntimePlatform.LinuxPlayer && + Application.platform != RuntimePlatform.LinuxEditor && + Application.platform != RuntimePlatform.LinuxServer) + { + return; + } + + try + { + var coreLib = typeof(Marshal).Assembly; + var nativeLibrary = coreLib.GetType("System.Runtime.InteropServices.NativeLibrary"); + var loadContextType = coreLib.GetType("System.Runtime.Loader.AssemblyLoadContext"); + if (nativeLibrary == null || loadContextType == null) + { + // Not running on CoreCLR (Mono / IL2CPP): Grpc.Core handles these itself. + return; + } + + s_TryLoad = nativeLibrary.GetMethod( + "TryLoad", new[] { typeof(string), typeof(IntPtr).MakeByRefType() }); + var getLoadContext = loadContextType.GetMethod( + "GetLoadContext", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(Assembly) }, null); + var resolvingEvent = loadContextType.GetEvent("ResolvingUnmanagedDll"); + if (s_TryLoad == null || getLoadContext == null || resolvingEvent == null) + { + return; + } + + var grpcAssembly = typeof(Grpc.Core.Channel).Assembly; + var loadContext = getLoadContext.Invoke(null, new object[] { grpcAssembly }); + if (loadContext == null) + { + return; + } + + Func handler = Resolve; + resolvingEvent.AddEventHandler(loadContext, handler); + } + catch (Exception e) + { + Debug.LogWarning($"ML-Agents: unable to install gRPC native library fallback: {e}"); + } + } + + static IntPtr Resolve(Assembly assembly, string libraryName) + { + if (libraryName != "libdl.so" || s_TryLoad == null) + { + return IntPtr.Zero; + } + + foreach (var candidate in k_LibdlCandidates) + { + var args = new object[] { candidate, IntPtr.Zero }; + if ((bool)s_TryLoad.Invoke(null, args)) + { + return (IntPtr)args[1]; + } + } + return IntPtr.Zero; + } + } +} +#endif diff --git a/com.unity.ml-agents/Runtime/Communicator/GrpcNativeLoader.cs.meta b/com.unity.ml-agents/Runtime/Communicator/GrpcNativeLoader.cs.meta new file mode 100644 index 00000000000..f8603011b7d --- /dev/null +++ b/com.unity.ml-agents/Runtime/Communicator/GrpcNativeLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84d250ba27ec4820a436afcbcb21a2ab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs b/com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs index 41fcae7e156..d26c3898812 100644 --- a/com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs +++ b/com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs @@ -229,6 +229,7 @@ void UpdateEnvironmentWithInput(UnityRLInputProto rlInput) UnityInputProto Initialize(int port, UnityOutputProto unityOutput, out UnityInputProto unityInput) { m_IsOpen = true; + GrpcNativeLoader.EnsureInstalled(); m_Channel = new Channel($"localhost:{port}", ChannelCredentials.Insecure); m_Client = new UnityToExternalProto.UnityToExternalProtoClient(m_Channel);