You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
retry_if_exception_cause_type spins forever when the exception cause chain contains a cycle (e.g. raise e from e) #658
When the exception cause chain contains a cycle, retry_if_exception_cause_type never returns — the predicate spins at 100% CPU and the retry can neither proceed nor stop.
The scan in tenacity/retry.py walks __cause__ until it reaches None:
A cyclic chain never reaches None. Because the spin happens inside the retry predicate, stop_after_attempt / stop_after_delay never get the chance to end the retry — stop conditions are only consulted after the predicate returns. The docstring's premise (“until finding an exception in the chain that has no __cause__”) doesn't hold for cyclic chains.
The simplest cycle is ordinary, legal Python:
fromtenacityimportRetrying, retry_if_exception_cause_type, stop_after_attemptdefboom():
try:
raiseValueError("inner")
exceptValueErrorase:
raiseefrome# self-referential __cause__r=Retrying(
retry=retry_if_exception_cause_type(KeyError),
stop=stop_after_attempt(2),
reraise=True,
)
r(boom) # never returns; 100% CPU
Two-node cycles (a.__cause__ = b; b.__cause__ = a, then raise a) hang the same way. For precedent: the stdlib traceback module explicitly guards against cause/context cycles when walking exception chains, so cyclic chains are treated as a real case in the ecosystem.
Reproduced on current master (c650fb4, v9.1.4); the loop is present in every release that ships this predicate.
Related note: the proposed retry_unless_exception_cause_type feature (#625 / #626) walks the cause chain the same way and would inherit this hang unless the walk is cycle-safe.
Fix is two lines (track seen exception ids; stop when the chain repeats). PR ready with a regression test covering both cycle shapes.
Activity
fix: stop hanging on cyclic exception __cause__ chains (jd#660)