Skip to content

3.12 setrecursionlimit is ignored in connection with @functools.cache #112215

Closed

Description

@PeterLuschny

Bug report

A minimal example:

#  fib.py 
import sys
sys.setrecursionlimit(2000)

from functools import cache

@cache
def fib(n):
    if n<1: return 0
    if n==1: return 1
    return fib(n-1) + fib(n-2)

print(fib(500))

CPython versions tested on:

CPython main branch

Operating systems tested on:

Windows

Edit: This (simpler) example is from @dimpase .

Linked PRs

Activity

  1. dimpase commented on Nov 17, 2023

    @dimpase
    Contributor

    also tested on Linux (with the call to sys.getwindowsversion() removed).

    As well, there is no discrepancy with 3.11 vs 3.12 if @cache is removed. So the working hypothesis is that @cache is to blame for the regression.

    The current master is bad too.
    Bisecting currently, last year things were still OK.

    In my testing I modified the lines

        sys.set_int_max_str_digits(5000)
        print(sys.version_info, sys.getwindowsversion())
    

    I removed the 1st one (it's very recent), and only left

        print(sys.version_info)
    
  2. dimpase commented on Nov 17, 2023

    @dimpase
    Contributor

    I found the 1st bad commit, 7644935, by bisecting:

    76449350b3467b85bcb565f9e2bf945bd150a66e is the first bad commit
    commit 76449350b3467b85bcb565f9e2bf945bd150a66e
    Author: Mark Shannon <mark@hotpy.org>
    Date:   Wed Oct 5 01:34:03 2022 +0100
    
        GH-91079: Decouple C stack overflow checks from Python recursion checks. (GH-96510)
    
     Include/cpython/pystate.h                          | 16 ++++-
     Include/internal/pycore_ceval.h                    | 21 +++---
     Include/internal/pycore_runtime_init.h             |  2 +-
     Lib/test/support/__init__.py                       |  5 +-
     Lib/test/test_ast.py                               |  6 +-
     Lib/test/test_call.py                              | 38 ++++++++++
     Lib/test/test_collections.py                       |  2 +-
     Lib/test/test_compile.py                           |  3 +-
     Lib/test/test_dynamic.py                           |  8 +--
     Lib/test/test_exceptions.py                        |  8 +--
     Lib/test/test_isinstance.py                        | 12 ++--
     Lib/test/test_marshal.py                           |  3 +-
     .../2022-09-05-09-56-32.gh-issue-91079.H4-DdU.rst  |  3 +
     Modules/_testinternalcapi.c                        |  4 +-
     Parser/asdl_c.py                                   |  9 +--
     Python/Python-ast.c                                |  9 +--
     Python/ast.c                                       |  9 +--
     Python/ast_opt.c                                   |  9 +--
     Python/ceval.c                                     | 81 +++++++++++++++-------
     Python/pystate.c                                   |  5 +-
     Python/symtable.c                                  |  9 +--
     Python/sysmodule.c                                 |  2 +-
     22 files changed, 165 insertions(+), 99 deletions(-)
     create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-09-05-09-56-32.gh-issue-91079.H4-DdU.rst
    
  3. dimpase commented on Nov 17, 2023

    @dimpase
    Contributor

    @markshannon - greetings from Oxford, by the way :-)

  4. sunmy2019 commented on Nov 18, 2023

    @sunmy2019
    Member

    Yes. I was suggesting exposing an API for the C recursion limit, but not receiving active feedback.

    See the discussion here: #107263

  5. dimpase commented on Nov 18, 2023

    @dimpase
    Contributor

    @PeterLuschny - basic Fibonacci

    #  fib.py 
    import sys
    sys.setrecursionlimit(2000)
    
    from functools import cache
    
    @cache
    def fib(n):
        if n<1: return 0
        if n==1: return 1
        return fib(n-1) + fib(n-2)
    
    print(fib(500))

    shows the bug just as well

  6. dimpase commented on Nov 18, 2023

    @dimpase
    Contributor

    it's not "probably in connection with @cache", it's definitely

  7. changed the title [-]3.12 setrecursionlimit is ignored, probably in connection with @cache[/-] [+]3.12 setrecursionlimit is ignored in connection with @cache[/+] on Nov 18, 2023
  8. added
    3.12only security fixes
    3.13only security fixes
    on Nov 18, 2023
  9. changed the title [-]3.12 setrecursionlimit is ignored in connection with @cache[/-] [+]3.12 setrecursionlimit is ignored in connection with `@functools.cache`[/+] on Nov 18, 2023
  10. dimpase commented on Nov 18, 2023

    @dimpase
    Contributor

    The bug is triggered by the use of the C implementation of _lru_cache_wrapper, in Modules/_functoolsmodule.c.
    To see this, disable it (then Python code will be used) by applying

    --- a/Lib/functools.py
    +++ b/Lib/functools.py
    @@ -641,7 +641,7 @@ def cache_clear():
         return wrapper
     
     try:
    -    from _functools import _lru_cache_wrapper
    +    from _functools import no_no_lru_cache_wrapper
     except ImportError:
         pass

    and see ./python <fib.py computing the answer, instead of erroring out.

    In a way, it's to be expected, as C code isn't governed by setrecursionlimit any more in 3.12+.

  11. sunmy2019 commented on Nov 20, 2023

    @sunmy2019
    Member

    @arhadthedev This is not related to performance. It's a new hard-coded limit not adjustable.

  12. 43 remaining items

  13. added a commit that references this issue on Sep 2, 2024
  14. Eclips4 commented on Nov 27, 2024

    @Eclips4
    Member

    What's the plan for 3.13? It looks like it still doesn't behave as expected

    I cannot reproduce error from #127346 on my macos, so it's probably a Windows issue?

  15. gpshead commented on Nov 27, 2024

    @gpshead
    Member

    beware that C stack size issues are typically local platform & build config dependent as to what numbers you need to use to bump into a given issue.

  16. rhettinger commented on Jan 12, 2025

    @rhettinger
    Contributor

    Another user reported this as a bug: #127346

    Also there is the question on StackOverflow: https://stackoverflow.com/questions/78389885/why-lru-cache-doesnt-work-on-desktops-but-only-works-in-the-online-python-int

    For the particular case of cache() and lru_cache(), we may need to provide an easy way to get to the pure python version that works as expected. Perhaps something like from functools import py_lru_cache as lru_cache. Without such an option, users would need to code their own caching decorator to be able to run dynamic programming like that found in the StackOverflow question. That is what the user had to do in #127346 . I'm pretty sure that is not what we want.

  17. markshannon commented on Feb 12, 2025

    @markshannon
    Member

    I think the solution to this is to implement the wrapper function in Python, but implement the cache management in C.
    That will fix the recursion issue, with little or no performance impact.

    The wrapper function would look something like this:

    from _functools import make_key, cache_get, cache_set, SENTINEL
    
    def wrapper(*args, **kwds):
        key = make_key(args, kwds, typed)
        result = cache_get(cache, key)
        if result is not SENTINEL:
            return result
        result = user_function(*args, **kwds)
        cache_set(cache, key, result)
        return result
  18. added
    stdlibStandard Library Python modules in the Lib/ directory
    3.14bugs and security fixes
    on Feb 14, 2025
  19. markshannon commented on May 6, 2025

    @markshannon
    Member

    This is fixed in 3.14.

  20. encukou commented on Nov 20, 2025

    @encukou
    Member

    The fix is backported to 3.13 & 3.12.

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

Metadata

Metadata

Assignees

Labels

3.12only security fixes3.13only security fixes3.14bugs and security fixesextension-modulesC modules in the Modules dirstdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions