fix(k1): stabilize repeated acquisition and live viewer recovery
This commit is contained in:
+40
-16
@@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
@@ -20,6 +22,7 @@ from k1link.viewer.rerun_bridge import (
|
||||
RerunSceneSettings,
|
||||
_live_time_panel,
|
||||
_point_colors,
|
||||
_select_available_grpc_port,
|
||||
)
|
||||
|
||||
|
||||
@@ -62,6 +65,33 @@ class DisconnectFailureRecording(FakeRecording):
|
||||
raise RuntimeError("synthetic disconnect failure")
|
||||
|
||||
|
||||
def test_rerun_port_selection_skips_a_recording_still_held_by_a_viewer(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as occupied:
|
||||
occupied.bind(("0.0.0.0", 0))
|
||||
occupied.listen()
|
||||
preferred_port = int(occupied.getsockname()[1])
|
||||
|
||||
selected_port = _select_available_grpc_port(preferred_port, search_span=8)
|
||||
recording = FakeRecording()
|
||||
with caplog.at_level(
|
||||
logging.INFO,
|
||||
logger="k1link.device_plugins.xgrids_k1.viewer_receiver",
|
||||
):
|
||||
bridge = RerunBridge(
|
||||
grpc_port=preferred_port,
|
||||
recording_factory=lambda _: recording, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
assert selected_port != preferred_port
|
||||
assert preferred_port < selected_port < preferred_port + 8
|
||||
assert caplog.records[-1].event_code == "rerun_grpc_port_rotated"
|
||||
assert caplog.records[-1].preferred_port == preferred_port
|
||||
assert caplog.records[-1].selected_port == selected_port
|
||||
bridge.close()
|
||||
|
||||
|
||||
def _message(
|
||||
topic: str,
|
||||
payload: bytes,
|
||||
@@ -266,7 +296,7 @@ def test_palettes_are_deterministic_and_custom_color_is_exact() -> None:
|
||||
assert custom_over_rgb.tolist() == [[16, 32, 48], [16, 32, 48]]
|
||||
|
||||
|
||||
def test_runtime_reuses_one_bridge_across_sequential_sessions(tmp_path: Path) -> None:
|
||||
def test_runtime_owns_fresh_bridge_for_each_sequential_session(tmp_path: Path) -> None:
|
||||
capture = tmp_path / "mqtt.raw.k1mqtt"
|
||||
point_topic = "RealtimePointcloud"
|
||||
pose_topic = "RealtimePath"
|
||||
@@ -300,10 +330,12 @@ def test_runtime_reuses_one_bridge_across_sequential_sessions(tmp_path: Path) ->
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
recording = FakeRecording()
|
||||
recordings: list[FakeRecording] = []
|
||||
created: list[RerunBridge] = []
|
||||
|
||||
def bridge_factory(**kwargs: object) -> RerunBridge:
|
||||
recording = FakeRecording()
|
||||
recordings.append(recording)
|
||||
bridge = RerunBridge(
|
||||
recording_factory=lambda _: recording, # type: ignore[arg-type]
|
||||
**kwargs, # type: ignore[arg-type]
|
||||
@@ -329,8 +361,8 @@ def test_runtime_reuses_one_bridge_across_sequential_sessions(tmp_path: Path) ->
|
||||
assert snapshot["metrics"]["mqtt_to_publish_ms"] is None
|
||||
runtime.stop(wait_seconds=5.0)
|
||||
assert runtime.snapshot()["phase"] == "idle"
|
||||
assert runtime.snapshot()["rerun_grpc_url"] == "rerun+http://127.0.0.1:9876/proxy"
|
||||
assert recording.disconnected is False
|
||||
assert runtime.snapshot()["rerun_grpc_url"] is None
|
||||
assert recordings[0].disconnected is True
|
||||
|
||||
runtime.start_replay(capture, speed=0.0)
|
||||
deadline = time.monotonic() + 5.0
|
||||
@@ -340,13 +372,12 @@ def test_runtime_reuses_one_bridge_across_sequential_sessions(tmp_path: Path) ->
|
||||
time.sleep(0.05)
|
||||
runtime.stop(wait_seconds=5.0)
|
||||
|
||||
assert len(created) == 1
|
||||
assert len(created) == 2
|
||||
assert runtime.snapshot()["metrics"]["pcl_frames"] == 1
|
||||
assert runtime.snapshot()["rerun_grpc_url"] == "rerun+http://127.0.0.1:9876/proxy"
|
||||
assert recording.disconnected is False
|
||||
assert runtime.snapshot()["rerun_grpc_url"] is None
|
||||
assert all(recording.disconnected for recording in recordings)
|
||||
runtime.close()
|
||||
assert runtime.snapshot()["rerun_grpc_url"] is None
|
||||
assert recording.disconnected is True
|
||||
|
||||
|
||||
def test_runtime_reports_bridge_close_failure_instead_of_false_idle(tmp_path: Path) -> None:
|
||||
@@ -385,18 +416,11 @@ def test_runtime_reports_bridge_close_failure_instead_of_false_idle(tmp_path: Pa
|
||||
time.sleep(0.01)
|
||||
snapshot = runtime.snapshot()
|
||||
|
||||
assert snapshot["phase"] == "idle"
|
||||
assert snapshot["rerun_grpc_url"] == "rerun+http://127.0.0.1:9876/proxy"
|
||||
assert recording.disconnected is False
|
||||
|
||||
with pytest.raises(RuntimeError, match="synthetic disconnect failure"):
|
||||
runtime.close()
|
||||
|
||||
snapshot = runtime.snapshot()
|
||||
assert snapshot["phase"] == "error"
|
||||
assert "synthetic disconnect failure" in snapshot["message"]
|
||||
assert snapshot["rerun_grpc_url"] is None
|
||||
assert recording.disconnected is True
|
||||
runtime.close()
|
||||
|
||||
|
||||
def test_close_during_blocked_factory_closes_the_late_bridge(tmp_path: Path) -> None:
|
||||
|
||||
Reference in New Issue
Block a user