-
-
Notifications
You must be signed in to change notification settings - Fork 32k
Incrementing class variable about 2**32 times causes the Python 3.12.1 interpreter to crash #113462
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I tested further. After compiling the code of the ...
4278190080
python: Python/generated_cases.c.h:3408: _PyEval_EvalFrameDefault: Assertion `type_version != 0' failed.
Aborted cpython/Python/generated_cases.c.h Lines 3452 to 3466 in f7c5a7a
I think this problem may be related to the bytecode length. BTW, this bug cannot be reproduced on 3.11. |
I'm not the expert here, but it seems like it's related to specialization and the following happened:
@brandtbucher should be the expert. |
hi @everyone .I am a beginner looking to contribute to this repo . any of you experienced folks pointing me in the right direction to my first issue would indeed be a great help . thanks . |
@sambhavnoobcoder unfortunately this might not be a good first issue to work on. This has something to do with a new and advanced feature in the CPython VM and it probably requires some deep knowledge of how the interpreter works. Up untill now, I'm not even sure if this is a bug that we should fix. I would suggest to start your first contribution with some easier issues. Maybe look for Of course, if you are really interested in the Python interpreter itself, you are welcome to take a look at it, but there is a learning curve and you should probably take it slow :) |
Slept on this and now I think this is something we should fix. The core factor is - is this something we could have met in a real-life product, or just a theoretical situation. I'm thinking it's the former. All the self defined classes(types) share the same global version counter which is 32 bit. Any specialization for For any program that runs for a reasonably long time, the version counter is destined to overflow. I'm not talking about years, but days or even hours. It's okay if we have a bug that happens after a program runs for 10 years, but 10 days would be too short. The easiest way to fix this is to make the version 64-bit, but that might have some performance concerns. I don't see a way to slow down (or perhaps safely reset) the version counter easily, but maybe there is one and @brandtbucher might know it :) |
Thanks for the report. The problem is that we are not checking for overflow in the bytecode specializer. I created an issue 2 years ago and a contributor is already working on a fix #89811 |
It's more complicated than gracefully fail in overflow right? Do we want to keep the program work when the version overflows? As I mentioned above - this could be a real life situation for long-running programs. The current mechanism assumes the version counter will not duplicate otherwise there could be something funny happening I suppose? |
Yes we want the program to keep working after overflowing. The proposed fix in that issue will prevent the specializer from even specializing bytecode once it sees that version overflows, so the |
Okay so no specialization once the version overflows. That is definitely much better than the current behavior and is obviously acceptable. However, that also means for something like server code which is supposed to be running for days or even months, specialization will only work for a small period of time. Then I realized it's not only about the size of a global counter, it's also about the cache size of the bytecode, so increasing the size of the counter is much more complicated. Maybe we can figure out some optimizations to slow down the version update to alleviate the issue. Anyway, I think to stop specializing once the version overflows is the right way to go at this point. Thanks for the info. |
Thanks for quickly looking into the issue. I would be happy if my programs just continued to run without crashing. I am (obviously) anyway waiting a long time for the programs to finish, so a slowdown (as in prior Python versions) is perfectly acceptable for my use cases. I am not sure if my application could be considered a "real-life product", but the issue caused the experiments in a research paper of mine not to be able to run to completion under Python 3.12. The experiments were originally done with Python 3.9 and measure the number of comparisons performed by various heap implementations on various input types and sizes. The below example capture my essential use of a class variable to count the number of comparisons performed (by Pythons built-in class Item:
comparisons = 0 # class variable
def __init__(self, key):
self.key = key
def __lt__(self, other):
Item.comparisons += 1 # increment class variable
return self.key < other.key
from random import randint
while True:
n = 1_000_000
values = [randint(1, n) for _ in range(n)]
items = [Item(value) for value in values]
comparisons_before = Item.comparisons
items = sorted(items)
comparisons_after = Item.comparisons
comparisons = comparisons_after - comparisons_before
print(f'{comparisons} comparisons done to sort {n} items')
print(f'Total comparisons until now {Item.comparisons}') The last lines printed with Python 3.12.1, before crashing:
|
Thanks for the report. I've marked the root/other issue as a release blocker for 3.12 and 3.13. This means no new versions of 3.12/3.13 can be released until we fix it. |
It's probably not completely easy for cpython to replicate, but what pypy does is to stop changing the version if a class attribute is found to change a lot. That way, a counter on the class still allows the caching of all other class attributes/methods. We keep a set of 'rapidly changing names' per class, and those names aren't cached and mutating them doesn't cause version changes. |
I somewhat recall we had a discussion somewhere to limit the number of version tags each class can consume, to reduce the exhaustion of tags. @markshannon do you recall? |
The idea was to have a per-class counter, and when that got to, say, 1000 no longer issue version number for that class. |
Using a class attribute as a counter will perform poorly, even after this bug is fixed.
|
I made a quick experiment with
As a lecturer on Introduction to Programming in Python, I try to draw connections to other languages. When talking about classes, I mention that Java and C++ have static class variables (where having a counter as a static int variable in the class is the canonical example you find in tutorials), and that this is also possible in Python. I would prefer to continue telling this story to help students migrate between languages (and just ignore the fact that this can come with a performance loss in Python). |
I understand the reason why this is hard to support in CPython, but this suggestion sounds quite bad to me. It's leakage of implementation details. Having a counter on a class is rather common, after all. |
Yeah I completely agree. We should make common code patterns faster in the interpreter, not change the code patterns to suit the interpreter. |
Ok this should be fixed on 3.11, 3.12, 3.13. I shall close this issue. Thanks to @lazorchakp and Mark for the fixes! |
@Fidget-Spinner did this really make into 3.11 and 3.12? I'm currently investigating a 3.13b1 regression I see starting with this change (#119462), and I can't seem to find any backports to older branches? |
The PR in the linked issue didnt. But the PRs checking for overflow did. #89811 |
Crash report
What happened?
The below example stores an integer counter as a class variable
C.counter
. Incrementing the counter usingC.counter += 1
between 2^32 - 2^24 and 2^32 times makes the Python 3.12.1 interpreter crash (after about 30 min) .Tested with Python 3.12.1 on Windows 11.
(In the original application, the class was a wrapper class implementing
__lt__
to count the number of comparisons performed by various algorithms)CPython versions tested on:
3.12
Operating systems tested on:
Windows
Output from running 'python -VV' on the command line:
Python 3.12.1 (tags/v3.12.1:2305ca5, Dec 7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)]
Linked PRs
The text was updated successfully, but these errors were encountered: