fix(box): handle RPC failure in get_status/get_sessions gracefully

When the Box runtime disconnects, there is a race between the heartbeat
flipping _available=false and the frontend polling get_status(). If the
poll arrives first, client.get_status() throws a ConnectionClosedError
which propagated as a 500, causing the frontend to show a grey dot
(null status) instead of a red dot with error details.

Now get_status() catches RPC errors and returns available=false with
the exception message as connector_error. get_sessions() returns an
empty list when unavailable or on RPC failure.
This commit is contained in:
Junyan Qin
2026-04-19 14:21:33 +08:00
committed by WangCham
parent d47803db2c
commit 7e0a1974b6
2 changed files with 18 additions and 2 deletions

View File

@@ -219,7 +219,12 @@ class BoxService:
self._shutdown_task = loop.create_task(self.shutdown())
async def get_sessions(self) -> list[dict]:
return await self.client.get_sessions()
if not self._available:
return []
try:
return await self.client.get_sessions()
except Exception:
return []
def build_spec(self, spec_payload: dict, skip_host_mount_validation: bool = False) -> BoxSpec:
spec_payload = dict(spec_payload)
@@ -581,7 +586,17 @@ class BoxService:
'recent_error_count': len(self._recent_errors),
'connector_error': self._connector_error,
}
runtime_status = await self.client.get_status()
try:
runtime_status = await self.client.get_status()
except Exception as exc:
# RPC failed — the runtime likely just disconnected and the
# heartbeat hasn't flipped _available yet.
return {
'available': False,
'profile': self.profile.name,
'recent_error_count': len(self._recent_errors),
'connector_error': str(exc),
}
return {
**runtime_status,
'available': True,

View File

@@ -182,6 +182,7 @@ async def test_box_service_get_sessions_delegates_to_client():
client.get_sessions = AsyncMock(return_value=[{'session_id': 'test-session'}])
service = BoxService(make_app(Mock()), client=client)
service._available = True
sessions = await service.get_sessions()