Skip to content

Kill the local plugin watcher with the shell via pdeathsig - #11385

Open
chadmandoo wants to merge 1 commit into
omacom:quattrofrom
chadmandoo:plugin-watcher-pdeathsig
Open

Kill the local plugin watcher with the shell via pdeathsig#11385
chadmandoo wants to merge 1 commit into
omacom:quattrofrom
chadmandoo:plugin-watcher-pdeathsig

Conversation

@chadmandoo

@chadmandoo chadmandoo commented Sep 11, 2026

Copy link
Copy Markdown

Closes #11383.

PluginRegistry spawned the local-plugin watcher as a bare Process, so an exit that skips destructors left it running: Qt leaves through _exit() when the Wayland connection fails, raising no signal. The inotifywait was reparented to systemd --user and held an inotify instance for the rest of the session. A deliberate omarchy-restart-shell does stop it cleanly — I measured an unpatched shell across a restart and it left no orphan — so this is the crash path specifically.

Enough of them exhaust fs.inotify.max_user_instances (1024 per UID), after which every inotify_init1() in the session fails with EMFILE. The error then surfaces in whatever application next asks for a watch, with nothing pointing back at the shell. On my machine a lock-path crash loop relaunched the shell 4437 times and stranded 979 watchers, taking the user to 1025 of 1024 instances; Alacritty was the first thing to refuse to start, with "too many open files".

The fix

shell/plugins/clipboard/Clipboard.qml already solves this, and says so:

Reap watchers left behind by a previous shell instance, then start our own. The pdeathsig on the watchers makes the kernel kill them whenever the shell exits, however it exits, so no further lifecycle management.

This applies the same setpriv --pdeathsig TERM idiom to the plugin watcher, which was the only long-lived Process child under shell/ still missing it.

Testing

Verified through Quickshell's real spawn path (Quickshell 0.3.1, Omarchy 4.0.3) with a minimal config, killing the shell with SIGKILL so no destructor runs:

child after kill -9 of the shell
before survives — reparented to systemd --user
after dies with the shell

Also checked for regressions in the watcher itself: inotifywait under setpriv still reports create and close_write events normally, and the child was still alive after 12 seconds with the shell running, so PDEATHSIG isn't firing early on the spawning thread.

test/shell.d/plugins-test.sh gains an assertion mirroring the two in clipboard-test.sh. It fails on the unpatched tree and passes with the change.

Ran the whole test/shell.d suite: 237 test files, 3 with failures — config-test.sh, snapper-test.sh and unowned-system-paths-test.sh, all three asking for an omarchy-pkgs checkout I don't have. They fail identically on an unmodified tree.

Not addressed here

The crash loop that exposed this looks like Quickshell's, not Omarchy's — the shell dies in lock-surface creation when the session is locked and the compositor has no valid Wayland output (Could not create EGL surface (EGL error 0x3000), then The Wayland connection experienced a fatal error: Invalid argument), and that string isn't in this repo. The watcher should not outlive the shell regardless of what kills it.

One related observation for whoever picks that up: the relaunch limiter in bin/omarchy-launch-shell allows 5 relaunches per 60-second window and resets the window on expiry, so a loop slower than 5/minute runs indefinitely inside the limit. Mine crashed about every 13 seconds and never tripped it.

🤖 Generated with Claude Code

The plugin watcher ran as a bare Process, so an exit that skips destructors left
it behind. Qt leaves through _exit() when the Wayland connection fails, raising
no signal, and the inotifywait was then reparented to `systemd --user` where it
held an inotify instance for the rest of the session. A deliberate
omarchy-restart-shell does stop it cleanly; the crash path does not.

Enough of them exhaust fs.inotify.max_user_instances, 1024 per UID, after which
every inotify_init1() in the session fails with EMFILE. The error then surfaces
in whatever application next asks for a watch, with no hint of where it came
from. A lock-path crash loop relaunched the shell 4437 times here and stranded
979 watchers, taking the user to 1025 of 1024 instances; Alacritty was the first
thing to refuse to start.

The clipboard watchers already solve this with setpriv --pdeathsig TERM, so this
applies the established idiom to the plugin watcher and asserts it in
plugins-test.sh the same way clipboard-test.sh does.

Closes omacom#11383

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plugin watcher outlives a shell crash, leaking an inotifywait each time until inotify instances are exhausted

1 participant