Closed kalekundert closed 2 years ago
I already dealt with this issue, but trying to make a MWE for the purpose of submitting a bug to python, I think I know what's going on a little more. Here's the MWE I came up with:
class descriptor:
def __get__(self, obj, cls=None):
try:
obj.method()
except RecursionError:
pass
return 'value'
class Obj:
x = descriptor()
y = descriptor()
def method(self):
return f'Obj({self.x!r}, {self.y!r})'
obj = Obj()
print(obj.x)
This isn't a perfect example, because in python>=3.8 it creates an infinite loop. I don't understand why, especially since my real code did not. In python==3.8, I get the following error message:
❯ py mwe.py
Fatal Python error: Cannot recover from stack overflow.
Python runtime state: initialized
Current thread 0x00007f0c9ea75740 (most recent call first):
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
File "mwe.py", line 7 in __get__
File "mwe.py", line 18 in method
...
[1] 285536 IOT instruction (core dumped) python3 mwe.py
So at least in this case, the problem is a stack overflow. My guess is that python fails to detect the recursion, and ultimately runs out of stack space.
The following tests generate IOT instruction (core dumped) errors with python 3.8, but not python 3.9 or 3.10:
Here's a console session:
Getting rid of the
Fragment.__repr__()
function solves the problem. This makes me think that the issue is somehow related to the logging that BYOC is doing.More specifically, the error occurs when the
attrs
variable inFragment.__repr__()
contains'conc'
and either'mw'
or'length'
. I expanded the list comprehension in that function into a for-loop, and that had no effect on the error. But when I addeddebug()
calls between the various statements, the error went away. This makes me think that this is a bug in python, perhaps relating to catching a recursion error.I can also prevent the error by commenting out the
"getting {self._name!r} parameter for {format_obj(obj)}"
log message inbyoc.param._calc_value()
. In fact, I can tell that the interpreter crashes just before entering theexcept Exception
block in theformat_obj()
function within_calc_value()
. This suggests that the bug has something to do with catching a recursion error, although I couldn't reproduce the bug with a simple MWE.Overall, I'm starting to wonder if calling
repr(obj)
when I can't find a value for an attribute is just asking for trouble. It is nice to be able to distinguish objects of the same type, but I really don't want my logging code to be triggering these crazy-ass bugs. Even though I think this is probably a bug in python (that was fixed in 3.9 but not 3.8), logging code shouldn't be tempting fate.