1

I have a program that needs to do very deep recursion. I increased the recursion limit to 10**6 using sys.setrecursionlimit(10**6). However, the process exited with this error code:

Process finished with exit code -1073741571 (0xC00000FD)

After reading this question, it turns out that recursion limit does not change stack size, so stack overflow happened. The solution on the question was to use threading.stack_size(), however I'm a beginner at Python and so I don't understand how to use threading.

So how to fix 0xC00000FD on main thread?

I have checked multiple times - I don't have an infinite loop, it is just that my recursion is very deep.

Edit 30/10/2021 : many people here recommend to rewrite the code to use iterative (loop) rather than recursive , and so i did that. Now my code works without stack overflow error anymore. But if anyone knows how to increase main thread stack size , feel free to answer here , i'm still curious if there is any way to increase python main thread stack size because it is possible to increase main thread stack size on other language i learned such as java.

  • 1
    You should probably just not use recursion, i.e. an explicit stack (`stack = []`, `stack.pop()`, `stack.append(…)`) and a loop. – Ry- Oct 25 '21 at 05:25
  • That's awfully deep recursion. Whatever you're doing can probably be done better another way. – Frank Yellin Oct 25 '21 at 05:25
  • @Ry- my code is object oriented , each object keep a reference to another object which also keep reference to another object and so on until the first object, to not use recursion i would have to rewrite my entire code :( – i'm ashamed with what i asked Oct 25 '21 at 05:28
  • 2
    Stack size for the main thread on windows is set by the linker, so this would be the stack size of python.exe. `dumpbin` says 2000000 (0x1E8480) for my local copy. If you are recursing deep enough to crash python.exe, you'll need to rethink your approach. – dirck Oct 25 '21 at 05:30
  • @i'mashamedwithwhatiasked: You don't have to redesign your data structures. You just need to traverse them iteratively instead of recursively. – user2357112 Oct 25 '21 at 05:32
  • 1
    What about writing a [mcve] and see how it can be rewritten. As stated in comment every recursive function can be rewritten with a stack and a loop. Additionally image are very poor media to convey textual data. Copy paste instead it will ease reading and research and it reduce storage and bandwidth consumption. – jlandercy Oct 25 '21 at 05:34
  • welp , then i guess i have no choice but to rewrite it using iterative approach . Thanks everyone. – i'm ashamed with what i asked Oct 25 '21 at 05:38
  • But i still accept answer to this question , so that i can try to increase stack size , if it crashes my computer before it can finish the recursion , it will courage me even more to convert it to iterative. I only to run this program once and get data out of it. After that , i can just delete it , so it would be good if i don't have to rewrite this code – i'm ashamed with what i asked Oct 25 '21 at 05:41
  • You may also update your post with an answer when you have one. Then the all community will benefit from it. – jlandercy Oct 25 '21 at 06:36
  • Does this answer your question? [Process finished with exit code -1073741571](https://stackoverflow.com/q/20629027/6045800) – Tomerikoo Oct 25 '21 at 06:55

2 Answers2

1

I was a bit surprised to find that setting thread stack size worked (on my Linux machine and I think Windows would work also). I wrote a test that recurses with and without a thread, results being:

$ python3 recurse.py
Testing unthreaded
try 10**4
10000
try 10**5
Segmentation fault (core dumped)
$ python3 recurse.py threaded
Testing threaded
try 10**4
10000
try 10**5
100000
try 10**6
Exception in thread Thread-3:
...
RecursionError: maximum recursion depth exceeded in comparison

The code creates a class that inherits from threading.Thread. The initializer starts and joins its own thread so just instantiate it and you are off to the races. Stack size control is global to the threading module - puzzling that the threading module isn't itself thread safe - so its set just before starting the code and reset when running.

import threading
import sys

# usage: recurse.py [threaded]

sys.setrecursionlimit(10**6)

class MyRecursionThread(threading.Thread):
    """Start the recursion function in a separate thread and
    wait til complete"""
    
    def __init__(self, depth):
        super().__init__()
        self.orig_stack_size = threading.stack_size()
        threading.stack_size(100000*4096) # guessing here
        self.depth = depth
        self.start()
        self.join()

    def run(self):
        # thread is started so return default. Why isn't this
        # thread safe in the threading module!?!
        threading.stack_size(self.orig_stack_size)
        self.result = the_recursive_function(0, self.depth)

def the_recursive_function(cur, depth):
    if cur < depth:
        return the_recursive_function(cur+1, depth)
    else:
        return cur

# probe depth
try:
    threaded = sys.argv[1] == "threaded"
except IndexError:
    threaded = False
    
print("Testing", "threaded" if threaded else "unthreaded")

for i in range(4, 8):
    print(f"try 10**{i}")
    if threaded:
        result = MyRecursionThread(10**i).result
    else:
        result = the_recursive_function(0, 10**i)
    print(result)
tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • "Stack size control is global to the threading module - puzzling that the threading module isn't itself thread safe" - I was expecting this to be an OS-level limitation, but looking at the actual OS-level mechanisms, both Windows and POSIX threads have an argument to set the stack size for a specific thread when creating that thread. – user2357112 Oct 25 '21 at 08:19
  • @user2357112supportsMonica - Historically, default thread stack sizes were pretty small but as virtual address space increased, you could pick a number that covers almost everything. Even so, being stingy has its advantages - fewer page allocations. I don't know how it works these days, but once a page was allocated for the stack, it stayed pinned to the stack so a single greedy stack usage impacted memory availability til it terminated. – tdelaney Oct 25 '21 at 15:08
1

If you end up having to convert your recursive functions to iterative approaches, This decorator/class may help ease the pain (depending on the nature of your recursions).

The decorator converts recursive calls in the function to iterative calls with an internal (unlimited) stack. Its main limitations are that it only supports simple recursions where the self-call is part of the return statement, and it requires a change in the way that return statement is expressed. Also, it will add significant overhead to the function calls which will impact performance.

the usage pattern is as follows:

@iterative                       # decorator to modify the function
def fn(...)                      # existing declaration
    ...                          # and implementation
    if baseCase return x         # no change to the base case
    return fn(...),lambda x: ... # isolate alterations to the recursed
                                 # result in a lambda

For example:

@iterative
def factorial(n):
    if n<2: return 1
    return factorial(n-1),lambda f:f*n

print(factorial(10)) # 3628800
            
print(len(str(factorial(10000)))) # 10000! has 35660 digits              
            

@iterative
def sumSquares(n):
    if n<2: return n
    return sumSquares(n-1),lambda f:f+n**2 
            
print(sumSquares(1000000)) # 333333833333500000 


@iterative
def fibo(n):
    if n<2: return n
    return fibo(n-1),fibo(n-2),lambda n1,n2: n1+n2

print(fibo(10))   # 55
print(fibo(1000)) # will work, ... waiting for output ...

Here is the decorator / class:

from collections import deque    
class iterative:
    def __init__(self,func):
        self.func     = func     # decorated function
        self.stack    = deque()  # [(argCount, function to call)]
        self.nested   = False    # nested call flag
        self.recursed = False    # call wants recursion
        self.results  = []       # result stack

    def __call__(self,*args,**kwargs): # replaces original function
        if self.nested:
            self.recursed = True       # defer recursive call to make 
            return lambda: self.func(*args,**kwargs)
        else:
            self.nested = True
            self.stack.append((0,lambda: self.func(*args,**kwargs))) # top
            while self.stack:
                useArgs,fn = self.stack.pop()      # unstack
                if useArgs:                        # lambda on results
                    args = self.results[-useArgs:]
                    self.results = self.results[:-useArgs]
                    self.results.append(fn(*args))          
                else:
                    self.recursed = False    # recursive call
                    result = fn()
                    if self.recursed:        # more recursion -> stack
                        *calls,final = result
                        self.stack.append((len(calls),final))
                        self.stack.extend([(0,r) for r in reversed(calls)])
                    else:
                        self.results.append(result) # base cond. -> results
            self.nested  = False
            return self.results.pop(-1) # final result (and reset)
Alain T.
  • 40,517
  • 4
  • 31
  • 51