Skip to content

Fix HA event-loop stall: add timeouts to login flow, defer blocking SSL context creation - #113

Open
MathieuTz wants to merge 1 commit into
simbaja:masterfrom
MathieuTz:fix/setup-timeout-and-blocking-ssl
Open

Fix HA event-loop stall: add timeouts to login flow, defer blocking SSL context creation#113
MathieuTz wants to merge 1 commit into
simbaja:masterfrom
MathieuTz:fix/setup-timeout-and-blocking-ssl

Conversation

@MathieuTz

Copy link
Copy Markdown

Summary

ge_home was reliably deadlocking Home Assistant's entire asyncio event loop for me — not just the integration, the whole process stopped making progress and needed docker restarting, roughly 15–25 times a day, concentrated between 02:00 and 05:00 local time, right after HA's startup custom-integration scan.

I dug into this over a few days with py-spy stack dumps and a controlled elimination test (disabling only ge_home: 18 freezes in 18 hours → zero over the next 13h49m, spanning the whole freeze window). That confirmed ge_home as the cause but not the exact line. Digging into the code turned up a concrete, fixable defect: every HTTP call in the OAuth/MFA login flow has no per-request timeout, and this flow runs synchronously inside async_setup_entry.

Root cause

gehomesdk/clients/async_login_flows.py makes up to MAX_REDIRECTS (10) chained session.get/session.post calls during login (page fetch, credentials POST, redirect-following, MFA/terms pages, code exchange), and websocket_client.py's _async_get_wss_credentials makes one more — none of them pass a timeout=. They fall back to whatever the caller's ClientSession was built with. For ha_gehome, that's Home Assistant's shared session (async_get_clientsession(hass)), which defaults to a 300-second total timeout. Since this whole flow is awaited synchronously as part of GeHomeUpdateCoordinator.async_setup()async_setup_entry, a single slow/hung response on GE's login pages can stall HA's own integration-setup task for minutes — directly on the startup critical path, which lines up with what I was seeing (freeze onset right at/after ge_home's setup, lasting several minutes).

Separately, GeWebsocketClient.__init__ calls ssl.create_default_context() synchronously — a disk read that Home Assistant's own blocking-call detector already flags when it runs on the event loop thread (#297, still reproducible on current main).

I want to be upfront that I can't prove with 100% certainty this is the only thing going on — I only have OS-thread-level py-spy dumps, not per-asyncio.Task state, so I can't point at the one specific stuck await. But this is a real, reproducible defect on exactly the code path the evidence points to, and it's correct hardening either way.

What this PR does

  1. async_login_flows.py — adds an explicit LOGIN_REQUEST_TIMEOUT = ClientTimeout(total=15) and passes it to all 8 session.get/session.post calls in the login/MFA/token flow, instead of inheriting the caller's session-wide default.
  2. websocket_client.py:
    • Adds the same treatment (WSS_CREDENTIALS_TIMEOUT, 15s) to the WSS-credentials fetch.
    • Defers ssl.create_default_context() out of __init__ into a new _async_ensure_ssl_context(), built lazily via loop.run_in_executor(...) on first connect instead of synchronously on construction. Caller-supplied ssl_context is unaffected (still used as-is, never rebuilt).

Both timeouts (15s) are generous for what should be fast login-page/JSON-API round trips, while still being far below the 300s a caller's session might otherwise allow.

Testing

  • pytest (existing suite, 12 tests) passes unmodified.
  • Verified in a fresh venv that the lazy SSL context path works correctly both with and without a caller-supplied ssl_context.
  • Live-validated against my real GE account and oven by patching the installed gehomesdk 2026.5.4 in my Home Assistant container and re-enabling ge_home: it connected cleanly (no import/setup errors, live-updating oven sensor data confirmed by watching a temperature reading change between polls, not just restored/stale state), and ran freeze-free for 22+ hours, spanning the entire 02:00–05:00 window that had reliably produced 15–25 freezes/day beforehand — with zero docker restarts and zero even needing a websocket reconnect in that window.

Happy to share the raw py-spy dumps or the watchdog/log evidence if useful, and happy to adjust the timeout values or split this into two smaller PRs if you'd prefer the login-timeout and blocking-SSL-context fixes reviewed separately.

Thanks for maintaining this — appliance cloud APIs are a pain to work with and this has otherwise worked well for me.

… default SSL context off the event loop thread

Every request in the OAuth/MFA login flow (async_login_flows.py) and the
WSS-credentials fetch relied on whatever timeout the caller's ClientSession
happened to be built with. For callers using Home Assistant's shared
session that's a 300s *total* timeout, and this flow can chain up to
MAX_REDIRECTS requests -- so a single slow/hung response on GE's login
pages can stall a caller for minutes, on a path that (for ha_gehome) runs
synchronously inside async_setup_entry.

Also stop calling ssl.create_default_context() synchronously in
GeWebsocketClient.__init__ -- it reads the system trust store from disk
and is a known Home Assistant blocking-call hazard when it runs on the
loop thread during integration setup. It is now built lazily in an
executor on first connect.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant