【问题标题】:Reliably kill node.js process launched via cmd process in a .NET Core app在 .NET Core 应用程序中可靠地杀死通过 cmd 进程启动的 node.js 进程
【发布时间】:2018-04-18 20:18:51
【问题描述】:

我要解决什么问题

为了改进我的构建管道,我想添加一个端到端测试步骤。我计划通过 CLI 工具(.NET“控制台应用程序”)来实现它。该工具将启动并编排一些npm/node 命令(进程)。

更具体地说,会有:

  1. 一个后端进程;
  2. 一个前端进程;
  3. 和一个测试过程。

当测试进程 (3) 完成时,CLI 工具应该优雅地终止后端 (1) 和前端 (2) 进程,如果 则返回 0 退出代码每个编排的进程都已成功终止。

麻烦

在下面的Minimal, Complete, and Verifiable example 中,我试图启动一个进程serviceAlikeProcess 和一个失败的进程(brokenWithErrorProcess)。当后一个失败时,我试图通过Kill(process)方法强制终止前一个。

!!! 因为它是suggested here,所以node/npm 进程正在通过cmd 进程启动。 IE。我首先启动了一个cmd 进程,然后将node test.js 写入其stdin 流。 node 进程启动得很好,但是当 cmd 进程稍后终止时,node 进程继续运行并产生输出。

我想这是因为 cmdnode 进程没有以父子关系链接(因为如果我从任务中手动终止 cmd 进程经理,我观察到完全相同的行为)。

问题

如何可靠地终止这两个进程?

想法:我正在考虑捕获node进程'pid,然后自己终止cmdnode进程,但我还没有找到捕获的方法那pid...

代码

程序.cs

using System;
using System.Diagnostics;
using System.IO;

namespace RunE2E
{
    public class Program
    {
        static string currentDirectory = Directory.GetCurrentDirectory();

        public static int Main(string[] args)
        {
            var serviceAlikeProcess = StartProcessViaCmd("node", "test.js", "");


            var brokenWithErrorProcess = StartProcessViaCmd("npm", "THIS IS NOT A REAL COMMAND, THEREFORE EXPECTED TO FAIL", "");
            brokenWithErrorProcess.Exited += (_, __) => KillProcess(serviceAlikeProcess);


            serviceAlikeProcess.WaitForExit();
            return serviceAlikeProcess.ExitCode;
        }

        private static Process StartProcessViaCmd(string command, string arguments, string workingDirectory)
        {
            workingDirectory = NormalizeWorkingDirectory(workingDirectory);

            var process = new Process
            {
                EnableRaisingEvents = true,
                StartInfo = new ProcessStartInfo
                {
                    FileName = "cmd",
                    Arguments = arguments,
                    WorkingDirectory = workingDirectory,
                    UseShellExecute = false,
                    RedirectStandardInput = true,
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true,
                }
            };

            process.ErrorDataReceived += (_, e) => handle(command, arguments, workingDirectory, "ERROR", e.Data);
            process.OutputDataReceived += (_, e) => handle(command, arguments, workingDirectory, "OUTPUT", e.Data);

            try
            {
                Console.WriteLine($"[{workingDirectory}] {command} {arguments}");

                var _ = process.Start();

                process.BeginOutputReadLine();
                process.StandardInput.WriteLine($"{command} {arguments} & exit");
            }
            catch (Exception exc)
            {
                Console.WriteLine($"[{workingDirectory}] {command} {arguments} : {exc}");
                throw;
            }
            return process;
        }

        static string NormalizeWorkingDirectory(string workingDirectory)
        {
            if (string.IsNullOrWhiteSpace(workingDirectory))
                return currentDirectory;
            else if (Path.IsPathRooted(workingDirectory))
                return workingDirectory;
            else
                return Path.GetFullPath(Path.Combine(currentDirectory, workingDirectory));
        }

        static Action<string, string, string, string, string> handle =
            (string command, string arguments, string workingDirectory, string level, string message) =>
            Console.WriteLine($"[{workingDirectory}] {command} {arguments} {level}: {message}");

        static void KillProcess(Process process)
        {
            if (process != null && !process.HasExited)
                process.Kill();
        }
    }
}

test.js

setInterval(() => {
    console.info(new Date());
}, 1000);

截图

在以编程方式启动任何进程之前

`cmd`进程被杀死后

控制台输出

【问题讨论】:

    标签: c# .net node.js cmd .net-core


    【解决方案1】:

    我真的真的不喜欢最终回答我自己的问题,尤其是当答案是基于一种 hack-ish 实现结果的方式时。

    但是,我知道这可能会节省其他人的时间。所以,这是我的解决方案:

    代码

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Threading;
    
    namespace RunE2E
    {
        public class Program
        {
            static string currentDirectory = Directory.GetCurrentDirectory();
    
            public static int Main(string[] args)
            {
                var serviceAlikeProcessResult = StartProcessViaCmd("node", "test.js", "");
                var serviceAlikeProcess = serviceAlikeProcessResult.MainProcess;
    
                var brokenWithErrorResult = StartProcessViaCmd("npm", "THIS IS NOT A REAL COMMAND, THEREFORE EXPECTED TO FAIL", "");
                var brokenWithErrorProcess = brokenWithErrorResult.MainProcess;
                brokenWithErrorProcess.Exited += (_, __) =>
                {
                    KillProcesses("Front-End", serviceAlikeProcessResult.MainProcess, serviceAlikeProcessResult.CreatedProcesses);
                    KillProcesses("E2E-Test", brokenWithErrorResult.MainProcess, brokenWithErrorResult.CreatedProcesses);
                };
    
                serviceAlikeProcess.WaitForExit();
                return serviceAlikeProcess.ExitCode;
            }
    
            private static CommandStartResult StartProcessViaCmd(string command, string arguments, string workingDirectory)
            {
                workingDirectory = NormalizeWorkingDirectory(workingDirectory);
    
                var process = new Process
                {
                    EnableRaisingEvents = true,
                    StartInfo = new ProcessStartInfo
                    {
                        FileName = "cmd",
                        Arguments = arguments,
                        WorkingDirectory = workingDirectory,
                        UseShellExecute = false,
                        RedirectStandardInput = true,
                        RedirectStandardError = true,
                        RedirectStandardOutput = true,
                        CreateNoWindow = true,
                    },
                };
                var createdProcesses = new List<Process>();
    
                process.ErrorDataReceived += (_, e) => handle(command, arguments, workingDirectory, "ERROR", e.Data);
                process.OutputDataReceived += (_, e) => handle(command, arguments, workingDirectory, "", e.Data);
    
                var commandId = $"[{workingDirectory}] {command} {arguments}";
                try
                {
                    WriteLine(commandId);
    
                    createdProcesses = StartProcessAndCapture(commandId, process);
    
                    process.BeginOutputReadLine();
                    process.StandardInput.WriteLine($"{command} {arguments} & exit");
                }
                catch (Exception exc)
                {
                    WriteLine($"{commandId}: {exc}");
                    throw;
                }
    
                return new CommandStartResult
                {
                    MainProcess = process,
                    CreatedProcesses = createdProcesses,
                };
            }
    
            static List<Process> StartProcessAndCapture(string commandId, Process processToStart)
            {
                var before = Process.GetProcesses().ToList();
                var beforePidSet = new HashSet<int>(before.Select(process => process.Id));
    
                var _ = processToStart.Start();
    
                Thread.Sleep(3000);
    
                var after = Process.GetProcesses().ToList();
                var newlyCreatedProcessIdList = new HashSet<int>(after.Select(process => process.Id));
                newlyCreatedProcessIdList.ExceptWith(beforePidSet);
                var createdProcesses = after.Where(process => newlyCreatedProcessIdList.Contains(process.Id)).ToList();
    
                foreach (var process in createdProcesses)
                    WriteLine($"{commandId} ||| [{process.Id}] {process.ProcessName}", ConsoleColor.Blue);
    
                return createdProcesses;
            }
    
            static string NormalizeWorkingDirectory(string workingDirectory)
            {
                if (string.IsNullOrWhiteSpace(workingDirectory))
                    return currentDirectory;
                else if (Path.IsPathRooted(workingDirectory))
                    return workingDirectory;
                else
                    return Path.GetFullPath(Path.Combine(currentDirectory, workingDirectory));
            }
    
            static Action<string, string, string, string, string> handle =
                (string command, string arguments, string workingDirectory, string level, string message) =>
                {
                    var defaultColor = Console.ForegroundColor;
                    Write($"[{workingDirectory}] ");
                    Write($"{command} ", ConsoleColor.DarkGreen);
                    Write($"{arguments}", ConsoleColor.Green);
                    Write($"{level} ", level == "" ? defaultColor : ConsoleColor.Red);
                    WriteLine($": {message}");
                };
    
            static void KillProcesses(string prefix, Process baseProcess, List<Process> processList)
            {
                processList = baseProcess == null ?
                    processList :
                    processList.Where(process => process.Id != baseProcess.Id).Append(baseProcess).ToList();
    
                foreach (var process in processList)
                    KillProcess(prefix, process);
            }
    
            static void KillProcess(string prefix, Process process)
            {
                if (process != null && !process.HasExited)
                    try
                    {
                        WriteLine(prefix + " | Kill (" + process.ProcessName + ") [" + process.Id + "]");
                        process.Kill();
                    }
                    catch (Win32Exception win32exc)
                    {
                        WriteLine(prefix + " | Kill (" + process.ProcessName + ") [" + process.Id + "]: " + win32exc.Message);
                    }
            }
    
            static void WaitForExit(Process process)
            {
                while (process.HasExited == false) { }
            }
    
            static object console = new object();
            static void Write(string text, ConsoleColor? color = null)
            {
                lock (console)
                {
                    var original = Console.ForegroundColor;
                    Console.ForegroundColor = color.HasValue ? color.Value : original;
                    Console.Write(text);
                    Console.ForegroundColor = original;
                }
            }
            static void WriteLine(string text = null, ConsoleColor? color = null)
            {
                lock (console)
                {
                    var original = Console.ForegroundColor;
                    Console.ForegroundColor = color.HasValue ? color.Value : original;
                    Console.WriteLine(text);
                    Console.ForegroundColor = original;
                }
            }
        }
    
        class CommandStartResult
        {
            public Process MainProcess { get; set; }
            public List<Process> CreatedProcesses { get; set; }
        }
    }
    

    此外,在处理 .NET Core 进程时,可能需要使用以下方法。

            private static CommandStartResult StartDotnetProcess(string arguments, string workingDirectory)
            {
                var command = "dotnet";
                workingDirectory = NormalizeWorkingDirectory(workingDirectory);
    
                var process = PrepareProcess(command, arguments, workingDirectory);
                var createdProcesses = new List<Process>();
                var commandId = $"[{workingDirectory}] {command} {arguments}";
                try
                {
                    WriteLine(commandId);
    
                    createdProcesses = StartProcessAndCapture(commandId, process);
    
                    process.BeginOutputReadLine();
                }
                catch (Exception exc)
                {
                    WriteLine($"{commandId} : {exc}");
                    throw;
                }
    
                return new CommandStartResult
                {
                    MainProcess = process,
                    CreatedProcesses = createdProcesses,
                };
            }
    
            private static Process PrepareProcess(
                string command,
                string arguments,
                string workingDirectory
            )
            {
                var process = new Process
                {
                    EnableRaisingEvents = true,
                    StartInfo = new ProcessStartInfo
                    {
                        FileName = command,
                        Arguments = arguments,
                        WorkingDirectory = workingDirectory,
                        UseShellExecute = false,
                        RedirectStandardOutput = true,
                        RedirectStandardError = true,
                    },
                };
    
                process.ErrorDataReceived += (_, e) => handle(command, arguments, workingDirectory, "ERROR", e.Data);
                process.OutputDataReceived += (_, e) => handle(command, arguments, workingDirectory, "", e.Data);
    
                process.StartInfo.Environment.Add("ASPNETCORE_ENVIRONMENT", "Development");
    
                return process;
            }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-01
      • 1970-01-01
      相关资源
      最近更新 更多