fix(sync): clear stale server listeners before restart

This commit is contained in:
Brad Stein 2026-04-27 22:34:46 -03:00
parent 898947a2b5
commit 5173e3cea7
6 changed files with 139 additions and 6 deletions

6
Cargo.lock generated
View File

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

View File

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

View File

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

View File

@ -113,6 +113,114 @@ is_attached_state() {
return 1
}
server_bind_addr() {
printf '%s\n' "${LESAVKA_SERVER_BIND_ADDR:-0.0.0.0:50051}"
}
server_bind_port() {
local bind_addr port
bind_addr=$(server_bind_addr)
port=${bind_addr##*:}
[[ $port =~ ^[0-9]+$ ]] || return 1
printf '%s\n' "$port"
}
list_server_listener_pids() {
local port
port=$(server_bind_port) || return 1
sudo lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null | sort -u
}
clear_stale_server_listener() {
local port pid cmdline found=0 unexpected=0
port=$(server_bind_port) || {
echo "⚠️ could not parse LESAVKA_SERVER_BIND_ADDR='$(server_bind_addr)'; skipping stale-listener cleanup."
return 0
}
while read -r pid; do
[[ -n $pid ]] || continue
found=1
cmdline=$(sudo ps -o args= -p "$pid" 2>/dev/null || true)
if [[ $cmdline == *"/usr/local/bin/lesavka-server"* ]] || [[ $cmdline == *"lesavka-server"* ]]; then
echo "⚠️ clearing stale lesavka-server listener on :$port (pid $pid)."
sudo kill -TERM "$pid" 2>/dev/null || true
else
echo "❌ TCP :$port is already owned by an unexpected process (pid $pid): ${cmdline:-<unknown>}." >&2
unexpected=1
fi
done < <(list_server_listener_pids || true)
if [[ "$unexpected" != "0" ]]; then
return 1
fi
if [[ "$found" == "0" ]]; then
return 0
fi
for _ in {1..30}; do
if ! list_server_listener_pids | grep -q .; then
echo "✅ cleared stale lesavka-server listeners on :$port."
return 0
fi
sleep 0.1
done
while read -r pid; do
[[ -n $pid ]] || continue
cmdline=$(sudo ps -o args= -p "$pid" 2>/dev/null || true)
if [[ $cmdline == *"/usr/local/bin/lesavka-server"* ]] || [[ $cmdline == *"lesavka-server"* ]]; then
echo "⚠️ stale lesavka-server listener on :$port survived SIGTERM; sending SIGKILL to pid $pid."
sudo kill -KILL "$pid" 2>/dev/null || true
else
echo "❌ TCP :$port remained busy after cleanup and is owned by an unexpected process (pid $pid): ${cmdline:-<unknown>}." >&2
return 1
fi
done < <(list_server_listener_pids || true)
for _ in {1..30}; do
if ! list_server_listener_pids | grep -q .; then
echo "✅ cleared stale lesavka-server listeners on :$port."
return 0
fi
sleep 0.1
done
echo "❌ lesavka-server listener on :$port survived cleanup; refusing to start a duplicate server." >&2
return 1
}
wait_for_unit_running() {
local unit=$1
for _ in {1..50}; do
if systemctl is-active --quiet "$unit"; then
if [[ $(systemctl show "$unit" -p SubState --value 2>/dev/null || true) == "running" ]]; then
return 0
fi
fi
sleep 0.2
done
return 1
}
validate_server_ready() {
local bind_addr
bind_addr=$(server_bind_addr)
if wait_for_unit_running lesavka-server; then
echo "✅ lesavka-server is active and running on ${bind_addr}."
return 0
fi
echo "❌ lesavka-server failed to reach active/running state on ${bind_addr}." >&2
sudo systemctl status lesavka-server --no-pager >&2 || true
if [[ -s /tmp/lesavka-server.stderr ]]; then
echo "---- /tmp/lesavka-server.stderr (tail) ----" >&2
sudo tail -n 40 /tmp/lesavka-server.stderr >&2 || true
echo "------------------------------------------" >&2
fi
return 1
}
normalize_hdmi_connector() {
local name="$1"
if [[ $name =~ (HDMI-A-[0-9]+)$ ]]; then
@ -719,7 +827,12 @@ fi
validate_uvc_gadget_ready
sudo truncate -s 0 /tmp/lesavka-server.stderr
sudo systemctl stop lesavka-server >/dev/null 2>&1 || true
clear_stale_server_listener
sudo systemctl reset-failed lesavka-server >/dev/null 2>&1 || true
sudo systemctl restart lesavka-server
validate_server_ready
INSTALLED_VERSION=$(manifest_package_version "$SRC_DIR/server/Cargo.toml" 2>/dev/null || true)
INSTALLED_SHA=$(git -C "$SCRIPT_REPO_ROOT" rev-parse --short HEAD 2>/dev/null || true)
PERSISTED_CAM_OUTPUT=$(grep '^LESAVKA_CAM_OUTPUT=' /etc/lesavka/server.env 2>/dev/null | tail -n1 | cut -d= -f2- || true)

View File

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

View File

@ -98,6 +98,26 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
SERVER_INSTALL.contains("lesavka-uvc already active; runtime settings unchanged."),
"install script should avoid unnecessary UVC restarts when nothing changed"
);
assert!(
SERVER_INSTALL.contains("clear_stale_server_listener"),
"install script should clear stale server listeners before restart"
);
assert!(
SERVER_INSTALL.contains("lsof -tiTCP:"),
"install script should inspect the bind port before starting a fresh server"
);
assert!(
SERVER_INSTALL.contains("unexpected process"),
"install script should fail loud instead of killing an unrelated process on the server port"
);
assert!(
SERVER_INSTALL.contains("validate_server_ready"),
"install script should verify that lesavka-server reaches a running state"
);
assert!(
SERVER_INSTALL.contains("failed to reach active/running state"),
"install script should explain server startup failures instead of claiming success"
);
}
#[test]