fix(shikane): detect silent deadlocks; re-apply profile on sway reload

watchdog: covers both observed deadlock flavours -- the loud one that repeats
"Received invalid input AtomicChangeDone at state Discarded", and the silent
one that logs nothing and just never leaves VariantInProgress.

shikane-recover-on-reload.sh (new, previously referenced by sway/config but
never committed): shikanectl reload -> sleep 2 -> watchdog check, falling
back to a service restart when reload cannot recover.

clamshell-mode.sh: documents why the laptop panel is no longer enabled
unconditionally on lid-open, which raced shikane's "External Display" profile
(eDP-1 disabled by design) and produced "Atomic commit failed: Device or
resource busy" churn.
This commit is contained in:
jozamudi committed 2026-10-27 18:32:33 -07:00
1 parent 0ceec0b4ab
commit d1bd6aae48
3 files changed
+43 -9

No files matched your search

+21 -8
View File
@@ -3,8 +3,11 @@
#
# Bug (shikane 1.1.1): if a variant application is discarded mid-way
# (e.g. by a failing DP atomic commit), the daemon gets stuck in
# VariantInProgress/Discarded and keeps logging
# [WARN shikane::variant] Received invalid input AtomicChangeDone at state Discarded
# VariantInProgress/Discarded. Two flavors observed:
# loud: keeps logging
# [WARN shikane::variant] Received invalid input AtomicChangeDone at state Discarded
# silent: no WARN at all; the variant state ends at "Discarded" and the
# daemon state machine simply never leaves VariantInProgress
# `shikanectl reload` does not recover it; a service restart does
# (re-applies the active profile in ~1s).
#
@@ -25,21 +28,31 @@ if [ -n "$latest3" ]; then
*VariantApplied*)
: ;; # healthy
*)
# InProgress/Discarded within the last 3 min: only a deadlock
# if the WARN signature is present (a legitimate slow transition
# has no WARN)
# Daemon still InProgress. Deadlock signatures (a legitimate
# slow transition has neither):
# loud: the WARN from the discarded-variant race
# silent: variant discarded but daemon never processed it
printf '%s\n' "$logs3" | grep -q 'invalid input' && stuck=1
if [ "$stuck" -eq 0 ]; then
lastvariant=$(printf '%s\n' "$logs3" \
| grep 'shikane::variant' | grep 'New variant state' | tail -1)
case "$lastvariant" in
*Discarded*) stuck=1 ;;
esac
fi
;;
esac
else
# No state change in the last 3 min — check the last known state
last10=$(journalctl --user -u "$unit" -q --since "-10min" 2>/dev/null \
# (unbounded: a stuck daemon may have gone silent long ago,
# e.g. across a laptop suspend)
lastline=$(journalctl --user -u "$unit" -q -n 500 2>/dev/null \
| grep 'daemon::state_machine' | tail -1)
case "$last10" in
case "$lastline" in
'' | *VariantApplied*)
: ;; # stable and applied, or nothing to check
*)
stuck=1 ;; # stuck in-progress/discarded for >3 min
stuck=1 ;; # stuck in-progress/discarded, any age
esac
fi