Skip to content

Fatal RangeError: Maximum call stack size exceeded when combining partial with infinite recursion #5959

Open

Description

@Liam-DeVoe

The following code has infinite recursion. In python, this raises RecursionError.

from functools import partial

def f(obj, *, unrelated=1) -> object:
    recur = partial(f, unrelated=1)
    assert isinstance(obj, list)
    for x in obj:
        recur(x)

a = []
a.append(a)
# expect RecursionError, but get a fatal emscripten-level error
f(a)

I would expect the same in pyodide, but instead, pyodide crashes fatally with:

  File "/Users/tybug/Desktop/Liam/coding/hypothesis/test_pyodide/run.py", line 13 in f
  ...
RangeError: Maximum call stack size exceeded
    at wasm://wasm/0268cb26:wasm-function[9549]:0x4bca27
    at wasm://wasm/0268cb26:wasm-function[2322]:0x21266f
    at wasm://wasm/0268cb26:wasm-function[2357]:0x213d21
    at wasm://wasm/0268cb26:wasm-function[2551]:0x2204dd
    at wasm://wasm/0268cb26:wasm-function[2552]:0x2205ff
    at wasm://wasm/0268cb26:wasm-function[7366]:0x422758
    at wasm://wasm/0268cb26:wasm-function[2590]:0x22553e
    at wasm://wasm/0268cb26:wasm-function[4592]:0x328962
    at wasm://wasm/0268cb26:wasm-function[1157]:0x1c47fb
    at wasm://wasm/0268cb26:wasm-function[1166]:0x1c5006 {
  pyodide_fatal_error: true
}

(this reproduces for example in the online pyodide REPL: https://pyodide.org/en/stable/console.html.)

The critical piece is partial: if this is removed, for example setting recur = f, then we get a standard RecursionError instead of a fatal pyodide error.


Versions:

pyodide CLI version: 0.3.0
pyodide-build version: 0.30.0
pyodide-lock version: 0.1.0a7
auditwheel_emscripten version: 0.1.0

Activity

  1. ryanking13 commented on Oct 20, 2025

    @ryanking13
    Member

    Thanks for the report! I can reproduce this in my Chrome (141.0), but not in firefox (144).

    Unfortunately, this is not a bug IMO, and the workaround is to reduce the size of the max recursion in Python to, such as 700 using

    sys.setrecursionlimit(700)
    

    (by default, we use 1000, which is the default value in CPython too, I think)


    The maximum call stack size in JS differs per browser. We regularly measure the stack size in our CI:

    browser  py_limit  py_usage  js_depth  py_depth  js_depth/py_usage  js_depth/py_depth
    node         1000     36.20     12299      1997                339                  6
    chrome       1000     37.40     12462      1997                333                  6
    

    Here, the js_depth/py_depth means how much JS call stack is used for a single Python function on average, and the js_depth is the maximum call stack size in the JS in the browser.

    Since the maximum call stack size is around 12,000 in JS, and each Python function call consumes around 6 JS call stack, the Python recursion limit 1000 would not hit the JS call stack maximum normally (1000 * 6 < 12000).

    However, in your example, I think using partial makes us consume extra JS call stacks per the function call (I cannot explain exactly why though), which made 1000 python recursion consume more than the 12000 JS call stack size, resulting in the Maximum call stack size exceeded.

    We cannot dynamically calculate and update the Python recursion limit, so I think the user should adjust it to prevent such errors.

  2. Liam-DeVoe commented on Oct 20, 2025

    @Liam-DeVoe
    Author

    That makes sense to me. As a bit of motivation: I ran into this while writing application-level code that handles RecursionError (to fall back to an alternative algorithm when infinite recursion is detected). This won't catch the fatal pyodide error. Though I would like it to - If pyodide gave me a way to catch and handle javascript "max call stack size exceeded" errors, I would catch that here as well. I don't know if this can be done in a sane way though.

    I'll consider rewriting this code to avoid partial, but it's such an ergonomic util that I wouldn't want to blanket avoid it just in case of running into this.

  3. ryanking13 commented on Oct 21, 2025

    @ryanking13
    Member

    If pyodide gave me a way to catch and handle javascript "max call stack size exceeded" errors, I would catch that here as well.

    That sounds good. Let me think about a way to do it, but probably @hoodmane has some experience with that.

  4. hoodmane commented on Oct 21, 2025

    @hoodmane
    Member

    Just set recursion limit lower? @markshannon recently tried quite hard to make stack checks work on wasm and it didn't work out at all so I'm convinced there's no reasonable way to do it.

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions