Skip to content

Runaway recursion on 3.13 and higher for _PY_FRAME_GENERAL #132744

Description

@Fidget-Spinner

Bug report

Bug description:

Reported here https://discuss.python.org/t/infinite-recursion/88900

def foo(depth = None):
    print(foo.count)
    foo.count  = 1
    return foo()

foo.count = 1
print(foo())

The problem is that normal Calls check the recursion limit remaining, but _PY_FRAME_GENERAL does not.

I'm hesitant to backport the fix to 3.13, as it might break existing code.

CPython versions tested on:

CPython main branch, 3.13

Operating systems tested on:

No response

Linked PRs

Activity

  1. added
    interpreter-core(Objects, Python, Grammar, and Parser dirs)
    3.13only security fixes
    3.14bugs and security fixes
    on Apr 20, 2025
  2. added a commit that references this issue on May 2, 2025
  3. brandtbucher commented on May 6, 2025

    @brandtbucher
    Member

    Can this be closed? I think we still need the manual backport.

  4. Fidget-Spinner commented on May 6, 2025

    @Fidget-Spinner
    MemberAuthor

    I think Mark intends to backport it but I'm ambivalent

  5. brandtbucher commented on May 6, 2025

    @brandtbucher
    Member

    @markshannon, looks like this needs a manual backport.

  6. added a commit that references this issue on Jul 12, 2025
  7. Fidget-Spinner commented on Aug 19, 2025

    @Fidget-Spinner
    MemberAuthor

    I marked this easy to welcome contributors to backport this to 3.13. You don't need to implement anything new, please instead see the already merged PR and follow the backport procedure here https://devguide.python.org/contrib/core-team/committing/#backporting-changes-to-an-older-version. the backport has to be manual.

  8. skv0zsneg commented on Aug 19, 2025

    @skv0zsneg
    Contributor

    Hello everyone! Can I work on this issue?

  9. Fidget-Spinner commented on Aug 19, 2025

    @Fidget-Spinner
    MemberAuthor

    @skv0zsneg yes please.

  10. Fidget-Spinner commented on Aug 19, 2025

    @Fidget-Spinner
    MemberAuthor

    For anyone wanting to contribute. These are the steps:

    • Copy the changes in bytecodes.c and the test to the 3.13 branch from my original PR.
    • Run make regen-cases on Linux or build.bat --regen on Windows.
    • Open PR.
  11. added a commit that references this issue on Aug 22, 2025
  12. marc-hb commented on Feb 12, 2026

    @marc-hb

    I'm hesitant to backport the fix to 3.13, as it might break existing code.

    3.13.8 commit ebccd1de88d indeed broke ruamel in a hyper-specific test case (only with pytest-xdist) and in a totally inexplicable way.

    In that specific test configuration, a default integer function parameter becomes a ruamel.yaml.reader.Reader object!!?! Hilarity ensues. This makes absolutely no sense.

    While the specific test configuration that reproduces requires a bit of a setup effort, once it's set up then git bisect is 100% deterministic and lands exactly on ebccd1de88d. 0% repro without that commit, 100% repro with it.

    See zephyrproject-rtos/west#908 (comment) for complete details.

  13. Fidget-Spinner commented on Feb 12, 2026

    @Fidget-Spinner
    MemberAuthor

    @marc-hb anything that this breaks should be buggy. You should up the sys recursion limit for that test.

  14. marc-hb commented on Feb 13, 2026

    @marc-hb

    You should up the sys recursion limit for that test.

    Interesting suggestion, thanks. I tried sys.setrecursionlimit(100) and sys.setrecursionlimit(10000) and they only make things faster or slower but they cause absolutely no functional change whatsoever. All the failing configurations still fail and all the passing configurations still pass.

    anything that this breaks should be buggy.

    So I bisected ruamel and the git bisect unambiguously landed on some 0.16.3 commit that merely moves some yaml_version field from one class to another which I can't possibly relate to anything that is happening...

    To make sure, I reverted that ruamel 0.16.3 commit on top of the latest 0.18.6 release, solved a few minor conflicts and that revert does make the test pass again!!

    marc-hb/ruamel-yaml@88a38c7

    Revert "move YAML directive info to scanner for TAG parsing of 1.2 URI"

    Reverts old 0.16.3 commit
    https://sourceforge.net/p/ruamel-yaml/code/ci/f57c3e16091a6dec8cde656d22afba305f03bf77
    on top of latest release 0.18.6.
    (= git commit marc-hb/ruamel-yaml@0df0bd6 in git mirror
    https://github.com/pycontribs/ruamel-yaml)

    This is crazy, I've never seen anything like it. This type of elusive bug usually involves non-determinism but not in this case! In this case git bisects are never ambiguous and all configurations that fail, fail exactly the same.

    Any idea?

  15. marc-hb commented on Feb 13, 2026

    @marc-hb

    Interesting suggestion, thanks. I tried sys.setrecursionlimit(100) and sys.setrecursionlimit(10000) and they only make things faster or slower but they cause absolutely no functional change whatsoever. All the failing configurations still fail and all the passing configurations still pass.

    I think I just got a breakthrough and I need to take that back. Apologies for using this issue for https://en.wikipedia.org/wiki/Rubber_duck_debugging

    Instead of making "exponential" variations, I just tried these tiny ones (all other things being equal)

    sys.setrecursionlimit(99) # PASS
    sys.setrecursionlimit(100) # FAIL
    sys.setrecursionlimit(101) # PASS

    So now I suspect some subtle try/catch bug in ruamel is being triggered by this change.

    I can see this happening with other libraries!

  16. marc-hb commented on Feb 19, 2026

    @marc-hb
  17. xxGodLiuxx commented on Sep 16, 2026

    @xxGodLiuxx

    Data point from production (Windows 11, CPython 3.13.5): a test double declared as
    def fake(path, **kw) that recursed into itself never raised RecursionError
    (sys.getrecursionlimit() == 1000). It reached a depth of 5.27 million frames in 2.4 s
    while allocating ~400-800 MB/s, and exhausted 128 GB of RAM plus the page file three
    times in one day (two resource-exhaustion shutdowns and one 0xEF bugcheck).
    The same function without **kw raises RecursionError at depth 998 as expected, so the
    _PY_FRAME_GENERAL path is the trigger here too (not only default arguments).

    Minimal reproduction:

    n = 0
    
    def f(path, **kw):
        global n
        n += 1
        if n >= 200_000:
            return
        return f(path)
    
    f("x")  # returns after 200,000 frames on 3.13.5; RecursionError expected

    Hoping this helps prioritize the 3.13 backport (gh-138032). Thank you.

  18. marc-hb commented on Sep 16, 2026

    @marc-hb

    Hoping this helps prioritize the 3.13 backport (#138032). Thank you.

    This backport was merged more than 1 year ago and released in 3.13.8...

    Note it also had a 3.13-specific regression, see above and in the summary I just posted in 138032.
    That regression was fixed by PR #145015 merged 6+ months ago and released in 3.13.13

    Data point from production (Windows 11, CPython 3.13.5)

    3.13.5 ??

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.13only security fixes3.14bugs and security fixeseasyinterpreter-core(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions