diff --git a/src/rdc/services/session_service.py b/src/rdc/services/session_service.py index 2989026..e233493 100644 --- a/src/rdc/services/session_service.py +++ b/src/rdc/services/session_service.py @@ -192,6 +192,7 @@ def open_session( max_attempts = 1 if remote_url is not None else 3 host = "127.0.0.1" detail = "unknown error" + started = False for _attempt in range(max_attempts): port = pick_port() token = secrets.token_hex(16) @@ -199,6 +200,7 @@ def open_session( ok, detail = wait_for_ping(host, port, token, timeout_s=resolved_timeout, proc=proc) if ok: + started = True break proc.terminate() try: @@ -207,9 +209,14 @@ def open_session( proc.kill() proc.wait() stderr = _read_daemon_stderr(proc) + timed_out = detail.startswith("timeout") if stderr: - detail = stderr - else: + # Keep the real reason (timeout, exit code) first; the log tail is context. + detail = f"{detail}; daemon log: {stderr}" + if timed_out: + detail += "; large captures may need a longer --timeout (or RDC_OPEN_TIMEOUT)" + break + if not started: return False, f"error: daemon failed to start ({detail}) -- hint: run 'rdc doctor'" _discard_daemon_stderr(proc) diff --git a/tests/unit/test_session_service.py b/tests/unit/test_session_service.py index 9f47478..a003bf7 100644 --- a/tests/unit/test_session_service.py +++ b/tests/unit/test_session_service.py @@ -277,6 +277,33 @@ def test_open_session_all_retries_fail(monkeypatch: pytest.MonkeyPatch, tmp_path assert "rdc doctor" in msg +def test_open_session_timeout_reports_timeout_first_and_does_not_retry( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + """A startup timeout leads the message, keeps the log as context, and is not retried.""" + monkeypatch.delenv("RDC_SESSION", raising=False) + monkeypatch.setattr(session_service, "load_session", lambda: None) + monkeypatch.setattr(session_service, "_renderdoc_available", lambda: False) + + log = tmp_path / "daemon.stderr" + log.write_text("GPU match fallback: choosing NVIDIA\n") + mock_proc = MagicMock() + mock_proc.pid = 999 + mock_proc._rdc_stderr_path = str(log) + starts: list[int] = [] + monkeypatch.setattr( + session_service, "start_daemon", lambda *a, **kw: starts.append(1) or mock_proc + ) + monkeypatch.setattr(session_service, "wait_for_ping", lambda *a, **kw: (False, "timeout (15s)")) + + ok, msg = session_service.open_session(Path("test.rdc")) + assert ok is False + assert len(starts) == 1 + assert msg.startswith("error: daemon failed to start (timeout (15s);") + assert "--timeout" in msg + assert msg.index("timeout (15s)") < msg.index("GPU match fallback") + + def test_close_session_fallback_kill_on_shutdown_error( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: