From c599beea8e1b6e36ac9acaee4533873aa9e591e1 Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Sun, 21 Jun 2020 07:14:18 +0000
Subject: [PATCH 01/13] [fix] Fix broken maven link

---
 docs/building/ubuntu-instructions.md | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/docs/building/ubuntu-instructions.md b/docs/building/ubuntu-instructions.md
index 8bb11b163..0e3dbdf40 100644
--- a/docs/building/ubuntu-instructions.md
+++ b/docs/building/ubuntu-instructions.md
@@ -35,14 +35,14 @@ If you already have all the pre-requisites, skip to the [build](ubuntu-instructi
        ```bash
        sudo update-alternatives --config java
        ```
-  3. Install **[Apache Maven 3.6.0+](https://maven.apache.org/download.cgi)**
+  3. Install **[Apache Maven 3.6.3+](https://maven.apache.org/download.cgi)**
      - Run the following command:
        ```bash
        mkdir -p ~/bin/maven
        cd ~/bin/maven
-       wget https://www-us.apache.org/dist/maven/maven-3/3.6.0/binaries/apache-maven-3.6.0-bin.tar.gz
-       tar -xvzf apache-maven-3.6.0-bin.tar.gz
-       ln -s apache-maven-3.6.0 current
+       wget https://www-us.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz
+       tar -xvzf apache-maven-3.6.3-bin.tar.gz
+       ln -s apache-maven-3.6.3 current
        export M2_HOME=~/bin/maven/current
        export PATH=${M2_HOME}/bin:${PATH}
        source ~/.bashrc
@@ -54,11 +54,11 @@ If you already have all the pre-requisites, skip to the [build](ubuntu-instructi
        <summary>&#x1F4D9; Click to see sample mvn -version output</summary>
        
        ```
-       Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-24T18:41:47Z)
-       Maven home: ~/bin/apache-maven-3.6.0
-       Java version: 1.8.0_191, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre
-       Default locale: en, platform encoding: UTF-8
-       OS name: "linux", version: "4.4.0-17763-microsoft", arch: "amd64", family: "unix"
+       Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
+       Maven home: ~/bin/apache-maven-3.6.3
+       Java version: 1.8.0_242, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre
+       Default locale: en_US, platform encoding: ANSI_X3.4-1968
+       OS name: "linux", version: "4.4.0-142-generic", arch: "amd64", family: "unix"
        ```
   4. Install **[Apache Spark 2.3+](https://spark.apache.org/downloads.html)**
      - Download [Apache Spark 2.3+](https://spark.apache.org/downloads.html) and extract it into a local folder (e.g., `~/bin/spark-2.3.2-bin-hadoop2.7`)

From 394b11f0950ecf8a0b64678f71937aba80b58366 Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Sun, 21 Jun 2020 14:14:41 +0000
Subject: [PATCH 02/13] Launch jvm bridge if needed at SparkSession starting

---
 .../Sql/JVMBridgeHelperTests.cs               | 101 +++++++++++
 .../Microsoft.Spark/Sql/JVMBridgeHelper.cs    | 167 ++++++++++++++++++
 .../Microsoft.Spark/Sql/SparkSession.cs       |  30 +++-
 3 files changed, 296 insertions(+), 2 deletions(-)
 create mode 100644 src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs
 create mode 100644 src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs

diff --git a/src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs b/src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs
new file mode 100644
index 000000000..d6bd66b33
--- /dev/null
+++ b/src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs
@@ -0,0 +1,101 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.NetworkInformation;
+using System.Net;
+using Microsoft.Spark.Interop.Ipc;
+using Microsoft.Spark.Network;
+using Microsoft.Spark.Sql;
+using Microsoft.Spark.Sql.Types;
+using Microsoft.Spark.UnitTest.TestUtils;
+using Microsoft.Spark.Utils;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Spark.UnitTest
+{
+    public class JVMBridgeHelperTests
+    {
+        [Theory]
+        [InlineData("1,2,3", 0, false)]
+        [InlineData("5567,5568,5569", 0, true)]
+        [InlineData("1,2,3", 5567, false)]
+        [InlineData("5567,5568,5569", 5567, true)]
+        [InlineData("1234,5678", 1234, true)]
+        public void IsDotnetBackendPortUsingTest(string testRunningPorts, int backendport, bool expect)
+        {
+            var envkey = "DOTNETBACKEND_PORT";
+            var oldenvValue = Environment.GetEnvironmentVariable(envkey);
+            try
+            {
+                if (backendport == 0)
+                {
+                    Environment.SetEnvironmentVariable(envkey, null);
+                }
+                else
+                {
+                    Environment.SetEnvironmentVariable(envkey, backendport.ToString());
+                }
+
+                var runningports = testRunningPorts
+                    .Split(",").Select(x => Convert.ToInt32(x)).ToList();
+                var mockIpInfos = runningports
+                    .Select(p =>
+                    {
+                        var m_info = new Mock<TcpConnectionInformation>();
+                        m_info.SetupGet(m => m.LocalEndPoint).Returns(new IPEndPoint(0, p));
+                        return m_info.Object;
+                    }).ToArray();
+                var ipinfo = new Mock<IPGlobalProperties>();
+                ipinfo.Setup(m => m.GetActiveTcpConnections())
+                    .Returns(mockIpInfos);
+
+                var ret = JVMBridgeHelper.IsDotnetBackendPortUsing(ipinfo.Object);
+                Assert.Equal(ret, expect);
+            }
+            finally
+            {
+                Environment.SetEnvironmentVariable(envkey, oldenvValue);
+            }
+        }
+
+        /// <summary>
+        /// The main test case of JVM bridge helper,
+        /// is simply run it and close it.
+        /// </summary>
+        [Fact]
+        public void JVMBridgeHelperMainPathTest() 
+        {
+            using(var helper = new JVMBridgeHelper()) {
+                // now we should be able to connect to JVM bridge
+                // or if system environment is not good, we should not failed.
+            }
+        }        
+
+        /// <summary>
+        /// Test with case that already have jvm Bridge case
+        /// </summary>
+        [Fact]
+        public void JVMBridgeHelperTestsWithSparkSessionWithBridgeReady() 
+        {
+            using(var helper = new JVMBridgeHelper()) {
+                // now we should be able to connect to JVM bridge
+                var spark = SparkSession.Builder().Master("local").GetOrCreate();
+                spark.Stop();
+            }
+        }
+
+        [Fact]
+        public void JVMBridgeHelperTestsWithSparkSessionWithBridgeNotReady() 
+        {
+            // now we should be able to connect to JVM bridge anytime
+            var spark = SparkSession.Builder().Master("local").GetOrCreate();
+            spark.Stop();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs b/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs
new file mode 100644
index 000000000..8bf23f1ef
--- /dev/null
+++ b/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs
@@ -0,0 +1,167 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Net.NetworkInformation;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using System.Runtime.InteropServices;
+using Microsoft.Spark.Services;
+using Microsoft.Spark.Interop;
+
+namespace Microsoft.Spark.Sql
+{
+    /// <summary>
+    /// An helper to launch dotnet jvm if needed
+    /// </summary>    
+    public class JVMBridgeHelper : IDisposable
+    {
+        /// <summary>
+        /// Customization for JVM Bridge jar file
+        /// If not exists, the helper will find out the jar in $DOTNET_WORKER_DIR folder.
+        /// </summary>
+        public static string JVMBridgeJarEnvName = "DOTNET_BRIDGE_JAR";
+
+        /// <summary>
+        /// DotnetRunner classname
+        /// </summary>
+        private static string RunnerClassname =
+            "org.apache.spark.deploy.dotnet.DotnetRunner";
+
+        private static string RunnerReadyMsg =
+            ".NET Backend running debug mode. Press enter to exit";
+
+        private static string RunnerAddressInUseMsg =
+            "java.net.BindException: Address already in use";
+
+
+        private static int maxWaitTimeoutMS = 60000;
+
+        /// <summary>
+        /// The running jvm bridge process , null means no such process
+        /// </summary>
+        private Process jvmBridge;
+
+        /// <summary>
+        /// Detect if we already have the runner by checking backend port is using or not.
+        /// </summary>
+        /// <param name="customIPGlobalProperties">custom IPGlobalProperties, null for System.Net.NetworkInformation</param>
+        /// <returns> True means backend port is occupied by the runner.</returns>
+        public static bool IsDotnetBackendPortUsing(
+            IPGlobalProperties customIPGlobalProperties = null)
+        {
+            var backendport = SparkEnvironment.ConfigurationService.GetBackendPortNumber();
+            var activeTcps =
+                (customIPGlobalProperties ?? IPGlobalProperties.GetIPGlobalProperties())
+                .GetActiveTcpConnections();
+            return activeTcps.Any(tcp => tcp.LocalEndPoint.Port == backendport);
+        }
+
+        public JVMBridgeHelper()
+        {
+            var jarpath = locateBridgeJar();
+            var sparksubmit = locateSparkSubmit();
+            if (string.IsNullOrWhiteSpace(jarpath) ||
+                string.IsNullOrWhiteSpace(sparksubmit))
+            {
+                // Cannot find correct launch informations, give up.
+                return;
+            }
+            var arguments = $"--class {RunnerClassname} {jarpath} debug";
+            var startupinfo = new ProcessStartInfo
+            {
+                FileName = sparksubmit,
+                Arguments = arguments,
+                RedirectStandardOutput = true,
+                RedirectStandardInput = true,
+                UseShellExecute = false,
+                CreateNoWindow = true,
+            };
+
+            jvmBridge = new Process() { StartInfo = startupinfo };
+            jvmBridge.Start();
+
+            // wait until we see .net backend started
+            Task<string> message;
+            while ((message = jvmBridge.StandardOutput.ReadLineAsync()) != null)
+            {
+                if (message.Wait(maxWaitTimeoutMS) == false)
+                {
+                    // wait timeout , giveup
+                    break;
+                }
+
+                if (message.Result.Contains(RunnerReadyMsg))
+                {
+                    // launched successfully!
+                    return;
+                }
+                if (message.Result.Contains(RunnerAddressInUseMsg))
+                {
+                    // failed to start for port is using, give up.
+                    break;
+                }
+            }
+            // wait timeout , or failed to startup
+            // give up.
+            jvmBridge.Close();
+            jvmBridge = null;
+        }
+
+        private string locateSparkSubmit()
+        {
+            var sparkHome = Environment.GetEnvironmentVariable("SPARK_HOME");
+            var filename = Path.Combine(sparkHome, "bin", "spark-submit");
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                filename += ".cmd";
+            }
+
+            if (!File.Exists(filename))
+            {
+                return string.Empty;
+            }
+
+            return filename;
+        }
+
+        private string locateBridgeJar()
+        {
+            var jarpath = Environment.GetEnvironmentVariable(JVMBridgeJarEnvName);
+            if (string.IsNullOrWhiteSpace(jarpath) == false)
+            {
+                return jarpath;
+            }
+
+            var workdir = Environment.GetEnvironmentVariable(
+                    ConfigurationService.WorkerDirEnvVarName);
+            if ((workdir != null) && Directory.Exists(workdir))
+            {
+                // let's find the approicate jar in the work dirctory.
+                var jarfile = new DirectoryInfo(workdir)
+                    .GetFiles("microsoft-spark-*.jar")
+                    .FirstOrDefault();
+                if (jarfile != null)
+                {
+                    return Path.Combine(jarfile.DirectoryName, jarfile.Name);
+                }
+            }
+
+            return string.Empty;
+        }
+
+        public void Dispose()
+        {
+            if (jvmBridge != null)
+            {
+                jvmBridge.StandardInput.WriteLine("\n");
+                // to avoid deadlock, read all output then wait for exit.
+                jvmBridge.StandardOutput.ReadToEndAsync();
+                jvmBridge.WaitForExit(maxWaitTimeoutMS);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/csharp/Microsoft.Spark/Sql/SparkSession.cs b/src/csharp/Microsoft.Spark/Sql/SparkSession.cs
index fc706081f..c9be02151 100644
--- a/src/csharp/Microsoft.Spark/Sql/SparkSession.cs
+++ b/src/csharp/Microsoft.Spark/Sql/SparkSession.cs
@@ -27,6 +27,12 @@ public sealed class SparkSession : IDisposable, IJvmObjectReferenceProvider
         private static readonly string s_sparkSessionClassName =
             "org.apache.spark.sql.SparkSession";
 
+        /// <summary>
+        /// The created jvm process
+        /// </summary>
+        /// <returns>The created jvm bridge process helper.</returns>
+        private static JVMBridgeHelper s_jvmbridge = null;
+
         /// <summary>
         /// Constructor for SparkSession.
         /// </summary>
@@ -59,7 +65,17 @@ internal SparkSession(JvmObjectReference jvmObject)
         /// Creates a Builder object for SparkSession.
         /// </summary>
         /// <returns>Builder object</returns>
-        public static Builder Builder() => new Builder();
+        public static Builder Builder()
+        {
+            // We could try to detect if we don't have jvm bridge,
+            // call the helper to launch jvm bridge process.
+            if ((s_jvmbridge == null) && 
+                (JVMBridgeHelper.IsDotnetBackendPortUsing() == false))
+            {
+                s_jvmbridge = new JVMBridgeHelper();
+            }
+            return new Builder();
+        }
 
         /// Note that *ActiveSession() APIs are not exposed because these APIs work with a
         /// thread-local variable, which stores the session variable. Since the Netty server
@@ -293,7 +309,17 @@ public UdfRegistration Udf() =>
         /// <summary>
         /// Stops the underlying SparkContext.
         /// </summary>
-        public void Stop() => _jvmObject.Invoke("stop");
+        public void Stop()
+        {
+            _jvmObject.Invoke("stop");
+
+            // if we have created the jvm bridge process, dispose it now.
+            if (s_jvmbridge != null)
+            {
+                s_jvmbridge.Dispose();
+                s_jvmbridge = null;
+            }
+        }
 
         /// <summary>
         /// Returns a single column schema of the given datatype.

From c376c0e5e83444af52d6c4725bdc9471b2004635 Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Sun, 21 Jun 2020 16:54:42 +0000
Subject: [PATCH 03/13] [fix] Add more test and fix Listening TCP issues.

---
 .../Sql/JVMBridgeHelperTests.cs               | 30 +++++++++++--------
 .../Microsoft.Spark/Sql/JVMBridgeHelper.cs    | 16 +++++-----
 2 files changed, 26 insertions(+), 20 deletions(-)

diff --git a/src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs b/src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs
index d6bd66b33..daa05133d 100644
--- a/src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs
+++ b/src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs
@@ -27,7 +27,7 @@ public class JVMBridgeHelperTests
         [InlineData("1,2,3", 5567, false)]
         [InlineData("5567,5568,5569", 5567, true)]
         [InlineData("1234,5678", 1234, true)]
-        public void IsDotnetBackendPortUsingTest(string testRunningPorts, int backendport, bool expect)
+        public void IsDotnetBackendPortUsingTest(string testListeningPorts, int backendport, bool expect)
         {
             var envkey = "DOTNETBACKEND_PORT";
             var oldenvValue = Environment.GetEnvironmentVariable(envkey);
@@ -42,18 +42,12 @@ public void IsDotnetBackendPortUsingTest(string testRunningPorts, int backendpor
                     Environment.SetEnvironmentVariable(envkey, backendport.ToString());
                 }
 
-                var runningports = testRunningPorts
-                    .Split(",").Select(x => Convert.ToInt32(x)).ToList();
-                var mockIpInfos = runningports
-                    .Select(p =>
-                    {
-                        var m_info = new Mock<TcpConnectionInformation>();
-                        m_info.SetupGet(m => m.LocalEndPoint).Returns(new IPEndPoint(0, p));
-                        return m_info.Object;
-                    }).ToArray();
+                var listeningEndpoints = testListeningPorts
+                    .Split(",").Select(x => new IPEndPoint(0, Convert.ToInt32(x))).ToArray();
+
                 var ipinfo = new Mock<IPGlobalProperties>();
-                ipinfo.Setup(m => m.GetActiveTcpConnections())
-                    .Returns(mockIpInfos);
+                ipinfo.Setup(m => m.GetActiveTcpListeners())
+                    .Returns(listeningEndpoints);
 
                 var ret = JVMBridgeHelper.IsDotnetBackendPortUsing(ipinfo.Object);
                 Assert.Equal(ret, expect);
@@ -75,7 +69,17 @@ public void JVMBridgeHelperMainPathTest()
                 // now we should be able to connect to JVM bridge
                 // or if system environment is not good, we should not failed.
             }
-        }        
+        }
+
+        [Fact]
+        public void JVMBridgeHelperWithoutSpark()
+        {
+            var oldhome = Environment.GetEnvironmentVariable("SPARK_HOME");
+            Environment.SetEnvironmentVariable("SPARK_HOME", null);
+            using(var helper = new JVMBridgeHelper()) {
+            }
+            Environment.SetEnvironmentVariable("SPARK_HOME", oldhome);
+        }
 
         /// <summary>
         /// Test with case that already have jvm Bridge case
diff --git a/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs b/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs
index 8bf23f1ef..ce5de0888 100644
--- a/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs
+++ b/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs
@@ -54,10 +54,10 @@ public static bool IsDotnetBackendPortUsing(
             IPGlobalProperties customIPGlobalProperties = null)
         {
             var backendport = SparkEnvironment.ConfigurationService.GetBackendPortNumber();
-            var activeTcps =
+            var listeningEndpoints =
                 (customIPGlobalProperties ?? IPGlobalProperties.GetIPGlobalProperties())
-                .GetActiveTcpConnections();
-            return activeTcps.Any(tcp => tcp.LocalEndPoint.Port == backendport);
+                .GetActiveTcpListeners();
+            return listeningEndpoints.Any(p => p.Port == backendport);
         }
 
         public JVMBridgeHelper()
@@ -97,11 +97,13 @@ public JVMBridgeHelper()
                 if (message.Result.Contains(RunnerReadyMsg))
                 {
                     // launched successfully!
+                    jvmBridge.StandardOutput.ReadToEndAsync();
                     return;
                 }
                 if (message.Result.Contains(RunnerAddressInUseMsg))
                 {
                     // failed to start for port is using, give up.
+                    jvmBridge.StandardOutput.ReadToEndAsync();
                     break;
                 }
             }
@@ -114,17 +116,19 @@ public JVMBridgeHelper()
         private string locateSparkSubmit()
         {
             var sparkHome = Environment.GetEnvironmentVariable("SPARK_HOME");
+            if (string.IsNullOrWhiteSpace(sparkHome))
+            {
+                return string.Empty;
+            }
             var filename = Path.Combine(sparkHome, "bin", "spark-submit");
             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
             {
                 filename += ".cmd";
             }
-
             if (!File.Exists(filename))
             {
                 return string.Empty;
             }
-
             return filename;
         }
 
@@ -158,8 +162,6 @@ public void Dispose()
             if (jvmBridge != null)
             {
                 jvmBridge.StandardInput.WriteLine("\n");
-                // to avoid deadlock, read all output then wait for exit.
-                jvmBridge.StandardOutput.ReadToEndAsync();
                 jvmBridge.WaitForExit(maxWaitTimeoutMS);
             }
         }

From a07e7817cec4a1a4c40518abcc4071f12e7ce18f Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Mon, 22 Jun 2020 01:32:10 +0000
Subject: [PATCH 04/13] Fix broken maven link in window build doc

---
 docs/building/windows-instructions.md | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/docs/building/windows-instructions.md b/docs/building/windows-instructions.md
index 84874a129..aad141b68 100644
--- a/docs/building/windows-instructions.md
+++ b/docs/building/windows-instructions.md
@@ -30,10 +30,10 @@ If you already have all the pre-requisites, skip to the [build](windows-instruct
   3. Install **[Java 1.8](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)** 
      - Select the appropriate version for your operating system e.g., jdk-8u201-windows-x64.exe for Win x64 machine.
      - Install using the installer and verify you are able to run `java` from your command-line
-  4. Install **[Apache Maven 3.6.0+](https://maven.apache.org/download.cgi)**
-     - Download [Apache Maven 3.6.0](http://mirror.metrocast.net/apache/maven/maven-3/3.6.0/binaries/apache-maven-3.6.0-bin.zip)
-     - Extract to a local directory e.g., `c:\bin\apache-maven-3.6.0\`
-     - Add Apache Maven to your [PATH environment variable](https://www.java.com/en/download/help/path.xml) e.g., `c:\bin\apache-maven-3.6.0\bin`
+  4. Install **[Apache Maven 3.6.3+](https://maven.apache.org/download.cgi)**
+     - Download [Apache Maven 3.6.3](http://mirror.metrocast.net/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.zip)
+     - Extract to a local directory e.g., `c:\bin\apache-maven-3.6.3\`
+     - Add Apache Maven to your [PATH environment variable](https://www.java.com/en/download/help/path.xml) e.g., `c:\bin\apache-maven-3.6.3\bin`
      - Verify you are able to run `mvn` from your command-line
   5. Install **[Apache Spark 2.3+](https://spark.apache.org/downloads.html)**
      - Download [Apache Spark 2.3+](https://spark.apache.org/downloads.html) and extract it into a local folder (e.g., `c:\bin\spark-2.3.2-bin-hadoop2.7\`) using [7-zip](https://www.7-zip.org/).

From ffabc5335443b6eb2e1de88c18abeb5046eb56c0 Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Mon, 22 Jun 2020 10:00:52 +0000
Subject: [PATCH 05/13] Remove integration test for unit test may not pass at
 env not ready.

---
 .../Sql/JVMBridgeHelperTests.cs               | 29 ++++---------------
 1 file changed, 5 insertions(+), 24 deletions(-)

diff --git a/src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs b/src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs
index daa05133d..a2b6ad2ca 100644
--- a/src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs
+++ b/src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs
@@ -63,9 +63,10 @@ public void IsDotnetBackendPortUsingTest(string testListeningPorts, int backendp
         /// is simply run it and close it.
         /// </summary>
         [Fact]
-        public void JVMBridgeHelperMainPathTest() 
+        public void JVMBridgeHelperMainPathTest()
         {
-            using(var helper = new JVMBridgeHelper()) {
+            using (var helper = new JVMBridgeHelper())
+            {
                 // now we should be able to connect to JVM bridge
                 // or if system environment is not good, we should not failed.
             }
@@ -76,30 +77,10 @@ public void JVMBridgeHelperWithoutSpark()
         {
             var oldhome = Environment.GetEnvironmentVariable("SPARK_HOME");
             Environment.SetEnvironmentVariable("SPARK_HOME", null);
-            using(var helper = new JVMBridgeHelper()) {
+            using (var helper = new JVMBridgeHelper())
+            {
             }
             Environment.SetEnvironmentVariable("SPARK_HOME", oldhome);
         }
-
-        /// <summary>
-        /// Test with case that already have jvm Bridge case
-        /// </summary>
-        [Fact]
-        public void JVMBridgeHelperTestsWithSparkSessionWithBridgeReady() 
-        {
-            using(var helper = new JVMBridgeHelper()) {
-                // now we should be able to connect to JVM bridge
-                var spark = SparkSession.Builder().Master("local").GetOrCreate();
-                spark.Stop();
-            }
-        }
-
-        [Fact]
-        public void JVMBridgeHelperTestsWithSparkSessionWithBridgeNotReady() 
-        {
-            // now we should be able to connect to JVM bridge anytime
-            var spark = SparkSession.Builder().Master("local").GetOrCreate();
-            spark.Stop();
-        }
     }
 }
\ No newline at end of file

From 32d50aafa229c7c8dabbd71517a8b7d5d2ce1944 Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Mon, 22 Jun 2020 14:56:15 +0000
Subject: [PATCH 06/13] Add logger to show jvm bridge launch info.

---
 src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs b/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs
index ce5de0888..21c065654 100644
--- a/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs
+++ b/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs
@@ -40,6 +40,9 @@ public class JVMBridgeHelper : IDisposable
 
         private static int maxWaitTimeoutMS = 60000;
 
+        private readonly ILoggerService _logger =
+            LoggerServiceFactory.GetLogger(typeof(JVMBridgeHelper));        
+
         /// <summary>
         /// The running jvm bridge process , null means no such process
         /// </summary>
@@ -82,6 +85,7 @@ public JVMBridgeHelper()
             };
 
             jvmBridge = new Process() { StartInfo = startupinfo };
+            _logger.LogInfo($"Launch JVM Bridge : {sparksubmit} {arguments}");
             jvmBridge.Start();
 
             // wait until we see .net backend started
@@ -98,6 +102,7 @@ public JVMBridgeHelper()
                 {
                     // launched successfully!
                     jvmBridge.StandardOutput.ReadToEndAsync();
+                    _logger.LogInfo($"Launch JVM Bridge ready");
                     return;
                 }
                 if (message.Result.Contains(RunnerAddressInUseMsg))
@@ -163,6 +168,7 @@ public void Dispose()
             {
                 jvmBridge.StandardInput.WriteLine("\n");
                 jvmBridge.WaitForExit(maxWaitTimeoutMS);
+                _logger.LogInfo($"JVM Bridge disposed.");
             }
         }
     }

From 70add1d1771d447b703a615cad4c2e0ac4677579 Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Mon, 22 Jun 2020 14:56:15 +0000
Subject: [PATCH 07/13] Add logger to show jvm bridge launch info.

---
 .vscode/launch.json                           | 27 ++++++++++++
 .vscode/settings.json                         |  3 ++
 .vscode/tasks.json                            | 42 +++++++++++++++++++
 .../Microsoft.Spark/Sql/Types/ComplexTypes.cs | 33 +++++++++++++--
 4 files changed, 101 insertions(+), 4 deletions(-)
 create mode 100644 .vscode/launch.json
 create mode 100644 .vscode/settings.json
 create mode 100644 .vscode/tasks.json

diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 000000000..4e219e522
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,27 @@
+{
+   // Use IntelliSense to find out which attributes exist for C# debugging
+   // Use hover for the description of the existing attributes
+   // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+   "version": "0.2.0",
+   "configurations": [
+        {
+            "name": ".NET Core Launch (console)",
+            "type": "coreclr",
+            "request": "launch",
+            "preLaunchTask": "build",
+            // If you have changed target frameworks, make sure to update the program path.
+            "program": "${workspaceFolder}/src/csharp/Microsoft.Spark.Worker/bin/Debug/netcoreapp3.1/Microsoft.Spark.Worker.dll",
+            "args": [],
+            "cwd": "${workspaceFolder}/src/csharp/Microsoft.Spark.Worker",
+            // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
+            "console": "internalConsole",
+            "stopAtEntry": false
+        },
+        {
+            "name": ".NET Core Attach",
+            "type": "coreclr",
+            "request": "attach",
+            "processId": "${command:pickProcess}"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..d6ec7df72
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+    "dotnet-test-explorer.testProjectPath": "src/csharp/Microsoft.Spark.UnitTest"
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 000000000..0f8011d05
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,42 @@
+{
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "build",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "build",
+                "${workspaceFolder}/src/csharp/Microsoft.Spark.Worker/Microsoft.Spark.Worker.csproj",
+                "/property:GenerateFullPaths=true",
+                "/consoleloggerparameters:NoSummary"
+            ],
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "publish",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "publish",
+                "${workspaceFolder}/src/csharp/Microsoft.Spark.Worker/Microsoft.Spark.Worker.csproj",
+                "/property:GenerateFullPaths=true",
+                "/consoleloggerparameters:NoSummary"
+            ],
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "watch",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "watch",
+                "run",
+                "${workspaceFolder}/src/csharp/Microsoft.Spark.Worker/Microsoft.Spark.Worker.csproj",
+                "/property:GenerateFullPaths=true",
+                "/consoleloggerparameters:NoSummary"
+            ],
+            "problemMatcher": "$msCompile"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/src/csharp/Microsoft.Spark/Sql/Types/ComplexTypes.cs b/src/csharp/Microsoft.Spark/Sql/Types/ComplexTypes.cs
index 2b65ea6d1..2fa5f4f2f 100644
--- a/src/csharp/Microsoft.Spark/Sql/Types/ComplexTypes.cs
+++ b/src/csharp/Microsoft.Spark/Sql/Types/ComplexTypes.cs
@@ -69,9 +69,22 @@ private DataType FromJson(JObject json)
             return this;
         }
 
-        internal override bool NeedConversion() => true;
+        internal override bool NeedConversion() => ElementType.NeedConversion();
 
-        internal override object FromInternal(object obj) => throw new NotImplementedException();
+        internal override object FromInternal(object obj)
+        {
+            if (!NeedConversion())
+            {
+                return obj;
+            }
+
+            var convertedObj = new List<object>();
+            foreach(object o in (dynamic)obj)
+            {
+                convertedObj.Add(ElementType.FromInternal(o));
+            }
+            return convertedObj;
+        }
     }
 
     /// <summary>
@@ -142,9 +155,21 @@ private DataType FromJson(JObject json)
             return this;
         }
 
-        internal override bool NeedConversion() => true;
+        internal override bool NeedConversion() => KeyType.NeedConversion() && ValueType.NeedConversion();
 
-        internal override object FromInternal(object obj) => throw new NotImplementedException();
+        internal override object FromInternal(object obj)
+        {
+            if (!NeedConversion())
+            {
+                return obj;
+            }
+            var convertedObj = new Dictionary<object, object>();
+            foreach(dynamic kv in (dynamic)obj)
+            {
+                convertedObj[kv.Key] = kv.Value;
+            }
+            return convertedObj;
+        }
     }
 
     /// <summary>

From 790bb093384a4debbce994c72e515db83e3eeb8a Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Mon, 22 Jun 2020 15:01:13 +0000
Subject: [PATCH 08/13] Revert "Add logger to show jvm bridge launch info."

This reverts commit 70add1d1771d447b703a615cad4c2e0ac4677579.
---
 .vscode/launch.json                           | 27 ------------
 .vscode/settings.json                         |  3 --
 .vscode/tasks.json                            | 42 -------------------
 .../Microsoft.Spark/Sql/Types/ComplexTypes.cs | 33 ++-------------
 4 files changed, 4 insertions(+), 101 deletions(-)
 delete mode 100644 .vscode/launch.json
 delete mode 100644 .vscode/settings.json
 delete mode 100644 .vscode/tasks.json

diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index 4e219e522..000000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
-   // Use IntelliSense to find out which attributes exist for C# debugging
-   // Use hover for the description of the existing attributes
-   // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
-   "version": "0.2.0",
-   "configurations": [
-        {
-            "name": ".NET Core Launch (console)",
-            "type": "coreclr",
-            "request": "launch",
-            "preLaunchTask": "build",
-            // If you have changed target frameworks, make sure to update the program path.
-            "program": "${workspaceFolder}/src/csharp/Microsoft.Spark.Worker/bin/Debug/netcoreapp3.1/Microsoft.Spark.Worker.dll",
-            "args": [],
-            "cwd": "${workspaceFolder}/src/csharp/Microsoft.Spark.Worker",
-            // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
-            "console": "internalConsole",
-            "stopAtEntry": false
-        },
-        {
-            "name": ".NET Core Attach",
-            "type": "coreclr",
-            "request": "attach",
-            "processId": "${command:pickProcess}"
-        }
-    ]
-}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index d6ec7df72..000000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-    "dotnet-test-explorer.testProjectPath": "src/csharp/Microsoft.Spark.UnitTest"
-}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
deleted file mode 100644
index 0f8011d05..000000000
--- a/.vscode/tasks.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
-    "version": "2.0.0",
-    "tasks": [
-        {
-            "label": "build",
-            "command": "dotnet",
-            "type": "process",
-            "args": [
-                "build",
-                "${workspaceFolder}/src/csharp/Microsoft.Spark.Worker/Microsoft.Spark.Worker.csproj",
-                "/property:GenerateFullPaths=true",
-                "/consoleloggerparameters:NoSummary"
-            ],
-            "problemMatcher": "$msCompile"
-        },
-        {
-            "label": "publish",
-            "command": "dotnet",
-            "type": "process",
-            "args": [
-                "publish",
-                "${workspaceFolder}/src/csharp/Microsoft.Spark.Worker/Microsoft.Spark.Worker.csproj",
-                "/property:GenerateFullPaths=true",
-                "/consoleloggerparameters:NoSummary"
-            ],
-            "problemMatcher": "$msCompile"
-        },
-        {
-            "label": "watch",
-            "command": "dotnet",
-            "type": "process",
-            "args": [
-                "watch",
-                "run",
-                "${workspaceFolder}/src/csharp/Microsoft.Spark.Worker/Microsoft.Spark.Worker.csproj",
-                "/property:GenerateFullPaths=true",
-                "/consoleloggerparameters:NoSummary"
-            ],
-            "problemMatcher": "$msCompile"
-        }
-    ]
-}
\ No newline at end of file
diff --git a/src/csharp/Microsoft.Spark/Sql/Types/ComplexTypes.cs b/src/csharp/Microsoft.Spark/Sql/Types/ComplexTypes.cs
index 2fa5f4f2f..2b65ea6d1 100644
--- a/src/csharp/Microsoft.Spark/Sql/Types/ComplexTypes.cs
+++ b/src/csharp/Microsoft.Spark/Sql/Types/ComplexTypes.cs
@@ -69,22 +69,9 @@ private DataType FromJson(JObject json)
             return this;
         }
 
-        internal override bool NeedConversion() => ElementType.NeedConversion();
+        internal override bool NeedConversion() => true;
 
-        internal override object FromInternal(object obj)
-        {
-            if (!NeedConversion())
-            {
-                return obj;
-            }
-
-            var convertedObj = new List<object>();
-            foreach(object o in (dynamic)obj)
-            {
-                convertedObj.Add(ElementType.FromInternal(o));
-            }
-            return convertedObj;
-        }
+        internal override object FromInternal(object obj) => throw new NotImplementedException();
     }
 
     /// <summary>
@@ -155,21 +142,9 @@ private DataType FromJson(JObject json)
             return this;
         }
 
-        internal override bool NeedConversion() => KeyType.NeedConversion() && ValueType.NeedConversion();
+        internal override bool NeedConversion() => true;
 
-        internal override object FromInternal(object obj)
-        {
-            if (!NeedConversion())
-            {
-                return obj;
-            }
-            var convertedObj = new Dictionary<object, object>();
-            foreach(dynamic kv in (dynamic)obj)
-            {
-                convertedObj[kv.Key] = kv.Value;
-            }
-            return convertedObj;
-        }
+        internal override object FromInternal(object obj) => throw new NotImplementedException();
     }
 
     /// <summary>

From 28ef78a5cb61072e245346379485d67301c8de85 Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Wed, 24 Jun 2020 13:02:00 +0000
Subject: [PATCH 09/13] Add logs for spark fixture processing

---
 src/csharp/Microsoft.Spark.E2ETest/SparkFixture.cs | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/csharp/Microsoft.Spark.E2ETest/SparkFixture.cs b/src/csharp/Microsoft.Spark.E2ETest/SparkFixture.cs
index 6d8dadbac..eda1400a1 100644
--- a/src/csharp/Microsoft.Spark.E2ETest/SparkFixture.cs
+++ b/src/csharp/Microsoft.Spark.E2ETest/SparkFixture.cs
@@ -9,6 +9,7 @@
 using System.Runtime.InteropServices;
 using Microsoft.Spark.Interop.Ipc;
 using Microsoft.Spark.Sql;
+using Microsoft.Spark.Services;
 using Microsoft.Spark.UnitTest.TestUtils;
 using Xunit;
 
@@ -21,6 +22,10 @@ namespace Microsoft.Spark.E2ETest
     /// </summary>
     public sealed class SparkFixture : IDisposable
     {
+        
+        private readonly ILoggerService _logger =
+            LoggerServiceFactory.GetLogger(typeof(SparkFixture));   
+
         /// <summary>
         /// The names of environment variables used by the SparkFixture.
         /// </summary>
@@ -112,11 +117,14 @@ public SparkFixture()
             Spark.SparkContext.SetLogLevel(DefaultLogLevel);
 
             Jvm = ((IJvmObjectReferenceProvider)Spark).Reference.Jvm;
+
+            _logger.LogInfo("SparkSession created.");
         }
 
         public void Dispose()
         {
             Spark.Dispose();
+            _logger.LogInfo("SparkSession disposed.");
 
             // CSparkRunner will exit upon receiving newline from
             // the standard input stream.

From fc2442cb3acb23274c00a460aba825da35b70325 Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Thu, 25 Jun 2020 05:04:57 +0000
Subject: [PATCH 10/13] Move JVMBridgeHelper to Utils and try to locate
 SparkHome

---
 .../{Sql => Utils}/JVMBridgeHelperTests.cs    |   0
 .../Microsoft.Spark/Sql/SparkSession.cs       |   1 +
 .../{Sql => Utils}/JVMBridgeHelper.cs         |  44 ++----
 .../Microsoft.Spark/Utils/SparkSettings.cs    | 149 ++++++++++++++++++
 4 files changed, 166 insertions(+), 28 deletions(-)
 rename src/csharp/Microsoft.Spark.UnitTest/{Sql => Utils}/JVMBridgeHelperTests.cs (100%)
 rename src/csharp/Microsoft.Spark/{Sql => Utils}/JVMBridgeHelper.cs (83%)
 create mode 100644 src/csharp/Microsoft.Spark/Utils/SparkSettings.cs

diff --git a/src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs b/src/csharp/Microsoft.Spark.UnitTest/Utils/JVMBridgeHelperTests.cs
similarity index 100%
rename from src/csharp/Microsoft.Spark.UnitTest/Sql/JVMBridgeHelperTests.cs
rename to src/csharp/Microsoft.Spark.UnitTest/Utils/JVMBridgeHelperTests.cs
diff --git a/src/csharp/Microsoft.Spark/Sql/SparkSession.cs b/src/csharp/Microsoft.Spark/Sql/SparkSession.cs
index d524185b3..cebc1e85b 100644
--- a/src/csharp/Microsoft.Spark/Sql/SparkSession.cs
+++ b/src/csharp/Microsoft.Spark/Sql/SparkSession.cs
@@ -11,6 +11,7 @@
 using Microsoft.Spark.Interop.Ipc;
 using Microsoft.Spark.Sql.Streaming;
 using Microsoft.Spark.Sql.Types;
+using Microsoft.Spark.Utils;
 
 namespace Microsoft.Spark.Sql
 {
diff --git a/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs b/src/csharp/Microsoft.Spark/Utils/JVMBridgeHelper.cs
similarity index 83%
rename from src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs
rename to src/csharp/Microsoft.Spark/Utils/JVMBridgeHelper.cs
index 21c065654..cb541115e 100644
--- a/src/csharp/Microsoft.Spark/Sql/JVMBridgeHelper.cs
+++ b/src/csharp/Microsoft.Spark/Utils/JVMBridgeHelper.cs
@@ -12,18 +12,23 @@
 using Microsoft.Spark.Services;
 using Microsoft.Spark.Interop;
 
-namespace Microsoft.Spark.Sql
+namespace Microsoft.Spark.Utils
 {
     /// <summary>
     /// An helper to launch dotnet jvm if needed
     /// </summary>    
-    public class JVMBridgeHelper : IDisposable
+    internal class JVMBridgeHelper : IDisposable
     {
         /// <summary>
         /// Customization for JVM Bridge jar file
         /// If not exists, the helper will find out the jar in $DOTNET_WORKER_DIR folder.
         /// </summary>
-        public static string JVMBridgeJarEnvName = "DOTNET_BRIDGE_JAR";
+        internal static string JVMBridgeJarEnvName = "DOTNET_BRIDGE_JAR";
+
+        /// <summary>
+        /// Generate spark settings for the running system
+        /// </summary>
+        internal static SparkSettings sparkSettings = new SparkSettings();
 
         /// <summary>
         /// DotnetRunner classname
@@ -53,7 +58,7 @@ public class JVMBridgeHelper : IDisposable
         /// </summary>
         /// <param name="customIPGlobalProperties">custom IPGlobalProperties, null for System.Net.NetworkInformation</param>
         /// <returns> True means backend port is occupied by the runner.</returns>
-        public static bool IsDotnetBackendPortUsing(
+        internal static bool IsDotnetBackendPortUsing(
             IPGlobalProperties customIPGlobalProperties = null)
         {
             var backendport = SparkEnvironment.ConfigurationService.GetBackendPortNumber();
@@ -63,12 +68,11 @@ public static bool IsDotnetBackendPortUsing(
             return listeningEndpoints.Any(p => p.Port == backendport);
         }
 
-        public JVMBridgeHelper()
+        internal JVMBridgeHelper()
         {
             var jarpath = locateBridgeJar();
-            var sparksubmit = locateSparkSubmit();
             if (string.IsNullOrWhiteSpace(jarpath) ||
-                string.IsNullOrWhiteSpace(sparksubmit))
+                string.IsNullOrWhiteSpace(sparkSettings.SPARK_SUBMIT))
             {
                 // Cannot find correct launch informations, give up.
                 return;
@@ -76,7 +80,7 @@ public JVMBridgeHelper()
             var arguments = $"--class {RunnerClassname} {jarpath} debug";
             var startupinfo = new ProcessStartInfo
             {
-                FileName = sparksubmit,
+                FileName = sparkSettings.SPARK_SUBMIT,
                 Arguments = arguments,
                 RedirectStandardOutput = true,
                 RedirectStandardInput = true,
@@ -85,7 +89,7 @@ public JVMBridgeHelper()
             };
 
             jvmBridge = new Process() { StartInfo = startupinfo };
-            _logger.LogInfo($"Launch JVM Bridge : {sparksubmit} {arguments}");
+            _logger.LogInfo($"Launch JVM Bridge : {sparkSettings.SPARK_SUBMIT} {arguments}");
             jvmBridge.Start();
 
             // wait until we see .net backend started
@@ -118,25 +122,9 @@ public JVMBridgeHelper()
             jvmBridge = null;
         }
 
-        private string locateSparkSubmit()
-        {
-            var sparkHome = Environment.GetEnvironmentVariable("SPARK_HOME");
-            if (string.IsNullOrWhiteSpace(sparkHome))
-            {
-                return string.Empty;
-            }
-            var filename = Path.Combine(sparkHome, "bin", "spark-submit");
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-            {
-                filename += ".cmd";
-            }
-            if (!File.Exists(filename))
-            {
-                return string.Empty;
-            }
-            return filename;
-        }
-
+        /// <summary>
+        /// Locate 
+        /// </summary>
         private string locateBridgeJar()
         {
             var jarpath = Environment.GetEnvironmentVariable(JVMBridgeJarEnvName);
diff --git a/src/csharp/Microsoft.Spark/Utils/SparkSettings.cs b/src/csharp/Microsoft.Spark/Utils/SparkSettings.cs
new file mode 100644
index 000000000..09a8c1d06
--- /dev/null
+++ b/src/csharp/Microsoft.Spark/Utils/SparkSettings.cs
@@ -0,0 +1,149 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Spark.Utils
+{
+    /// <summary>
+    /// Discover the spark settings
+    /// </summary>
+    internal class SparkSettings
+    {
+        private static string SPARK_HOME_ENV_KEY = "SPARK_HOME";
+
+        /// <summary>
+        /// The spark home
+        /// This would be empty string when failed to findout.
+        /// </summary>
+        public string SPARK_HOME {get; private set;}
+
+        /// <summary>
+        /// The spark submit file
+        /// </summary>
+        public string SPARK_SUBMIT {get;private set;}
+
+
+        public Version Version { get; private set; }
+
+        private static string sparksubmitcmd = 
+            RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
+            ? "spark-submit.cmd" : "spark-submit";
+
+        /// <summary>
+        /// Init the spark settings
+        /// </summary>
+        public SparkSettings()
+        {
+            find_spark_home();
+            this.SPARK_SUBMIT = locateSparkSubmit(SPARK_HOME);
+            InitVersion();
+        }
+
+        /// <summary>
+        /// Locate SPARK_HOME path
+        /// </summary>
+        /// <returns>Return empty string when failed to find out.</returns>
+        private void find_spark_home()
+        {
+            SPARK_HOME = Environment.GetEnvironmentVariable(SPARK_HOME_ENV_KEY);
+            if (string.IsNullOrWhiteSpace(SPARK_HOME) == false)
+            {
+                return;
+            }
+            foreach (var possiblehome in possible_spark_home())
+            {
+                if (isSparkHome(possiblehome))
+                {
+                    SPARK_HOME = possiblehome;
+                    Environment.SetEnvironmentVariable(SPARK_HOME_ENV_KEY, SPARK_HOME);
+                    return;
+                }
+            }
+        }
+
+        private IEnumerable<string> possible_spark_home()
+        {
+            var pathSeperator = ':';
+            var envPath = "PATH";
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                pathSeperator = ';';
+                envPath = "Path";
+            }
+            // try Env Pathes if we could locate spark
+            foreach (var path in Environment.GetEnvironmentVariable(envPath).Split(pathSeperator))
+            {
+                yield return path;
+                yield return Path.GetFullPath(Path.Combine(path, ".."));
+            }
+        }
+
+        /// <summary>
+        /// find out the existing spark-submit file
+        /// </summary>
+        private string locateSparkSubmit(string sparkhome)
+        {
+            var fn = Path.Combine(sparkhome, "bin", sparksubmitcmd);
+            return File.Exists(fn)
+                ? fn : string.Empty;
+        }
+
+        private string locateJarFolder(string sparkhome)
+        {
+            var possible = new string[] { "jars", "assembly" };
+            foreach (var tryfolder in possible)
+            {
+                var folder = Path.Combine(sparkhome, tryfolder);
+                if (Directory.Exists(folder))
+                {
+                    return folder;
+                }
+            }
+            return string.Empty;
+        }
+
+        /// <summary>
+        /// Check if it is a reasonable SPARK_HOME
+        /// </summary>
+        private bool isSparkHome(string path)
+        {
+            return (locateSparkSubmit(path) != string.Empty) &&
+                (locateJarFolder(path) != string.Empty);
+        }
+
+        /// <summary>
+        /// After init spark home, try to find out spark version
+        /// </summary>
+        private void InitVersion()
+        {
+            var releasefile = $"{SPARK_HOME}{Path.DirectorySeparatorChar}RELEASE";
+            var sparkversionstr = string.Empty;
+            if (File.Exists(releasefile))
+            {
+                // First line of the RELEASE file under SPARK_HOME will be something similar to:
+                // Spark 2.3.2 built for Hadoop 2.7.3
+                var firstLine = File.ReadLines(releasefile).FirstOrDefault();
+                var columns = firstLine?.Split(' ');
+                if (columns.Length >= 1)
+                {
+                    sparkversionstr = columns[1];
+                }
+            }
+            if (string.IsNullOrWhiteSpace(sparkversionstr))
+            {
+                this.Version = new Version();
+            }
+            else
+            {
+                this.Version = new Version(sparkversionstr);
+            }
+        }
+
+    }
+}
\ No newline at end of file

From 14b6db05e1fc20587eba7141c5f6d73685922573 Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Thu, 25 Jun 2020 07:33:23 +0000
Subject: [PATCH 11/13] Fix spark home is null at unit-test

---
 src/csharp/Microsoft.Spark/Utils/SparkSettings.cs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/csharp/Microsoft.Spark/Utils/SparkSettings.cs b/src/csharp/Microsoft.Spark/Utils/SparkSettings.cs
index 09a8c1d06..aa5a61e4d 100644
--- a/src/csharp/Microsoft.Spark/Utils/SparkSettings.cs
+++ b/src/csharp/Microsoft.Spark/Utils/SparkSettings.cs
@@ -54,6 +54,7 @@ private void find_spark_home()
             SPARK_HOME = Environment.GetEnvironmentVariable(SPARK_HOME_ENV_KEY);
             if (string.IsNullOrWhiteSpace(SPARK_HOME) == false)
             {
+                SPARK_HOME = string.Empty;
                 return;
             }
             foreach (var possiblehome in possible_spark_home())
@@ -89,7 +90,7 @@ private IEnumerable<string> possible_spark_home()
         /// </summary>
         private string locateSparkSubmit(string sparkhome)
         {
-            var fn = Path.Combine(sparkhome, "bin", sparksubmitcmd);
+            var fn = Path.Combine(sparkhome ?? string.Empty, "bin", sparksubmitcmd);
             return File.Exists(fn)
                 ? fn : string.Empty;
         }

From 2fad72b51e08422f19d3eed0bcc26b64a787530a Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Tue, 7 Jul 2020 22:35:31 +0000
Subject: [PATCH 12/13] Fix the jvm bridge helper dispose issue

---
 src/csharp/Microsoft.Spark/Sql/SparkSession.cs | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/src/csharp/Microsoft.Spark/Sql/SparkSession.cs b/src/csharp/Microsoft.Spark/Sql/SparkSession.cs
index cebc1e85b..c67900a21 100644
--- a/src/csharp/Microsoft.Spark/Sql/SparkSession.cs
+++ b/src/csharp/Microsoft.Spark/Sql/SparkSession.cs
@@ -345,13 +345,6 @@ public UdfRegistration Udf() =>
         public void Stop()
         {
             _jvmObject.Invoke("stop");
-
-            // if we have created the jvm bridge process, dispose it now.
-            if (s_jvmbridge != null)
-            {
-                s_jvmbridge.Dispose();
-                s_jvmbridge = null;
-            }
         }
 
         /// <summary>

From f7b10169170f3cdb6a2e07233f2986e9d1f7544c Mon Sep 17 00:00:00 2001
From: laneser <laneser.kuo@gmail.com>
Date: Tue, 7 Jul 2020 22:38:15 +0000
Subject: [PATCH 13/13] restore the Stop()

---
 src/csharp/Microsoft.Spark/Sql/SparkSession.cs | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/csharp/Microsoft.Spark/Sql/SparkSession.cs b/src/csharp/Microsoft.Spark/Sql/SparkSession.cs
index c67900a21..6e9008ff5 100644
--- a/src/csharp/Microsoft.Spark/Sql/SparkSession.cs
+++ b/src/csharp/Microsoft.Spark/Sql/SparkSession.cs
@@ -342,10 +342,7 @@ public UdfRegistration Udf() =>
         /// <summary>
         /// Stops the underlying SparkContext.
         /// </summary>
-        public void Stop()
-        {
-            _jvmObject.Invoke("stop");
-        }
+        public void Stop() => _jvmObject.Invoke("stop");
 
         /// <summary>
         /// Returns a single column schema of the given datatype.