Skip to content

Incomplete fix for RemoteProtocolError - parallel requests cause unhandled NetworkError exceptions #89

@NickBorgers

Description

@NickBorgers

Summary

The fix in PR #88 for issue SpanPanel/span#156 is incomplete. While RemoteProtocolError is now handled correctly, parallel API requests in get_all_data() can still fail with unhandled exceptions when one request destroys the shared client while others are in-flight.

Related

Problem

When get_all_data() runs parallel requests via asyncio.gather():

  1. get_status() uses requires_auth=False → gets its own client (OK)
  2. get_panel_state(), get_circuits(), get_storage_soe() use requires_auth=Trueshare self._client

When a stale connection causes RemoteProtocolError:

  1. One task catches it and destroys self._client via __aexit__
  2. Other parallel tasks still have in-flight requests using the same underlying httpx client
  3. Those requests fail with ReadError, WriteError, or CloseError (all subclasses of NetworkError)
  4. These exceptions are not caught by the retry loop
  5. They bubble up and get wrapped as SpanPanelAPIError("Unexpected error: {e}")
  6. Since these exceptions often have empty messages, logs show: "Unexpected error: "

Evidence

Home Assistant logs show two types of errors:

ERROR: Unexpected error: Server disconnected without sending a response.  ← RemoteProtocolError (handled)
ERROR: Unexpected error:                                                   ← ReadError/WriteError (unhandled, empty message)

The empty error messages are caused by httpx exceptions with no message:

>>> str(httpx.ReadError(''))
''
>>> str(httpx.WriteError(''))
''

Exception Hierarchy

httpx.TransportError
├── httpx.NetworkError
│   ├── httpx.ReadError      ← NOT HANDLED
│   ├── httpx.WriteError     ← NOT HANDLED
│   └── httpx.CloseError     ← NOT HANDLED
└── httpx.ProtocolError
    └── httpx.RemoteProtocolError  ← HANDLED in PR #88

Suggested Fix

  1. Catch httpx.TransportError (or at minimum httpx.NetworkError) instead of just RemoteProtocolError:
except (httpx.RemoteProtocolError, httpx.NetworkError):
    # Server closed connection or network error - connection pool likely corrupted
    # Destroy client to force fresh connection pool on retry
    ...
  1. Consider adding locking around client destruction/recreation to prevent race conditions, or

  2. Use separate clients for parallel requests instead of sharing self._client

Environment

  • span-panel-api: 1.1.14
  • span (integration): 1.30b1 (manifest shows 1.3.0)
  • Home Assistant: 2024.x
  • Multiple SPAN panels (which may increase likelihood of hitting this race condition)

This issue was written by Claude (claude-opus-4-5-20250121) via Claude Code, based on analysis of the installed code and error logs.

🤖 Generated with Claude Code

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