Fix sing-box crash startup action state
Require sing-box to survive a dedicated startup stability window before Podkop Plus continues startup. This is generic runtime dependency validation, not special handling for rule-set, list, URL, or specific sing-box FATAL text. When startup fails, Podkop Plus rolls back runtime/DNS state and the UI service action is closed through a short post-command wait instead of staying in the starting state until the full action timeout. Keep the diagnostic restart action hidden during start/stop transitions, even if runtime probes briefly report Podkop Plus as running before the transition finishes. This prevents the restart button from flickering during failed startup.
This commit is contained in:
@@ -77,9 +77,36 @@ fi
|
||||
if initd_ucode initd-should-ignore-config-change-reload on_config_change on_config_change 1 0 >/dev/null 2>&1; then
|
||||
fail "running service should not ignore config-change reload"
|
||||
fi
|
||||
if initd_ucode initd-should-ignore-config-change-reload on_config_change on_config_change 0 1 >/dev/null 2>&1; then
|
||||
fail "enabled service should not ignore config-change reload"
|
||||
initd_ucode initd-should-ignore-config-change-reload on_config_change on_config_change 0 1 >/dev/null ||
|
||||
fail "stopped service should ignore config-change reload even when autostart is enabled"
|
||||
initd_ucode initd-should-queue-config-change-reload on_config_change on_config_change 0 start >/dev/null ||
|
||||
fail "config-change reload during service start should be queued"
|
||||
if initd_ucode initd-should-queue-config-change-reload on_config_change on_config_change 0 "" >/dev/null 2>&1; then
|
||||
fail "stopped service without active service action should not queue config-change reload"
|
||||
fi
|
||||
if initd_ucode initd-should-queue-config-change-reload on_config_change on_config_change 1 reload >/dev/null 2>&1; then
|
||||
fail "running service should not use stopped-service queue path"
|
||||
fi
|
||||
|
||||
pending_file="$WORK_DIR/reload.pending"
|
||||
if PODKOP_PENDING_RELOAD_FILE="$pending_file" \
|
||||
initd_ucode reload-begin-fixture on_config_change 123 0 1 start >/dev/null 2>&1; then
|
||||
fail "queued config-change reload should not run immediately while service is starting"
|
||||
fi
|
||||
[ -f "$pending_file" ] ||
|
||||
fail "config-change reload during service start should create pending reload"
|
||||
grep -Fq 'reason=on_config_change' "$pending_file" ||
|
||||
fail "pending reload should preserve config-change reason"
|
||||
|
||||
rm -f "$pending_file"
|
||||
if PODKOP_PENDING_RELOAD_FILE="$pending_file" \
|
||||
initd_ucode reload-begin-fixture pending 123 1 1 start >/dev/null 2>&1; then
|
||||
fail "pending reload should not run while another service action is active"
|
||||
fi
|
||||
[ -f "$pending_file" ] ||
|
||||
fail "pending reload should stay pending while another service action is active"
|
||||
grep -Fq 'reason=pending' "$pending_file" ||
|
||||
fail "pending reload should preserve pending reason while delayed by active action"
|
||||
|
||||
initd_ucode initd-should-restore-dnsmasq-on-start-fixture "" 0 >/dev/null ||
|
||||
fail "unclean shutdown should request dnsmasq restore on start"
|
||||
|
||||
@@ -85,6 +85,26 @@ grep -Fq 'mode == "reload-sing-box-runtime"' "$STATE_UC" ||
|
||||
fail "service/state.uc must own sing-box runtime reload"
|
||||
grep -Fq '"wait-podkop-stable-start"' "$LIFECYCLE_UC" ||
|
||||
fail "service/lifecycle.uc must verify stable runtime after sing-box start/reload"
|
||||
grep -Fq 'SING_BOX_START_STABLE_MIN_AGE' "$LIFECYCLE_UC" ||
|
||||
fail "service/lifecycle.uc must use a dedicated sing-box start stability window"
|
||||
sing_box_start_line="$(grep -nF 'command_success_from_args([ "/etc/init.d/sing-box", "start" ])' "$LIFECYCLE_UC" | head -n1 | cut -d: -f1)"
|
||||
early_stable_line="$(awk -v start="$sing_box_start_line" 'NR > start && /"wait-podkop-stable-start"/ { print NR; exit }' "$LIFECYCLE_UC")"
|
||||
start_stable_min_age_line="$(awk -v start="$sing_box_start_line" 'NR > start && /SING_BOX_START_STABLE_MIN_AGE/ { print NR; exit }' "$LIFECYCLE_UC")"
|
||||
deferred_bootstrap_line="$(grep -nF '"run-deferred-bootstrap"' "$LIFECYCLE_UC" | head -n1 | cut -d: -f1)"
|
||||
[ -n "$sing_box_start_line" ] ||
|
||||
fail "service/lifecycle.uc must start sing-box through init.d"
|
||||
[ -n "$early_stable_line" ] ||
|
||||
fail "service/lifecycle.uc must verify stable runtime immediately after sing-box start"
|
||||
[ "$early_stable_line" -lt "$deferred_bootstrap_line" ] ||
|
||||
fail "service/lifecycle.uc must verify sing-box stability before deferred bootstrap work"
|
||||
[ -n "$start_stable_min_age_line" ] && [ "$start_stable_min_age_line" -lt "$deferred_bootstrap_line" ] ||
|
||||
fail "service/lifecycle.uc must use the dedicated sing-box start stability window before deferred bootstrap work"
|
||||
sing_box_reload_line="$(grep -nF '"reload-sing-box-runtime"' "$LIFECYCLE_UC" | head -n1 | cut -d: -f1)"
|
||||
reload_stable_min_age_line="$(awk -v start="$sing_box_reload_line" 'NR > start && /SING_BOX_START_STABLE_MIN_AGE/ { print NR; exit }' "$LIFECYCLE_UC")"
|
||||
[ -n "$sing_box_reload_line" ] ||
|
||||
fail "service/lifecycle.uc must reload sing-box through service/state.uc"
|
||||
[ -n "$reload_stable_min_age_line" ] ||
|
||||
fail "service/lifecycle.uc must use the dedicated sing-box start stability window after sing-box reload"
|
||||
grep -Fq 'Reload verification failed after sing-box was reloaded; stopping Podkop Plus runtime' "$LIFECYCLE_UC" ||
|
||||
fail "service/lifecycle.uc must fail reload when sing-box does not stay stable"
|
||||
grep -Fq 'Reload runtime restart verification failed after Podkop Plus was started; rolling back DNS changes' "$LIFECYCLE_UC" ||
|
||||
|
||||
@@ -9,6 +9,7 @@ WORK_DIR="$(mktemp -d)"
|
||||
export ZAPRET_DEFAULT_NFQWS_OPT="--default-zapret"
|
||||
export ZAPRET2_DEFAULT_NFQWS2_OPT="--default-zapret2"
|
||||
export BYEDPI_DEFAULT_CMD_OPTS="--default-bye"
|
||||
export PODKOP_FAKE_INIT_CAPTURE="$WORK_DIR/pending-reload-init.args"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
@@ -111,6 +112,23 @@ if state_ucode consume-pending-reload "$PENDING_RELOAD_FILE" >/dev/null 2>&1; th
|
||||
fail "missing pending reload should not be consumed"
|
||||
fi
|
||||
|
||||
cat >"$WORK_DIR/fake-init" <<'SH'
|
||||
#!/bin/sh
|
||||
printf '%s\n' "$1" >"$PODKOP_FAKE_INIT_CAPTURE"
|
||||
SH
|
||||
chmod +x "$WORK_DIR/fake-init"
|
||||
state_ucode mark-pending-reload "$PENDING_RELOAD_FILE" "reload_busy"
|
||||
state_ucode run-pending-reload-if-requested "$PENDING_RELOAD_FILE" "$WORK_DIR/fake-init"
|
||||
for _ in $(seq 1 20); do
|
||||
[ -s "$PODKOP_FAKE_INIT_CAPTURE" ] && break
|
||||
sleep 0.1
|
||||
done
|
||||
assert_eq "reload" \
|
||||
"$(cat "$PODKOP_FAKE_INIT_CAPTURE")" \
|
||||
"pending reload should invoke init.d reload"
|
||||
[ ! -e "$PENDING_RELOAD_FILE" ] ||
|
||||
fail "pending reload should be consumed when worker is started"
|
||||
|
||||
LOCK_DIR="$WORK_DIR/runtime.lock"
|
||||
state_ucode acquire-runtime-dir-lock "$LOCK_DIR" "$$" ||
|
||||
fail "ucode should acquire runtime dir lock"
|
||||
|
||||
@@ -3,6 +3,7 @@ set -eo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
PODKOP_BIN="$ROOT_DIR/podkop/files/usr/bin/podkop"
|
||||
PODKOP_INIT="$ROOT_DIR/podkop/files/etc/init.d/podkop"
|
||||
CLI_UC="$PODKOP_BIN"
|
||||
LIFECYCLE_UC="$ROOT_DIR/podkop/files/usr/lib/service/lifecycle.uc"
|
||||
INITD_UC="$ROOT_DIR/podkop/files/usr/lib/service/initd.uc"
|
||||
@@ -56,12 +57,24 @@ require_pattern 'module_background(UPDATES_UC, [ "list-update" ])' \
|
||||
"startup list_update background job must be owned by service/lifecycle.uc"
|
||||
require_pattern 'module_background(DIAGNOSTICS_UC, [ "get-system-info" ])' \
|
||||
"startup system-info background job must be owned by service/lifecycle.uc"
|
||||
require_pattern 'startup_config_fingerprint = external_config_fingerprint();' \
|
||||
"startup must snapshot runtime-relevant config after validation"
|
||||
require_pattern 'mark_pending_reload_if_config_changed(startup_config_fingerprint, "config_changed_during_start")' \
|
||||
"startup must queue reload when config changes while service is starting"
|
||||
require_pattern 'pending_reload_log_context(reason)' \
|
||||
"startup queued reload must be visible in logs"
|
||||
require_pattern 'return "current reload";' \
|
||||
"reload-time queued reload must be distinguishable in logs"
|
||||
require_file_pattern "$LIFECYCLE_UC" '>/dev/null 2>&1 1000>&- &' \
|
||||
"service/lifecycle.uc background modules must close inherited procd lock fd"
|
||||
require_file_pattern "$INITD_UC" 'reload pending >/dev/null 2>&1 1000>&- &' \
|
||||
"service/initd.uc pending reload worker must close inherited procd lock fd"
|
||||
require_file_pattern "$STATE_UC" 'reload pending >/dev/null 2>&1 1000>&- &' \
|
||||
"service/state.uc pending reload worker must close inherited procd lock fd"
|
||||
require_file_pattern "$PODKOP_INIT" 'PODKOP_LAST_START_STATUS="$?"' \
|
||||
"init.d start_service must preserve backend start status for rc.common"
|
||||
require_file_pattern "$PODKOP_INIT" 'service_started()' \
|
||||
"init.d must return preserved start status through rc.common service_started hook"
|
||||
require_file_pattern "$UI_UC" '>/dev/null 2>&1 1000>&- & echo $!' \
|
||||
"service/ui.uc service action workers must close inherited procd lock fd"
|
||||
require_file_pattern "$UPDATES_UC" '>/dev/null 2>&1 1000>&- & echo $!' \
|
||||
@@ -73,14 +86,16 @@ require_file_pattern "$NFQUEUE_RUNTIME_UC" '>>" + shell_quote(logfile) + " 2>&1
|
||||
require_file_pattern "$BYEDPI_RUNTIME_UC" '>>" + shell_quote(logfile) + " 2>&1 1000>&- & echo $!' \
|
||||
"byedpi supervisor must close inherited procd lock fd"
|
||||
awk '
|
||||
/function start\(\)/ { in_start = 1 }
|
||||
in_start && /if \(status != 0\) \{/ { in_failure = 1 }
|
||||
in_failure && /stop_main\(\);/ { saw_stop = 1 }
|
||||
in_failure && /dnsmasq_restore_fail_safe\(\);/ && saw_stop { ok = 1; exit }
|
||||
in_start && /^}/ { exit }
|
||||
/function cleanup_failed_runtime\(\)/ { in_cleanup = 1 }
|
||||
in_cleanup && /stop_main\(\);/ { saw_stop = 1 }
|
||||
in_cleanup && /dnsmasq_restore_fail_safe\(\);/ && saw_stop { saw_dns = 1 }
|
||||
in_cleanup && /mark_runtime_stopped_clean\(\);/ && saw_dns { ok = 1; exit }
|
||||
in_cleanup && /^}/ { exit }
|
||||
END { exit ok ? 0 : 1 }
|
||||
' "$LIFECYCLE_UC" ||
|
||||
fail "service start failure path must stop partial runtime before DNS rollback"
|
||||
fail "failed runtime cleanup must stop partial runtime, roll back DNS, and mark clean shutdown"
|
||||
grep -Fq 'cleanup_failed_runtime();' "$LIFECYCLE_UC" ||
|
||||
fail "service failure paths must use failed runtime cleanup"
|
||||
require_pattern "tproxy-marking-rule4-present" \
|
||||
"service stop must use direct ucode IPv4 tproxy marking rule check"
|
||||
require_pattern "tproxy-marking-rule6-present" \
|
||||
|
||||
@@ -63,6 +63,14 @@ grep -Fq '"podkop-stably-running"' "$UI_UC" ||
|
||||
fail "UI Podkop status must use stable runtime state to avoid crash-loop flicker"
|
||||
grep -Fq '"sing-box-service-stable"' "$UI_UC" ||
|
||||
fail "UI sing-box status must use stable runtime state to avoid crash-loop flicker"
|
||||
grep -Fq 'run_pending_reload_after_service_action(action, success)' "$UI_UC" ||
|
||||
fail "UI service actions must run pending reload after the current action finishes"
|
||||
grep -Fq 'service_action_wait_for_expected_state(action, SERVICE_ACTION_TIMEOUT_SECONDS, SERVICE_ACTION_SETTLE_SECONDS)' "$UI_UC" ||
|
||||
fail "UI service action worker must keep action state until the expected service state is stable"
|
||||
grep -Fq 'let args = [ SERVICE_INIT, action ];' "$UI_UC" ||
|
||||
fail "UI service action worker must use init.d so procd triggers stay registered"
|
||||
grep -Fq 'start_service_action("reload", "initd", "pending")' "$UI_UC" ||
|
||||
fail "pending reload must enter the normal reload service action immediately"
|
||||
if grep -n -E 'pgrep.*sing-box|service-list-instance-running' "$UI_UC" >/dev/null 2>&1; then
|
||||
fail "service/ui.uc must not use transient sing-box process probes for visible status"
|
||||
fi
|
||||
@@ -228,6 +236,9 @@ job_id="$(ui_ucode service-action-begin-if-idle reload test)"
|
||||
[ -n "$job_id" ] || fail "service-action-begin-if-idle should create a job"
|
||||
service_state="$PODKOP_UI_SERVICE_ACTION_DIR/$job_id.json"
|
||||
[ -f "$service_state" ] || fail "service action state file should be created"
|
||||
assert_eq "reload" \
|
||||
"$(ui_ucode active-service-action)" \
|
||||
"active-service-action should use the default service action dir"
|
||||
|
||||
if ui_ucode service-action-begin-if-idle restart test >/dev/null 2>&1; then
|
||||
fail "second service action should be rejected while first is running"
|
||||
|
||||
Reference in New Issue
Block a user