Say out loud that the portal backend is crash-looping
xdg-desktop-portal-hyprland 1.4.1 segfaults in its own shutdown path: exit() runs the CPortalManager destructor, which tears down wlr-foreign-toplevel handles and xdg-output proxies after the display has gone, and dies in wl_map_insert_at. systemd restarts it, so the desktop keeps working and the only symptom is screen sharing dropping mid-session and the occasional document-portal casualty. Sixty crashes in one day left no trace anywhere a person would look. It took reading coredumpctl to find, and nobody reads coredumpctl on a desktop that appears to be working -- which is exactly the shape of problem the health page exists for. On this machine the new check reported twenty-eight in the last day the moment it was wired up. Reported rather than repaired, and it says so. There is nothing to restart that systemd has not already restarted, and no newer build in the Hyprland COPR to install: the fix is upstream. A button that did nothing would be worse than a sentence that explains. Below five in a day stays green, because an upstream bug that only bites on shutdown will occasionally bite and a health page that is never green teaches people to ignore it.
This commit is contained in:
@@ -117,7 +117,7 @@ class RepairResult:
|
||||
|
||||
CHECK_ORDER = (
|
||||
"desktop.hyprland", "desktop.quickshell", "desktop.notifications", "desktop.portals",
|
||||
"desktop.document-portal",
|
||||
"desktop.document-portal", "desktop.portal-stability",
|
||||
"desktop.hyprpaper", "desktop.hypridle", "desktop.hyprlock", "desktop.vicinae", "input.pipewire",
|
||||
"input.clipboard", "input.wallpaper", "input.capture", "input.ocr", "input.brightness",
|
||||
"integration.nextcloud", "integration.rustdesk", "integration.kdeconnect", "integration.bluebubbles",
|
||||
@@ -182,6 +182,7 @@ CHECK_TITLES = {
|
||||
"desktop.notifications": "Notifications",
|
||||
"desktop.portals": "Desktop portals",
|
||||
"desktop.document-portal": "Document portal",
|
||||
"desktop.portal-stability": "Portal stability",
|
||||
"desktop.hyprpaper": "Hyprpaper",
|
||||
"desktop.hypridle": "Hypridle",
|
||||
"desktop.hyprlock": "Lock screen",
|
||||
@@ -371,6 +372,61 @@ def check_portals(config: DoctorConfig) -> Check:
|
||||
return Check("desktop.portals", "desktop-foundation", "Desktop portals", "warning", detail)
|
||||
|
||||
|
||||
def check_portal_stability(config: DoctorConfig) -> Check:
|
||||
"""Whether the portal backend is crash-looping.
|
||||
|
||||
xdg-desktop-portal-hyprland segfaults in its own shutdown path on 1.4.1:
|
||||
exit() runs the CPortalManager destructor, which tears down
|
||||
wlr-foreign-toplevel handles and xdg-output proxies after the display has
|
||||
gone, and dies in wl_map_insert_at. systemd restarts it, so the desktop
|
||||
keeps working and the only symptom is screen sharing dying mid-call and
|
||||
the occasional document-portal casualty.
|
||||
|
||||
That combination -- harmful but self-repairing -- is exactly what a health
|
||||
page is for. Sixty crashes in a day left no trace anywhere a person would
|
||||
look; it took reading coredumpctl to find out, and nobody reads coredumpctl
|
||||
on a desktop that appears to be working.
|
||||
|
||||
Reported rather than repaired. There is nothing to restart that systemd has
|
||||
not already restarted, and no newer build in the Hyprland COPR to install:
|
||||
the fix is upstream. Saying so plainly is more use than a button that does
|
||||
nothing.
|
||||
"""
|
||||
result = run_command(
|
||||
("coredumpctl", "list", "--no-pager", "--since", "24 hours ago",
|
||||
"/usr/libexec/xdg-desktop-portal-hyprland"),
|
||||
config,
|
||||
)
|
||||
# No coredumps at all makes coredumpctl exit non-zero, which is the good
|
||||
# case and must not read as a broken probe.
|
||||
if result.state == "missing":
|
||||
return Check("desktop.portal-stability", "desktop-foundation", "Portal stability",
|
||||
"ok", "Crash reporting is not installed, so this cannot be checked.")
|
||||
if result.state not in {"ok", "failed"}:
|
||||
return Check("desktop.portal-stability", "desktop-foundation", "Portal stability",
|
||||
"ok", "Could not read the crash log.")
|
||||
|
||||
crashes = sum(
|
||||
1 for line in result.stdout.splitlines()
|
||||
if "xdg-desktop-portal-hyprland" in line
|
||||
)
|
||||
if crashes == 0:
|
||||
return Check("desktop.portal-stability", "desktop-foundation", "Portal stability",
|
||||
"ok", "The portal backend has not crashed today.")
|
||||
|
||||
# One or two in a day is the ordinary cost of an upstream bug that only
|
||||
# bites on shutdown. A dozen means something is driving it, and screen
|
||||
# sharing will be unreliable.
|
||||
detail = (
|
||||
f"The Hyprland portal backend crashed {crashes} time"
|
||||
f"{'s' if crashes != 1 else ''} in the last day. It restarts itself, so "
|
||||
"the desktop keeps working, but screen sharing can drop mid-session. "
|
||||
"This is an upstream fault in its shutdown path, not a setting."
|
||||
)
|
||||
return Check("desktop.portal-stability", "desktop-foundation", "Portal stability",
|
||||
"warning" if crashes >= 5 else "ok", detail)
|
||||
|
||||
|
||||
def check_document_portal(config: DoctorConfig) -> Check:
|
||||
"""Whether the flatpak document portal is actually mounted.
|
||||
|
||||
@@ -719,7 +775,7 @@ def unavailable_versions() -> list[dict[str, str]]:
|
||||
|
||||
def collect_checks(config: DoctorConfig) -> list[Check]:
|
||||
probes: dict[str, Callable[[], Check]] = {
|
||||
"desktop.hyprland": lambda: check_hyprland(config), "desktop.quickshell": lambda: check_quickshell(config), "desktop.notifications": lambda: check_notifications(config), "desktop.portals": lambda: check_portals(config), "desktop.document-portal": lambda: check_document_portal(config),
|
||||
"desktop.hyprland": lambda: check_hyprland(config), "desktop.quickshell": lambda: check_quickshell(config), "desktop.notifications": lambda: check_notifications(config), "desktop.portals": lambda: check_portals(config), "desktop.portal-stability": lambda: check_portal_stability(config), "desktop.document-portal": lambda: check_document_portal(config),
|
||||
"desktop.hyprpaper": lambda: service_check("desktop.hyprpaper", "Hyprpaper", "hyprpaper", config, Action("repair", "Restart Hyprpaper")), "desktop.hypridle": lambda: service_check("desktop.hypridle", "Hypridle", "hypridle", config, Action("repair", "Restart Hypridle")), "desktop.hyprlock": lambda: check_hyprlock(config), "desktop.vicinae": lambda: service_check("desktop.vicinae", "Vicinae", "vicinae", config, Action("repair", "Restart Vicinae")), "input.pipewire": lambda: service_check("input.pipewire", "PipeWire", "pipewire", config),
|
||||
"input.clipboard": lambda: simple_ipc_check("input.clipboard", "Clipboard", "clipboard", config), "input.wallpaper": lambda: simple_ipc_check("input.wallpaper", "Wallpaper", "wallpaper", config), "input.capture": lambda: simple_ipc_check("input.capture", "Capture", "capture", config), "input.ocr": lambda: executable_check("input.ocr", "OCR", "tesseract", config), "input.brightness": lambda: check_brightness(config),
|
||||
"integration.nextcloud": lambda: check_nextcloud(config), "integration.rustdesk": lambda: check_rustdesk(config), "integration.kdeconnect": lambda: check_kdeconnect(config), "integration.bluebubbles": lambda: check_bluebubbles(config), "integration.home-assistant": lambda: check_home_assistant(config), "integration.calendar": lambda: check_calendar(config),
|
||||
|
||||
Reference in New Issue
Block a user