From d1bd6aae488404d7e85663137bf4a38960cc1c7d Mon Sep 17 00:00:00 2001 From: Josue Zamudio Date: Tue, 27 Oct 2026 18:32:33 -0700 Subject: [PATCH] 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. --- .config/sway/scripts/clamshell-mode.sh | 10 ++++++- .../sway/scripts/shikane-recover-on-reload.sh | 13 +++++++++ .config/sway/scripts/shikane-watchdog.sh | 29 ++++++++++++++----- 3 files changed, 43 insertions(+), 9 deletions(-) create mode 100755 .config/sway/scripts/shikane-recover-on-reload.sh diff --git a/.config/sway/scripts/clamshell-mode.sh b/.config/sway/scripts/clamshell-mode.sh index 98de4c3..9237008 100755 --- a/.config/sway/scripts/clamshell-mode.sh +++ b/.config/sway/scripts/clamshell-mode.sh @@ -1,4 +1,12 @@ #!/bin/sh +# Lid-driven laptop panel handling. +# +# shikane is authoritative for output layout/scaling +# (~/.config/shikane/config.toml + shikane-watchdog.timer). This script used to +# run `swaymsg output eDP-1 enable` whenever the lid was open, which on every +# `swaymsg reload` raced the shikane "External Display" profile (eDP-1 off) and +# contributed to "Atomic commit failed: Device or resource busy" churn. +# It now only ever disables. LAPTOP_OUTPUT="eDP-1" LID_STATE_FILE="/proc/acpi/button/lid/LID/state" @@ -6,7 +14,7 @@ LID_STATE_FILE="/proc/acpi/button/lid/LID/state" read -r LS < "$LID_STATE_FILE" case "$LS" in -*open) swaymsg output "$LAPTOP_OUTPUT" enable ;; *closed) swaymsg output "$LAPTOP_OUTPUT" disable ;; +*open) : ;; # leave to shikane *) echo "Could not get lid state" >&2 ; exit 1 ;; esac diff --git a/.config/sway/scripts/shikane-recover-on-reload.sh b/.config/sway/scripts/shikane-recover-on-reload.sh new file mode 100755 index 0000000..5ebacfa --- /dev/null +++ b/.config/sway/scripts/shikane-recover-on-reload.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# shikane-recover-on-reload: run after every sway startup/reload +# (wired up via exec_always in the sway config). +# +# Re-applies the shikane profile, then verifies the daemon came out +# the other side: if the re-apply hit the commit-failure race, the +# watchdog check (2s later) restarts the service to recover. + +set -u + +shikanectl reload 2>/dev/null +sleep 2 +exec /home/work/.config/sway/scripts/shikane-watchdog.sh diff --git a/.config/sway/scripts/shikane-watchdog.sh b/.config/sway/scripts/shikane-watchdog.sh index 494b91c..5b55899 100755 --- a/.config/sway/scripts/shikane-watchdog.sh +++ b/.config/sway/scripts/shikane-watchdog.sh @@ -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