fix(sync): recover hidden server listeners

This commit is contained in:
Brad Stein 2026-04-28 02:04:29 -03:00
parent a9aefd470c
commit b69513f088
6 changed files with 75 additions and 6 deletions

6
Cargo.lock generated
View File

@ -1642,7 +1642,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]] [[package]]
name = "lesavka_client" name = "lesavka_client"
version = "0.14.36" version = "0.14.37"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-stream", "async-stream",
@ -1676,7 +1676,7 @@ dependencies = [
[[package]] [[package]]
name = "lesavka_common" name = "lesavka_common"
version = "0.14.36" version = "0.14.37"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@ -1688,7 +1688,7 @@ dependencies = [
[[package]] [[package]]
name = "lesavka_server" name = "lesavka_server"
version = "0.14.36" version = "0.14.37"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package] [package]
name = "lesavka_client" name = "lesavka_client"
version = "0.14.36" version = "0.14.37"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "lesavka_common" name = "lesavka_common"
version = "0.14.36" version = "0.14.37"
edition = "2024" edition = "2024"
build = "build.rs" build = "build.rs"

View File

@ -134,6 +134,11 @@ list_server_listener_pids() {
} | sed '/^$/d' | sort -u } | sed '/^$/d' | sort -u
} }
list_server_bound_inactive_lines() {
local port=$1
sudo ss -H -B -tn "sport = :$port" 2>/dev/null | sed '/^$/d' || true
}
list_server_listener_inodes_proc() { list_server_listener_inodes_proc() {
local port=$1 hex_port local port=$1 hex_port
hex_port=$(printf '%04X' "$port") hex_port=$(printf '%04X' "$port")
@ -183,6 +188,39 @@ server_listener_presence_detail() {
printf 'listener inodes on :%s => %s' "$port" "${inodes[*]}" printf 'listener inodes on :%s => %s' "$port" "${inodes[*]}"
} }
server_bound_inactive_detail() {
local port=$1
local detail
detail=$(list_server_bound_inactive_lines "$port" | paste -sd ';' -)
if [[ -z $detail ]]; then
printf 'no bound-inactive tcp sockets for :%s' "$port"
return 0
fi
printf 'bound-inactive tcp sockets on :%s => %s' "$port" "$detail"
}
destroy_hidden_server_listener() {
local port=$1
echo "⚠️ TCP :$port is listening without a visible owning PID; asking the kernel to drop the stale socket state."
sudo ss -K state connected "sport = :$port or dport = :$port" >/dev/null 2>&1 || true
sudo ss -K state listening "sport = :$port" >/dev/null 2>&1 || true
for _ in {1..30}; do
if ! list_server_listener_inodes_proc "$port" | grep -q .; then
if list_server_bound_inactive_lines "$port" | grep -q .; then
echo "❌ TCP :$port stopped listening but remains bound after kernel cleanup; $(server_bound_inactive_detail "$port")." >&2
return 1
fi
echo "✅ kernel dropped the hidden TCP :$port socket state."
return 0
fi
sleep 0.1
done
echo "❌ TCP :$port still appears to be listening after kernel socket cleanup; $(server_listener_presence_detail "$port")." >&2
return 1
}
clear_stale_server_listener() { clear_stale_server_listener() {
local port pid cmdline found=0 unexpected=0 local port pid cmdline found=0 unexpected=0
port=$(server_bind_port) || { port=$(server_bind_port) || {
@ -208,9 +246,16 @@ clear_stale_server_listener() {
fi fi
if [[ "$found" == "0" ]]; then if [[ "$found" == "0" ]]; then
if list_server_listener_inodes_proc "$port" | grep -q .; then if list_server_listener_inodes_proc "$port" | grep -q .; then
if destroy_hidden_server_listener "$port"; then
return 0
fi
echo "❌ TCP :$port is listening but no owning PID could be identified; $(server_listener_presence_detail "$port")." >&2 echo "❌ TCP :$port is listening but no owning PID could be identified; $(server_listener_presence_detail "$port")." >&2
return 1 return 1
fi fi
if list_server_bound_inactive_lines "$port" | grep -q .; then
echo "❌ TCP :$port is not listening but remains bound; $(server_bound_inactive_detail "$port")." >&2
return 1
fi
return 0 return 0
fi fi
@ -246,6 +291,10 @@ clear_stale_server_listener() {
echo "❌ TCP :$port still appears to be listening after cleanup; $(server_listener_presence_detail "$port")." >&2 echo "❌ TCP :$port still appears to be listening after cleanup; $(server_listener_presence_detail "$port")." >&2
return 1 return 1
fi fi
if list_server_bound_inactive_lines "$port" | grep -q .; then
echo "❌ TCP :$port stopped listening but remains bound after cleanup; $(server_bound_inactive_detail "$port")." >&2
return 1
fi
echo "❌ lesavka-server listener on :$port survived cleanup; refusing to start a duplicate server." >&2 echo "❌ lesavka-server listener on :$port survived cleanup; refusing to start a duplicate server." >&2
return 1 return 1

View File

@ -10,7 +10,7 @@ bench = false
[package] [package]
name = "lesavka_server" name = "lesavka_server"
version = "0.14.36" version = "0.14.37"
edition = "2024" edition = "2024"
autobins = false autobins = false

View File

@ -122,10 +122,30 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
SERVER_INSTALL.contains("socket:["), SERVER_INSTALL.contains("socket:["),
"install script should map listening socket inodes back to owning PIDs" "install script should map listening socket inodes back to owning PIDs"
); );
assert!(
SERVER_INSTALL.contains("ss -K state connected"),
"install script should clear hidden connected socket state before retrying the server port"
);
assert!(
SERVER_INSTALL.contains("ss -K state listening"),
"install script should ask the kernel to drop a hidden listening socket when no PID is visible"
);
assert!(
SERVER_INSTALL.contains("list_server_bound_inactive_lines"),
"install script should check for sockets that remain bound after listener teardown"
);
assert!(
SERVER_INSTALL.contains("bound-inactive tcp sockets"),
"install script should explain when a hidden socket stops listening but still blocks the port"
);
assert!( assert!(
SERVER_INSTALL.contains("unexpected process"), SERVER_INSTALL.contains("unexpected process"),
"install script should fail loud instead of killing an unrelated process on the server port" "install script should fail loud instead of killing an unrelated process on the server port"
); );
assert!(
SERVER_INSTALL.contains("kernel dropped the hidden TCP"),
"install script should report when kernel-level stale-socket cleanup succeeds"
);
assert!( assert!(
SERVER_INSTALL.contains("no owning PID could be identified"), SERVER_INSTALL.contains("no owning PID could be identified"),
"install script should fail loud when a port is listening but procfs cannot identify the owner" "install script should fail loud when a port is listening but procfs cannot identify the owner"