Skip to content

Commit

Permalink
fix(tools): kill child process on timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
ElijahReva committed Dec 19, 2023
1 parent 30aa4a8 commit 92787fb
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 3 deletions.
11 changes: 10 additions & 1 deletion source/Nuke.Tooling/IProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,16 @@ public interface IProcess : IDisposable
void Kill();

/// <summary>
/// Waits for the process to exit. If the process is not exiting within a given timeout, <see cref="Kill"/> is called.
/// Calls platform specific code to kill entire process tree.
/// </summary>
/// <remarks>
/// For Windows it call "taskkill /T /F /PID {process.Id}"
/// For Unix it call combination of "pgrep and kill commands"
/// </remarks>
void KillTree();

/// <summary>
/// Waits for the process to exit. If the process is not exiting within a given timeout, <see cref="KillTree"/> is called.
/// </summary>
/// <returns>
/// Returns <c>true</c>, if the process exited on its own.
Expand Down
114 changes: 114 additions & 0 deletions source/Nuke.Tooling/NativeProcessExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2023 Maintainers of NUKE.
// Distributed under the MIT License.
// https://github.com/nuke-build/nuke/blob/master/LICENSE

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

namespace Nuke.Common.Tooling;

// https://raw.githubusercontent.com/dotnet/cli/master/test/Microsoft.DotNet.Tools.Tests.Utilities/Extensions/ProcessExtensions.cs
internal static class NativeProcessExtension
{
private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);

internal static void KillTree(this Process process)
{
process.KillTree(_defaultTimeout);
}

internal static void KillTree(this Process process, TimeSpan timeout)
{
string stdout;
if (_isWindows)
{
RunProcessAndWaitForExit(
"taskkill",
$"/T /F /PID {process.Id}",
timeout,
out stdout);
}
else
{
var children = new HashSet<int>();
GetAllChildIdsUnix(process.Id, children, timeout);
foreach (var childId in children)
{
KillProcessUnix(childId, timeout);
}
KillProcessUnix(process.Id, timeout);
}
}

private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
{
string stdout;
var exitCode = RunProcessAndWaitForExit(
"pgrep",
$"-P {parentId}",
timeout,
out stdout);

if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
{
using (var reader = new StringReader(stdout))
{
while (true)
{
var text = reader.ReadLine();
if (text == null)
{
return;
}

int id;
if (int.TryParse(text, out id))
{
children.Add(id);
// Recursively get the children
GetAllChildIdsUnix(id, children, timeout);
}
}
}
}
}

private static void KillProcessUnix(int processId, TimeSpan timeout)
{
string stdout;
RunProcessAndWaitForExit(
"kill",
$"-TERM {processId}",
timeout,
out stdout);
}

private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout)
{
var startInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
RedirectStandardOutput = true,
UseShellExecute = false
};

var process = Process.Start(startInfo);

stdout = null;
if (process.WaitForExit((int)timeout.TotalMilliseconds))
{
stdout = process.StandardOutput.ReadToEnd();
}
else
{
process.Kill();
}

return process.ExitCode;
}
}
9 changes: 7 additions & 2 deletions source/Nuke.Tooling/Process2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ public void Dispose()

public void Kill()
{
_process.Kill();
_process.Kill();
}

public void KillTree()
{
_process.KillTree();
}

public bool WaitForExit()
Expand All @@ -53,7 +58,7 @@ public bool WaitForExit()
// use _process.StartTime
var hasExited = _process.WaitForExit(_timeout ?? -1);
if (!hasExited)
_process.Kill();
_process.KillTree();
return hasExited;
}
}

0 comments on commit 92787fb

Please sign in to comment.