IGNITE-7013 .NET: macOS support added This closes #3091
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/0b849abc Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/0b849abc Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/0b849abc Branch: refs/heads/ignite-7016 Commit: 0b849abce4514ffe949915c85b15ad6aae23ee33 Parents: d2c21bc Author: Pavel Tupitsyn <[email protected]> Authored: Tue Nov 28 18:32:16 2017 +0300 Committer: Pavel Tupitsyn <[email protected]> Committed: Tue Nov 28 18:32:16 2017 +0300 ---------------------------------------------------------------------- .../Common/TestLogger.cs | 74 ++++ .../Common/TestUtils.DotNetCore.cs | 46 ++- .../Client/ClientConnectionTest.cs | 4 +- .../Log/CustomLoggerTest.cs | 7 - .../TestUtils.Common.cs | 3 - .../Apache.Ignite.Core.csproj | 5 +- .../Apache.Ignite.Core/IgniteConfiguration.cs | 7 +- .../dotnet/Apache.Ignite.Core/Ignition.cs | 2 +- .../Impl/Client/ClientSocket.cs | 12 +- .../Apache.Ignite.Core/Impl/IgniteManager.cs | 8 + .../Apache.Ignite.Core/Impl/IgniteUtils.cs | 242 ----------- .../dotnet/Apache.Ignite.Core/Impl/Shell.cs | 2 + .../Impl/Unmanaged/DllLoader.cs | 176 -------- .../Impl/Unmanaged/Jni/DllLoader.cs | 210 ++++++++++ .../Impl/Unmanaged/Jni/Jvm.cs | 80 +--- .../Impl/Unmanaged/Jni/JvmDll.cs | 414 +++++++++++++++++++ .../Impl/Unmanaged/Jni/JvmInitArgs.cs | 33 ++ .../Impl/Unmanaged/Jni/JvmOption.cs | 34 ++ .../Apache.Ignite.Core/Impl/Unmanaged/Os.cs | 6 + modules/platforms/dotnet/Apache.Ignite.ndproj | 2 +- 20 files changed, 837 insertions(+), 530 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Common/TestLogger.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Common/TestLogger.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Common/TestLogger.cs new file mode 100644 index 0000000..8253fb8 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Common/TestLogger.cs @@ -0,0 +1,74 @@ +/* + * 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.Tests.DotNetCore.Common +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using Apache.Ignite.Core.Log; + + /// <summary> + /// 'dotnet test' swallows console output. This logger writes to a file to overcome this. + /// </summary> + internal class TestLogger : ILogger + { + /** */ + public static readonly TestLogger Instance = new TestLogger(); + + /** */ + private readonly StreamWriter _file; + + /// <summary> + /// Prevents a default instance of the <see cref="TestLogger"/> class from being created. + /// </summary> + private TestLogger() + { + var binDir = Path.GetDirectoryName(GetType().Assembly.Location); + var file = Path.Combine(binDir, "dotnet-test.log"); + + if (File.Exists(file)) + { + File.Delete(file); + } + + _file = File.AppendText(file); + } + + /** <inheritdoc /> */ + public void Log(LogLevel level, string message, object[] args, IFormatProvider formatProvider, string category, + string nativeErrorInfo, Exception ex) + { + lock (_file) + { + var text = args != null + ? string.Format(formatProvider ?? CultureInfo.InvariantCulture, message, args) + : message; + + _file.WriteLine(text); + _file.Flush(); + } + } + + /** <inheritdoc /> */ + public bool IsEnabled(LogLevel level) + { + return level > LogLevel.Debug; + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Common/TestUtils.DotNetCore.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Common/TestUtils.DotNetCore.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Common/TestUtils.DotNetCore.cs index 0f51593..c0586c3 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Common/TestUtils.DotNetCore.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Common/TestUtils.DotNetCore.cs @@ -18,40 +18,54 @@ namespace Apache.Ignite.Core.Tests { using System; - using System.Collections.Generic; + using System.Diagnostics; using System.IO; using System.Linq; + using Apache.Ignite.Core.Log; + using Apache.Ignite.Core.Tests.DotNetCore.Common; public static partial class TestUtils { - /** */ - private static readonly IList<string> JvmOpts = - new List<string> - { - "-Duser.timezone=UTC" - - // Uncomment to debug Java - //"-Xdebug", - //"-Xnoagent", - //"-Djava.compiler=NONE", - //"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" - }; - /// <summary> /// Gets the default code-based test configuration. /// </summary> public static IgniteConfiguration GetTestConfiguration(string name = null) { + TestLogger.Instance.Info("GetTestConfiguration: " + GetTestName()); + return new IgniteConfiguration { DiscoverySpi = GetStaticDiscovery(), Localhost = "127.0.0.1", - JvmOptions = JvmOpts, - IgniteInstanceName = name + JvmOptions = TestJavaOptions(), + IgniteInstanceName = name, + Logger = TestLogger.Instance }; } /// <summary> + /// Gets the name of the test. + /// </summary> + private static string GetTestName() + { + var st = new StackTrace(); + + for (var i = 0; i < st.FrameCount; i++) + { + var frame = st.GetFrame(i); + var method = frame.GetMethod(); + + if (method.DeclaringType != typeof(TestUtils) + && method.DeclaringType != typeof(TestBase)) + { + return $"{method.DeclaringType.Name}.{method.Name}"; + } + } + + return st.GetFrames().Skip(2).Select(x => x.ToString()).FirstOrDefault() ?? "unknown"; + } + + /// <summary> /// Creates a uniquely named, empty temporary directory on disk and returns the full path of that directory. /// </summary> /// <returns>The full path of the temporary directory.</returns> http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs index d965b72..4b9af70 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs @@ -81,7 +81,9 @@ namespace Apache.Ignite.Core.Tests.Client { Host = "localhost", Port = 2000, - PortRange = 1 + PortRange = 1, + SocketSendBufferSize = 100, + SocketReceiveBufferSize = 50 } }; http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Log/CustomLoggerTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Log/CustomLoggerTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Log/CustomLoggerTest.cs index e80bd3f..50d2dbf 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Log/CustomLoggerTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Log/CustomLoggerTest.cs @@ -78,13 +78,6 @@ namespace Apache.Ignite.Core.Tests.Log { Assert.IsTrue(TestLogger.Entries.Any(x => x.Level == level), "No messages with level " + level); } - - // Check IgniteHome and classpath messages. - Assert.IsTrue(TestLogger.Entries.Any(x => x.Level == LogLevel.Debug && - x.Message == "Classpath resolved to: {0}")); - - Assert.IsTrue(TestLogger.Entries.Any(x => x.Level == LogLevel.Debug && - x.Message == "IGNITE_HOME resolved to: {0}")); } /// <summary> http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.Common.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.Common.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.Common.cs index 4f5f3f4..2430300 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.Common.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.Common.cs @@ -46,7 +46,6 @@ namespace Apache.Ignite.Core.Tests private const int DfltBusywaitSleepInterval = 200; /** */ - private static readonly IList<string> TestJvmOpts = Environment.Is64BitProcess ? new List<string> { @@ -230,8 +229,6 @@ namespace Apache.Ignite.Core.Tests return false; } - - /// <summary> /// Waits for condition, polling in busy wait loop. /// </summary> http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj ---------------------------------------------------------------------- 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 6deaa9b..5adb501 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj @@ -142,7 +142,7 @@ <Compile Include="Impl\DataRegionMetrics.cs" /> <Compile Include="Impl\PersistentStore\PersistentStoreMetrics.cs" /> <Compile Include="Impl\Shell.cs" /> - <Compile Include="Impl\Unmanaged\DllLoader.cs" /> + <Compile Include="Impl\Unmanaged\Jni\DllLoader.cs" /> <Compile Include="Impl\Unmanaged\Jni\AppDomains.cs" /> <Compile Include="Impl\Unmanaged\Jni\CallbackDelegates.cs" /> <Compile Include="Impl\Unmanaged\Jni\Callbacks.cs" /> @@ -150,10 +150,13 @@ <Compile Include="Impl\Unmanaged\Jni\EnvDelegates.cs" /> <Compile Include="Impl\Unmanaged\Jni\Env.cs" /> <Compile Include="Impl\Unmanaged\Jni\EnvInterface.cs" /> + <Compile Include="Impl\Unmanaged\Jni\JvmDll.cs" /> + <Compile Include="Impl\Unmanaged\Jni\JvmInitArgs.cs" /> <Compile Include="Impl\Unmanaged\Jni\JvmInterface.cs" /> <Compile Include="Impl\PlatformDisposableTargetAdapter.cs" /> <Compile Include="Impl\PlatformJniTarget.cs" /> <Compile Include="Impl\Unmanaged\Jni\GlobalRef.cs" /> + <Compile Include="Impl\Unmanaged\Jni\JvmOption.cs" /> <Compile Include="Impl\Unmanaged\Jni\MethodId.cs" /> <Compile Include="Impl\Unmanaged\Jni\NativeMethod.cs" /> <Compile Include="Impl\Unmanaged\Jni\JniResult.cs" /> http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs index 29b8519..8c39091 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs @@ -847,10 +847,9 @@ namespace Apache.Ignite.Core public string SpringConfigUrl { get; set; } /// <summary> - /// Path jvm.dll file. If not set, it's location will be determined - /// using JAVA_HOME environment variable. - /// If path is neither set nor determined automatically, an exception - /// will be thrown. + /// Path to jvm.dll (libjvm.so on Linux, libjvm.dylib on Mac) file. + /// If not set, it's location will be determined using JAVA_HOME environment variable. + /// If path is neither set nor determined automatically, an exception will be thrown. /// </summary> public string JvmDllPath { get; set; } http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs index 41419ac..46b9ec5 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs @@ -227,7 +227,7 @@ namespace Apache.Ignite.Core CheckServerGc(cfg, log); // 2. Create context. - IgniteUtils.LoadDlls(cfg.JvmDllPath, log); + JvmDll.Load(cfg.JvmDllPath, log); var cbs = IgniteManager.CreateJvmContext(cfg, log); var env = cbs.Jvm.AttachCurrentThread(); http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientSocket.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientSocket.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientSocket.cs index e565f31..078927b 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientSocket.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientSocket.cs @@ -213,11 +213,19 @@ namespace Apache.Ignite.Core.Impl.Client { var socket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp) { - SendBufferSize = cfg.SocketSendBufferSize, - ReceiveBufferSize = cfg.SocketReceiveBufferSize, NoDelay = cfg.TcpNoDelay }; + if (cfg.SocketSendBufferSize != IgniteClientConfiguration.DefaultSocketBufferSize) + { + socket.SendBufferSize = cfg.SocketSendBufferSize; + } + + if (cfg.SocketReceiveBufferSize != IgniteClientConfiguration.DefaultSocketBufferSize) + { + socket.ReceiveBufferSize = cfg.SocketReceiveBufferSize; + } + socket.Connect(ipEndPoint); return socket; http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs index ce84003..47260a7 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs @@ -108,6 +108,14 @@ namespace Apache.Ignite.Core.Impl /// <returns>JVM.</returns> private static Jvm CreateJvm(IgniteConfiguration cfg, ILogger log) { + // Do not bother with classpath when JVM exists. + var jvm = Jvm.Get(true); + + if (jvm != null) + { + return jvm; + } + var cp = Classpath.CreateClasspath(cfg, log: log); var jvmOpts = GetMergedJvmOptions(cfg); http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteUtils.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteUtils.cs index 022a5ed..aff028e 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteUtils.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteUtils.cs @@ -19,9 +19,7 @@ namespace Apache.Ignite.Core.Impl { using System; using System.Collections.Generic; - using System.Globalization; using System.IO; - using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; @@ -31,9 +29,6 @@ namespace Apache.Ignite.Core.Impl using Apache.Ignite.Core.Impl.Binary; using Apache.Ignite.Core.Impl.Cluster; using Apache.Ignite.Core.Impl.Common; - using Apache.Ignite.Core.Impl.Unmanaged; - using Apache.Ignite.Core.Log; - using Microsoft.Win32; using BinaryReader = Apache.Ignite.Core.Impl.Binary.BinaryReader; /// <summary> @@ -41,49 +36,9 @@ namespace Apache.Ignite.Core.Impl /// </summary> internal static class IgniteUtils { - /** Environment variable: JAVA_HOME. */ - private const string EnvJavaHome = "JAVA_HOME"; - - /** Lookup paths. */ - private static readonly string[] JvmDllLookupPaths = Os.IsWindows - ? new[] - { - // JRE paths - @"bin\server", - @"bin\client", - - // JDK paths - @"jre\bin\server", - @"jre\bin\client", - @"jre\bin\default" - } - : new[] - { - // JRE paths - "lib/amd64/server", - "lib/amd64/client", - - // JDK paths - "jre/lib/amd64/server", - "jre/lib/amd64/client" - }; - - /** Registry lookup paths. */ - private static readonly string[] JreRegistryKeys = - { - @"Software\JavaSoft\Java Runtime Environment", - @"Software\Wow6432Node\JavaSoft\Java Runtime Environment" - }; - - /** Jvm dll file name. */ - internal static readonly string FileJvmDll = Os.IsWindows ? "jvm.dll" : "libjvm.so"; - /** Prefix for temp directory names. */ private const string DirIgniteTmp = "Ignite_"; - /** Loaded. */ - private static bool _loaded; - /** Thread-local random. */ [ThreadStatic] private static Random _rnd; @@ -127,25 +82,6 @@ namespace Apache.Ignite.Core.Impl } /// <summary> - /// Load JVM DLL if needed. - /// </summary> - /// <param name="configJvmDllPath">JVM DLL path from config.</param> - /// <param name="log">Log.</param> - public static void LoadDlls(string configJvmDllPath, ILogger log) - { - if (_loaded) - { - log.Debug("JNI dll is already loaded."); - return; - } - - // 1. Load JNI dll. - LoadJvmDll(configJvmDllPath, log); - - _loaded = true; - } - - /// <summary> /// Create new instance of specified class. /// </summary> /// <param name="typeName">Class name</param> @@ -195,185 +131,7 @@ namespace Apache.Ignite.Core.Impl } } - /// <summary> - /// Loads the JVM DLL. - /// </summary> - private static void LoadJvmDll(string configJvmDllPath, ILogger log) - { - var messages = new List<string>(); - foreach (var dllPath in GetJvmDllPaths(configJvmDllPath)) - { - log.Debug("Trying to load JVM dll from [option={0}, path={1}]...", dllPath.Key, dllPath.Value); - - var errInfo = LoadDll(dllPath.Value, FileJvmDll); - if (errInfo == null) - { - log.Debug("jvm.dll successfully loaded from [option={0}, path={1}]", dllPath.Key, dllPath.Value); - return; - } - - var message = string.Format(CultureInfo.InvariantCulture, "[option={0}, path={1}, error={2}]", - dllPath.Key, dllPath.Value, errInfo); - messages.Add(message); - - log.Debug("Failed to load jvm.dll: " + message); - - if (dllPath.Value == configJvmDllPath) - break; // if configJvmDllPath is specified and is invalid - do not try other options - } - - if (!messages.Any()) // not loaded and no messages - everything was null - messages.Add(string.Format(CultureInfo.InvariantCulture, - "Please specify IgniteConfiguration.JvmDllPath or {0}.", EnvJavaHome)); - - if (messages.Count == 1) - throw new IgniteException(string.Format(CultureInfo.InvariantCulture, "Failed to load {0} ({1})", - FileJvmDll, messages[0])); - - var combinedMessage = - messages.Aggregate((x, y) => string.Format(CultureInfo.InvariantCulture, "{0}\n{1}", x, y)); - - throw new IgniteException(string.Format(CultureInfo.InvariantCulture, "Failed to load {0}:\n{1}", - FileJvmDll, combinedMessage)); - } - - /// <summary> - /// Try loading DLLs first using file path, then using it's simple name. - /// </summary> - /// <param name="filePath"></param> - /// <param name="simpleName"></param> - /// <returns>Null in case of success, error info in case of failure.</returns> - private static string LoadDll(string filePath, string simpleName) - { - string res = null; - - if (filePath != null) - { - res = DllLoader.Load(filePath); - - if (res == null) - { - return null; // Success. - } - } - - // Failed to load using file path, fallback to simple name. - var res2 = DllLoader.Load(simpleName); - - if (res2 == null) - { - return null; // Success. - } - - return res; - } - - /// <summary> - /// Gets the JVM DLL paths in order of lookup priority. - /// </summary> - private static IEnumerable<KeyValuePair<string, string>> GetJvmDllPaths(string configJvmDllPath) - { - if (!string.IsNullOrEmpty(configJvmDllPath)) - { - yield return new KeyValuePair<string, string>("IgniteConfiguration.JvmDllPath", configJvmDllPath); - } - - var javaHomeDir = Environment.GetEnvironmentVariable(EnvJavaHome); - - if (!string.IsNullOrEmpty(javaHomeDir)) - { - foreach (var path in JvmDllLookupPaths) - { - yield return - new KeyValuePair<string, string>(EnvJavaHome, Path.Combine(javaHomeDir, path, FileJvmDll)); - } - } - - foreach (var keyValuePair in GetJvmDllPathsFromRegistry().Concat(GetJvmDllPathsFromSymlink())) - { - yield return keyValuePair; - } - } - - /// <summary> - /// Gets Jvm dll paths from Windows registry. - /// </summary> - private static IEnumerable<KeyValuePair<string, string>> GetJvmDllPathsFromRegistry() - { - if (!Os.IsWindows) - { - yield break; - } - - foreach (var regPath in JreRegistryKeys) - { - using (var jSubKey = Registry.LocalMachine.OpenSubKey(regPath)) - { - if (jSubKey == null) - continue; - - var curVer = jSubKey.GetValue("CurrentVersion") as string; - - // Current version comes first - var versions = new[] {curVer}.Concat(jSubKey.GetSubKeyNames().Where(x => x != curVer)); - foreach (var ver in versions.Where(v => !string.IsNullOrEmpty(v))) - { - using (var verKey = jSubKey.OpenSubKey(ver)) - { - var dllPath = verKey == null ? null : verKey.GetValue("RuntimeLib") as string; - - if (dllPath != null) - yield return new KeyValuePair<string, string>(verKey.Name, dllPath); - } - } - } - } - } - - /// <summary> - /// Gets the Jvm dll paths from symlink. - /// </summary> - private static IEnumerable<KeyValuePair<string, string>> GetJvmDllPathsFromSymlink() - { - if (Os.IsWindows) - { - yield break; - } - - const string javaExec = "/usr/bin/java"; - if (!File.Exists(javaExec)) - { - yield break; - } - - var file = Shell.BashExecute("readlink -f /usr/bin/java"); - // /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java - - var dir = Path.GetDirectoryName(file); - // /usr/lib/jvm/java-8-openjdk-amd64/jre/bin - - if (dir == null) - { - yield break; - } - - var libFolder = Path.GetFullPath(Path.Combine(dir, "../lib/")); - if (!Directory.Exists(libFolder)) - { - yield break; - } - - // Predefined path: /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so - yield return new KeyValuePair<string, string>(javaExec, - Path.Combine(libFolder, "amd64", "server", FileJvmDll)); - - // Last resort - custom paths: - foreach (var f in Directory.GetFiles(libFolder, FileJvmDll, SearchOption.AllDirectories)) - { - yield return new KeyValuePair<string, string>(javaExec, f); - } - } /// <summary> /// Creates a uniquely named, empty temporary directory on disk and returns the full path of that directory. http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Shell.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Shell.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Shell.cs index e48c8fe..45678f0 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Shell.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Shell.cs @@ -18,10 +18,12 @@ namespace Apache.Ignite.Core.Impl { using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; /// <summary> /// Shell utils (cmd/bash). /// </summary> + [ExcludeFromCodeCoverage] internal static class Shell { /// <summary> http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/DllLoader.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/DllLoader.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/DllLoader.cs deleted file mode 100644 index 61de8e4..0000000 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/DllLoader.cs +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Runtime.InteropServices; - - /// <summary> - /// Dynamically loads unmanaged DLLs with respect to current platform. - /// </summary> - internal static class DllLoader - { - /** Lazy symbol binding. */ - private const int RtldLazy = 1; - - /** Global symbol access. */ - private const int RtldGlobal = 8; - - /// <summary> - /// ERROR_BAD_EXE_FORMAT constant. - /// </summary> - // ReSharper disable once InconsistentNaming - private const int ERROR_BAD_EXE_FORMAT = 193; - - /// <summary> - /// ERROR_MOD_NOT_FOUND constant. - /// </summary> - // ReSharper disable once InconsistentNaming - private const int ERROR_MOD_NOT_FOUND = 126; - - /// <summary> - /// Loads specified DLL. - /// </summary> - /// <returns>Null when successful; error message otherwise.</returns> - public static string Load(string dllPath) - { - if (Os.IsWindows) - { - return NativeMethodsWindows.LoadLibrary(dllPath) == IntPtr.Zero - ? FormatWin32Error(Marshal.GetLastWin32Error()) ?? "Unknown error" - : null; - } - - if (Os.IsLinux) - { - if (Os.IsMono) - { - return NativeMethodsMono.dlopen(dllPath, RtldGlobal | RtldLazy) == IntPtr.Zero - ? GetErrorText(NativeMethodsMono.dlerror()) - : null; - } - - if (Os.IsNetCore) - { - return NativeMethodsCore.dlopen(dllPath, RtldGlobal | RtldLazy) == IntPtr.Zero - ? GetErrorText(NativeMethodsCore.dlerror()) - : null; - } - - return NativeMethodsLinux.dlopen(dllPath, RtldGlobal | RtldLazy) == IntPtr.Zero - ? GetErrorText(NativeMethodsLinux.dlerror()) - : null; - } - - throw new InvalidOperationException("Unsupported OS: " + Environment.OSVersion); - } - - /// <summary> - /// Gets the error text. - /// </summary> - private static string GetErrorText(IntPtr charPtr) - { - return Marshal.PtrToStringAnsi(charPtr) ?? "Unknown error"; - } - - /// <summary> - /// Formats the Win32 error. - /// </summary> - [ExcludeFromCodeCoverage] - private static string FormatWin32Error(int errorCode) - { - if (errorCode == ERROR_BAD_EXE_FORMAT) - { - var mode = Environment.Is64BitProcess ? "x64" : "x86"; - - return string.Format("DLL could not be loaded (193: ERROR_BAD_EXE_FORMAT). " + - "This is often caused by x64/x86 mismatch. " + - "Current process runs in {0} mode, and DLL is not {0}.", mode); - } - - if (errorCode == ERROR_MOD_NOT_FOUND) - { - return "DLL could not be loaded (126: ERROR_MOD_NOT_FOUND). " + - "This can be caused by missing dependencies. "; - } - - return string.Format("{0}: {1}", errorCode, new Win32Exception(errorCode).Message); - } - - /// <summary> - /// Windows. - /// </summary> - private static class NativeMethodsWindows - { - [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, - ThrowOnUnmappableChar = true)] - internal static extern IntPtr LoadLibrary(string filename); - } - - /// <summary> - /// Linux. - /// </summary> - private static class NativeMethodsLinux - { - [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("libdl.so", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, - ThrowOnUnmappableChar = true)] - internal static extern IntPtr dlopen(string filename, int flags); - - [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("libdl.so", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, - ThrowOnUnmappableChar = true)] - internal static extern IntPtr dlerror(); - } - - /// <summary> - /// libdl.so depends on libc6-dev on Linux, use Mono instead. - /// </summary> - private static class NativeMethodsMono - { - [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("__Internal", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, - ThrowOnUnmappableChar = true)] - internal static extern IntPtr dlopen(string filename, int flags); - - [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("__Internal", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, - ThrowOnUnmappableChar = true)] - internal static extern IntPtr dlerror(); - } - - /// <summary> - /// libdl.so depends on libc6-dev on Linux, use libcoreclr instead. - /// </summary> - private static class NativeMethodsCore - { - [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("libcoreclr.so", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, - ThrowOnUnmappableChar = true)] - internal static extern IntPtr dlopen(string filename, int flags); - - [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("libcoreclr.so", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, - ThrowOnUnmappableChar = true)] - internal static extern IntPtr dlerror(); - } - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/DllLoader.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 new file mode 100644 index 0000000..578135d --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/DllLoader.cs @@ -0,0 +1,210 @@ +/* + * 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.Jni +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.InteropServices; + + /// <summary> + /// Dynamically loads unmanaged DLLs with respect to current platform. + /// </summary> + internal static class DllLoader + { + /** Lazy symbol binding. */ + private const int RtldLazy = 1; + + /** Global symbol access. */ + private const int RtldGlobal = 8; + + /// <summary> + /// ERROR_BAD_EXE_FORMAT constant. + /// </summary> + // ReSharper disable once InconsistentNaming + private const int ERROR_BAD_EXE_FORMAT = 193; + + /// <summary> + /// ERROR_MOD_NOT_FOUND constant. + /// </summary> + // ReSharper disable once InconsistentNaming + private const int ERROR_MOD_NOT_FOUND = 126; + + /// <summary> + /// Loads specified DLL. + /// </summary> + /// <returns>Library handle and error message.</returns> + public static KeyValuePair<IntPtr, string> Load(string dllPath) + { + if (Os.IsWindows) + { + var ptr = NativeMethodsWindows.LoadLibrary(dllPath); + return new KeyValuePair<IntPtr, string>(ptr, ptr == IntPtr.Zero + ? FormatWin32Error(Marshal.GetLastWin32Error()) ?? "Unknown error" + : null); + } + + if (Os.IsMacOs) + { + var ptr = NativeMethodsMacOs.dlopen(dllPath, RtldGlobal | RtldLazy); + return new KeyValuePair<IntPtr, string>(ptr, ptr == IntPtr.Zero + ? GetErrorText(NativeMethodsMacOs.dlerror()) + : null); + } + + if (Os.IsLinux) + { + if (Os.IsMono) + { + var ptr = NativeMethodsMono.dlopen(dllPath, RtldGlobal | RtldLazy); + return new KeyValuePair<IntPtr, string>(ptr, ptr == IntPtr.Zero + ? GetErrorText(NativeMethodsMono.dlerror()) + : null); + } + + if (Os.IsNetCore) + { + var ptr = NativeMethodsCore.dlopen(dllPath, RtldGlobal | RtldLazy); + return new KeyValuePair<IntPtr, string>(ptr, ptr == IntPtr.Zero + ? GetErrorText(NativeMethodsCore.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); + } + + /// <summary> + /// Gets the error text. + /// </summary> + private static string GetErrorText(IntPtr charPtr) + { + return Marshal.PtrToStringAnsi(charPtr) ?? "Unknown error"; + } + + /// <summary> + /// Formats the Win32 error. + /// </summary> + [ExcludeFromCodeCoverage] + private static string FormatWin32Error(int errorCode) + { + if (errorCode == ERROR_BAD_EXE_FORMAT) + { + var mode = Environment.Is64BitProcess ? "x64" : "x86"; + + return string.Format("DLL could not be loaded (193: ERROR_BAD_EXE_FORMAT). " + + "This is often caused by x64/x86 mismatch. " + + "Current process runs in {0} mode, and DLL is not {0}.", mode); + } + + if (errorCode == ERROR_MOD_NOT_FOUND) + { + return "DLL could not be loaded (126: ERROR_MOD_NOT_FOUND). " + + "This can be caused by missing dependencies. "; + } + + return string.Format("{0}: {1}", errorCode, new Win32Exception(errorCode).Message); + } + + /// <summary> + /// Windows. + /// </summary> + private static class NativeMethodsWindows + { + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, + ThrowOnUnmappableChar = true)] + internal static extern IntPtr LoadLibrary(string filename); + } + + /// <summary> + /// Linux. + /// </summary> + private static class NativeMethodsLinux + { + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("libdl.so", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, + ThrowOnUnmappableChar = true)] + internal static extern IntPtr dlopen(string filename, int flags); + + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("libdl.so", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, + ThrowOnUnmappableChar = true)] + internal static extern IntPtr dlerror(); + } + + /// <summary> + /// libdl.so depends on libc6-dev on Linux, use Mono instead. + /// </summary> + private static class NativeMethodsMono + { + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("__Internal", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, + ThrowOnUnmappableChar = true)] + internal static extern IntPtr dlopen(string filename, int flags); + + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("__Internal", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, + ThrowOnUnmappableChar = true)] + internal static extern IntPtr dlerror(); + } + + /// <summary> + /// libdl.so depends on libc6-dev on Linux, use libcoreclr instead. + /// </summary> + private static class NativeMethodsCore + { + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("libcoreclr.so", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, + ThrowOnUnmappableChar = true)] + internal static extern IntPtr dlopen(string filename, int flags); + + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("libcoreclr.so", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, + ThrowOnUnmappableChar = true)] + internal static extern IntPtr dlerror(); + } + + /// <summary> + /// macOs uses "libSystem.dylib". + /// </summary> + internal static class NativeMethodsMacOs + { + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("libSystem.dylib", CharSet = CharSet.Ansi, BestFitMapping = false, + ThrowOnUnmappableChar = true)] + internal static extern IntPtr dlopen(string filename, int flags); + + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("libSystem.dylib", CharSet = CharSet.Ansi, BestFitMapping = false, + ThrowOnUnmappableChar = true)] + internal static extern IntPtr dlerror(); + + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("libSystem.dylib", CharSet = CharSet.Ansi, BestFitMapping = false, + ThrowOnUnmappableChar = true)] + internal static extern IntPtr dlsym(IntPtr handle, string symbol); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/Jvm.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/Jvm.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/Jvm.cs index ebff15b..3699751 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/Jvm.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/Jvm.cs @@ -126,11 +126,11 @@ namespace Apache.Ignite.Core.Impl.Unmanaged.Jni /// <summary> /// Gets the JVM. /// </summary> - public static Jvm Get() + public static Jvm Get(bool ignoreMissing = false) { var res = _instance; - if (res == null) + if (res == null && !ignoreMissing) { throw new IgniteException("JVM has not been created."); } @@ -205,7 +205,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged.Jni int existingJvmCount; // Use existing JVM if present. - var res = JniNativeMethods.JNI_GetCreatedJavaVMs(out jvm, 1, out existingJvmCount); + var res = JvmDll.Instance.GetCreatedJvms(out jvm, 1, out existingJvmCount); if (res != JniResult.Success) { throw new IgniteException("JNI_GetCreatedJavaVMs failed: " + res); @@ -238,7 +238,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged.Jni } IntPtr env; - res = JniNativeMethods.JNI_CreateJavaVM(out jvm, out env, &args); + res = JvmDll.Instance.CreateJvm(out jvm, out env, &args); if (res != JniResult.Success) { throw new IgniteException("JNI_CreateJavaVM failed: " + res); @@ -255,78 +255,6 @@ namespace Apache.Ignite.Core.Impl.Unmanaged.Jni del = (T) (object) Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)); } - /// <summary> - /// JavaVMOption. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable")] - [StructLayout(LayoutKind.Sequential, Pack = 0)] - private struct JvmOption - { - public IntPtr optionString; - private readonly IntPtr extraInfo; - } - - /// <summary> - /// JavaVMInitArgs. - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 0)] - private struct JvmInitArgs - { - public int version; - public int nOptions; - public JvmOption* options; - private readonly byte ignoreUnrecognized; - } - - private static class JniNativeMethods - { - internal static JniResult JNI_CreateJavaVM(out IntPtr pvm, out IntPtr penv, - JvmInitArgs* args) - { - return Os.IsWindows - ? JniNativeMethodsWindows.JNI_CreateJavaVM(out pvm, out penv, args) - : JniNativeMethodsLinux.JNI_CreateJavaVM(out pvm, out penv, args); - } - - internal static JniResult JNI_GetCreatedJavaVMs(out IntPtr pvm, int size, out int size2) - { - return Os.IsWindows - ? JniNativeMethodsWindows.JNI_GetCreatedJavaVMs(out pvm, size, out size2) - : JniNativeMethodsLinux.JNI_GetCreatedJavaVMs(out pvm, size, out size2); - } - } - - /// <summary> - /// DLL imports. - /// </summary> - private static class JniNativeMethodsWindows - { - [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("jvm.dll", CallingConvention = CallingConvention.StdCall)] - internal static extern JniResult JNI_CreateJavaVM(out IntPtr pvm, out IntPtr penv, - JvmInitArgs* args); - - [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("jvm.dll", CallingConvention = CallingConvention.StdCall)] - internal static extern JniResult JNI_GetCreatedJavaVMs(out IntPtr pvm, int size, - [Out] out int size2); - } - - /// <summary> - /// DLL imports. - /// </summary> - private static class JniNativeMethodsLinux - { - [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("libjvm.so", CallingConvention = CallingConvention.StdCall)] - internal static extern JniResult JNI_CreateJavaVM(out IntPtr pvm, out IntPtr penv, - JvmInitArgs* args); - - [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("libjvm.so", CallingConvention = CallingConvention.StdCall)] - internal static extern JniResult JNI_GetCreatedJavaVMs(out IntPtr pvm, int size, - [Out] out int size2); - } /// <summary> /// Provides access to <see cref="Callbacks"/> instance in the default AppDomain. http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/JvmDll.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/JvmDll.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/JvmDll.cs new file mode 100644 index 0000000..28c85ef --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/JvmDll.cs @@ -0,0 +1,414 @@ +/* + * 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.Jni +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using Apache.Ignite.Core.Common; + using Apache.Ignite.Core.Log; + using Microsoft.Win32; + + /// <summary> + /// Jvm.dll loader (libjvm.so on Linux, libjvm.dylib on macOs). + /// </summary> + internal class JvmDll + { + /** Cached instance. */ + private static JvmDll _instance; + + /** Environment variable: JAVA_HOME. */ + private const string EnvJavaHome = "JAVA_HOME"; + + /** Lookup paths. */ + private static readonly string[] JvmDllLookupPaths = Os.IsWindows + ? new[] + { + // JRE paths + @"bin\server", + @"bin\client", + + // JDK paths + @"jre\bin\server", + @"jre\bin\client", + @"jre\bin\default" + } + : new[] + { + // JRE paths + "lib/server", + "lib/client", + "lib/amd64/server", + "lib/amd64/client", + + // JDK paths + "jre/lib/server", + "jre/lib/client", + "jre/lib/amd64/server", + "jre/lib/amd64/client" + }; + + /** Registry lookup paths. */ + private static readonly string[] JreRegistryKeys = + { + @"Software\JavaSoft\Java Runtime Environment", + @"Software\Wow6432Node\JavaSoft\Java Runtime Environment" + }; + + /** Jvm dll file name. */ + internal static readonly string FileJvmDll = Os.IsWindows + ? "jvm.dll" + : Os.IsMacOs + ? "libjvm.dylib" + : "libjvm.so"; + + /** */ + private unsafe delegate JniResult CreateJvmDel(out IntPtr pvm, out IntPtr penv, JvmInitArgs* args); + + /** */ + private delegate JniResult GetCreatedJvmsDel(out IntPtr pvm, int size, out int size2); + + /** */ + private readonly CreateJvmDel _createJvm; + + /** */ + private readonly GetCreatedJvmsDel _getCreatedJvms; + + /// <summary> + /// Initializes a new instance of the <see cref="JvmDll"/> class. + /// </summary> + private unsafe JvmDll(IntPtr ptr) + { + if (Os.IsMacOs) + { + if (ptr == IntPtr.Zero) + { + // Retrieve already loaded dll by name. + // This happens in default AppDomain when Ignite starts in another domain. + var res = DllLoader.Load(FileJvmDll); + ptr = res.Key; + + if (res.Key == IntPtr.Zero) + { + throw new IgniteException( + string.Format("{0} has not been loaded: {1}", FileJvmDll, res.Value)); + } + } + + // dlopen + DllImport combo does not work on macOs, so we have to call dlsym manually. + var createJvmPtr = DllLoader.NativeMethodsMacOs.dlsym(ptr, "JNI_CreateJavaVM"); + _createJvm = (CreateJvmDel) Marshal.GetDelegateForFunctionPointer(createJvmPtr, typeof(CreateJvmDel)); + + var getJvmsPtr = DllLoader.NativeMethodsMacOs.dlsym(ptr, "JNI_GetCreatedJavaVMs"); + _getCreatedJvms = (GetCreatedJvmsDel) Marshal.GetDelegateForFunctionPointer(getJvmsPtr, + typeof(GetCreatedJvmsDel)); + } + else if (Os.IsWindows) + { + _createJvm = JniNativeMethodsWindows.JNI_CreateJavaVM; + _getCreatedJvms = JniNativeMethodsWindows.JNI_GetCreatedJavaVMs; + } + else + { + _createJvm = JniNativeMethodsLinux.JNI_CreateJavaVM; + _getCreatedJvms = JniNativeMethodsLinux.JNI_GetCreatedJavaVMs; + } + } + + /// <summary> + /// Gets the instance. + /// </summary> + public static JvmDll Instance + { + get { return _instance ?? (_instance = new JvmDll(IntPtr.Zero)); } + } + + /// <summary> + /// Creates the JVM. + /// </summary> + public unsafe JniResult CreateJvm(out IntPtr pvm, out IntPtr penv, JvmInitArgs* args) + { + return _createJvm(out pvm, out penv, args); + } + + /// <summary> + /// Gets the created JVMS. + /// </summary> + public JniResult GetCreatedJvms(out IntPtr pvm, int size, out int size2) + { + return _getCreatedJvms(out pvm, size, out size2); + } + + /// <summary> + /// Loads the JVM DLL into process memory. + /// </summary> + public static void Load(string configJvmDllPath, ILogger log) + { + // Load only once. + // Locking is performed by the caller three, omit here. + if (_instance != null) + { + log.Debug("JNI dll is already loaded."); + return; + } + + var messages = new List<string>(); + foreach (var dllPath in GetJvmDllPaths(configJvmDllPath)) + { + log.Debug("Trying to load {0} from [option={1}, path={2}]...", FileJvmDll, dllPath.Key, dllPath.Value); + + var res = LoadDll(dllPath.Value, FileJvmDll); + if (res.Key != IntPtr.Zero) + { + log.Debug("{0} successfully loaded from [option={1}, path={2}]", + FileJvmDll, dllPath.Key, dllPath.Value); + + _instance = new JvmDll(res.Key); + + return; + } + + var message = string.Format(CultureInfo.InvariantCulture, "[option={0}, path={1}, error={2}]", + dllPath.Key, dllPath.Value, res.Value); + messages.Add(message); + + log.Debug("Failed to load {0}: {1}", FileJvmDll, message); + + if (dllPath.Value == configJvmDllPath) + break; // if configJvmDllPath is specified and is invalid - do not try other options + } + + if (!messages.Any()) // not loaded and no messages - everything was null + { + messages.Add(string.Format(CultureInfo.InvariantCulture, + "Please specify IgniteConfiguration.JvmDllPath or {0}.", EnvJavaHome)); + } + + if (messages.Count == 1) + { + throw new IgniteException(string.Format(CultureInfo.InvariantCulture, "Failed to load {0} ({1})", + FileJvmDll, messages[0])); + } + + var combinedMessage = + messages.Aggregate((x, y) => string.Format(CultureInfo.InvariantCulture, "{0}\n{1}", x, y)); + + throw new IgniteException(string.Format(CultureInfo.InvariantCulture, "Failed to load {0}:\n{1}", + FileJvmDll, combinedMessage)); + } + + /// <summary> + /// Try loading DLLs first using file path, then using it's simple name. + /// </summary> + /// <param name="filePath"></param> + /// <param name="simpleName"></param> + /// <returns>Null in case of success, error info in case of failure.</returns> + private static KeyValuePair<IntPtr, string> LoadDll(string filePath, string simpleName) + { + var res = new KeyValuePair<IntPtr, string>(); + + if (filePath != null) + { + res = DllLoader.Load(filePath); + + if (res.Key != IntPtr.Zero) + { + return res; // Success. + } + } + + // Failed to load using file path, fallback to simple name. + var res2 = DllLoader.Load(simpleName); + + if (res2.Key != IntPtr.Zero) + { + return res2; // Success. + } + + return res.Value != null ? res : res2; + } + + /// <summary> + /// Gets the JVM DLL paths in order of lookup priority. + /// </summary> + private static IEnumerable<KeyValuePair<string, string>> GetJvmDllPaths(string configJvmDllPath) + { + if (!string.IsNullOrEmpty(configJvmDllPath)) + { + yield return new KeyValuePair<string, string>("IgniteConfiguration.JvmDllPath", configJvmDllPath); + } + + var javaHomeDir = Environment.GetEnvironmentVariable(EnvJavaHome); + + if (!string.IsNullOrEmpty(javaHomeDir)) + { + foreach (var path in JvmDllLookupPaths) + { + yield return + new KeyValuePair<string, string>(EnvJavaHome, Path.Combine(javaHomeDir, path, FileJvmDll)); + } + } + + foreach (var keyValuePair in + GetJvmDllPathsWindows() + .Concat(GetJvmDllPathsLinux()) + .Concat(GetJvmDllPathsMacOs())) + { + yield return keyValuePair; + } + } + + /// <summary> + /// Gets Jvm dll paths from Windows registry. + /// </summary> + private static IEnumerable<KeyValuePair<string, string>> GetJvmDllPathsWindows() + { + if (!Os.IsWindows) + { + yield break; + } + + foreach (var regPath in JreRegistryKeys) + { + using (var jSubKey = Registry.LocalMachine.OpenSubKey(regPath)) + { + if (jSubKey == null) + continue; + + var curVer = jSubKey.GetValue("CurrentVersion") as string; + + // Current version comes first + var versions = new[] {curVer}.Concat(jSubKey.GetSubKeyNames().Where(x => x != curVer)); + + foreach (var ver in versions.Where(v => !string.IsNullOrEmpty(v))) + { + using (var verKey = jSubKey.OpenSubKey(ver)) + { + var dllPath = verKey == null ? null : verKey.GetValue("RuntimeLib") as string; + + if (dllPath != null) + yield return new KeyValuePair<string, string>(verKey.Name, dllPath); + } + } + } + } + } + + /// <summary> + /// Gets the Jvm dll paths from /usr/bin/java symlink. + /// </summary> + private static IEnumerable<KeyValuePair<string, string>> GetJvmDllPathsLinux() + { + if (Os.IsWindows || Os.IsMacOs) + { + yield break; + } + + const string javaExec = "/usr/bin/java"; + if (!File.Exists(javaExec)) + { + yield break; + } + + var file = Shell.BashExecute("readlink -f /usr/bin/java"); + // /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java + + var dir = Path.GetDirectoryName(file); + // /usr/lib/jvm/java-8-openjdk-amd64/jre/bin + + if (dir == null) + { + yield break; + } + + var libFolder = Path.GetFullPath(Path.Combine(dir, "../lib/")); + if (!Directory.Exists(libFolder)) + { + yield break; + } + + // Predefined path: /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so + yield return new KeyValuePair<string, string>(javaExec, + Path.Combine(libFolder, "amd64", "server", FileJvmDll)); + + // Last resort - custom paths: + foreach (var f in Directory.GetFiles(libFolder, FileJvmDll, SearchOption.AllDirectories)) + { + yield return new KeyValuePair<string, string>(javaExec, f); + } + } + + /// <summary> + /// Gets the JVM DLL paths on macOs. + /// </summary> + private static IEnumerable<KeyValuePair<string, string>> GetJvmDllPathsMacOs() + { + const string jvmDir = "/Library/Java/JavaVirtualMachines"; + + if (!Directory.Exists(jvmDir)) + { + yield break; + } + + const string subDir = "Contents/Home"; + + foreach (var dir in Directory.GetDirectories(jvmDir)) + { + foreach (var path in JvmDllLookupPaths) + { + yield return + new KeyValuePair<string, string>(dir, Path.Combine(dir, subDir, path, FileJvmDll)); + } + } + } + + /// <summary> + /// DLL imports. + /// </summary> + private static unsafe class JniNativeMethodsWindows + { + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("jvm.dll", CallingConvention = CallingConvention.StdCall)] + internal static extern JniResult JNI_CreateJavaVM(out IntPtr pvm, out IntPtr penv, JvmInitArgs* args); + + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("jvm.dll", CallingConvention = CallingConvention.StdCall)] + internal static extern JniResult JNI_GetCreatedJavaVMs(out IntPtr pvm, int size, + [Out] out int size2); + } + + /// <summary> + /// DLL imports. + /// </summary> + private static unsafe class JniNativeMethodsLinux + { + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("libjvm.so", CallingConvention = CallingConvention.StdCall)] + internal static extern JniResult JNI_CreateJavaVM(out IntPtr pvm, out IntPtr penv, JvmInitArgs* args); + + [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("libjvm.so", CallingConvention = CallingConvention.StdCall)] + internal static extern JniResult JNI_GetCreatedJavaVMs(out IntPtr pvm, int size, + [Out] out int size2); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/JvmInitArgs.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/JvmInitArgs.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/JvmInitArgs.cs new file mode 100644 index 0000000..0b3c655 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/JvmInitArgs.cs @@ -0,0 +1,33 @@ +/* + * 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.Jni +{ + using System.Runtime.InteropServices; + + /// <summary> + /// JavaVMInitArgs. + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 0)] + internal unsafe struct JvmInitArgs + { + public int version; + public int nOptions; + public JvmOption* options; + private readonly byte ignoreUnrecognized; + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/JvmOption.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/JvmOption.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/JvmOption.cs new file mode 100644 index 0000000..3e239d1 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Jni/JvmOption.cs @@ -0,0 +1,34 @@ +/* + * 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.Jni +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.InteropServices; + + /// <summary> + /// JavaVMOption. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable")] + [StructLayout(LayoutKind.Sequential, Pack = 0)] + internal struct JvmOption + { + public IntPtr optionString; + private readonly IntPtr extraInfo; + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Os.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Os.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Os.cs index 22ab447..535aa4c 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Os.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/Os.cs @@ -39,6 +39,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged || platform == PlatformID.Win32S || platform == PlatformID.Win32Windows; + IsMacOs = IsLinux && Shell.BashExecute("uname").Contains("Darwin"); IsMono = Type.GetType("Mono.Runtime") != null; IsNetCore = !IsMono; } @@ -62,5 +63,10 @@ namespace Apache.Ignite.Core.Impl.Unmanaged /// Linux. /// </summary> public static bool IsLinux { get; private set; } + + /// <summary> + /// MacOs. + /// </summary> + public static bool IsMacOs { get; private set; } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/0b849abc/modules/platforms/dotnet/Apache.Ignite.ndproj ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.ndproj b/modules/platforms/dotnet/Apache.Ignite.ndproj index aa30019..5ed9e3c 100644 --- a/modules/platforms/dotnet/Apache.Ignite.ndproj +++ b/modules/platforms/dotnet/Apache.Ignite.ndproj @@ -2478,7 +2478,7 @@ select new { <Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><