From 343240fec180d74d11bd5e4b4af4dbd122e45e83 Mon Sep 17 00:00:00 2001 From: Vitali Graf Date: Wed, 15 Apr 2026 19:13:17 +0200 Subject: [PATCH] =?UTF-8?q?fix(media):=20Circuit-Breaker=20f=C3=BCr=20D-Bu?= =?UTF-8?q?s-Connect-Fehler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nach dem ersten fehlgeschlagenen Bus-Connect wird der Controller dauerhaft deaktiviert, statt bei jedem Hotkey-Druck einen neuen Connect-Versuch zu starten. Co-Authored-By: Claude Opus 4.6 --- tests/test_media_mpris.py | 24 ++++++++++++++++++++++++ whisper_local/media/_mpris.py | 9 ++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/test_media_mpris.py b/tests/test_media_mpris.py index e3dd14d..c8f1f4c 100644 --- a/tests/test_media_mpris.py +++ b/tests/test_media_mpris.py @@ -173,3 +173,27 @@ async def test_pause_is_noop_when_bus_unreachable(monkeypatch, caplog): assert controller._paused == [] assert any("D-Bus" in r.message or "bus" in r.message.lower() for r in caplog.records) + + +@pytest.mark.asyncio +async def test_pause_skips_reconnect_after_bus_failure(monkeypatch): + """Bus-Connect-Fehler darf nicht bei jedem Aufruf erneut versucht werden.""" + from whisper_local.media._mpris import MprisController + + connect_calls = 0 + + async def failing_connect(self): + nonlocal connect_calls + connect_calls += 1 + raise RuntimeError("no session bus") + + monkeypatch.setattr( + "dbus_next.aio.MessageBus.connect", failing_connect, raising=False + ) + controller = MprisController() + + await controller.pause() + await controller.pause() + await controller.pause() + + assert connect_calls == 1 diff --git a/whisper_local/media/_mpris.py b/whisper_local/media/_mpris.py index 502af07..232a8ed 100644 --- a/whisper_local/media/_mpris.py +++ b/whisper_local/media/_mpris.py @@ -18,8 +18,11 @@ class MprisController: def __init__(self) -> None: self._paused: list[str] = [] self._bus: Any = None + self._bus_broken: bool = False async def _ensure_bus(self) -> Any: + if self._bus_broken: + raise RuntimeError("D-Bus session bus is unavailable") if self._bus is None: from dbus_next.aio import MessageBus self._bus = await MessageBus().connect() @@ -65,7 +68,11 @@ class MprisController: try: names = await self._list_player_names() except Exception as e: - logger.warning("D-Bus nicht erreichbar, überspringe Media-Pause: %s", e) + if not self._bus_broken: + logger.warning( + "D-Bus nicht erreichbar, Media-Pause dauerhaft deaktiviert: %s", e + ) + self._bus_broken = True self._paused = [] return results = await asyncio.gather(