From da186c5404718c8861dc8163c2ec2f3c2e9a551c Mon Sep 17 00:00:00 2001 From: Joseph Yaksich Date: Wed, 29 Jul 2026 20:54:14 +0000 Subject: [PATCH] Retry transient fresh LXC starts --- CHANGELOG.md | 5 +++++ scripts/1helm-lxc-runtime | 27 +++++++++++++++++++++------ test/site.mjs | 1 + 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76c8dd3..c5669af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 bootstrap. A channel no longer repeatedly creates and cleans up its computer after starting `apt` during the guest-network race. +- A fresh channel now verifies and retries the same container when LXC's first + daemonized start transiently loses its state-socket reply. The one-off + `wait_on_daemonized_start` error no longer makes the channel appear broken + while the automatic fleet pass successfully creates it moments later. + - 1Helm's narrow owned LXC forwarding rules are inserted ahead of host firewall policies such as Docker's `FORWARD=DROP`, allowing resident guests to reach package mirrors without replacing Docker or Tailscale chains. diff --git a/scripts/1helm-lxc-runtime b/scripts/1helm-lxc-runtime index 6a90ba0..cbd652c 100755 --- a/scripts/1helm-lxc-runtime +++ b/scripts/1helm-lxc-runtime @@ -78,13 +78,28 @@ cpu_count() { printf '%s' "$total" } start() { - [[ "$(state "$1")" == "RUNNING" ]] || lxc-start -P "$LXC_PATH" -n "$1" -d - local attempt - for attempt in {1..200}; do - [[ "$(state "$1")" == "RUNNING" ]] && return - sleep 0.1 + local name="$1" start_attempt state_attempt detail="" + [[ "$(state "$name")" == "RUNNING" ]] && return + # A freshly unpacked container can hit LXC's daemonized-start socket race: + # lxc-start exits after wait_on_daemonized_start loses the state message even + # though the container definition is intact and immediately startable. Check + # the authoritative state after every invocation and retry that same exact + # validated machine instead of failing channel creation on the transient. + for start_attempt in {1..3}; do + if ! detail="$(lxc-start -P "$LXC_PATH" -n "$name" -d 2>&1)"; then + for state_attempt in {1..50}; do + [[ "$(state "$name")" == "RUNNING" ]] && return + sleep 0.1 + done + continue + fi + for state_attempt in {1..200}; do + [[ "$(state "$name")" == "RUNNING" ]] && return + sleep 0.1 + done done - die "container did not start" + [[ -z "$detail" ]] || printf '%s\n' "$detail" >&2 + die "container did not start after 3 verified attempts" } wait_for_guest_network() { local name="$1" attempt diff --git a/test/site.mjs b/test/site.mjs index 439d57c..250508a 100644 --- a/test/site.mjs +++ b/test/site.mjs @@ -210,6 +210,7 @@ test("installer assets are explicit and syntax-valid", () => { assert.match(lxcHelper, /wait_for_guest_network[\s\S]*10\\\.0\\\.3\\\.[\s\S]*via 10\\\.0\\\.3\\\.1 dev eth0/, "fresh LXC provisioning waits for its DHCP address and default route"); assert.match(lxcHelper, /archive\.ubuntu\.com[\s\S]*security\.ubuntu\.com[\s\S]*curl -4[\s\S]*InRelease/, "fresh LXC provisioning proves DNS and outbound IPv4 HTTP before package bootstrap"); assert.match(lxcHelper, /start "\$name"[\s\S]*wait_for_guest_network "\$name"[\s\S]*apt-get update -o Acquire::ForceIPv4=true -o APT::Update::Error-Mode=any/, "package bootstrap begins only after bounded guest-network readiness"); + assert.match(lxcHelper, /for start_attempt in \{1\.\.3\}[\s\S]*lxc-start[\s\S]*state "\$name"[\s\S]*continue[\s\S]*container did not start after 3 verified attempts/, "a transient daemonized LXC state-socket failure is retried against the same validated machine"); assert.match(lxcHelper, /cpuset\.cpus\.effective/, "LXC CPU limits are selected from the service's actually delegated host CPUs"); assert.match(lxcHelper, /cpu_count/, "LXC inspection counts noncontiguous delegated CPU lists correctly"); assert.match(lxcNetwork, /1helm-lxc-net-owned/, "the bridge wrapper stops only a bridge it started");