Skip to content

Fix retry_if_exception_cause_type infinite loop on cyclic exception cause chains#659

Open
earfman wants to merge 2 commits into
jd:mainfrom
earfman:fix-retry-cause-cycle-hang
Open

Fix retry_if_exception_cause_type infinite loop on cyclic exception cause chains#659
earfman wants to merge 2 commits into
jd:mainfrom
earfman:fix-retry-cause-cycle-hang

Conversation

@earfman

@earfman earfman commented Jul 19, 2026

Copy link
Copy Markdown

Fixes #658.

Problem

retry_if_exception_cause_type.__call__ walks the __cause__ chain until it reaches None. A cyclic chain — the simplest being ordinary raise e from e — never reaches None, so the predicate spins forever at 100% CPU. Because the spin is inside the retry predicate, stop_after_attempt / stop_after_delay never get a chance to end the retry: stop conditions are only consulted after the predicate returns. Two-node cycles (a.__cause__ = b; b.__cause__ = a) hang the same way. The stdlib traceback module guards against exactly these cause/context cycles when walking exception chains.

Fix

Track the ids of exceptions already visited and stop the walk when the chain repeats:

seen: set[int] = set()
exc = retry_state.outcome.exception()
while exc is not None and id(exc) not in seen:
    if isinstance(exc.__cause__, self.exception_cause_types):
        return True
    seen.add(id(exc))
    exc = exc.__cause__

Behavior on acyclic chains is unchanged; a cyclic chain now finishes scanning each distinct exception once and returns the correct result.

Tests

Adds test_retry_if_exception_cause_type_with_cause_cycle covering both cycle shapes (raise e from e and a two-node cycle). The new test hangs indefinitely on current main without the fix (only bounded by an external timeout) and passes in milliseconds with it. Full suite: 172 passed, 1 skipped, 12 subtests.

Notes

As flagged in #658, the proposed retry_unless_exception_cause_type feature (#625 / #626) walks the cause chain the same way — if this fix lands, that feature can reuse the cycle-safe walk.

Prepared with AI assistance and independently verified before submission (fresh clone, hang reproduced on HEAD c650fb4, fix reviewed, full suite green, regression test confirmed non-vacuous).

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.

retry_if_exception_cause_type spins forever when the exception cause chain contains a cycle (e.g. raise e from e)

1 participant