Skip to content

Add retry logic for httpx RemoteProtocolError: Server disconnected without sending a response #156

@NickBorgers

Description

@NickBorgers

Problem

The SPAN Panel integration frequently fails with the error:

Server disconnected without sending a response.

This occurs even when the SPAN panels are fully reachable and responding normally to direct HTTP requests.

Evidence

Integration logs show repeated failures:

ERROR custom_components.span_panel.span_panel_api: Failed to get all panel data: Unexpected error: Server disconnected without sending a response.
WARNING custom_components.span_panel.span_panel: Panel update failed (Unexpected error: Server disconnected without sending a response.); marking offline

But the panels respond fine to direct requests:

$ ping -c 2 10.212.101.111
2 packets transmitted, 2 packets received, 0% packet loss

$ curl -sk -o /dev/null -w '%{http_code}' https://10.212.101.111/api/v1/panel
412  # Expected without auth token

Root Cause

This is a well-documented httpx connection pooling issue:

  1. httpx maintains a connection pool for HTTP keep-alive
  2. The SPAN panel closes idle connections after its keep-alive timeout expires
  3. httpx attempts to reuse a connection that was already closed server-side
  4. Result: RemoteProtocolError: Server disconnected without sending a response

This is not a network issue - it's a client-side connection pool race condition.

Impact

When this error occurs repeatedly:

  • The integration marks panels as offline despite them being reachable
  • Aggressive retries (15-second polling) accumulate failed requests
  • This can cascade to affect Home Assistant's event loop responsiveness
  • In severe cases, HA's web UI becomes unresponsive

Suggested Fix

Add retry logic specifically for RemoteProtocolError. Other Home Assistant integrations have implemented this:

Example approach:

from httpcore import RemoteProtocolError

async def fetch_with_retry(self, url, max_retries=1):
    for attempt in range(max_retries + 1):
        try:
            return await self._client.get(url)
        except RemoteProtocolError:
            if attempt == max_retries:
                raise
            # Connection was closed server-side, retry with fresh connection
            await self._client.aclose()
            self._client = self._create_client()

Alternatively, configure httpx to disable connection pooling or reduce keep-alive timeout:

limits = httpx.Limits(max_keepalive_connections=0)
client = httpx.AsyncClient(limits=limits)

Environment

  • Home Assistant version: 2025.12.4
  • SPAN Panel integration version: latest from HACS
  • SPAN Panel firmware: current
  • Two SPAN panels configured with 15-second scan interval

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions