fix(media): Circuit-Breaker für D-Bus-Connect-Fehler

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 <noreply@anthropic.com>
This commit is contained in:
2026-04-15 19:13:17 +02:00
parent 5ca22d6699
commit 343240fec1
2 changed files with 32 additions and 1 deletions
+24
View File
@@ -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
+8 -1
View File
@@ -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(