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 2021/02/13 18:32:26 UTC

[ignite] branch master updated: IGNITE-12941 .NET: Fix and document single file deployment on .NET 5

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 57c39a1  IGNITE-12941 .NET: Fix and document single file deployment on .NET 5
57c39a1 is described below

commit 57c39a16074fbfa9c97f776fffb7cb9d52a4cbcd
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Sat Feb 13 21:31:51 2021 +0300

    IGNITE-12941 .NET: Fix and document single file deployment on .NET 5
    
    * Fix `DllNotFoundException: Unable to load shared library 'libcoreclr.so'` error in single file mode on .NET 5 by adding a custom `DllImportResolver`
    * Update Deployment and Troubleshooting documentation with single file deployment specifics
---
 .../_docs/net-specific/net-deployment-options.adoc |  13 ++-
 docs/_docs/net-specific/net-troubleshooting.adoc   |  19 ++++
 .../Apache.Ignite.Core.Tests/ProjectFilesTest.cs   |  47 +++++++++
 .../Apache.Ignite.Core/Apache.Ignite.Core.csproj   |   1 +
 .../Impl/Unmanaged/Jni/DllLoader.cs                |  10 +-
 .../Impl/Unmanaged/NativeLibraryUtils.cs           | 108 +++++++++++++++++++++
 .../Impl/Unmanaged/UnmanagedThread.cs              |  20 ++--
 modules/platforms/dotnet/DEVNOTES.txt              |   6 +-
 8 files changed, 213 insertions(+), 11 deletions(-)

diff --git a/docs/_docs/net-specific/net-deployment-options.adoc b/docs/_docs/net-specific/net-deployment-options.adoc
index 9a16852..00348a2 100644
--- a/docs/_docs/net-specific/net-deployment-options.adoc
+++ b/docs/_docs/net-specific/net-deployment-options.adoc
@@ -24,7 +24,7 @@ This page introduces several most-commonly used deployment options of Ignite.NET
 
 == NuGet Deployment
 
-`Apache.Ignite` NuGet package includes a `lib` folder with all the required jar files. This folder has 
+`Apache.Ignite` NuGet package includes a `lib` folder with all the required jar files. This folder has
 the `<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>` build action, and is copied automatically to the output directory
 during the build or publish process.
 
@@ -52,6 +52,17 @@ tab:MyApp.csproj[]
 ----
 --
 
+== Single File Deployment
+
+Ignite.NET supports link:https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file[single file deployment] that is available in .NET Core 3 / .NET 5+.
+
+* Use the `IncludeAllContentForSelfExtract` MSBuild property to include jar files into the single-file bundle, or ship them separately.
+* See xref:net-troubleshooting.adoc#libcoreclr-not-found[Troubleshooting: DllNotFoundException] for a workaround that is required
+on .NET 5 with some Ignite versions.
+
+Publish command example:
+
+`dotnet publish --self-contained true -r linux-x64 -p:PublishSingleFile=true -p:IncludeAllContentForSelfExtract=true`
 
 == Full Binary Package Deployment
 
diff --git a/docs/_docs/net-specific/net-troubleshooting.adoc b/docs/_docs/net-specific/net-troubleshooting.adoc
index 8d9986b..cc4617b 100644
--- a/docs/_docs/net-specific/net-troubleshooting.adoc
+++ b/docs/_docs/net-specific/net-troubleshooting.adoc
@@ -159,3 +159,22 @@ For example, when `direct-io` is used, and .NET code requires starting a child p
 move the process handling logic to Java side and invoke it with
 link:developers-guide/distributed-computing/distributed-computing[Compute] `ExecuteJavaTask` API.
 Alternatively, use Services API to call Java service from .NET.
+
+=== [[libcoreclr-not-found]] DllNotFoundException: Unable to load shared library 'libcoreclr.so' or one of its dependencies
+
+Occurs on .NET 5 in a single-file publish mode (e.g. `dotnet publish --self-contained true -r linux-x64 -p:PublishSingleFile=true`).
+
+==== Workaround
+
+Add the following code before starting the Ignite node:
+
+[tabs]
+--
+tab:C#[]
+[source,csharp]
+----
+NativeLibrary.SetDllImportResolver(
+    typeof(Ignition).Assembly,
+    (lib, _, _) => lib == "libcoreclr.so" ? (IntPtr) (-1) : IntPtr.Zero);
+----
+--
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ProjectFilesTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ProjectFilesTest.cs
index 50d1fd9..5fdf70d 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ProjectFilesTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ProjectFilesTest.cs
@@ -87,6 +87,53 @@ namespace Apache.Ignite.Core.Tests
                 "Invalid optimize setting in release mode: ");
         }
 
+        /// <summary>
+        /// Tests that there are no public types in Apache.Ignite.Core.Impl namespace.
+        /// </summary>
+        [Test]
+        public void TestImplNamespaceHasNoPublicTypes()
+        {
+            var excluded = new[]
+            {
+                "ProjectFilesTest.cs", 
+                "CopyOnWriteConcurrentDictionary.cs",
+                "IgniteArgumentCheck.cs",
+                "DelegateConverter.cs",
+                "IgniteHome.cs",
+                "TypeCaster.cs",
+                "FutureType.cs",
+                "CollectionExtensions.cs",
+                "IQueryEntityInternal.cs",
+                "ICacheInternal.cs",
+                "CacheEntry.cs",
+                "HandleRegistry.cs",
+                "BinaryObjectHeader.cs"
+            };
+            
+            var csFiles = TestUtils.GetDotNetSourceDir().GetFiles("*.cs", SearchOption.AllDirectories);
+
+            foreach (var csFile in csFiles)
+            {
+                if (excluded.Contains(csFile.Name))
+                {
+                    continue;
+                }
+
+                var text = File.ReadAllText(csFile.FullName);
+
+                if (!text.Contains("namespace Apache.Ignite.Core.Impl"))
+                {
+                    continue;
+                }
+
+                StringAssert.DoesNotContain("public class", text, csFile.FullName);
+                StringAssert.DoesNotContain("public static class", text, csFile.FullName);
+                StringAssert.DoesNotContain("public interface", text, csFile.FullName);
+                StringAssert.DoesNotContain("public enum", text, csFile.FullName);
+                StringAssert.DoesNotContain("public struct", text, csFile.FullName);
+            }
+        }
+
 #if NETCOREAPP
         /// <summary>
         /// Tests that all .cs files are included in the project.
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
index b49de3e..a0eb1e4 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
@@ -151,6 +151,7 @@
     <Compile Include="Impl\Transactions\TransactionRollbackOnlyProxy.cs" />
     <Compile Include="Impl\Transactions\TransactionCollectionImpl.cs" />
     <Compile Include="Impl\Unmanaged\Jni\Jvm.CrossAppDomain.cs" />
+    <Compile Include="Impl\Unmanaged\NativeLibraryUtils.cs" />
     <Compile Include="Impl\Unmanaged\UnmanagedThread.cs" />
     <Compile Include="Log\ConsoleLogger.cs" />
     <Compile Include="Log\IDateTimeProvider.cs" />
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 578135d..e7c41aa 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
@@ -47,6 +47,14 @@ namespace Apache.Ignite.Core.Impl.Unmanaged.Jni
         private const int ERROR_MOD_NOT_FOUND = 126;
 
         /// <summary>
+        /// Initializes the <see cref="DllLoader"/> class.
+        /// </summary>
+        static DllLoader()
+        {
+            NativeLibraryUtils.SetDllImportResolvers();
+        }
+
+        /// <summary>
         /// Loads specified DLL.
         /// </summary>
         /// <returns>Library handle and error message.</returns>
@@ -207,4 +215,4 @@ namespace Apache.Ignite.Core.Impl.Unmanaged.Jni
             internal static extern IntPtr dlsym(IntPtr handle, string symbol);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/NativeLibraryUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/NativeLibraryUtils.cs
new file mode 100644
index 0000000..1527ba5
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/NativeLibraryUtils.cs
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Impl.Unmanaged
+{
+    using System;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Linq.Expressions;
+    using System.Reflection;
+
+    /// <summary>
+    /// Native library call utilities.
+    /// </summary>
+    internal static class NativeLibraryUtils
+    {
+        /** */
+        private static readonly object SyncRoot = new object();
+
+        /** */
+        private static bool _resolversInitialized;
+
+        /// <summary>
+        /// Sets dll import resolvers.
+        /// </summary>
+        public static void SetDllImportResolvers()
+        {
+            lock (SyncRoot)
+            {
+                if (_resolversInitialized)
+                {
+                    return;
+                }
+
+                if (Os.IsLinux)
+                {
+                    SetLibcoreclrResolver();
+                }
+
+                _resolversInitialized = true;
+            }
+        }
+
+        /// <summary>
+        /// Sets dll import resolvers.
+        /// </summary>
+        private static void SetLibcoreclrResolver()
+        {
+            // Init custom resolver for .NET 5+ single-file apps.
+            // Do it with Reflection, because SetDllImportResolver is not available on some frameworks,
+            // and multi-targeting is not yet implemented.
+            //
+            // The code below is equivalent to:
+            // NativeLibrary.SetDllImportResolver(typeof(Ignition).Assembly, (libName, _, _) => Resolve(libName));
+            var dllImportResolverType = Type.GetType("System.Runtime.InteropServices.DllImportResolver");
+            var dllImportSearchPathType = Type.GetType("System.Runtime.InteropServices.DllImportSearchPath");
+            var nativeLibraryType = Type.GetType("System.Runtime.InteropServices.NativeLibrary");
+
+            if (dllImportResolverType == null || dllImportSearchPathType == null || nativeLibraryType == null)
+            {
+                return;
+            }
+
+            var setDllImportResolverMethod = nativeLibraryType.GetMethod("SetDllImportResolver");
+
+            if (setDllImportResolverMethod == null)
+            {
+                return;
+            }
+
+            var libraryName = Expression.Parameter(typeof(string));
+            var assembly = Expression.Parameter(typeof(Assembly));
+            var searchPath = Expression.Parameter(typeof(Nullable<>).MakeGenericType(dllImportSearchPathType));
+
+            Expression<Func<string, IntPtr>> call = lib => Resolve(lib);
+            var resolve = Expression.Invoke(call, libraryName);
+
+            var dllImportResolver = Expression.Lambda(dllImportResolverType, resolve, libraryName, assembly, searchPath);
+            var resolveDelegate = dllImportResolver.Compile();
+
+            setDllImportResolverMethod.Invoke(null, new object[] {typeof(Ignition).Assembly, resolveDelegate});
+        }
+
+        /// <summary>
+        /// Resolves the native library.
+        /// </summary>
+        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reflection")]
+        private static IntPtr Resolve(string libraryName)
+        {
+            return libraryName == "libcoreclr.so"
+                ? (IntPtr) (-1)  // Self-referencing binary.
+                : IntPtr.Zero;   // Skip.
+        }
+    }
+}
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 352d22f..67a1665 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedThread.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedThread.cs
@@ -34,6 +34,14 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
         public delegate void ThreadExitCallback(IntPtr threadLocalValue);
 
         /// <summary>
+        /// Initializes the <see cref="UnmanagedThread"/> class.
+        /// </summary>
+        static UnmanagedThread()
+        {
+            NativeLibraryUtils.SetDllImportResolvers();
+        }
+
+        /// <summary>
         /// Sets the thread exit callback, and returns an id to pass to <see cref="EnableCurrentThreadExitEvent"/>.
         /// </summary>
         /// <param name="callbackPtr">
@@ -103,10 +111,10 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
             }
             else if (Os.IsLinux)
             {
-                var res = Os.IsMono 
+                var res = Os.IsMono
                     ? NativeMethodsMono.pthread_key_delete(callbackId)
                     : NativeMethodsLinux.pthread_key_delete(callbackId);
-                
+
                 NativeMethodsLinux.CheckResult(res);
             }
             else
@@ -139,10 +147,10 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
             }
             else if (Os.IsLinux)
             {
-                var res = Os.IsMono 
+                var res = Os.IsMono
                     ? NativeMethodsMono.pthread_setspecific(callbackId, threadLocalValue)
                     : NativeMethodsLinux.pthread_setspecific(callbackId, threadLocalValue);
-                
+
                 NativeMethodsLinux.CheckResult(res);
             }
             else
@@ -220,7 +228,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
             [DllImport("libSystem.dylib")]
             public static extern int pthread_setspecific(int key, IntPtr value);
         }
-        
+
         /// <summary>
         /// Mono on Linux requires __Internal instead of libcoreclr.so.
         /// </summary>
@@ -229,7 +237,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
             [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Reviewed.")]
             [DllImport("__Internal")]
             public static extern int pthread_key_create(IntPtr key, IntPtr destructorCallback);
-            
+
             [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Reviewed.")]
             [DllImport("__Internal")]
             public static extern int pthread_key_delete(int key);
diff --git a/modules/platforms/dotnet/DEVNOTES.txt b/modules/platforms/dotnet/DEVNOTES.txt
index 6d4cf6d..8ce7b87 100644
--- a/modules/platforms/dotnet/DEVNOTES.txt
+++ b/modules/platforms/dotnet/DEVNOTES.txt
@@ -54,10 +54,10 @@ Getting started:
   dotnet test Apache.Ignite.Core.Tests.DotNetCore.csproj
 
 * Run specific test:
-  dotnet test Apache.Ignite.Core.Tests.DotNetCore.csproj --filter "TestCategory!=LONG_TEST&TestCategory!=EXAMPLES_TEST"
-
-* Run a smaller subset of tests:
+  dotnet test Apache.Ignite.Core.Tests.DotNetCore.csproj --filter CacheTest  
 
+* Run a smaller subset of tests - exclude long tests and examples tests:
+  dotnet test Apache.Ignite.Core.Tests.DotNetCore.csproj --filter "TestCategory!=LONG_TEST&TestCategory!=EXAMPLES_TEST"
 
 * IDE: Open Apache.Ignite.DotNetCore.sln