From 30737043f8ec052ad860cadcb2e0783dc0069f8a Mon Sep 17 00:00:00 2001 From: Tomaz Zaman Date: Thu, 13 Aug 2026 01:01:49 +0200 Subject: [PATCH 56/62] cmm: procd-supervise cmm with QoS re-apply on restart (S4) Convert cmm.init from SysV to procd with unlimited respawn, so a cmm crash restarts instead of silently taking down the fast path. A new cmm-supervise wrapper re-applies cmmqos on every (re)start -- procd re-execs the instance command on respawn, so this covers crashes, not just first boot. cmmqos.init apply() hardened for busybox: flock -x (busybox has no -w), pidof (busybox pgrep -x matches the full argv, so it never matched a cmm started with args), and skip-when-no-directives (cmm's qm-config refuses a directive-less file, so a disabled/empty config is a clean no-op instead of a logged error). Co-Authored-By: Claude Opus 4.8 --- package/ask/cmm/files/cmm-supervise | 27 ++++++++++++++ package/ask/cmm/files/cmm.init | 29 +++++++++++--- package/mono/cmmqos/files/cmmqos.init | 54 ++++++++++++++++++++++----- 3 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 package/ask/cmm/files/cmm-supervise diff --git a/package/ask/cmm/files/cmm-supervise b/package/ask/cmm/files/cmm-supervise new file mode 100644 index 0000000000..34a234849f --- /dev/null +++ b/package/ask/cmm/files/cmm-supervise @@ -0,0 +1,27 @@ +#!/bin/sh +# procd runs this as the cmm instance command (see /etc/init.d/cmm). It +# re-applies CEETM QoS once cmm's CLI is ready, then execs cmm in the +# foreground. Because procd re-execs this wrapper on every respawn, the QoS +# re-apply happens after a crash-restart too, not only on first boot -- cmm's +# QoS state is in-memory and does not survive a restart. +# +# The re-applier is forked BEFORE the exec below, so it keeps running as a +# child of the cmm process image and does its work in parallel with cmm start. + +if [ -x /etc/init.d/cmmqos ]; then + ( + # cmm writes its pidfile before its CLI socket is listening, so a live + # process is not a readiness signal. Just retry the apply: it fails + # cleanly (non-zero) until cmm accepts 'cmm -c qm-config', then succeeds + # once. flock inside cmmqos serialises us against the boot-time apply. + i=0 + while [ $i -lt 30 ]; do + sleep 1 + /etc/init.d/cmmqos apply >/dev/null 2>&1 && exit 0 + i=$((i + 1)) + done + logger -t cmm "cmm-supervise: QoS not applied within 30s of cmm (re)start" + ) & +fi + +exec /usr/bin/cmm -F -f /etc/config/fastforward -n 131072 diff --git a/package/ask/cmm/files/cmm.init b/package/ask/cmm/files/cmm.init index 3d93c9eb8f..bd2da8a049 100644 --- a/package/ask/cmm/files/cmm.init +++ b/package/ask/cmm/files/cmm.init @@ -1,8 +1,16 @@ #!/bin/sh /etc/rc.common -# ASK Connection Manager. cmm self-daemonizes (no foreground mode), so this -# is a plain SysV script rather than a procd service. Modules cdx (30), +# ASK Connection Manager, supervised by procd. cmm runs in the foreground +# (-F, added upstream) so procd tracks its PID and respawns it on a crash +# instead of letting the fast path silently die. Modules cdx (30), # auto_bridge (31) and fci (52) are loaded by kmodloader before START=54. +# +# cmmqos (START=55) programs CEETM QoS *into* the running cmm and must stay +# after us. cmm's QoS state is in-memory and is lost on a restart, so the +# instance command is the cmm-supervise wrapper, which re-applies QoS after +# cmm's CLI comes up. procd re-execs that wrapper on every respawn, so the +# re-apply happens after a crash-restart too, not only on first boot. +USE_PROCD=1 START=54 STOP=10 @@ -11,16 +19,25 @@ vwd_fast_path() { echo "$1" > /sys/class/vwd/vwd0/vwd_fast_path_enable } -start() { +start_service() { [ -e /dev/cdx_ctrl ] || { echo "cmm: /dev/cdx_ctrl not present, cdx.ko not loaded - not starting" >&2 return 1 } vwd_fast_path 1 - /usr/bin/cmm -f /etc/config/fastforward -n 131072 + + procd_open_instance cmm + procd_set_param command /usr/sbin/cmm-supervise + # threshold 3600s / timeout 5s / retry 0 == unlimited respawns: a fast-path + # daemon must keep trying rather than give up. Crash storms are loud in the + # log (stdout/stderr below), not silent. + procd_set_param respawn 3600 5 0 + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance } -stop() { - killall cmm 2>/dev/null +stop_service() { + # procd SIGTERMs cmm; its handler exits cleanly and removes the pidfile. vwd_fast_path 0 } diff --git a/package/mono/cmmqos/files/cmmqos.init b/package/mono/cmmqos/files/cmmqos.init index 18328eccc9..c508ee2251 100644 --- a/package/mono/cmmqos/files/cmmqos.init +++ b/package/mono/cmmqos/files/cmmqos.init @@ -3,12 +3,20 @@ # directives and applies them live via 'cmm -c qm-config' (no cmm restart, # which would drop the fast path). Disabling + reloading applies an empty # config, which item 7's qm-config flushes-then-applies == QoS off. +# +# 'apply' is exposed as an action so the cmm-supervise wrapper can re-push +# QoS after cmm (re)starts. apply() takes an flock because that wrapper and +# the boot-time start_service can now run it concurrently. USE_PROCD=1 START=55 STOP=09 +EXTRA_COMMANDS="apply" +EXTRA_HELP=" apply Render and push the QoS config into the running cmm" + CONF=/tmp/cmmqos.conf +LOCK=/tmp/cmmqos.lock BOARD="mono,gateway-dk" qm() { printf 'set qm %s\n' "$*" >> "$CONF"; } @@ -100,9 +108,11 @@ render_rate() { esac } -apply() { +# Render the config and push it into the running cmm. Returns non-zero until +# cmm's CLI actually accepts the config, so cmm-supervise's retry loop keeps +# trying after a (re)start rather than giving up on the first not-ready reply. +_apply_locked() { [ "$(board_name)" = "$BOARD" ] || return 0 - pgrep -x cmm >/dev/null 2>&1 || { logger -t cmmqos "cmm not running - deferring QoS apply"; return 0; } config_load cmmqos local enabled; config_get_bool enabled global enabled 0 @@ -118,16 +128,42 @@ apply() { config_foreach render_sec_rate sec_rate config_foreach render_rate rate fi - # enabled=0 -> empty file -> qm-config flushes-then-applies == QoS off + # cmm's qm-config REFUSES a directive-less file — it will not wipe all QoS + # that way (verified on hw: empty => "is empty"; comment-only => "has no + # directives; refusing"). So "disabled" or "nothing rendered" means there is + # nothing to push: succeed and leave cmm's live QoS untouched. (To actually + # clear live QoS, restart cmm — the supervise wrapper re-applies on restart, + # so a disabled config simply comes back up with no QoS programmed.) + if ! grep -q '^set qm ' "$CONF"; then + logger -t cmmqos "QoS off / no directives - nothing to apply" + return 0 + fi - if cmm -c "qm-config $CONF" >/dev/null 2>&1; then - [ "$enabled" = "1" ] \ - && logger -t cmmqos "applied $(grep -c '^set qm' "$CONF") QoS directives" \ - || logger -t cmmqos "flushed QoS (disabled)" - else - logger -t cmmqos "ERROR: cmm rejected the QoS config (qm-config failed)" + # pidof, NOT 'pgrep -x cmm': busybox pgrep matches the full argv, so + # 'pgrep -x cmm' never matches a cmm started with args (-f -n ...). + pidof cmm >/dev/null 2>&1 || { + logger -t cmmqos "cmm not running - QoS apply deferred" return 1 + } + + if cmm -c "qm-config $CONF" >/dev/null 2>&1; then + logger -t cmmqos "applied $(grep -c '^set qm' "$CONF") QoS directives" + return 0 fi + logger -t cmmqos "cmm not ready or rejected QoS config - will retry" + return 1 +} + +apply() { + # Cheap board gate before taking the lock; nothing to do off-target. + [ "$(board_name)" = "$BOARD" ] || return 0 + ( + # busybox flock has no -w (timeout) — that is util-linux only. Block on + # -x until acquired; holders are short (render a temp file + one + # 'cmm -c'), so a plain blocking lock is safe and race-free here. + flock -x 9 || { logger -t cmmqos "apply: could not lock $LOCK"; exit 1; } + _apply_locked + ) 9>"$LOCK" } start_service() { apply; } -- 2.47.3