代码之家  ›  专栏  ›  技术社区  ›  Igor Soloydenko

可靠地终止节点。在中通过cmd进程启动js进程。NET Core应用程序

  •  2
  • Igor Soloydenko  · 技术社区  · 7 年前

    我想解决什么问题

    为了改进构建管道,我想添加一个端到端测试步骤。我计划通过CLI工具(.NET“Console App”)实现它。该工具将启动并协调一些 npm / node 命令(进程)。

    更具体地说,将有:

    1. 后端流程;
    2. 前端流程;
    3. 以及测试过程。

    当测试过程( 3 )完成后,CLI工具应终止后端( 1 )和前端( 2 )优雅地处理,再加上回报 0 退出代码if 每一个 已成功终止编排的进程。

    麻烦

    在我的 Minimal, Complete, and Verifiable example 下面我正在尝试启动一个流程 serviceAlikeProcess 还有一个失败的过程( brokenWithErrorProcess ).当后一个失败时,我试图通过 Kill(process) 方法

    !!! 照现在的样子 suggested here 这个 节点 / npm公司 正在通过以下方式启动流程: cmd 过程一、 e.我首先旋转一个 cmd命令 处理,然后写入 node test.js 至its stdin 流动这个 节点 进程启动得很好,但当 cmd命令 进程稍后终止,则 节点 进程保持运行并生成输出。

    我想这是因为 cmd命令 节点 未在父子关系中链接进程 (因为如果我手动终止 cmd命令 从任务管理器的过程中,我观察到了相同的行为)。

    问题

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

    想法: 我在考虑捕获 节点 进程' pid 然后终止两者 cmd命令 节点 我自己处理,但我还没有找到一种方法来捕捉 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();
            }
        }
    }
    

    测验js公司

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

    屏幕截图

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

    enter image description here

    在“cmd”进程被终止后

    enter image description here

    控制台输出

    enter image description here

    1 回复  |  直到 7 年前
        1
  •  4
  •   Igor Soloydenko    7 年前

    我真的 真正地 我不喜欢最后回答我自己的问题,尤其是当答案是基于一种达到结果的黑客方式时。

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

    密码

    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核心流程。

            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;
            }