mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-16 13:20:39 -07:00
fix: replace netstat with TcpStream + PowerShell for port detection (#277)
On Windows, Voicebox shelled out to netstat.exe on startup to check for existing server processes. On systems with corrupted DLLs, netstat fails with 0xc0000142, causing an infinite loading loop. Replace with: - TcpStream::connect_timeout() for port-in-use checks (pure Rust) - PowerShell Get-NetTCPConnection for port-to-PID lookup (built-in cmdlet) - tasklist for process name verification (unchanged) Closes #277
This commit is contained in:
@@ -48,7 +48,7 @@ Windows SmartScreen may warn that the app is unrecognized.
|
||||
lsof -i :17493
|
||||
|
||||
# Windows
|
||||
netstat -ano | findstr :17493
|
||||
powershell -Command "Get-NetTCPConnection -LocalPort 17493 -State Listen"
|
||||
```
|
||||
|
||||
Kill the process using the port:
|
||||
|
||||
+61
-59
@@ -12,6 +12,46 @@ use tokio::sync::mpsc;
|
||||
const LEGACY_PORT: u16 = 8000;
|
||||
const SERVER_PORT: u16 = 17493;
|
||||
|
||||
/// Find a voicebox-server process listening on a given port (Windows only).
|
||||
///
|
||||
/// Uses `TcpListener::bind` to confirm the port is occupied, then falls back
|
||||
/// to `tasklist` to scan for a voicebox process. Returns the PID if found.
|
||||
/// This replaces the previous `netstat -ano` approach which could fail on
|
||||
/// systems with corrupted system DLLs (see #277).
|
||||
#[cfg(windows)]
|
||||
fn find_voicebox_pid_on_port(port: u16) -> Option<u32> {
|
||||
use std::process::Command;
|
||||
|
||||
// Use PowerShell's Get-NetTCPConnection to find the PID listening on the port.
|
||||
// This is a built-in cmdlet that doesn't depend on netstat.exe.
|
||||
let ps_script = format!(
|
||||
"Get-NetTCPConnection -LocalPort {} -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess",
|
||||
port
|
||||
);
|
||||
if let Ok(output) = Command::new("powershell")
|
||||
.args(["-NoProfile", "-Command", &ps_script])
|
||||
.output()
|
||||
{
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
for line in output_str.lines() {
|
||||
if let Ok(pid) = line.trim().parse::<u32>() {
|
||||
// Verify this PID is a voicebox process
|
||||
if let Ok(tasklist_output) = Command::new("tasklist")
|
||||
.args(["/FI", &format!("PID eq {}", pid), "/FO", "CSV", "/NH"])
|
||||
.output()
|
||||
{
|
||||
let tasklist_str = String::from_utf8_lossy(&tasklist_output.stdout);
|
||||
if tasklist_str.to_lowercase().contains("voicebox") {
|
||||
return Some(pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
struct ServerState {
|
||||
child: Mutex<Option<tauri_plugin_shell::process::CommandChild>>,
|
||||
server_pid: Mutex<Option<u32>>,
|
||||
@@ -68,31 +108,16 @@ async fn start_server(
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::process::Command;
|
||||
if let Ok(output) = Command::new("netstat")
|
||||
.args(["-ano"])
|
||||
.output()
|
||||
{
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
for line in output_str.lines() {
|
||||
if line.contains(&format!(":{}", SERVER_PORT)) && line.contains("LISTENING") {
|
||||
if let Some(pid_str) = line.split_whitespace().last() {
|
||||
if let Ok(pid) = pid_str.parse::<u32>() {
|
||||
if let Ok(tasklist_output) = Command::new("tasklist")
|
||||
.args(["/FI", &format!("PID eq {}", pid), "/FO", "CSV", "/NH"])
|
||||
.output()
|
||||
{
|
||||
let tasklist_str = String::from_utf8_lossy(&tasklist_output.stdout);
|
||||
if tasklist_str.to_lowercase().contains("voicebox") {
|
||||
println!("Found existing voicebox-server on port {} (PID: {}), reusing it", SERVER_PORT, pid);
|
||||
// Store the PID so we can kill it on exit if needed
|
||||
*state.server_pid.lock().unwrap() = Some(pid);
|
||||
return Ok(format!("http://127.0.0.1:{}", SERVER_PORT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
use std::net::TcpStream;
|
||||
if TcpStream::connect_timeout(
|
||||
&format!("127.0.0.1:{}", SERVER_PORT).parse().unwrap(),
|
||||
std::time::Duration::from_secs(1),
|
||||
).is_ok() {
|
||||
// Port is in use — check if it's a voicebox process via tasklist
|
||||
if let Some(pid) = find_voicebox_pid_on_port(SERVER_PORT) {
|
||||
println!("Found existing voicebox-server on port {} (PID: {}), reusing it", SERVER_PORT, pid);
|
||||
*state.server_pid.lock().unwrap() = Some(pid);
|
||||
return Ok(format!("http://127.0.0.1:{}", SERVER_PORT));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,24 +127,20 @@ async fn start_server(
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::process::Command;
|
||||
// Find processes listening on legacy port 8000 with their command names
|
||||
if let Ok(output) = Command::new("lsof")
|
||||
.args(["-i", &format!(":{}", LEGACY_PORT), "-sTCP:LISTEN"])
|
||||
.output()
|
||||
{
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
for line in output_str.lines().skip(1) { // Skip header line
|
||||
// lsof output format: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
|
||||
for line in output_str.lines().skip(1) {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() >= 2 {
|
||||
let command = parts[0];
|
||||
let pid_str = parts[1];
|
||||
|
||||
// Only kill if it's a voicebox-server process
|
||||
if command.contains("voicebox") {
|
||||
if let Ok(pid) = pid_str.parse::<i32>() {
|
||||
println!("Found orphaned voicebox-server on legacy port {} (PID: {}, CMD: {}), killing it...", LEGACY_PORT, pid, command);
|
||||
// Kill the process group
|
||||
let _ = Command::new("kill")
|
||||
.args(["-9", "--", &format!("-{}", pid)])
|
||||
.output();
|
||||
@@ -137,35 +158,16 @@ async fn start_server(
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::process::Command;
|
||||
// On Windows, find PIDs on legacy port 8000, then check their names
|
||||
if let Ok(output) = Command::new("netstat")
|
||||
.args(["-ano"])
|
||||
.output()
|
||||
{
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
for line in output_str.lines() {
|
||||
if line.contains(&format!(":{}", LEGACY_PORT)) && line.contains("LISTENING") {
|
||||
if let Some(pid_str) = line.split_whitespace().last() {
|
||||
if let Ok(pid) = pid_str.parse::<u32>() {
|
||||
// Get process name for this PID
|
||||
if let Ok(tasklist_output) = Command::new("tasklist")
|
||||
.args(["/FI", &format!("PID eq {}", pid), "/FO", "CSV", "/NH"])
|
||||
.output()
|
||||
{
|
||||
let tasklist_str = String::from_utf8_lossy(&tasklist_output.stdout);
|
||||
if tasklist_str.to_lowercase().contains("voicebox") {
|
||||
println!("Found orphaned voicebox-server on legacy port {} (PID: {}), killing it...", LEGACY_PORT, pid);
|
||||
let _ = Command::new("taskkill")
|
||||
.args(["/PID", &pid.to_string(), "/T", "/F"])
|
||||
.output();
|
||||
} else {
|
||||
println!("Legacy port {} is in use by non-voicebox process (PID: {}), not killing", LEGACY_PORT, pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
use std::net::TcpStream;
|
||||
if TcpStream::connect_timeout(
|
||||
&format!("127.0.0.1:{}", LEGACY_PORT).parse().unwrap(),
|
||||
std::time::Duration::from_secs(1),
|
||||
).is_ok() {
|
||||
if let Some(pid) = find_voicebox_pid_on_port(LEGACY_PORT) {
|
||||
println!("Found orphaned voicebox-server on legacy port {} (PID: {}), killing it...", LEGACY_PORT, pid);
|
||||
let _ = std::process::Command::new("taskkill")
|
||||
.args(["/PID", &pid.to_string(), "/T", "/F"])
|
||||
.output();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user