IGNITE-3363 Forward Java output to the .NET console This closes #833
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/4e468a8c Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/4e468a8c Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/4e468a8c Branch: refs/heads/ignite-1232 Commit: 4e468a8cda774b1712231a0c356431f5a0910e88 Parents: b7d6ef7 Author: Pavel Tupitsyn <[email protected]> Authored: Wed Jul 6 17:50:42 2016 +0300 Committer: Pavel Tupitsyn <[email protected]> Committed: Wed Jul 6 17:50:42 2016 +0300 ---------------------------------------------------------------------- .../platform/PlatformAbstractBootstrap.java | 5 + .../processors/platform/PlatformBootstrap.java | 6 + .../processors/platform/PlatformIgnition.java | 7 +- .../callback/PlatformCallbackGateway.java | 10 ++ .../callback/PlatformCallbackUtils.java | 8 + .../dotnet/PlatformDotNetBootstrap.java | 12 ++ .../dotnet/PlatformDotNetConsoleStream.java | 54 ++++++ .../cpp/jni/include/ignite/jni/exports.h | 3 + .../platforms/cpp/jni/include/ignite/jni/java.h | 6 + modules/platforms/cpp/jni/project/vs/module.def | 2 + modules/platforms/cpp/jni/src/exports.cpp | 8 + modules/platforms/cpp/jni/src/java.cpp | 57 +++++- .../Apache.Ignite.Core.Tests.csproj | 1 + .../ConsoleRedirectTest.cs | 177 +++++++++++++++++++ .../Apache.Ignite.Core.Tests/TestRunner.cs | 21 ++- .../Impl/Unmanaged/IgniteJniNativeMethods.cs | 6 + .../Impl/Unmanaged/UnmanagedCallbacks.cs | 36 +++- .../Impl/Unmanaged/UnmanagedUtils.cs | 16 ++ 18 files changed, 426 insertions(+), 9 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformAbstractBootstrap.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformAbstractBootstrap.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformAbstractBootstrap.java index 6fc46c4..a28677f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformAbstractBootstrap.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformAbstractBootstrap.java @@ -49,6 +49,11 @@ public abstract class PlatformAbstractBootstrap implements PlatformBootstrap { } } + /** {@inheritDoc} */ + @Override public void init() { + // No-op. + } + /** * Get configuration transformer closure. * http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformBootstrap.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformBootstrap.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformBootstrap.java index 956a02d..07847a7 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformBootstrap.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformBootstrap.java @@ -36,4 +36,10 @@ public interface PlatformBootstrap { */ public PlatformProcessor start(IgniteConfiguration cfg, @Nullable GridSpringResourceContext springCtx, long envPtr, long dataPtr); + + /** + * Init the bootstrap. + * + */ + public void init(); } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformIgnition.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformIgnition.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformIgnition.java index 928aa66..d754b7c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformIgnition.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformIgnition.java @@ -61,6 +61,11 @@ public class PlatformIgnition { Thread.currentThread().setContextClassLoader(PlatformProcessor.class.getClassLoader()); try { + PlatformBootstrap bootstrap = bootstrap(factoryId); + + // This should be done before Spring XML initialization so that redirected stream is picked up. + bootstrap.init(); + IgniteBiTuple<IgniteConfiguration, GridSpringResourceContext> cfg = configuration(springCfgPath); if (gridName != null) @@ -68,8 +73,6 @@ public class PlatformIgnition { else gridName = cfg.get1().getGridName(); - PlatformBootstrap bootstrap = bootstrap(factoryId); - PlatformProcessor proc = bootstrap.start(cfg.get1(), cfg.get2(), envPtr, dataPtr); PlatformProcessor old = instances.put(gridName, proc); http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackGateway.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackGateway.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackGateway.java index 88532fc..41d3802 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackGateway.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackGateway.java @@ -1040,6 +1040,16 @@ public class PlatformCallbackGateway { } /** + * Redirects the console output to platform. + * + * @param str String to write. + * @param isErr Whether this is stdErr or stdOut. + */ + public static void consoleWrite(String str, boolean isErr) { + PlatformCallbackUtils.consoleWrite(str, isErr); + } + + /** * Enter gateway. */ protected void enter() { http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackUtils.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackUtils.java index 7b36e5e..63c6682 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackUtils.java @@ -545,6 +545,14 @@ public class PlatformCallbackUtils { static native void affinityFunctionDestroy(long envPtr, long ptr); /** + * Redirects the console output. + * + * @param str String to write. + * @param isErr Whether this is stdErr or stdOut. + */ + static native void consoleWrite(String str, boolean isErr); + + /** * Private constructor. */ private PlatformCallbackUtils() { http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetBootstrap.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetBootstrap.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetBootstrap.java index 837ded9..9278246 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetBootstrap.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetBootstrap.java @@ -20,11 +20,23 @@ package org.apache.ignite.internal.processors.platform.dotnet; import org.apache.ignite.internal.processors.platform.PlatformAbstractBootstrap; import org.apache.ignite.internal.processors.platform.PlatformAbstractConfigurationClosure; +import java.io.PrintStream; + /** * Interop .Net bootstrap. */ public class PlatformDotNetBootstrap extends PlatformAbstractBootstrap { /** {@inheritDoc} */ + @Override public void init() { + // Initialize console propagation. + // This call is idempotent, doing it on each node start is fine. + System.setOut(new PrintStream(new PlatformDotNetConsoleStream(false))); + System.setErr(new PrintStream(new PlatformDotNetConsoleStream(true))); + + super.init(); + } + + /** {@inheritDoc} */ @Override protected PlatformAbstractConfigurationClosure closure(long envPtr) { return new PlatformDotNetConfigurationClosure(envPtr); } http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConsoleStream.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConsoleStream.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConsoleStream.java new file mode 100644 index 0000000..028c3ab --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConsoleStream.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +package org.apache.ignite.internal.processors.platform.dotnet; + +import org.apache.ignite.internal.processors.platform.callback.PlatformCallbackGateway; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Stream that writes to the .NET console. + */ +public class PlatformDotNetConsoleStream extends OutputStream { + /** Indicates whether this is an error stream. */ + private final boolean isErr; + + /** + * Ctor. + * + * @param err Error stream flag. + */ + public PlatformDotNetConsoleStream(boolean err) { + isErr = err; + } + + /** {@inheritDoc} */ + @Override public void write(byte[] b, int off, int len) throws IOException { + String s = new String(b, off, len); + + PlatformCallbackGateway.consoleWrite(s, isErr); + } + + /** {@inheritDoc} */ + @Override public void write(int b) throws IOException { + String s = String.valueOf((char) b); + + PlatformCallbackGateway.consoleWrite(s, isErr); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/platforms/cpp/jni/include/ignite/jni/exports.h ---------------------------------------------------------------------- diff --git a/modules/platforms/cpp/jni/include/ignite/jni/exports.h b/modules/platforms/cpp/jni/include/ignite/jni/exports.h index bf278e2..3f400fb 100644 --- a/modules/platforms/cpp/jni/include/ignite/jni/exports.h +++ b/modules/platforms/cpp/jni/include/ignite/jni/exports.h @@ -181,6 +181,9 @@ extern "C" { bool IGNITE_CALL IgniteListenableCancel(gcj::JniContext* ctx, void* obj); bool IGNITE_CALL IgniteListenableIsCancelled(gcj::JniContext* ctx, void* obj); + + void IGNITE_CALL IgniteSetConsoleHandler(gcj::ConsoleWriteHandler consoleHandler); + void IGNITE_CALL IgniteRemoveConsoleHandler(gcj::ConsoleWriteHandler consoleHandler); } #endif //_IGNITE_JNI_EXPORTS \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/platforms/cpp/jni/include/ignite/jni/java.h ---------------------------------------------------------------------- diff --git a/modules/platforms/cpp/jni/include/ignite/jni/java.h b/modules/platforms/cpp/jni/include/ignite/jni/java.h index 13e6e8d..41d7caa 100644 --- a/modules/platforms/cpp/jni/include/ignite/jni/java.h +++ b/modules/platforms/cpp/jni/include/ignite/jni/java.h @@ -105,6 +105,8 @@ namespace ignite typedef void(JNICALL *AffinityFunctionAssignPartitionsHandler)(void* target, long long ptr, long long inMemPtr, long long outMemPtr); typedef void(JNICALL *AffinityFunctionRemoveNodeHandler)(void* target, long long ptr, long long memPtr); typedef void(JNICALL *AffinityFunctionDestroyHandler)(void* target, long long ptr); + + typedef void(JNICALL *ConsoleWriteHandler)(const char* chars, int charsLen, unsigned char isErr); /** * JNI handlers holder. @@ -505,6 +507,8 @@ namespace ignite static int Reallocate(long long memPtr, int cap); static void Detach(); static void Release(jobject obj); + static void SetConsoleHandler(ConsoleWriteHandler consoleHandler); + static int RemoveConsoleHandler(ConsoleWriteHandler consoleHandler); jobject IgnitionStart(char* cfgPath, char* name, int factoryId, long long dataPtr); jobject IgnitionStart(char* cfgPath, char* name, int factoryId, long long dataPtr, JniErrorInfo* errInfo); @@ -757,6 +761,8 @@ namespace ignite JNIEXPORT void JNICALL JniAffinityFunctionAssignPartitions(JNIEnv *env, jclass cls, jlong envPtr, jlong ptr, jlong inMemPtr, jlong outMemPtr); JNIEXPORT void JNICALL JniAffinityFunctionRemoveNode(JNIEnv *env, jclass cls, jlong envPtr, jlong ptr, jlong memPtr); JNIEXPORT void JNICALL JniAffinityFunctionDestroy(JNIEnv *env, jclass cls, jlong envPtr, jlong ptr); + + JNIEXPORT void JNICALL JniConsoleWrite(JNIEnv *env, jclass cls, jstring str, jboolean isErr); } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/platforms/cpp/jni/project/vs/module.def ---------------------------------------------------------------------- diff --git a/modules/platforms/cpp/jni/project/vs/module.def b/modules/platforms/cpp/jni/project/vs/module.def index cfe658c..ddddace 100644 --- a/modules/platforms/cpp/jni/project/vs/module.def +++ b/modules/platforms/cpp/jni/project/vs/module.def @@ -134,4 +134,6 @@ IgniteProcessorCreateNearCache @131 IgniteProcessorGetOrCreateNearCache @132 IgniteProcessorGetCacheNames @133 IgniteProjectionForServers @134 +IgniteSetConsoleHandler @135 +IgniteRemoveConsoleHandler @136 \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/platforms/cpp/jni/src/exports.cpp ---------------------------------------------------------------------- diff --git a/modules/platforms/cpp/jni/src/exports.cpp b/modules/platforms/cpp/jni/src/exports.cpp index 6c4760c..2950d15 100644 --- a/modules/platforms/cpp/jni/src/exports.cpp +++ b/modules/platforms/cpp/jni/src/exports.cpp @@ -558,4 +558,12 @@ extern "C" { bool IGNITE_CALL IgniteListenableIsCancelled(gcj::JniContext* ctx, void* obj) { return ctx->ListenableIsCancelled(static_cast<jobject>(obj)); } + + void IGNITE_CALL IgniteSetConsoleHandler(gcj::ConsoleWriteHandler consoleHandler) { + gcj::JniContext::SetConsoleHandler(consoleHandler); + } + + void IGNITE_CALL IgniteRemoveConsoleHandler(gcj::ConsoleWriteHandler consoleHandler) { + gcj::JniContext::RemoveConsoleHandler(consoleHandler); + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/platforms/cpp/jni/src/java.cpp ---------------------------------------------------------------------- diff --git a/modules/platforms/cpp/jni/src/java.cpp b/modules/platforms/cpp/jni/src/java.cpp index 87f1e03..685afd3 100644 --- a/modules/platforms/cpp/jni/src/java.cpp +++ b/modules/platforms/cpp/jni/src/java.cpp @@ -15,9 +15,11 @@ * limitations under the License. */ -#include <cstring> #include <string> #include <exception> +#include <vector> +#include <algorithm> +#include <stdexcept> #include "ignite/jni/utils.h" #include "ignite/common/concurrent.h" @@ -368,6 +370,8 @@ namespace ignite JniMethod M_PLATFORM_CALLBACK_UTILS_AFFINITY_FUNCTION_ASSIGN_PARTITIONS = JniMethod("affinityFunctionAssignPartitions", "(JJJJ)V", true); JniMethod M_PLATFORM_CALLBACK_UTILS_AFFINITY_FUNCTION_REMOVE_NODE = JniMethod("affinityFunctionRemoveNode", "(JJJ)V", true); JniMethod M_PLATFORM_CALLBACK_UTILS_AFFINITY_FUNCTION_DESTROY = JniMethod("affinityFunctionDestroy", "(JJ)V", true); + + JniMethod M_PLATFORM_CALLBACK_UTILS_CONSOLE_WRITE = JniMethod("consoleWrite", "(Ljava/lang/String;Z)V", true); const char* C_PLATFORM_UTILS = "org/apache/ignite/internal/processors/platform/utils/PlatformUtils"; JniMethod M_PLATFORM_UTILS_REALLOC = JniMethod("reallocate", "(JI)V", true); @@ -436,8 +440,10 @@ namespace ignite /* STATIC STATE. */ gcc::CriticalSection JVM_LOCK; + gcc::CriticalSection CONSOLE_LOCK; JniJvm JVM; bool PRINT_EXCEPTION = false; + std::vector<ConsoleWriteHandler> consoleWriteHandlers; /* HELPER METHODS. */ @@ -828,7 +834,7 @@ namespace ignite void RegisterNatives(JNIEnv* env) { { - JNINativeMethod methods[59]; + JNINativeMethod methods[60]; int idx = 0; @@ -910,6 +916,7 @@ namespace ignite AddNativeMethod(methods + idx++, M_PLATFORM_CALLBACK_UTILS_AFFINITY_FUNCTION_ASSIGN_PARTITIONS, reinterpret_cast<void*>(JniAffinityFunctionAssignPartitions)); AddNativeMethod(methods + idx++, M_PLATFORM_CALLBACK_UTILS_AFFINITY_FUNCTION_REMOVE_NODE, reinterpret_cast<void*>(JniAffinityFunctionRemoveNode)); AddNativeMethod(methods + idx++, M_PLATFORM_CALLBACK_UTILS_AFFINITY_FUNCTION_DESTROY, reinterpret_cast<void*>(JniAffinityFunctionDestroy)); + AddNativeMethod(methods + idx++, M_PLATFORM_CALLBACK_UTILS_CONSOLE_WRITE, reinterpret_cast<void*>(JniConsoleWrite)); jint res = env->RegisterNatives(FindClass(env, C_PLATFORM_CALLBACK_UTILS), methods, idx); @@ -2501,6 +2508,35 @@ namespace ignite } } + void JniContext::SetConsoleHandler(ConsoleWriteHandler consoleHandler) { + if (!consoleHandler) + throw std::invalid_argument("consoleHandler can not be null"); + + CONSOLE_LOCK.Enter(); + + consoleWriteHandlers.push_back(consoleHandler); + + CONSOLE_LOCK.Leave(); + } + + int JniContext::RemoveConsoleHandler(ConsoleWriteHandler consoleHandler) { + if (!consoleHandler) + throw std::invalid_argument("consoleHandler can not be null"); + + CONSOLE_LOCK.Enter(); + + int oldSize = static_cast<int>(consoleWriteHandlers.size()); + + consoleWriteHandlers.erase(remove(consoleWriteHandlers.begin(), consoleWriteHandlers.end(), + consoleHandler), consoleWriteHandlers.end()); + + int removedCnt = oldSize - static_cast<int>(consoleWriteHandlers.size()); + + CONSOLE_LOCK.Leave(); + + return removedCnt; + } + void JniContext::ThrowToJava(char* msg) { JNIEnv* env = Attach(); @@ -2867,6 +2903,23 @@ namespace ignite JNIEXPORT void JNICALL JniAffinityFunctionDestroy(JNIEnv *env, jclass cls, jlong envPtr, jlong ptr) { IGNITE_SAFE_PROC(env, envPtr, AffinityFunctionDestroyHandler, affinityFunctionDestroy, ptr); } + + JNIEXPORT void JNICALL JniConsoleWrite(JNIEnv *env, jclass cls, jstring str, jboolean isErr) { + CONSOLE_LOCK.Enter(); + + if (consoleWriteHandlers.size() > 0) { + ConsoleWriteHandler consoleWrite = consoleWriteHandlers.at(0); + + const char* strChars = env->GetStringUTFChars(str, nullptr); + const int strCharsLen = env->GetStringUTFLength(str); + + consoleWrite(strChars, strCharsLen, isErr); + + env->ReleaseStringUTFChars(str, strChars); + } + + CONSOLE_LOCK.Leave(); + } } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj index a601dbd..d0d1934 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj @@ -113,6 +113,7 @@ <Compile Include="Compute\SerializableClosureTaskTest.cs" /> <Compile Include="Compute\TaskAdapterTest.cs" /> <Compile Include="Compute\TaskResultTest.cs" /> + <Compile Include="ConsoleRedirectTest.cs" /> <Compile Include="Dataload\DataStreamerTest.cs" /> <Compile Include="Dataload\DataStreamerTestTopologyChange.cs" /> <Compile Include="DataStructures\AtomicLongTest.cs" /> http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ConsoleRedirectTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ConsoleRedirectTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ConsoleRedirectTest.cs new file mode 100644 index 0000000..bb44dcc --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ConsoleRedirectTest.cs @@ -0,0 +1,177 @@ +/* + * 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 +{ + using System; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using Apache.Ignite.Core.Common; + using Apache.Ignite.Core.Communication.Tcp; + using NUnit.Framework; + + /// <summary> + /// Tests that Java console output is redirected to .NET console. + /// </summary> + public class ConsoleRedirectTest + { + /** */ + private StringBuilder _outSb; + + /** */ + private StringBuilder _errSb; + + /** */ + private TextWriter _stdOut; + + /** */ + private TextWriter _stdErr; + + /// <summary> + /// Sets up the test. + /// </summary> + [SetUp] + public void SetUp() + { + _stdOut = Console.Out; + _stdErr = Console.Error; + + _outSb = new StringBuilder(); + Console.SetOut(new StringWriter(_outSb)); + + _errSb = new StringBuilder(); + Console.SetError(new StringWriter(_errSb)); + } + + /// <summary> + /// Tears down the test. + /// </summary> + [TearDown] + public void TearDown() + { + Console.SetOut(_stdOut); + Console.SetError(_stdErr); + } + + /// <summary> + /// Tests the startup output. + /// </summary> + [Test] + public void TestStartupOutput() + { + using (Ignition.Start(TestUtils.GetTestConfiguration())) + { + Assert.IsTrue(_outSb.ToString().Contains("[ver=1, servers=1, clients=0,")); + } + } + + /// <summary> + /// Tests startup error in Java. + /// </summary> + [Test] + public void TestStartupJavaError() + { + // Invalid config + Assert.Throws<IgniteException>(() => + Ignition.Start(new IgniteConfiguration(TestUtils.GetTestConfiguration()) + { + CommunicationSpi = new TcpCommunicationSpi + { + IdleConnectionTimeout = TimeSpan.MinValue + } + })); + + Assert.IsTrue(_errSb.ToString().Contains("SPI parameter failed condition check: idleConnTimeout > 0")); + } + + /// <summary> + /// Tests multiple appdomains and multiple console handlers. + /// </summary> + [Test] + public void TestMultipleDomains() + { + using (var ignite = Ignition.Start(TestUtils.GetTestConfiguration())) + { + Assert.IsTrue(_outSb.ToString().Contains("[ver=1, servers=1, clients=0,")); + + // Run twice + RunInNewDomain(); + RunInNewDomain(); + + Assert.AreEqual(5, ignite.GetCluster().TopologyVersion); + + var outTxt = _outSb.ToString(); + + // Check output from another domain (2 started + 2 stopped = 4) + Assert.AreEqual(4, Regex.Matches(outTxt, ">>> Grid name: newDomainGrid").Count); + + // Both domains produce the topology snapshot on node enter + Assert.AreEqual(2, Regex.Matches(outTxt, "ver=2, servers=2, clients=0,").Count); + Assert.AreEqual(1, Regex.Matches(outTxt, "ver=3, servers=1, clients=0,").Count); + Assert.AreEqual(2, Regex.Matches(outTxt, "ver=4, servers=2, clients=0,").Count); + Assert.AreEqual(1, Regex.Matches(outTxt, "ver=5, servers=1, clients=0,").Count); + } + } + + /// <summary> + /// Runs the Ignite in a new domain. + /// </summary> + private static void RunInNewDomain() + { + AppDomain childDomain = null; + + try + { + childDomain = AppDomain.CreateDomain("Child", null, new AppDomainSetup + { + ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase, + ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, + ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName, + LoaderOptimization = LoaderOptimization.MultiDomainHost + }); + + var runner = (IIgniteRunner)childDomain.CreateInstanceAndUnwrap( + typeof(IgniteRunner).Assembly.FullName, typeof(IgniteRunner).FullName); + + runner.Run(); + } + finally + { + if (childDomain != null) + AppDomain.Unload(childDomain); + } + } + + private interface IIgniteRunner + { + void Run(); + } + + private class IgniteRunner : MarshalByRefObject, IIgniteRunner + { + public void Run() + { + var ignite = Ignition.Start(new IgniteConfiguration(TestUtils.GetTestConfiguration()) + { + GridName = "newDomainGrid" + }); + Ignition.Stop(ignite.Name, true); + } + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestRunner.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestRunner.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestRunner.cs index 149fa35..5403ebe 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestRunner.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestRunner.cs @@ -48,7 +48,7 @@ namespace Apache.Ignite.Core.Tests return; } - TestOne(typeof(AffinityFunctionTest), "TestSimpleInheritance"); + TestOne(typeof(ConsoleRedirectTest), "TestMultipleDomains"); //TestAll(typeof (AffinityFunctionTest)); //TestAllInAssembly(); @@ -56,7 +56,12 @@ namespace Apache.Ignite.Core.Tests private static int TestOne(Type testClass, string method) { - string[] args = { "/run:" + testClass.FullName + "." + method, Assembly.GetAssembly(testClass).Location }; + string[] args = + { + "/noshadow", + "/run:" + testClass.FullName + "." + method, + Assembly.GetAssembly(testClass).Location + }; int returnCode = Runner.Main(args); @@ -68,7 +73,11 @@ namespace Apache.Ignite.Core.Tests private static void TestAll(Type testClass) { - string[] args = { "/run:" + testClass.FullName, Assembly.GetAssembly(testClass).Location }; + string[] args = + { + "/noshadow", + "/run:" + testClass.FullName, Assembly.GetAssembly(testClass).Location + }; int returnCode = Runner.Main(args); @@ -78,7 +87,11 @@ namespace Apache.Ignite.Core.Tests private static void TestAllInAssembly() { - string[] args = { Assembly.GetAssembly(typeof(InteropMemoryTest)).Location }; + string[] args = + { + "/noshadow", + Assembly.GetAssembly(typeof(InteropMemoryTest)).Location + }; int returnCode = Runner.Main(args); http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/IgniteJniNativeMethods.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/IgniteJniNativeMethods.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/IgniteJniNativeMethods.cs index 4619080..2da4192 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/IgniteJniNativeMethods.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/IgniteJniNativeMethods.cs @@ -429,5 +429,11 @@ namespace Apache.Ignite.Core.Impl.Unmanaged [DllImport(IgniteUtils.FileIgniteJniDll, EntryPoint = "IgniteListenableIsCancelled")] [return: MarshalAs(UnmanagedType.U1)] public static extern bool ListenableIsCancelled(void* ctx, void* target); + + [DllImport(IgniteUtils.FileIgniteJniDll, EntryPoint = "IgniteSetConsoleHandler")] + public static extern void SetConsoleHandler(void* consoleHandler); + + [DllImport(IgniteUtils.FileIgniteJniDll, EntryPoint = "IgniteRemoveConsoleHandler")] + public static extern int RemoveConsoleHandler(void* consoleHandler); } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs index ef901cb..4cbdf8f 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs @@ -55,6 +55,13 @@ namespace Apache.Ignite.Core.Impl.Unmanaged Justification = "This class instance usually lives as long as the app runs.")] internal unsafe class UnmanagedCallbacks { + /** Console write delegate. */ + private static readonly ConsoleWriteDelegate ConsoleWriteDel = ConsoleWrite; + + /** Console write pointer. */ + private static readonly void* ConsoleWritePtr = + Marshal.GetFunctionPointerForDelegate(ConsoleWriteDel).ToPointer(); + /** Unmanaged context. */ private volatile UnmanagedContext _ctx; @@ -71,7 +78,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged /** Initialized flag. */ private readonly ManualResetEventSlim _initEvent = new ManualResetEventSlim(false); - /** Actions to be called upon Ignite initialisation. */ + /** Actions to be called upon Ignite initialization. */ private readonly List<Action<Ignite>> _initActions = new List<Action<Ignite>>(); /** GC handle to UnmanagedCallbacks instance to prevent it from being GCed. */ @@ -172,6 +179,8 @@ namespace Apache.Ignite.Core.Impl.Unmanaged private delegate void AffinityFunctionRemoveNodeDelegate(void* target, long ptr, long memPtr); private delegate void AffinityFunctionDestroyDelegate(void* target, long ptr); + private delegate void ConsoleWriteDelegate(sbyte* chars, int charsLen, bool isErr); + /// <summary> /// constructor. /// </summary> @@ -1105,6 +1114,23 @@ namespace Apache.Ignite.Core.Impl.Unmanaged }); } + private static void ConsoleWrite(sbyte* chars, int charsLen, bool isErr) + { + try + { + var str = IgniteUtils.Utf8UnmanagedToString(chars, charsLen); + + var target = isErr ? Console.Error : Console.Out; + + target.Write(str); + + } + catch (Exception ex) + { + Console.Error.WriteLine("ConsoleWrite unmanaged callback failed: " + ex); + } + } + #endregion #region AffinityFunction @@ -1291,5 +1317,13 @@ namespace Apache.Ignite.Core.Impl.Unmanaged _handleRegistry.Close(); } + + /// <summary> + /// Gets the console write handler. + /// </summary> + public static void* ConsoleWriteHandler + { + get { return ConsoleWritePtr; } + } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/4e468a8c/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedUtils.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedUtils.cs index 924259c..0e9556d 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedUtils.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedUtils.cs @@ -18,6 +18,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged { using System; + using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Apache.Ignite.Core.Common; @@ -48,6 +49,21 @@ namespace Apache.Ignite.Core.Impl.Unmanaged if (ptr == IntPtr.Zero) throw new IgniteException(string.Format("Failed to load {0}: {1}", IgniteUtils.FileIgniteJniDll, Marshal.GetLastWin32Error())); + + AppDomain.CurrentDomain.DomainUnload += CurrentDomain_DomainUnload; + + JNI.SetConsoleHandler(UnmanagedCallbacks.ConsoleWriteHandler); + } + + /// <summary> + /// Handles the DomainUnload event of the current AppDomain. + /// </summary> + private static void CurrentDomain_DomainUnload(object sender, EventArgs e) + { + // Clean the handler to avoid JVM crash. + var removedCnt = JNI.RemoveConsoleHandler(UnmanagedCallbacks.ConsoleWriteHandler); + + Debug.Assert(removedCnt == 1); } /// <summary>
