<commit_before>﻿/*
 * SonarLint for Visual Studio
 * Copyright (C) 2016-2021 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Abstractions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Integration;
using SonarLint.VisualStudio.Integration.Helpers;
using SonarLint.VisualStudio.TypeScript.NodeJSLocator;

namespace SonarLint.VisualStudio.TypeScript.EslintBridgeClient
{
    internal interface IEslintBridgeProcess : IDisposable
    {
        /// <summary>
        /// Ensures that the process is running. Returns the port that server is running on.
        /// </summary>
        Task<int> Start();

        /// <summary>
        /// Terminates running process.
        /// </summary>
        void Stop();
    }

    internal sealed class EslintBridgeProcess : IEslintBridgeProcess
    {
        private static readonly object Lock = new object();

        private readonly string eslintBridgeStartupScriptPath;
        private readonly INodeLocator nodeLocator;
        private readonly IFileSystem fileSystem;
        private readonly ILogger logger;
        private TaskCompletionSource<int> startTask;

        internal Process Process;

        private const int NO_PROCESS = 0;
        int processId = NO_PROCESS;

        internal EslintBridgeProcess(string eslintBridgeStartupScriptPath,
            INodeLocator nodeLocator,
            ILogger logger)
            : this(eslintBridgeStartupScriptPath, nodeLocator, new FileSystem(), logger)
        {
        }

        internal EslintBridgeProcess(string eslintBridgeStartupScriptPath,
            INodeLocator nodeLocator,
            IFileSystem fileSystem,
            ILogger logger)
        {
            this.eslintBridgeStartupScriptPath = eslintBridgeStartupScriptPath;
            this.nodeLocator = nodeLocator;
            this.fileSystem = fileSystem;
            this.logger = logger;
        }

        public Task<int> Start()
        {
            lock (Lock)
            {
                var shouldSpawnNewProcess = startTask == null ||
                                            startTask.Task.IsFaulted ||
                                            Process == null ||
                                            Process.HasExited;

                if (!shouldSpawnNewProcess)
                {
                    return startTask.Task;
                }

                startTask = new TaskCompletionSource<int>();

                try
                {
                    StartServer();
                }
                catch (Exception ex)
                {
                    startTask.SetException(ex);
                }

                return startTask.Task;
            }
        }

        public void Stop()
        {
            lock (Lock)
            {
                TerminateRunningProcess();
            }
        }

        public void Dispose()
        {
            Stop();
        }

        private void StartServer()
        {
            var nodePath = FindNodePath();
            var serverStartupScriptLocation = FindServerScriptFile();

            var psi = new ProcessStartInfo
            {
                FileName = nodePath,
                RedirectStandardError = true,
                RedirectStandardOutput = true,
                UseShellExecute = false, // required if we want to capture the error output
                ErrorDialog = false,
                CreateNoWindow = true,
                Arguments = serverStartupScriptLocation
            };

            Process = new Process { StartInfo = psi };
            Process.ErrorDataReceived += OnErrorDataReceived;
            Process.OutputDataReceived += OnOutputDataReceived;

            Process.Start();
            // Storing the process so we don't have to worry about checking whether Process is null when logging,
            // and so that if any messages are logged after Process is set to null we know which process it related to.
            processId = Process.Id;
            logger.WriteLine(Resources.INFO_ServerProcessId, processId);
            logger.LogDebug($"[eslint-bridge] [process id: {processId}] Server process HasExited: {1}.", processId, Process.HasExited);

            Process.BeginErrorReadLine();
            Process.BeginOutputReadLine();

            // Note: we need to call the "BeginXXX" methods before registering to the exit event.
            // Otherwise, if the underlying process crashes immediately, the event handler could
            // have set "Process" to null before we get a chance to call the "BeginXXX" methods ->
            // we lose the error output and get a null ref exception instead.
            Process.Exited += Process_Exited;
        }

        private void Process_Exited(object sender, EventArgs e)
        {
            logger.LogDebug($"[eslint-bridge] [process id: {processId}] Process exited.", processId);
            ClearProcessData();
        }

        private void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data))
            {<commit_msg>Fix for potential race condition terminating eslintbridge (#2447)<commit_after>﻿/*
 * SonarLint for Visual Studio
 * Copyright (C) 2016-2021 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Abstractions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Integration;
using SonarLint.VisualStudio.Integration.Helpers;
using SonarLint.VisualStudio.TypeScript.NodeJSLocator;

namespace SonarLint.VisualStudio.TypeScript.EslintBridgeClient
{
    internal interface IEslintBridgeProcess : IDisposable
    {
        /// <summary>
        /// Ensures that the process is running. Returns the port that server is running on.
        /// </summary>
        Task<int> Start();

        /// <summary>
        /// Terminates running process.
        /// </summary>
        void Stop();
    }

    internal sealed class EslintBridgeProcess : IEslintBridgeProcess
    {
        private static readonly object Lock = new object();

        private readonly string eslintBridgeStartupScriptPath;
        private readonly INodeLocator nodeLocator;
        private readonly IFileSystem fileSystem;
        private readonly ILogger logger;
        private TaskCompletionSource<int> startTask;

        internal Process Process;

        private const int NO_PROCESS = 0;
        int processId = NO_PROCESS;

        internal EslintBridgeProcess(string eslintBridgeStartupScriptPath,
            INodeLocator nodeLocator,
            ILogger logger)
            : this(eslintBridgeStartupScriptPath, nodeLocator, new FileSystem(), logger)
        {
        }

        internal EslintBridgeProcess(string eslintBridgeStartupScriptPath,
            INodeLocator nodeLocator,
            IFileSystem fileSystem,
            ILogger logger)
        {
            this.eslintBridgeStartupScriptPath = eslintBridgeStartupScriptPath;
            this.nodeLocator = nodeLocator;
            this.fileSystem = fileSystem;
            this.logger = logger;
        }

        public Task<int> Start()
        {
            lock (Lock)
            {
                var shouldSpawnNewProcess = startTask == null ||
                                            startTask.Task.IsFaulted ||
                                            Process == null ||
                                            Process.HasExited;

                if (!shouldSpawnNewProcess)
                {
                    return startTask.Task;
                }

                startTask = new TaskCompletionSource<int>();

                try
                {
                    StartServer();
                }
                catch (Exception ex)
                {
                    startTask.SetException(ex);
                }

                return startTask.Task;
            }
        }

        public void Stop()
        {
            lock (Lock)
            {
                TerminateRunningProcess();
            }
        }

        public void Dispose()
        {
            Stop();
        }

        private void StartServer()
        {
            var nodePath = FindNodePath();
            var serverStartupScriptLocation = FindServerScriptFile();

            var psi = new ProcessStartInfo
            {
                FileName = nodePath,
                RedirectStandardError = true,
                RedirectStandardOutput = true,
                UseShellExecute = false, // required if we want to capture the error output
                ErrorDialog = false,
                CreateNoWindow = true,
                Arguments = serverStartupScriptLocation
            };

            Process = new Process { StartInfo = psi };
            Process.ErrorDataReceived += OnErrorDataReceived;
            Process.OutputDataReceived += OnOutputDataReceived;

            Process.Start();
            // Storing the process so we don't have to worry about checking whether Process is null when logging,
            // and so that if any messages are logged after Process is set to null we know which process it related to.
            processId = Process.Id;
            logger.WriteLine(Resources.INFO_ServerProcessId, processId);
            logger.LogDebug($"[eslint-bridge] [process id: {processId}] Server process HasExited: {Process.HasExited}.");

            Process.BeginErrorReadLine();
            Process.BeginOutputReadLine();

            // Note: we need to call the "BeginXXX" methods before registering to the exit event.
            // Otherwise, if the underlying process crashes immediately, the event handler could
            // have set "Process" to null before we get a chance to call the "BeginXXX" methods ->
            // we lose the error output and get a null ref exception instead.
            Process.Exited += Process_Exited;
        }

        private void Process_Exited(object sender, EventArgs e)
        {
            if(sender is Process terminatedProcess)
            {
                logger.LogDebug($"[eslint-bridge] [process id: {terminatedProcess.Id}] Process exited.");
                terminatedProcess.Exited -= Process_Exited;
            }
        }

        private void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data))
            {
{'fraction_non_alphanumeric': 0.0669200264375413, 'fraction_numerical': 0.003883013879709187, 'mean_word_length': 1.8890214797136038, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1560556', 'n_tokens_mistral': 2967, 'n_tokens_neox': 2790, 'n_words': 943}