Skip to content

Commit b008fbc

Browse files
authored
Wait for process kill in Geekbench (#559)
1 parent e931b86 commit b008fbc

File tree

2 files changed

+40
-23
lines changed

2 files changed

+40
-23
lines changed

src/VirtualClient/VirtualClient.Actions/GeekBench/GeekbenchExecutor.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,10 @@ public string CommandLine
7979
protected override async Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken)
8080
{
8181
this.DeleteResultsFile(telemetryContext);
82-
string commandLineArguments = this.GetCommandLineArguments();
8382

8483
using (BackgroundOperations profiling = BackgroundOperations.BeginProfiling(this, cancellationToken))
8584
{
86-
await this.ExecuteWorkloadAsync(this.ExecutablePath, commandLineArguments, telemetryContext, cancellationToken);
85+
await this.ExecuteWorkloadAsync(this.ExecutablePath, this.CommandLine, telemetryContext, cancellationToken);
8786
}
8887
}
8988

@@ -190,7 +189,7 @@ protected override async Task InitializeAsync(EventContext telemetryContext, Can
190189
// GeekBench runs a secondary process on both Windows and Linux systems. When we
191190
// kill the parent process, it does not kill the processes the parent spun off. This
192191
// ensures that all of the process are stopped/killed.
193-
this.processManager.SafeKill(this.SupportingExecutables.ToArray(), this.Logger);
192+
this.processManager.Kill(this.SupportingExecutables.ToArray(), this.Logger);
194193
}
195194
}
196195
}
@@ -295,14 +294,9 @@ private Task ExecuteWorkloadAsync(string pathToExe, string commandLineArguments,
295294
// GeekBench runs a secondary process on both Windows and Linux systems. When we
296295
// kill the parent process, it does not kill the processes the parent spun off. This
297296
// ensures that all of the process are stopped/killed.
298-
this.processManager.SafeKill(this.SupportingExecutables.ToArray(), this.Logger);
297+
this.processManager.Kill(this.SupportingExecutables.ToArray(), this.Logger);
299298
}
300299
});
301300
}
302-
303-
private string GetCommandLineArguments()
304-
{
305-
return $"{this.CommandLine}";
306-
}
307301
}
308302
}

src/VirtualClient/VirtualClient.Core/ProcessExtensions.cs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ namespace VirtualClient
55
{
66
using System;
77
using System.Collections.Generic;
8+
using System.Diagnostics;
89
using System.Diagnostics.CodeAnalysis;
910
using System.IO;
1011
using System.Linq;
1112
using System.Text.RegularExpressions;
13+
using System.Threading;
1214
using Microsoft.Extensions.Logging;
1315
using VirtualClient.Common;
1416
using VirtualClient.Common.Extensions;
@@ -86,18 +88,19 @@ public static bool IsErrored(this IProcessProxy process, IEnumerable<int> succes
8688
}
8789

8890
/// <summary>
89-
/// Kills the process if it is still running and handles any errors that
90-
/// can occurs if the process has gone out of scope.
91+
/// Kills the associated process and/or it's child/dependent processes if it is still running and
92+
/// handles any errors that can occurs if the process has gone out of scope.
9193
/// </summary>
9294
/// <param name="process">The process to kill.</param>
95+
/// <param name="entireProcessTree">true to kill asociated process and it's descendents, false to only kill the process.</param>
9396
/// <param name="logger">The logger to use to write trace information.</param>
94-
public static void SafeKill(this IProcessProxy process, ILogger logger = null)
97+
public static void SafeKill(this IProcessProxy process, ILogger logger = null, bool entireProcessTree = false)
9598
{
9699
if (process != null)
97100
{
98101
try
99102
{
100-
process.Kill();
103+
process.Kill(entireProcessTree);
101104
}
102105
catch (Exception exc)
103106
{
@@ -108,26 +111,46 @@ public static void SafeKill(this IProcessProxy process, ILogger logger = null)
108111
}
109112

110113
/// <summary>
111-
/// Kills the associated process and it's child/dependent processes if it is still running and
112-
/// handles any errors that can occurs if the process has gone out of scope.
114+
/// Kills the associated process and/or its child/dependent processes if it is still running.
115+
/// Retries up to 5 times using exponential backoff with a total wait limit of 3 minutes.
116+
/// Logs error when failed to kill.
113117
/// </summary>
114118
/// <param name="process">The process to kill.</param>
115-
/// <param name="entireProcessTree">true to kill asociated process and it's descendents, false to only kill the process.</param>
119+
/// <param name="entireProcessTree">true to kill associated process and its descendants; false to only kill the process.</param>
120+
/// <param name="timeout">Max duration to wait for exit, default to 3 minutes.</param>
116121
/// <param name="logger">The logger to use to write trace information.</param>
117-
public static void SafeKill(this IProcessProxy process, bool entireProcessTree, ILogger logger = null)
122+
public static void Kill(this IProcessProxy process, ILogger logger = null, bool entireProcessTree = false, TimeSpan timeout = default)
118123
{
119-
if (process != null)
124+
timeout = timeout == default ? TimeSpan.FromMinutes(3) : timeout;
125+
const int maxRetries = 5;
126+
127+
Stopwatch stopWatch = Stopwatch.StartNew();
128+
129+
for (int attempt = 1; attempt <= maxRetries; attempt++)
120130
{
131+
int delaySeconds = Math.Min((int)Math.Pow(2, attempt), (int)(timeout - stopWatch.Elapsed).TotalSeconds);
132+
if (process.HasExited || delaySeconds <= 0)
133+
{
134+
break; // Exit if timeout is reached or process exited.
135+
}
136+
121137
try
122138
{
123139
process.Kill(entireProcessTree);
140+
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(delaySeconds));
141+
// CancellationToken should cancel first, the timeout here is precaution.
142+
process.WaitForExitAsync(cts.Token, timeout).GetAwaiter().GetResult();
124143
}
125-
catch (Exception exc)
144+
catch (Exception ex)
126145
{
127-
// Best effort here.
128-
logger?.LogTraceMessage($"Kill Process Failure. Error = {exc.Message}");
146+
logger?.LogTraceMessage($"Attempt {attempt}: Kill failed. {ex.Message}");
129147
}
130148
}
149+
150+
if (!process.HasExited)
151+
{
152+
logger?.LogError("Failed to kill process after retries.");
153+
}
131154
}
132155

133156
/// <summary>
@@ -137,7 +160,7 @@ public static void SafeKill(this IProcessProxy process, bool entireProcessTree,
137160
/// <param name="processManager">The process manager used to find the processes.</param>
138161
/// <param name="processNames">The names/paths of the processes to kill.</param>
139162
/// <param name="logger">The logger to use to write trace information.</param>
140-
public static void SafeKill(this ProcessManager processManager, IEnumerable<string> processNames, ILogger logger = null)
163+
public static void Kill(this ProcessManager processManager, IEnumerable<string> processNames, ILogger logger = null)
141164
{
142165
processManager.ThrowIfNull(nameof(processManager));
143166

@@ -155,7 +178,7 @@ public static void SafeKill(this ProcessManager processManager, IEnumerable<stri
155178
{
156179
foreach (IProcessProxy sideProcess in processes)
157180
{
158-
sideProcess.SafeKill(logger);
181+
sideProcess.Kill(logger);
159182
}
160183
}
161184
}

0 commit comments

Comments
 (0)