Description
On Linux with a systemd user service, a crash loop can silently accumulate dozens of stuck processes until the OOM killer intervenes.
Root cause (two cooperating bugs)
1. handle_exception spawns a new subprocess on every unhandled exception (app_manager.py):
if self.status != Status.KILL:
path = executable_info.get_local_executable("discord_fm", "main.py")
subprocess.Popen(path + ["--ignore-open"])
The --ignore-open flag bypasses the already-running check, so the new instance starts unconditionally.
2. sys.exit() does not terminate the process when called from within pystray's GTK context. close() calls sys.exit(), but when the exception originates inside the GTK main loop, the process stays alive. Old instances accumulate in memory.
Trigger
xfce4-notifyd (the notification daemon) timing out on D-Bus init, which pystray hits every startup:
gi.repository.GLib.GError: g-io-error-quark: Error calling StartServiceByName
for org.freedesktop.Notifications: Timeout was reached (24)
This fires ~26 seconds after each start (2s startup + 24s D-Bus timeout), spawning a new instance every cycle.
Observed impact
93 simultaneous instances, 4.7 GB RAM, OOM kill after ~4.5 hours.
Suggested fixes
- Short-term: Remove the
subprocess.Popen self-respawn from handle_exception. Under systemd, Restart=on-failure handles this correctly without accumulating zombie instances. The self-respawn pattern is only safe if the old process is guaranteed to exit, which it isn't in the GTK context.
- Long-term: Replace
sys.exit() in close() with an explicit Gtk.main_quit() call (or equivalent) so the GTK loop actually stops, then let the normal process exit proceed.
- Separate issue: The D-Bus notification timeout should be caught and handled gracefully rather than being an unhandled exception — pystray's
_initialize() could fall back to a non-notification tray backend.
Environment
Debian 13, LightDM, XFCE, discord_fm v0.11.1-beta, systemd user service.
Description
On Linux with a systemd user service, a crash loop can silently accumulate dozens of stuck processes until the OOM killer intervenes.
Root cause (two cooperating bugs)
1.
handle_exceptionspawns a new subprocess on every unhandled exception (app_manager.py):The
--ignore-openflag bypasses the already-running check, so the new instance starts unconditionally.2.
sys.exit()does not terminate the process when called from within pystray's GTK context.close()callssys.exit(), but when the exception originates inside the GTK main loop, the process stays alive. Old instances accumulate in memory.Trigger
xfce4-notifyd(the notification daemon) timing out on D-Bus init, which pystray hits every startup:This fires ~26 seconds after each start (2s startup + 24s D-Bus timeout), spawning a new instance every cycle.
Observed impact
93 simultaneous instances, 4.7 GB RAM, OOM kill after ~4.5 hours.
Suggested fixes
subprocess.Popenself-respawn fromhandle_exception. Under systemd,Restart=on-failurehandles this correctly without accumulating zombie instances. The self-respawn pattern is only safe if the old process is guaranteed to exit, which it isn't in the GTK context.sys.exit()inclose()with an explicitGtk.main_quit()call (or equivalent) so the GTK loop actually stops, then let the normal process exit proceed._initialize()could fall back to a non-notification tray backend.Environment
Debian 13, LightDM, XFCE, discord_fm v0.11.1-beta, systemd user service.