You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2022/03/29 15:54:20 UTC

[ignite] branch master updated: IGNITE-16749 .NET: Fix EntryPointNotFoundException on Alpine Linux (#9916)

This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 6fa0658  IGNITE-16749 .NET: Fix EntryPointNotFoundException on Alpine Linux (#9916)
6fa0658 is described below

commit 6fa06587ca31083e1f714ba285e5b18fde88995d
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Tue Mar 29 18:53:25 2022 +0300

    IGNITE-16749 .NET: Fix EntryPointNotFoundException on Alpine Linux (#9916)
    
    On some Linux distros we can load `dlopen` and `pthread_*` symbols from `libcoreclr.so` to avoid dependency on `libc-dev`. However, this does not work on Alpine.
    
    Add exception handler: if `libcoreclr` approach does not work, fall back to `libpthread.so` and `libdl.so`.
    
    * `DllLoader` is called only once to load jvm.dll, it is fine to handle the exception inline.
    * `UnmanagedThread` is called many times: perform the check once and set up delegates.
    
    Tested on Alpine 3.15, Ubuntu 20.04, macOs Catalina 2019, Windows 10.
---
 .../Impl/Unmanaged/Jni/DllLoader.cs                |  23 +-
 .../Impl/Unmanaged/UnmanagedThread.cs              | 318 +++++++++++++++------
 2 files changed, 246 insertions(+), 95 deletions(-)

diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/DllLoader.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/DllLoader.cs
index e7c41aa..494600d 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/DllLoader.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/DllLoader.cs
@@ -86,18 +86,21 @@ namespace Apache.Ignite.Core.Impl.Unmanaged.Jni
                         : null);
                 }
 
-                if (Os.IsNetCore)
+                // Depending on the Linux distro, dlopen is either present in libdl or in libcoreclr.
+                try
                 {
-                    var ptr = NativeMethodsCore.dlopen(dllPath, RtldGlobal | RtldLazy);
+                    var ptr = NativeMethodsLinuxLibcoreclr.dlopen(dllPath, RtldGlobal | RtldLazy);
                     return new KeyValuePair<IntPtr, string>(ptr, ptr == IntPtr.Zero
-                        ? GetErrorText(NativeMethodsCore.dlerror())
+                        ? GetErrorText(NativeMethodsLinuxLibcoreclr.dlerror())
+                        : null);
+                }
+                catch (EntryPointNotFoundException)
+                {
+                    var ptr = NativeMethodsLinuxLibdl.dlopen(dllPath, RtldGlobal | RtldLazy);
+                    return new KeyValuePair<IntPtr, string>(ptr, ptr == IntPtr.Zero
+                        ? GetErrorText(NativeMethodsLinuxLibdl.dlerror())
                         : null);
                 }
-
-                var lptr = NativeMethodsLinux.dlopen(dllPath, RtldGlobal | RtldLazy);
-                return new KeyValuePair<IntPtr, string>(lptr, lptr == IntPtr.Zero
-                    ? GetErrorText(NativeMethodsLinux.dlerror())
-                    : null);
             }
 
             throw new InvalidOperationException("Unsupported OS: " + Environment.OSVersion);
@@ -149,7 +152,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged.Jni
         /// <summary>
         /// Linux.
         /// </summary>
-        private static class NativeMethodsLinux
+        private static class NativeMethodsLinuxLibdl
         {
             [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")]
             [DllImport("libdl.so", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false,
@@ -181,7 +184,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged.Jni
         /// <summary>
         /// libdl.so depends on libc6-dev on Linux, use libcoreclr instead.
         /// </summary>
-        private static class NativeMethodsCore
+        private static class NativeMethodsLinuxLibcoreclr
         {
             [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")]
             [DllImport("libcoreclr.so", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false,
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedThread.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedThread.cs
index 67a1665..e84fe4a 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedThread.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedThread.cs
@@ -27,6 +27,15 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
     /// </summary>
     internal static class UnmanagedThread
     {
+        /** */
+        private static readonly Func<IntPtr, int> SetThreadExitCallbackDelegate;
+
+        /** */
+        private static readonly Action<int> RemoveThreadExitCallbackDelegate;
+
+        /** */
+        private static readonly Action<int, IntPtr> EnableCurrentThreadExitEventDelegate;
+
         /// <summary>
         /// Delegate for <see cref="SetThreadExitCallback"/>.
         /// </summary>
@@ -36,9 +45,61 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
         /// <summary>
         /// Initializes the <see cref="UnmanagedThread"/> class.
         /// </summary>
+        [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")]
+        [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
         static UnmanagedThread()
         {
             NativeLibraryUtils.SetDllImportResolvers();
+
+            if (Os.IsWindows)
+            {
+                SetThreadExitCallbackDelegate = SetThreadExitCallbackWindows;
+                RemoveThreadExitCallbackDelegate = RemoveThreadExitCallbackWindows;
+                EnableCurrentThreadExitEventDelegate = EnableCurrentThreadExitEventWindows;
+            }
+            else if (Os.IsMacOs)
+            {
+                SetThreadExitCallbackDelegate = SetThreadExitCallbackMacOs;
+                RemoveThreadExitCallbackDelegate = RemoveThreadExitCallbackMacOs;
+                EnableCurrentThreadExitEventDelegate = EnableCurrentThreadExitEventMacOs;
+            }
+            else if (Os.IsLinux)
+            {
+                if (Os.IsMono)
+                {
+                    SetThreadExitCallbackDelegate = SetThreadExitCallbackMono;
+                    RemoveThreadExitCallbackDelegate = RemoveThreadExitCallbackMono;
+                    EnableCurrentThreadExitEventDelegate = EnableCurrentThreadExitEventMono;
+                }
+                else
+                {
+                    unsafe
+                    {
+                        // Depending on the Linux distro, use either libcoreclr or libpthread.
+                        try
+                        {
+                            int tlsIndex;
+
+                            CheckResult(NativeMethodsLinuxLibcoreclr.pthread_key_create(new IntPtr(&tlsIndex), IntPtr.Zero));
+                            CheckResult(NativeMethodsLinuxLibcoreclr.pthread_key_delete(tlsIndex));
+
+                            SetThreadExitCallbackDelegate = SetThreadExitCallbackLibcoreclr;
+                            RemoveThreadExitCallbackDelegate = RemoveThreadExitCallbackLibcoreclr;
+                            EnableCurrentThreadExitEventDelegate = EnableCurrentThreadExitEventLibcoreclr;
+                        }
+                        catch (EntryPointNotFoundException)
+                        {
+                            SetThreadExitCallbackDelegate = SetThreadExitCallbackLibpthread;
+                            RemoveThreadExitCallbackDelegate = RemoveThreadExitCallbackLibpthread;
+                            EnableCurrentThreadExitEventDelegate = EnableCurrentThreadExitEventLibpthread;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                throw new InvalidOperationException("Unsupported OS: " + Environment.OSVersion);
+            }
         }
 
         /// <summary>
@@ -47,115 +108,195 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
         /// <param name="callbackPtr">
         /// Pointer to a callback function that matches <see cref="ThreadExitCallback"/>.
         /// </param>
-        public static unsafe int SetThreadExitCallback(IntPtr callbackPtr)
+        public static int SetThreadExitCallback(IntPtr callbackPtr)
         {
             Debug.Assert(callbackPtr != IntPtr.Zero);
 
-            if (Os.IsWindows)
-            {
-                var res = NativeMethodsWindows.FlsAlloc(callbackPtr);
+            return SetThreadExitCallbackDelegate(callbackPtr);
+        }
 
-                if (res == NativeMethodsWindows.FLS_OUT_OF_INDEXES)
-                {
-                    throw new InvalidOperationException("FlsAlloc failed: " + Marshal.GetLastWin32Error());
-                }
+        /// <summary>
+        /// Removes thread exit callback that has been set with <see cref="SetThreadExitCallback"/>.
+        /// NOTE: callback may be called as a result of this method call on some platforms.
+        /// </summary>
+        /// <param name="callbackId">Callback id returned from <see cref="SetThreadExitCallback"/>.</param>
+        public static void RemoveThreadExitCallback(int callbackId)
+        {
+            RemoveThreadExitCallbackDelegate(callbackId);
+        }
 
-                return res;
-            }
+        /// <summary>
+        /// Enables thread exit event for current thread.
+        /// </summary>
+        public static void EnableCurrentThreadExitEvent(int callbackId, IntPtr threadLocalValue)
+        {
+            Debug.Assert(threadLocalValue != IntPtr.Zero);
 
-            if (Os.IsMacOs)
-            {
-                int tlsIndex;
-                var res = NativeMethodsMacOs.pthread_key_create(new IntPtr(&tlsIndex), callbackPtr);
+            EnableCurrentThreadExitEventDelegate(callbackId, threadLocalValue);
+        }
 
-                NativeMethodsLinux.CheckResult(res);
+        /// <summary>
+        /// Sets the thread exit callback.
+        /// </summary>
+        private static unsafe int SetThreadExitCallbackMacOs(IntPtr callbackPtr)
+        {
+            int tlsIndex;
+            var res = NativeMethodsMacOs.pthread_key_create(new IntPtr(&tlsIndex), callbackPtr);
 
-                return tlsIndex;
-            }
+            CheckResult(res);
 
-            if (Os.IsLinux)
-            {
-                int tlsIndex;
-                var res = Os.IsMono
-                    ? NativeMethodsMono.pthread_key_create(new IntPtr(&tlsIndex), callbackPtr)
-                    : NativeMethodsLinux.pthread_key_create(new IntPtr(&tlsIndex), callbackPtr);
+            return tlsIndex;
+        }
 
-                NativeMethodsLinux.CheckResult(res);
+        /// <summary>
+        /// Sets the thread exit callback.
+        /// </summary>
+        private static int SetThreadExitCallbackWindows(IntPtr callbackPtr)
+        {
+            var res = NativeMethodsWindows.FlsAlloc(callbackPtr);
 
-                return tlsIndex;
+            if (res == NativeMethodsWindows.FLS_OUT_OF_INDEXES)
+            {
+                throw new InvalidOperationException("FlsAlloc failed: " + Marshal.GetLastWin32Error());
             }
 
-            throw new InvalidOperationException("Unsupported OS: " + Environment.OSVersion);
+            return res;
+        }
+
+        /// <summary>
+        /// Sets the thread exit callback.
+        /// </summary>
+        private static unsafe int SetThreadExitCallbackMono(IntPtr callbackPtr)
+        {
+            int tlsIndex;
+
+            CheckResult(NativeMethodsMono.pthread_key_create(new IntPtr(&tlsIndex), callbackPtr));
+
+            return tlsIndex;
+        }
+
+        /// <summary>
+        /// Sets the thread exit callback.
+        /// </summary>
+        private static unsafe int SetThreadExitCallbackLibcoreclr(IntPtr callbackPtr)
+        {
+            int tlsIndex;
+
+            CheckResult(NativeMethodsLinuxLibcoreclr.pthread_key_create(new IntPtr(&tlsIndex), callbackPtr));
+
+            return tlsIndex;
+        }
+
+        /// <summary>
+        /// Sets the thread exit callback.
+        /// </summary>
+        private static unsafe int SetThreadExitCallbackLibpthread(IntPtr callbackPtr)
+        {
+            int tlsIndex;
+
+            CheckResult(NativeMethodsLinuxLibpthread.pthread_key_create(new IntPtr(&tlsIndex), callbackPtr));
+
+            return tlsIndex;
         }
 
         /// <summary>
         /// Removes thread exit callback that has been set with <see cref="SetThreadExitCallback"/>.
-        /// NOTE: callback may be called as a result of this method call on some platforms.
         /// </summary>
-        /// <param name="callbackId">Callback id returned from <see cref="SetThreadExitCallback"/>.</param>
-        public static void RemoveThreadExitCallback(int callbackId)
+        private static void RemoveThreadExitCallbackLibpthread(int callbackId)
         {
-            if (Os.IsWindows)
-            {
-                var res = NativeMethodsWindows.FlsFree(callbackId);
+            CheckResult(NativeMethodsLinuxLibpthread.pthread_key_delete(callbackId));
+        }
 
-                if (!res)
-                {
-                    throw new InvalidOperationException("FlsFree failed: " + Marshal.GetLastWin32Error());
-                }
-            }
-            else if (Os.IsMacOs)
-            {
-                var res = NativeMethodsMacOs.pthread_key_delete(callbackId);
-                NativeMethodsLinux.CheckResult(res);
-            }
-            else if (Os.IsLinux)
-            {
-                var res = Os.IsMono
-                    ? NativeMethodsMono.pthread_key_delete(callbackId)
-                    : NativeMethodsLinux.pthread_key_delete(callbackId);
+        /// <summary>
+        /// Removes thread exit callback that has been set with <see cref="SetThreadExitCallback"/>.
+        /// </summary>
+        private static void RemoveThreadExitCallbackLibcoreclr(int callbackId)
+        {
+            CheckResult(NativeMethodsLinuxLibcoreclr.pthread_key_delete(callbackId));
+        }
 
-                NativeMethodsLinux.CheckResult(res);
-            }
-            else
+        /// <summary>
+        /// Removes thread exit callback that has been set with <see cref="SetThreadExitCallback"/>.
+        /// </summary>
+        private static void RemoveThreadExitCallbackMono(int callbackId)
+        {
+            CheckResult(NativeMethodsMono.pthread_key_delete(callbackId));
+        }
+
+        /// <summary>
+        /// Removes thread exit callback that has been set with <see cref="SetThreadExitCallback"/>.
+        /// </summary>
+        private static void RemoveThreadExitCallbackMacOs(int callbackId)
+        {
+            CheckResult(NativeMethodsMacOs.pthread_key_delete(callbackId));
+        }
+
+        /// <summary>
+        /// Removes thread exit callback that has been set with <see cref="SetThreadExitCallback"/>.
+        /// </summary>
+        private static void RemoveThreadExitCallbackWindows(int callbackId)
+        {
+            var res = NativeMethodsWindows.FlsFree(callbackId);
+
+            if (!res)
             {
-                throw new InvalidOperationException("Unsupported OS: " + Environment.OSVersion);
+                throw new InvalidOperationException("FlsFree failed: " + Marshal.GetLastWin32Error());
             }
         }
 
         /// <summary>
         /// Enables thread exit event for current thread.
         /// </summary>
-        public static void EnableCurrentThreadExitEvent(int callbackId, IntPtr threadLocalValue)
+        private static void EnableCurrentThreadExitEventLibpthread(int callbackId, IntPtr threadLocalValue)
         {
-            Debug.Assert(threadLocalValue != IntPtr.Zero);
+            CheckResult(NativeMethodsLinuxLibpthread.pthread_setspecific(callbackId, threadLocalValue));
+        }
 
-            // Store any value so that destructor callback is fired.
-            if (Os.IsWindows)
-            {
-                var res = NativeMethodsWindows.FlsSetValue(callbackId, threadLocalValue);
+        /// <summary>
+        /// Enables thread exit event for current thread.
+        /// </summary>
+        private static void EnableCurrentThreadExitEventLibcoreclr(int callbackId, IntPtr threadLocalValue)
+        {
+            CheckResult(NativeMethodsLinuxLibcoreclr.pthread_setspecific(callbackId, threadLocalValue));
+        }
 
-                if (!res)
-                {
-                    throw new InvalidOperationException("FlsSetValue failed: " + Marshal.GetLastWin32Error());
-                }
-            }
-            else if (Os.IsMacOs)
+        /// <summary>
+        /// Enables thread exit event for current thread.
+        /// </summary>
+        private static void EnableCurrentThreadExitEventMono(int callbackId, IntPtr threadLocalValue)
+        {
+            CheckResult(NativeMethodsMono.pthread_setspecific(callbackId, threadLocalValue));
+        }
+
+        /// <summary>
+        /// Enables thread exit event for current thread.
+        /// </summary>
+        private static void EnableCurrentThreadExitEventMacOs(int callbackId, IntPtr threadLocalValue)
+        {
+            CheckResult(NativeMethodsMacOs.pthread_setspecific(callbackId, threadLocalValue));
+        }
+
+        /// <summary>
+        /// Enables thread exit event for current thread.
+        /// </summary>
+        private static void EnableCurrentThreadExitEventWindows(int callbackId, IntPtr threadLocalValue)
+        {
+            var res = NativeMethodsWindows.FlsSetValue(callbackId, threadLocalValue);
+
+            if (!res)
             {
-                var res = NativeMethodsMacOs.pthread_setspecific(callbackId, threadLocalValue);
-                NativeMethodsLinux.CheckResult(res);
+                throw new InvalidOperationException("FlsSetValue failed: " + Marshal.GetLastWin32Error());
             }
-            else if (Os.IsLinux)
-            {
-                var res = Os.IsMono
-                    ? NativeMethodsMono.pthread_setspecific(callbackId, threadLocalValue)
-                    : NativeMethodsLinux.pthread_setspecific(callbackId, threadLocalValue);
+        }
 
-                NativeMethodsLinux.CheckResult(res);
-            }
-            else
+        /// <summary>
+        /// Checks native call result.
+        /// </summary>
+        private static void CheckResult(int res)
+        {
+            if (res != 0)
             {
-                throw new InvalidOperationException("Unsupported OS: " + Environment.OSVersion);
+                throw new InvalidOperationException("Native call failed: " + res);
             }
         }
 
@@ -185,7 +326,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
         /// <summary>
         /// Linux imports.
         /// </summary>
-        private static class NativeMethodsLinux
+        private static class NativeMethodsLinuxLibcoreclr
         {
             [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Reviewed.")]
             [DllImport("libcoreclr.so")]
@@ -198,17 +339,24 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
             [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Reviewed.")]
             [DllImport("libcoreclr.so")]
             public static extern int pthread_setspecific(int key, IntPtr value);
+        }
 
-            /// <summary>
-            /// Checks native call result.
-            /// </summary>
-            public static void CheckResult(int res)
-            {
-                if (res != 0)
-                {
-                    throw new InvalidOperationException("Native call failed: " + res);
-                }
-            }
+        /// <summary>
+        /// Linux imports.
+        /// </summary>
+        private static class NativeMethodsLinuxLibpthread
+        {
+            [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Reviewed.")]
+            [DllImport("libpthread.so")]
+            public static extern int pthread_key_create(IntPtr key, IntPtr destructorCallback);
+
+            [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Reviewed.")]
+            [DllImport("libpthread.so")]
+            public static extern int pthread_key_delete(int key);
+
+            [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Reviewed.")]
+            [DllImport("libpthread.so")]
+            public static extern int pthread_setspecific(int key, IntPtr value);
         }
 
         /// <summary>