From e338d15922aec307d90fb2e33007e9db758dafba Mon Sep 17 00:00:00 2001 From: Josue Zamudio Date: Mon, 28 Sep 2026 14:03:00 -0700 Subject: [PATCH] add shikane-watchdog user service to auto-recover shikane deadlocks --- .config/sway/scripts/shikane-watchdog.sh | 49 +++++++++++++++++++ .config/systemd/user/shikane-watchdog.service | 6 +++ .config/systemd/user/shikane-watchdog.timer | 10 ++++ .../shikane-watchdog.timer | 1 + README.md | 22 +++++++++ 5 files changed, 88 insertions(+) create mode 100755 .config/sway/scripts/shikane-watchdog.sh create mode 100644 .config/systemd/user/shikane-watchdog.service create mode 100644 .config/systemd/user/shikane-watchdog.timer create mode 120000 .config/systemd/user/timers.target.wants/shikane-watchdog.timer diff --git a/.config/sway/scripts/shikane-watchdog.sh b/.config/sway/scripts/shikane-watchdog.sh new file mode 100755 index 0000000..494b91c --- /dev/null +++ b/.config/sway/scripts/shikane-watchdog.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# shikane-watchdog: auto-recover shikane from its deadlock bug. +# +# 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 +# `shikanectl reload` does not recover it; a service restart does +# (re-applies the active profile in ~1s). +# +# A healthy daemon's last state-machine line is "VariantApplied". + +set -u + +unit=shikane +stuck=0 + +logs3=$(journalctl --user -u "$unit" -q --since "-3min" 2>/dev/null) +[ -z "$logs3" ] && exit 0 + +latest3=$(printf '%s\n' "$logs3" | grep 'daemon::state_machine' | tail -1) + +if [ -n "$latest3" ]; then + case "$latest3" in + *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) + printf '%s\n' "$logs3" | grep -q 'invalid input' && stuck=1 + ;; + 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 \ + | grep 'daemon::state_machine' | tail -1) + case "$last10" in + '' | *VariantApplied*) + : ;; # stable and applied, or nothing to check + *) + stuck=1 ;; # stuck in-progress/discarded for >3 min + esac +fi + +if [ "$stuck" -eq 1 ]; then + echo "shikane-watchdog: shikane stuck, restarting" >&2 + systemctl --user restart "$unit" +fi diff --git a/.config/systemd/user/shikane-watchdog.service b/.config/systemd/user/shikane-watchdog.service new file mode 100644 index 0000000..9252530 --- /dev/null +++ b/.config/systemd/user/shikane-watchdog.service @@ -0,0 +1,6 @@ +[Unit] +Description=shikane watchdog - restart shikane if its state machine is stuck + +[Service] +Type=oneshot +ExecStart=/home/work/.config/sway/scripts/shikane-watchdog.sh diff --git a/.config/systemd/user/shikane-watchdog.timer b/.config/systemd/user/shikane-watchdog.timer new file mode 100644 index 0000000..220a5f2 --- /dev/null +++ b/.config/systemd/user/shikane-watchdog.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Run shikane watchdog every 2 minutes + +[Timer] +OnBootSec=2min +OnUnitInactiveSec=2min +AccuracySec=5s + +[Install] +WantedBy=timers.target diff --git a/.config/systemd/user/timers.target.wants/shikane-watchdog.timer b/.config/systemd/user/timers.target.wants/shikane-watchdog.timer new file mode 120000 index 0000000..eec543c --- /dev/null +++ b/.config/systemd/user/timers.target.wants/shikane-watchdog.timer @@ -0,0 +1 @@ +/home/work/.config/systemd/user/shikane-watchdog.timer \ No newline at end of file diff --git a/README.md b/README.md index 2c4fe1a..582ef7c 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,25 @@ stow . ``` If stow is not installed use ./install.sh from .dotfiles repo +# User services + +## shikane-watchdog + +`.config/systemd/user/shikane-watchdog.timer` (every 2 min, user unit) +runs `.config/sway/scripts/shikane-watchdog.sh`, which restarts the +`shikane` user service if its state machine is stuck. + +Why: shikane 1.1.1 deadlocks when a profile application is discarded +mid-way (e.g. by a failing DP atomic commit on the external display). +The daemon then keeps logging +`[WARN shikane::variant] Received invalid input AtomicChangeDone at state Discarded` +and `shikanectl reload` no longer recovers it. The watchdog detects +that signature in the journal and runs `systemctl --user restart shikane` +(re-applies the active profile in ~1s). It never restarts a healthy +or legitimately transitioning daemon. + +```console +systemctl --user status shikane-watchdog.timer # check it's active +systemctl --user disable --now shikane-watchdog.timer # disable +``` +