-
-
Save TehGM/c953b670ad8019b2b2be6af7b14807c2 to your computer and use it in GitHub Desktop.
Test for process start leaking memory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics; | |
using System.Runtime.InteropServices; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace ProcessStartLeakTest | |
{ | |
class Program | |
{ | |
static int _iteration = 0; | |
static async Task Main(string[] args) | |
{ | |
TimeSpan waitTime = TimeSpan.Zero; | |
TerminalResult result; | |
// for Raspberry Pi: "xprintidle" | |
string prc = "ifconfig"; | |
// for windows: "ipconfig" | |
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | |
prc = "ipconfig"; | |
for (; ; ) | |
{ | |
_iteration++; | |
result = await ExecuteAndWaitAsync(prc, false).ConfigureAwait(false); | |
if (waitTime > TimeSpan.Zero) | |
await Task.Delay(waitTime).ConfigureAwait(false); | |
} | |
} | |
// note this code has some parts that aren't even needed - I was simply trying anything to solve this problem at this point | |
public static async Task<TerminalResult> ExecuteAndWaitAsync(string command, bool asRoot, CancellationToken cancellationToken = default) | |
{ | |
using Process prc = CreateNewProcess(command, asRoot); | |
// we need to redirect stdstreams to read them | |
prc.StartInfo.RedirectStandardOutput = true; | |
prc.StartInfo.RedirectStandardError = true; | |
// start the process | |
Console.WriteLine($"{_iteration}: Starting the process"); | |
using Task waitForExitTask = WaitForExitAsync(prc, cancellationToken); | |
prc.Start(); | |
// read streams | |
string[] streamResults = await Task.WhenAll(prc.StandardOutput.ReadToEndAsync(), prc.StandardError.ReadToEndAsync()).ConfigureAwait(false); | |
// wait till it fully exits, but no longer than half a second | |
// this prevents hanging when process has already finished, but takes long time to fully close | |
await Task.WhenAny(waitForExitTask, Task.Delay(500, cancellationToken)).ConfigureAwait(false); | |
// if process still didn't exit, force kill it | |
if (!prc.HasExited) | |
prc.Kill(true); // doing it with a try-catch approach instead of HasExited check gives no difference | |
return new TerminalResult(streamResults[0], streamResults[1]); | |
} | |
public static Task<int> WaitForExitAsync(Process process, CancellationToken cancellationToken = default) | |
{ | |
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(); | |
IDisposable tokenRegistration = null; | |
EventHandler callback = null; | |
tokenRegistration = cancellationToken.Register(() => | |
{ | |
Unregister(); | |
tcs.TrySetCanceled(cancellationToken); | |
}); | |
callback = (sender, args) => | |
{ | |
Unregister(); | |
tcs.TrySetResult(process.ExitCode); | |
}; | |
process.Exited += callback; | |
process.EnableRaisingEvents = true; | |
void Unregister() | |
{ | |
lock (tcs) | |
{ | |
if (tokenRegistration == null) | |
return; | |
process.EnableRaisingEvents = false; | |
process.Exited -= callback; | |
tokenRegistration?.Dispose(); | |
tokenRegistration = null; | |
} | |
} | |
return tcs.Task; | |
} | |
private static Process CreateNewProcess(string command, bool asRoot) | |
{ | |
Console.WriteLine($"{_iteration}: Creating process: {command}"); | |
Process prc = new Process(); | |
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) | |
{ | |
string escapedCommand = command.Replace("\"", "\\\""); | |
// if as root, just sudo it | |
if (asRoot) | |
prc.StartInfo = new ProcessStartInfo("/bin/bash", $"-c \"sudo {escapedCommand}\""); | |
// if not as root, we need to open it as current user | |
// this may still run as root if the process is running as root | |
else | |
prc.StartInfo = new ProcessStartInfo("/bin/bash", $"-c \"{escapedCommand}\""); | |
} | |
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | |
{ | |
prc.StartInfo = new ProcessStartInfo("CMD.exe", $"/C {command}"); | |
if (asRoot) | |
prc.StartInfo.Verb = "runas"; | |
} | |
else | |
throw new PlatformNotSupportedException($"{nameof(ExecuteAndWaitAsync)} is only supported on Windows and Linux platforms."); | |
prc.StartInfo.UseShellExecute = false; | |
prc.StartInfo.CreateNoWindow = true; | |
return prc; | |
} | |
} | |
public class TerminalResult | |
{ | |
public string Output { get; } | |
public string Errors { get; } | |
public bool HasErrors => !string.IsNullOrEmpty(this.Errors); | |
public TerminalResult(string output, string errors) | |
{ | |
this.Output = output; | |
this.Errors = errors; | |
} | |
public TerminalResult(string output) | |
: this(output, null) { } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment