mirror of
https://github.com/Comfy-Org/comfy-python-sdk.git
synced 2026-11-04 23:37:47 +00:00
[PR #214] fix(jobs): back off the events() reconnect pause on streams that keep dropping #214
Labels
No labels
audit:ai-readiness
audit:community
audit:github
audit:health-scoring
audit:policy
audit:release
audit:supply-chain
effort:medium
effort:small
effort:small
effort:trivial
priority:nice-to-have
priority:nice-to-have
priority:recommended
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/comfy-python-sdk#214
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/comfy-python-sdk/pull/214
Author: @mattmillerai
Created: 10/9/2026
Status: 🔄 Open
Base:
main← Head:matt/be-19980-events-reconnect-backoff📝 Commits (2)
82a7e87fix(jobs): back off the events() reconnect pause on streams that keep dropping64fa27dfix(jobs): reset the events() reconnect backoff on connection lifetime, not delivered frames📊 Changes
5 files changed (+182 additions, -5 deletions)
View changed files
📝
CHANGELOG.md(+10 -0)📝
src/comfy_sdk/jobs.py(+27 -2)📝
tests/conftest.py(+16 -1)📝
tests/test_events.py(+127 -0)📝
uv.lock(+2 -2)📄 Description
ELI-5
When the live event stream for a job closes without saying the job is finished, the SDK checks the job's status and then reconnects. Until now it always waited 0.1 s before reconnecting. If a server or proxy keeps closing the stream straight away, that turns into about 10 reconnects and 10 status checks every second for as long as the job runs. That happens whether it closes with no data or right after the status snapshot every connection starts with. Now each short-lived reconnect waits twice as long as the one before, up to 5 s. Once a connection stays open for at least 5 s, the wait goes back to 0.1 s.
What changed
src/comfy_sdk/jobs.py:Job.events()andAsyncJob.events()now create a single_core.backoff_schedule(_RECONNECT_PAUSE, 2.0, _MAX_RECONNECT_PAUSE)per call, so the pauses go 0.1, 0.2, 0.4, 0.8, 1.6, 3.2, 5.0, 5.0, … The new constant_MAX_RECONNECT_PAUSE = 5.0matches the default cap inbackoff_schedule. The code comment explains the cap:events()has no deadline of its own, so a dropped healthy stream still has to notice a terminal status within seconds._HEALTHY_STREAM_SECONDS(= the 5 s cap), measured from connect until the stream ended. Receiving frames does not count as a sign of health: per thegetJobEventscontract every connection opens with astatus/progresssnapshot, so resetting after any frame (this PR's first revision) left the snapshot-then-close storm unfixed. A connection that lasted at least the cap cannot reconnect faster than a capped pause would allow.httpx.HTTPError/httpx.StreamErrormid-stream. TheApiErrorbranch is untouched, so 501 still ends iteration silently and other statuses, 429 included, still raise right away. The first pause is still 0.1 s.tests/conftest.py: a new stub mode,sse_mode = "drop". Every connection gets 200text/event-streamand closes without a terminal frame, by default with no frames at all.sse_drop_snapshot = Truesends the on-connectstatus: running+progresssnapshot before closing.sse_hold_on_connect = N/sse_hold_secondshold connection N open first, usingthreading.Event().waitbecause tests faketime.sleep.tests/test_events.py: five new tests:[0.1, 0.2, 0.4, 0.8, 1.6, 3.2, 5.0, 5.0]; the sync one also checks that connects and polls are each at most pauses + 10.1, 0.2, 0.4, 0.1, 0.2, 0.4CHANGELOG.md: new entry under Unreleased → Fixed.uv.lock: resynced topyproject.toml. Main's mypy specifier bump (#203) did not update the lock, souv runrewrote it. This PR has no dependency changes of its own.Judgment calls
asyncio.sleepon the module, becausejobs.pyimportsasyncioinside the function. The fake records every call and still yields to the loop withreal_sleep(0). The exact-list assertion would fail if anything else in that test path calledasyncio.sleep.wait()/result()polling), and the 5 s cap already bounds a client at one connect plus one poll every 5 s.Provenance
ruff check .,ruff format --check .andmypy srcall cleanbuild-check,codegen-drift(no generated or vendored files touched)Residual
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.