[PR #12] [MERGED] fix: read-idle timeout on SSE so a stalled stream can't hang events() #19

Closed
opened 2026-10-26 22:40:43 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/comfy-python-sdk/pull/12
Author: @wei-hai
Created: 7/22/2026
Status: ✅ Merged
Merged: 7/22/2026
Merged by: @wei-hai

Base: main ← Head: fix/sse-idle-timeout


📝 Commits (2)

  • 157765b fix: read-idle timeout on SSE so a stalled stream can't hang events()
  • 8d217ec test: cover async SSE recovery + timeout=None opt-out

📊 Changes

3 files changed (+113 additions, -10 deletions)

View changed files

📝 src/comfy_low/transport.py (+19 -6)
📝 tests/conftest.py (+16 -4)
➕ tests/test_sse_idle.py (+78 -0)

📄 Description

Found running a real >10-minute job against prod: the SSE stream went silent at ~3 min (no frames, no keepalive, no close — a "zombie" connection), and job.events() hung — still blocked minutes after the job had already completed. poll/wait/run were unaffected (the job itself finished fine).

Root cause

ComfyLow.get_job_events (sync + async) opened the SSE stream with timeout=None, which disables httpx's read timeout — so iter_lines() blocks forever on a stalled-but-open connection. The high-level Job.events() reconnect/poll-fallback only runs when the stream ends (the generator returns or raises), which never happens for a zombie → events() hangs.

Fix

Give get_job_events a read-idle timeout (_SSE_IDLE_TIMEOUT, well above the server's keepalive interval) by default. A stalled stream now raises httpx.ReadTimeout, which Job.events() already catches and turns into a poll (terminal?) / reconnect. timeout=None still available to opt out.

Tests

Added a threaded stub stall mode (sends a couple frames, then holds the socket open and silent) and a regression test asserting events() recovers via the poll fallback in ~0.3s instead of waiting for close / hanging. Full suite: 74 passed; ruff/format/mypy/hygiene clean.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Comfy-Org/comfy-python-sdk/pull/12 **Author:** [@wei-hai](https://github.com/wei-hai) **Created:** 7/22/2026 **Status:** ✅ Merged **Merged:** 7/22/2026 **Merged by:** [@wei-hai](https://github.com/wei-hai) **Base:** `main` ← **Head:** `fix/sse-idle-timeout` --- ### 📝 Commits (2) - [`157765b`](https://github.com/Comfy-Org/comfy-python-sdk/commit/157765bf637d74718c67ef88c0b56beacf71fb66) fix: read-idle timeout on SSE so a stalled stream can't hang events() - [`8d217ec`](https://github.com/Comfy-Org/comfy-python-sdk/commit/8d217ec82cfcd26c99d3b89effb0b3dd5b14f5b1) test: cover async SSE recovery + timeout=None opt-out ### 📊 Changes **3 files changed** (+113 additions, -10 deletions) <details> <summary>View changed files</summary> 📝 `src/comfy_low/transport.py` (+19 -6) 📝 `tests/conftest.py` (+16 -4) ➕ `tests/test_sse_idle.py` (+78 -0) </details> ### 📄 Description Found running a real >10-minute job against prod: the SSE stream went silent at ~3 min (no frames, no keepalive, no close — a "zombie" connection), and `job.events()` **hung** — still blocked minutes after the job had already completed. `poll`/`wait`/`run` were unaffected (the job itself finished fine). ## Root cause `ComfyLow.get_job_events` (sync + async) opened the SSE stream with `timeout=None`, which **disables** httpx's read timeout — so `iter_lines()` blocks forever on a stalled-but-open connection. The high-level `Job.events()` reconnect/poll-fallback only runs when the stream **ends** (the generator returns or raises), which never happens for a zombie → `events()` hangs. ## Fix Give `get_job_events` a **read-idle timeout** (`_SSE_IDLE_TIMEOUT`, well above the server's keepalive interval) by default. A stalled stream now raises `httpx.ReadTimeout`, which `Job.events()` **already catches** and turns into a poll (terminal?) / reconnect. `timeout=None` still available to opt out. ## Tests Added a threaded stub `stall` mode (sends a couple frames, then holds the socket open and silent) and a regression test asserting `events()` recovers via the poll fallback in ~0.3s instead of waiting for close / hanging. Full suite: 74 passed; ruff/format/mypy/hygiene clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-26 22:40:43 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Comfy-Org/comfy-python-sdk#19
No description provided.