Skip to content

Commit 688f8c4

Browse files
authored
Use PowerShell instead of WMIC for detecting zombie process on Windows (#3258)
* Use PowerShell instead of WMIC for detecting zombie process on Windows WMIC is not available in recent Windows versions. The new solution with PowerShell should work for Windows 8 and newer and Windows Server 2012 and newer. The change is deliberately as small as possible, leaving most of the old WMIC handling in place because there is a plan to deprecate this class anyway. On performance note: executing the check via PowerShell is notably slower than with WMIC (low hundreds of millis vs tens of millis). However, since this check is used only to detect zombie process and only once per tens of seconds, this will hopefully be good enough. Much better and faster implementation could be done once the minimal supported Java moves to 9 or later via ProcessHandle, until then this should do.
1 parent e5c01a6 commit 688f8c4

3 files changed

Lines changed: 26 additions & 18 deletions

File tree

.github/workflows/maven-verify.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ jobs:
4949
!**/hs_err_pid*
5050
!surefire-its/target/ConsoleOutputIT_*/target/surefire-reports/*-jvmRun*-events.bin
5151
timeout-minutes: 600
52-
os-matrix: '[ "ubuntu-latest", "windows-2022", "macos-latest" ]'
52+
os-matrix: '[ "ubuntu-latest", "windows-latest", "macos-latest" ]'

surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ final class PpidChecker {
7070
IS_OS_WINDOWS ? createWindowsCreationDateFormat() : null;
7171
private static final String WMIC_CREATION_DATE = "CreationDate";
7272
private static final String WINDOWS_SYSTEM_ROOT_ENV = "SystemRoot";
73-
private static final String RELATIVE_PATH_TO_WMIC = "System32\\Wbem";
74-
private static final String SYSTEM_PATH_TO_WMIC =
75-
"%" + WINDOWS_SYSTEM_ROOT_ENV + "%\\" + RELATIVE_PATH_TO_WMIC + "\\";
73+
private static final String RELATIVE_PATH_TO_POWERSHELL = "System32\\WindowsPowerShell\\v1.0";
74+
private static final String SYSTEM_PATH_TO_POWERSHELL =
75+
System.getenv(WINDOWS_SYSTEM_ROOT_ENV) + "\\" + RELATIVE_PATH_TO_POWERSHELL + "\\";
7676
private static final String PS_ETIME_HEADER = "ELAPSED";
7777
private static final String PS_PID_HEADER = "PID";
7878

@@ -192,6 +192,7 @@ ProcessInfo windows() {
192192
@Nonnull
193193
ProcessInfo consumeLine(String line, ProcessInfo previousProcessInfo) throws Exception {
194194
if (previousProcessInfo.isInvalid() && !line.isEmpty()) {
195+
// we still use WMIC output format even though we now use PowerShell to produce it
195196
if (hasHeader) {
196197
// now the line is CreationDate, e.g. 20180406142327.741074+120
197198
if (line.length() != WMIC_CREATION_DATE_VALUE_LENGTH) {
@@ -210,13 +211,19 @@ ProcessInfo consumeLine(String line, ProcessInfo previousProcessInfo) throws Exc
210211
return previousProcessInfo;
211212
}
212213
};
213-
String wmicPath = hasWmicStandardSystemPath() ? SYSTEM_PATH_TO_WMIC : "";
214-
return reader.execute(
215-
"CMD",
216-
"/A",
217-
"/X",
218-
"/C",
219-
wmicPath + "wmic process where (ProcessId=" + ppid + ") get " + WMIC_CREATION_DATE);
214+
215+
String psPath = hasPowerShellStandardSystemPath() ? SYSTEM_PATH_TO_POWERSHELL : "";
216+
// mimic output format of the original check:
217+
// wmic process where (ProcessId=<ppid>) get CreationDate
218+
String psCommand = String.format(
219+
"Add-Type -AssemblyName System.Management; "
220+
+ "$p = Get-CimInstance Win32_Process -Filter 'ProcessId=%2$s'; "
221+
+ "if ($p) { "
222+
+ " Write-Output '%1$s'; "
223+
+ " [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($p.CreationDate) "
224+
+ "}",
225+
WMIC_CREATION_DATE, ppid);
226+
return reader.execute(psPath + "powershell", "-NoProfile", "-NonInteractive", "-Command", psCommand);
220227
}
221228

222229
void destroyActiveCommands() {
@@ -254,9 +261,10 @@ private static boolean canExecuteStandardUnixPs() {
254261
}
255262
}
256263

257-
private static boolean hasWmicStandardSystemPath() {
264+
private static boolean hasPowerShellStandardSystemPath() {
258265
String systemRoot = System.getenv(WINDOWS_SYSTEM_ROOT_ENV);
259-
return isNotBlank(systemRoot) && new File(systemRoot, RELATIVE_PATH_TO_WMIC + "\\wmic.exe").isFile();
266+
return isNotBlank(systemRoot)
267+
&& new File(systemRoot, RELATIVE_PATH_TO_POWERSHELL + "\\powershell.exe").isFile();
260268
}
261269

262270
static long fromDays(Matcher matcher) {

surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -365,15 +365,15 @@ public void shouldParseBusyboxHoursEtime() {
365365
}
366366

367367
@Test
368-
public void shouldHaveSystemPathToWmicOnWindows() throws Exception {
368+
public void shouldHaveSystemPathToPowerShellOnWindows() throws Exception {
369369
assumeTrue(IS_OS_WINDOWS);
370370
assumeThat(System.getenv("SystemRoot"), is(notNullValue()));
371371
assumeThat(System.getenv("SystemRoot"), is(not("")));
372-
assumeTrue(new File(System.getenv("SystemRoot"), "System32\\Wbem").isDirectory());
373-
assumeTrue(new File(System.getenv("SystemRoot"), "System32\\Wbem\\wmic.exe").isFile());
374-
assertThat((Boolean) invokeMethod(PpidChecker.class, "hasWmicStandardSystemPath"))
372+
assumeTrue(new File(System.getenv("SystemRoot"), "System32\\WindowsPowerShell\\v1.0").isDirectory());
373+
assumeTrue(new File(System.getenv("SystemRoot"), "System32\\WindowsPowerShell\\v1.0\\powershell.exe").isFile());
374+
assertThat((Boolean) invokeMethod(PpidChecker.class, "hasPowerShellStandardSystemPath"))
375375
.isTrue();
376-
assertThat(new File(System.getenv("SystemRoot"), "System32\\Wbem\\wmic.exe"))
376+
assertThat(new File(System.getenv("SystemRoot"), "System32\\WindowsPowerShell\\v1.0\\powershell.exe"))
377377
.isFile();
378378
}
379379

0 commit comments

Comments
 (0)