This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Recursive calls crash interpreter when checking exceptions
Type: crash Stage: resolved
Components: Interpreter Core Versions: Python 3.10
process
Status: closed Resolution: duplicate
Dependencies: Superseder:
Assigned To: Nosy List: Dennis Sweeney, Mark.Shannon, ronaldoussoren, xxm
Priority: normal Keywords:

Created on 2020-11-30 06:01 by xxm, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (6)
msg382111 - (view) Author: Xinmeng Xia (xxm) Date: 2020-11-30 06:01
The following program 1 can crash in Python 3. We have reproduce it in the Python version 3.5, 3.6, 3.7, 3.8, 3.9, 3.10. This bug seems to be similar to issue 36272, however, the tracking system shows issue 36272 has been fixed and the program 2, which triggers issue 36272, will not cause crash (“core dump”) in Python 3.8, 3.9, 3.10.
We have attached the stack trace in the end of this report.

Program 1: 
========================
import logging

def rec():
	try:
		logging.error("foo")
	except:
		pass
	rec()
rec()
========================

Program 2: 
========================
import logging

def rec():
	logging.error("foo")
	rec()
rec()
======================
The error message is like following:
“
ERROR:root:foo
ERROR:root:foo
ERROR:root:foo
ERROR:root:foo
…
ERROR:root:foo
ERROR:root:foo
ERROR:root:foo
Fatal Python error: _Py_CheckRecursiveCall: Cannot recover from stack overflow.
Python runtime state: initialized

Current thread 0x00007f0fa440b700 (most recent call first):
  File "/usr/local/python310/lib/python3.10/logging/__init__.py", line 420 in usesTime
  File "/usr/local/python310/lib/python3.10/logging/__init__.py", line 643 in usesTime
  File "/usr/local/python310/lib/python3.10/logging/__init__.py", line 675 in format
  File "/usr/local/python310/lib/python3.10/logging/__init__.py", line 938 in format
  File "/usr/local/python310/lib/python3.10/logging/__init__.py", line 1094 in emit
  File "/usr/local/python310/lib/python3.10/logging/__init__.py", line 963 in handle
  File "/usr/local/python310/lib/python3.10/logging/__init__.py", line 1673 in callHandlers
  File "/usr/local/python310/lib/python3.10/logging/__init__.py", line 1611 in handle
  File "/usr/local/python310/lib/python3.10/logging/__init__.py", line 1601 in _log
  File "/usr/local/python310/lib/python3.10/logging/__init__.py", line 1483 in error
  File "/usr/local/python310/lib/python3.10/logging/__init__.py", line 2080 in error
  File "/home/xxm/Desktop/methodfuzzer/error/loggingtest.py", line 8 in rec
  File "/home/xxm/Desktop/methodfuzzer/error/loggingtest.py", line 12 in rec
  File "/home/xxm/Desktop/methodfuzzer/error/loggingtest.py", line 12 in rec
  File "/home/xxm/Desktop/methodfuzzer/error/loggingtest.py", line 12 in rec
….
  File "/home/xxm/Desktop/methodfuzzer/error/loggingtest.py", line 12 in rec
  File "/home/xxm/Desktop/methodfuzzer/error/loggingtest.py", line 12 in rec
  File "/home/xxm/Desktop/methodfuzzer/error/loggingtest.py", line 12 in rec
  File "/home/xxm/Desktop/methodfuzzer/error/loggingtest.py", line 12 in rec
  File "/home/xxm/Desktop/methodfuzzer/error/loggingtest.py", line 12 in rec
  ...
Aborted (core dumped)”
msg382112 - (view) Author: Dennis Sweeney (Dennis Sweeney) * (Python committer) Date: 2020-11-30 06:51
This might be the expected behavior. See https://bugs.python.org/issue25222

If you already caught a RecursionError and you keep recursing anyway, once you go 50 levels beyond sys.getrecursionlimit(), the interpreter crashes regardless of what is `except`ed. In /Python/ceval.c, there's this:

    if (tstate->overflowed) {
        if (tstate->recursion_depth > recursion_limit + 50) {
            /* Overflowing while handling an overflow. Give up. */
            Py_FatalError("Cannot recover from stack overflow.");
        }
        return 0;
    }

In your Program 2, when the interpreter raises a `RecursionError`, it is raised normally and everything is fine.

In your Program 1, when the interpreter raises a `RecursionError`, it is `except`ed, so the interpreter thinks it's okay to keep going, and when it does, it raises more `RecursionError`s, which it keeps `except`ing, until it finally can't go any farther ( > 50 + sys.getrecursionlimit()), and has no option but to crash.

"Cannot recover from stack overflow." seems to make sense to me: when the interpreter tries to recover, the code won't let it.
msg382114 - (view) Author: Ronald Oussoren (ronaldoussoren) * (Python committer) Date: 2020-11-30 08:10
See also #42500
msg382117 - (view) Author: Xinmeng Xia (xxm) Date: 2020-11-30 08:19
But program like following program 3 will not cause any core dump, RecursionError is also being caught in this  Recursion.
program 3
def rec():
	try:
		rec()
	except:
		pass
rec()

Beside,I use sys.setrecursionlimit(80), and the program 1 still cause core dump.I print sys.getrecursionlimit(),the value is 1000. 80 is << 50 +1000, it shouldn't cause core dump.
msg382128 - (view) Author: Dennis Sweeney (Dennis Sweeney) * (Python committer) Date: 2020-11-30 09:04
sys.getrecursionlimit() returns whatever was passed to the most recent call of sys.setrecursionlimit(...), with some system default (here 1000).

Catching a RecursionError might be fine sometimes, but the issue is that Program 1 catches a RecursionError *and then keeps recursing more* rather than stopping.

I think it might have to be the responsibility of the Python user to make sure that if a RecursionError is to be caught, that the program can recover without making things much worse. It's my understanding that the extra buffer of +50 is to make sure that the programmer has room to stop the overflow and do any necessary cleanup [1].

If no attempt is made to clean up, then it seems reasonable to me that Python should crash, unless there's some idea of what could happen that I'm missing. The interpreter could allow arbitrary recursion during the cleanup until the C stack overflows, but that sort of defeats the point of the recursion checker. It could raise some new ExtraSuperRecurionError, but that doesn't fix anything: what if *that* error is caught ;) ?

[1] to get back to a recursion depth lower than some lower threshold: https://github.com/python/cpython/blob/master/Include/internal/pycore_ceval.h#L98
msg382151 - (view) Author: Mark Shannon (Mark.Shannon) * (Python committer) Date: 2020-11-30 14:24
Duplicate of 42500
History
Date User Action Args
2022-04-11 14:59:38adminsetgithub: 86675
2020-11-30 14:24:29Mark.Shannonsetstatus: open -> closed

nosy: + Mark.Shannon
messages: + msg382151

resolution: duplicate
stage: resolved
2020-11-30 09:04:41Dennis Sweeneysetmessages: + msg382128
2020-11-30 08:19:09xxmsetmessages: + msg382117
2020-11-30 08:10:55ronaldoussorensetnosy: + ronaldoussoren
messages: + msg382114
2020-11-30 06:51:27Dennis Sweeneysetnosy: + Dennis Sweeney
messages: + msg382112
2020-11-30 06:01:25xxmcreate