Detach the pending-migrations toast from the notifier
omarchy-migrate-notify handed its notification to `systemd-run --scope`,
which is synchronous: the calling process becomes the payload, so
systemd-run does not return until the toast has been answered -- and if
the toast is clicked, not until the migration terminal it opens has been
closed.
omarchy-migrate-notify.service is Type=oneshot, which defaults to
TimeoutStartUSec=infinity, so the unit sat in activating for exactly that
long. On a machine that had not yet picked up c7e327b0, which orders the
notifier after graphical-session.target, that held the target open --
and wayland-wm-app-daemon.service is ordered after the same target, so it
never started. Every keybinding goes through uwsm-app, which waits ten
seconds for that daemon's pipes and then reports "App failure -- Timed
out waiting for pipes!" instead of launching anything. Keybindings were
dead for two minutes and seventeen seconds, until a pacman hook ran
`systemctl reload user@*.service`, whose re-exec of the user manager
broke the scope's bus connection and let the oneshot finish. That also
made systemd-run exit non-zero, so the notifier fell through to the
terminal fallback meant for having no user manager at all, and printed
the migration list into the journal.
Hand the notification to a transient service instead. systemd-run
returns once the unit has started rather than once the payload is done,
so the oneshot completes in milliseconds and nothing ordered after the
target waits on a toast. Put it in background-graphical.slice, which
systemd ships with PartOf=graphical-session.target, so an unanswered
toast ends at logout rather than outliving the session.
The terminal a clicked toast opens is unaffected: uwsm-app re-registers
it into app-graphical.slice, outside this unit's cgroup.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1472fc1b38
commit
b6e1a43278
@@ -40,11 +40,14 @@ if update_in_progress; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# A transient service rather than a scope, which would block here until the
|
||||
# toast was answered and keep this oneshot activating for that whole time. The
|
||||
# graphical slice ends an unanswered toast at logout.
|
||||
unit="omarchy-migrations-notification-$(date +%Y%m%d%H%M%S)"
|
||||
systemd-run --user --scope --unit="$unit" bash -lc "$notify_command" >/dev/null 2>&1 && exit 0
|
||||
systemd-run --user --collect --slice=background-graphical.slice --unit="$unit" bash -lc "$notify_command" >/dev/null 2>&1 && exit 0
|
||||
|
||||
# Reached when there is no user manager to run the scope under, such as a
|
||||
# non-graphical shell, so fall back to telling the user in the terminal.
|
||||
# Reached when the notification could not be handed off, so fall back to telling
|
||||
# the user in the terminal.
|
||||
print_pending_migrations() {
|
||||
echo "Omarchy has pending migrations. Run omarchy-migrate in a terminal to apply them:"
|
||||
while IFS= read -r migration; do
|
||||
|
||||
@@ -29,7 +29,13 @@ if [[ ${OMARCHY_TEST_SYSTEMD_RUN:-run} == "fail" ]]; then
|
||||
fi
|
||||
|
||||
command=${!#}
|
||||
bash -c "$command"
|
||||
|
||||
# --scope blocks on the command, a transient service detaches it.
|
||||
if [[ " $* " == *" --scope "* ]]; then
|
||||
bash -c "$command"
|
||||
else
|
||||
setsid bash -c "$command" >/dev/null 2>&1 &
|
||||
fi
|
||||
SH
|
||||
chmod +x "$stub_bin/systemd-run"
|
||||
|
||||
@@ -61,7 +67,18 @@ chmod +x "$stub_bin/omarchy-notification-wait"
|
||||
|
||||
cat >"$stub_bin/omarchy-notification-send" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$@" >"$OMARCHY_TEST_NOTIFY_ARGS"
|
||||
# Written whole so a reader polling for the file never sees half the arguments.
|
||||
printf '%s\n' "$@" >"$OMARCHY_TEST_NOTIFY_ARGS.partial"
|
||||
mv "$OMARCHY_TEST_NOTIFY_ARGS.partial" "$OMARCHY_TEST_NOTIFY_ARGS"
|
||||
|
||||
# Stands in for a toast nobody has answered yet, until the test releases it.
|
||||
if [[ -n ${OMARCHY_TEST_NOTIFY_HOLD:-} ]]; then
|
||||
for _ in {1..200}; do
|
||||
[[ -e $OMARCHY_TEST_NOTIFY_HOLD ]] || break
|
||||
sleep 0.05
|
||||
done
|
||||
: >"$OMARCHY_TEST_NOTIFY_ANSWERED"
|
||||
fi
|
||||
SH
|
||||
chmod +x "$stub_bin/omarchy-notification-send"
|
||||
|
||||
@@ -77,9 +94,20 @@ run_notify() {
|
||||
OMARCHY_TEST_SYSTEMD_RUN="${2:-run}" \
|
||||
OMARCHY_TEST_LOCK_DURING_WAIT="${OMARCHY_TEST_LOCK_DURING_WAIT:-0}" \
|
||||
OMARCHY_TEST_LOCK_HOLDER_PID="$test_tmp/lock-holder-pid" \
|
||||
OMARCHY_TEST_NOTIFY_HOLD="${OMARCHY_TEST_NOTIFY_HOLD:-}" \
|
||||
OMARCHY_TEST_NOTIFY_ANSWERED="$test_tmp/notify-answered" \
|
||||
"$ROOT/bin/omarchy-migrate-notify"
|
||||
}
|
||||
|
||||
# The notification outlives the notifier, so its arguments land after it exits.
|
||||
wait_for_notify_args() {
|
||||
for _ in {1..200}; do
|
||||
[[ -s $test_tmp/notify-args ]] && return 0
|
||||
sleep 0.05
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
run_notify 0 >"$test_tmp/not-pending.out" 2>"$test_tmp/not-pending.err"
|
||||
[[ ! -s $test_tmp/not-pending.out ]] || fail "migration notifier stays quiet on stdout without pending migrations"
|
||||
[[ ! -s $test_tmp/not-pending.err ]] || fail "migration notifier stays quiet on stderr without pending migrations"
|
||||
@@ -91,6 +119,7 @@ grep -q '200-migration.sh' "$test_tmp/pending.err" || fail "migration notifier l
|
||||
pass "migration notifier reports pending migrations"
|
||||
|
||||
run_notify 1 >"$test_tmp/notified.out" 2>"$test_tmp/notified.err"
|
||||
wait_for_notify_args || fail "migration notifier sends a notification for pending migrations"
|
||||
grep -Fx 'Pending Omarchy Migrations' "$test_tmp/notify-args" >/dev/null || fail "migration notifier uses pending migrations title"
|
||||
grep -Fx 'Click to run 1 pending migration.' "$test_tmp/notify-args" >/dev/null || fail "migration notifier describes the pending migration"
|
||||
grep -Fx '' "$test_tmp/notify-args" >/dev/null || fail "migration notifier includes the large-slot glyph"
|
||||
@@ -114,7 +143,8 @@ pass "migration notifier stays quiet while omarchy update holds its lock"
|
||||
exec {update_lock_fd}>&-
|
||||
|
||||
run_notify 1 >/dev/null 2>&1
|
||||
grep -Fx 'Pending Omarchy Migrations' "$test_tmp/notify-args" >/dev/null ||
|
||||
wait_for_notify_args &&
|
||||
grep -Fx 'Pending Omarchy Migrations' "$test_tmp/notify-args" >/dev/null ||
|
||||
fail "migration notifier resumes notifying once the update lock is released"
|
||||
pass "migration notifier resumes notifying after the update releases its lock"
|
||||
|
||||
@@ -142,8 +172,20 @@ exec {foreign_lock_fd}>"$foreign_lock"
|
||||
flock -n "$foreign_lock_fd" || fail "test could not hold the foreign update lock"
|
||||
|
||||
run_notify 1 >/dev/null 2>&1
|
||||
grep -Fx 'Pending Omarchy Migrations' "$test_tmp/notify-args" >/dev/null ||
|
||||
wait_for_notify_args &&
|
||||
grep -Fx 'Pending Omarchy Migrations' "$test_tmp/notify-args" >/dev/null ||
|
||||
fail "migration notifier ignores update locks outside its own runtime directory"
|
||||
pass "migration notifier ignores update locks outside its own runtime directory"
|
||||
|
||||
exec {foreign_lock_fd}>&-
|
||||
|
||||
# The notifier is a Type=oneshot with no start timeout, so blocking on the toast
|
||||
# leaves it activating until the user answers.
|
||||
rm -f "$test_tmp/notify-args" "$test_tmp/notify-answered"
|
||||
: >"$test_tmp/notify-hold"
|
||||
OMARCHY_TEST_NOTIFY_HOLD="$test_tmp/notify-hold" run_notify 1 >/dev/null 2>&1
|
||||
wait_for_notify_args || fail "migration notifier sends the notification it detaches"
|
||||
[[ ! -e $test_tmp/notify-answered ]] ||
|
||||
fail "migration notifier waited for the toast to be answered before exiting"
|
||||
rm -f "$test_tmp/notify-hold"
|
||||
pass "migration notifier exits while the toast is still unanswered"
|
||||
|
||||
Reference in New Issue
Block a user