force kill windows test processes

In situations like timeouts, windows will hang when attempting to close the test process streams. In this case, we have to force kill the test process externally. This is the same process as force killing hardhat nodes after they are already terminated, but windows refuses hangs when closing their process streams. This commit creates a forceKillProcess utility that allows a process to be killed by its process name and matching commandline criteria, like TestId (for test process) or --port (for hardhat)
This commit is contained in:
Eric 2025-03-24 19:39:38 +11:00
parent cbe3305dc5
commit 75047f52be
No known key found for this signature in database
5 changed files with 149 additions and 128 deletions

View File

@ -14,6 +14,7 @@ import pkg/codex/conf
import pkg/codex/utils/trackedfutures
import ./codexclient
import ./nodeprocess
import ./utils
export codexclient
export chronicles
@ -159,20 +160,6 @@ method onOutputLineCaptured(node: HardhatProcess, line: string) =
discard logFile.closeFile()
node.logFile = none IoHandle
proc killHardhatByPort(
port: int
): Future[CommandExResponse] {.
async: (
raises: [
AsyncProcessError, AsyncProcessTimeoutError, CancelledError, ValueError, OSError
]
)
.} =
let path = splitFile(currentSourcePath()).dir / "scripts" / "winkillhardhat.sh"
let cmd = &"{absolutePath(path)} killvendorport {port}"
trace "Forcefully killing windows hardhat process", port, cmd
return await execCommandEx(cmd, timeout = 5.seconds)
proc closeProcessStreams(node: HardhatProcess) {.async: (raises: []).} =
when not defined(windows):
if not node.process.isNil:
@ -186,7 +173,7 @@ proc closeProcessStreams(node: HardhatProcess) {.async: (raises: []).} =
error "Failed to get port from Hardhat args"
return
try:
let cmdResult = await killHardhatByPort(port)
let cmdResult = await forceKillProcess("node.exe", &"--port {port}")
if cmdResult.status > 0:
error "Failed to forcefully kill windows hardhat process",
port, exitCode = cmdResult.status, stderr = cmdResult.stdError

View File

@ -1,110 +0,0 @@
#!/bin/bash
# List all node.exe processes with command line arguments
list_node_processes() {
powershell.exe -Command "Get-WmiObject Win32_Process -Filter \"name = 'node.exe'\" | Select-Object ProcessId, Name, CommandLine | Format-Table -AutoSize"
}
# Find node processes containing vendor\codex-contracts-eth in command line
find_vendor_node_processes() {
echo "Looking for node processes with 'vendor\\codex-contracts-eth' in command line..."
powershell.exe -Command "Get-WmiObject Win32_Process -Filter \"name = 'node.exe'\" | Where-Object { \$_.CommandLine -match 'vendor\\\\codex-contracts-eth' } | Select-Object ProcessId, Name, CommandLine | Format-Table -AutoSize"
}
# Find node processes running on a specific port
find_node_by_port() {
local port=$1
echo "Looking for node processes running on port $port..."
powershell.exe -Command "Get-WmiObject Win32_Process -Filter \"name = 'node.exe'\" | Where-Object { \$_.CommandLine -match '--port $port' } | Select-Object ProcessId, Name, CommandLine | Format-Table -AutoSize"
}
# Kill all node.exe processes containing vendor\codex-contracts-eth
kill_vendor_node_processes() {
echo "Finding and killing node.exe processes containing 'vendor\\codex-contracts-eth'..."
powershell.exe -Command "
\$processes = Get-WmiObject Win32_Process -Filter \"name = 'node.exe'\" | Where-Object { \$_.CommandLine -match 'vendor\\\\codex-contracts-eth' };
if (\$processes) {
foreach (\$process in \$processes) {
Stop-Process -Id \$process.ProcessId -Force;
Write-Host \"Killed process \$(\$process.ProcessId)\";
}
} else {
Write-Host \"No matching node.exe processes found\";
}
"
}
# Kill node.exe process running on a specific port
kill_node_by_port() {
local port=$1
echo "Finding and killing node.exe process running on port $port..."
powershell.exe -Command "
\$processes = Get-WmiObject Win32_Process -Filter \"name = 'node.exe'\" | Where-Object { \$_.CommandLine -match '--port $port' };
if (\$processes) {
foreach (\$process in \$processes) {
Stop-Process -Id \$process.ProcessId -Force;
Write-Host \"Killed process \$(\$process.ProcessId) running on port $port\";
}
} else {
Write-Host \"No node.exe process found running on port $port\";
}
"
}
# Kill node.exe process with both vendor string and specific port
kill_vendor_node_by_port() {
local port=$1
echo "Finding and killing node.exe process with 'vendor\\codex-contracts-eth' running on port $port..."
powershell.exe -Command "
\$processes = Get-WmiObject Win32_Process -Filter \"name = 'node.exe'\" | Where-Object { \$_.CommandLine -match 'vendor\\\\codex-contracts-eth' -and \$_.CommandLine -match '--port $port' };
if (\$processes) {
foreach (\$process in \$processes) {
Stop-Process -Id \$process.ProcessId -Force;
Write-Host \"Killed process \$(\$process.ProcessId) running on port $port\";
}
} else {
Write-Host \"No matching node.exe process found running on port $port\";
}
"
}
# Check if being run directly or sourced
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
# If run directly (not sourced), provide command line interface
case "$1" in
list)
list_node_processes
;;
find)
find_vendor_node_processes
;;
findport)
if [ -z "$2" ]; then
echo "Usage: $0 findport PORT_NUMBER"
exit 1
fi
find_node_by_port "$2"
;;
killall)
kill_vendor_node_processes
;;
killport)
if [ -z "$2" ]; then
echo "Usage: $0 killport PORT_NUMBER"
exit 1
fi
kill_node_by_port "$2"
;;
killvendorport)
if [ -z "$2" ]; then
echo "Usage: $0 killvendorport PORT_NUMBER"
exit 1
fi
kill_vendor_node_by_port "$2"
;;
*)
echo "Usage: $0 {list|find|findport PORT|killall|killport PORT|killvendorport PORT}"
exit 1
;;
esac
fi

View File

@ -0,0 +1,97 @@
#!/bin/bash
# List all processes with a specific name
list() {
local name=$1
echo "Listing all processes named '$name'..."
powershell.exe -Command "Get-CimInstance Win32_Process -Filter \"name = '$name'\" | Select-Object ProcessId, Name, CommandLine | Format-Table -AutoSize"
}
# Search for processes with a specific name and command line pattern
search() {
local name=$1
local pattern=$2
echo "Searching for '$name' processes with command line matching '$pattern'..."
powershell.exe -Command "
\$processes = Get-CimInstance Win32_Process -Filter \"name = '$name'\" | Where-Object { \$_.CommandLine -match '$pattern' };
if (\$processes) {
\$processes | Select-Object ProcessId, Name, CommandLine | Format-Table -AutoSize;
} else {
Write-Host \"No matching '$name' processes found\";
}
"
}
# Kill all processes with a specific name
killall() {
local name=$1
echo "Finding and killing all '$name' processes..."
powershell.exe -Command "
\$processes = Get-CimInstance Win32_Process -Filter \"name = '$name'\";
if (\$processes) {
foreach (\$process in \$processes) {
Stop-Process -Id \$process.ProcessId -Force;
Write-Host \"Killed process \$(\$process.ProcessId)\";
}
} else {
Write-Host \"No '$name' processes found\";
}
"
}
# Kill processes with a specific name and command line pattern
kill() {
local name=$1
local pattern=$2
echo "Finding and killing '$name' processes with command line matching '$pattern'..."
powershell.exe -Command "
\$processes = Get-CimInstance Win32_Process -Filter \"name = '$name'\" | Where-Object { \$_.CommandLine -match '$pattern' };
if (\$processes) {
foreach (\$process in \$processes) {
Stop-Process -Id \$process.ProcessId -Force;
Write-Host \"Killed process \$(\$process.ProcessId)\";
}
} else {
Write-Host \"No matching '$name' processes found\";
}
"
}
# Check if being run directly or sourced
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
# If run directly (not sourced), provide command line interface
case "$1" in
list)
if [ -z "$2" ]; then
echo "Usage: $0 list PROCESS_NAME"
exit 1
fi
list "$2"
;;
search)
if [ -z "$2" ] || [ -z "$3" ]; then
echo "Usage: $0 search PROCESS_NAME COMMANDLINE_PATTERN"
exit 1
fi
search "$2" "$3"
;;
killall)
if [ -z "$2" ]; then
echo "Usage: $0 killall PROCESS_NAME"
exit 1
fi
killall "$2"
;;
kill)
if [ -z "$2" ] || [ -z "$3" ]; then
echo "Usage: $0 kill PROCESS_NAME COMMANDLINE_PATTERN"
exit 1
fi
kill "$2" "$3"
;;
*)
echo "Usage: $0 {list PROCESS_NAME|search PROCESS_NAME COMMANDLINE_PATTERN|killall PROCESS_NAME|kill PROCESS_NAME COMMANDLINE_PATTERN}"
exit 1
;;
esac
fi

View File

@ -3,7 +3,6 @@ import std/strformat
import std/terminal
from std/times import fromUnix, format, now
from std/unicode import toUpper
import std/unittest
import pkg/chronos
import pkg/chronos/asyncproc
import pkg/codex/conf
@ -389,6 +388,38 @@ proc teardownHardhat(test: IntegrationTest, hardhat: Hardhat) {.async: (raises:
test.manager.hardhats.keepItIf(it != hardhat)
proc closeProcessStreams(test: IntegrationTest) {.async: (raises: []).} =
logScope:
name = test.config.name
when not defined(windows):
if not test.process.isNil:
trace "Closing test process' streams"
await test.process.closeWait()
trace "Test process' streams closed"
else:
# Windows hangs when attempting to close the test's process streams, so try
# to kill the process externally.
try:
let cmdResult = await test.forceKillProcess("nim.exe", &"-d:TestId {test.testId}")
if cmdResult.status > 0:
error "Failed to forcefully kill windows test process",
port, exitCode = cmdResult.status, stderr = cmdResult.stdError
else:
trace "Successfully killed windows test process by force",
port, exitCode = cmdResult.status, stdout = cmdResult.stdOutput
except ValueError, OSError:
let eMsg = getCurrentExceptionMsg()
error "Failed to forcefully kill windows test process, bad path to command",
error = eMsg
except CancelledError as e:
discard
except AsyncProcessError as e:
error "Failed to forcefully kill windows test process", port, error = e.msg
except AsyncProcessTimeoutError as e:
error "Timeout while forcefully killing windows test process",
port, error = e.msg
proc teardownTest(test: IntegrationTest) {.async: (raises: []).} =
logScope:
test = test.config.name
@ -409,8 +440,8 @@ proc teardownTest(test: IntegrationTest) {.async: (raises: []).} =
let e = getCurrentException()
warn "Test process failed to terminate, check for zombies", error = e.msg
await test.process.closeWait()
trace "Test process output streams closed"
await test.closeProcessStreams()
test.process = nil
proc teardown(test: IntegrationTest, hardhat: ?Hardhat) {.async: (raises: []).} =
if test.config.startHardhat and hardhat =? hardhat and not hardhat.process.isNil:

View File

@ -1,5 +1,6 @@
import std/os
import pkg/chronos
import pkg/chronos/asyncproc
import pkg/codex/logutils
{.push raises: [].}
@ -64,3 +65,18 @@ proc appendFile*(filename: string, content: string) {.raises: [IOError].} =
raise newException(IOError, "cannot open and write " & filename & ": " & e.msg)
finally:
close(f)
when defined(windows):
proc forceKillProcess(
processName, matchingCriteria: string
): Future[CommandExResponse] {.
async: (
raises: [
AsyncProcessError, AsyncProcessTimeoutError, CancelledError, ValueError, OSError
]
)
.} =
let path = splitFile(currentSourcePath()).dir / "scripts" / "winkillprocess.sh"
let cmd = &"{absolutePath(path)} kill {processName} \"{matchingCriteria}\""
trace "Forcefully killing windows process", processName, matchingCriteria, cmd
return await execCommandEx(cmd, timeout = 5.seconds)