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
Deep-freezed modules create inconsistency in sys.gettotalrefcount() (_Py_Reftotal) #90607
Comments
Using the C program below, I see that _Py_RefTotal is decreasing at each iteration: #include <Python.h>
int main(int argc, char *argv[])
{
for (int i=1; i <= 100; i++) {
Py_SetProgramName(L"./_testembed");
Py_Initialize();
Py_Finalize();
printf("Loop #%d: %zd refs\n", i, _Py_RefTotal);
}
} Example of output: It seems to be a regression caused by this change: commit 1cbaa50
Before the change, _Py_RefTotal was stable: I found this issue while working on bpo-46417 which is related to bpo-1635741. |
Hm, the deep-frozen objects are statically initialized with a very large refcount that isn't accounted for (they are intended to be immortal). It seems that Py_Finalize() somehow decrefs those objects. I guess this means we need some kind of flag indicating certain objects are immortal (Eric has proposed several schemes), then we could just mark these objects as immortal. This reminds me, since #30715 (which I merged yesterday) the deep-frozen objects also reference the small ints directly, as well as the singleton for b"". Is this even safe across Py_Finalize()/Py_Initialize()? If not, we'll need to roll that back as well. |
The problem is only _Py_RefTotal. Maybe frozen_only_do_patchups() should increment _Py_RefTotal when Python it build with Py_DEBUG macro defined, so it can be safely decremented in Py_Finalize()? Adding a flag in PyObject/PyTypeObject and modifying Py_DECREF() sounds more controversial. I suggest to do that later ;-) (I am not convinced that it's the best approach.) I would suggest to write a PEP for immortal objects. |
I don't know. |
I was hoping @eric.snow could tell me. |
The small ints and the empty bytes object each have "immortal" refcounts too (999999999, just like you did in deepfreeze). So they would cause a similar behavior to what Victor reported. Otherwise I wouldn't expect any problems across Py_Finalize()/Py_Initialize(). |
Is there a way to disable deepfreeze when building Python? It makes the Python build way slower. For example, a full build (after "make clean") of Python 3.10 takes 14.9 seconds on my laptop, whereas Python 3.11 takes 24.6 seconds (1.6x slower). It makes my workflow (trial-and-error based ;-)) less efficient. Moreover, I would like to disable it to investigate why _Py_RefTotal is now negative at Python exit: Note: I pushed many changes in bpo-46417 to clear static types and a few "static" objects at Python exit (in Py_Finalize()). |
See also bpo-46476: "Not all memory allocated by _Py_Quicken() is released at Python exit". |
It looks like this isn't easy, sorry. :-( Adding Christian Heimes in case he has a suggestion. |
If you modify the freeze_modules script to generate code like https://github.com/python/cpython/compare/main...tiran:split_frozen?expand=1 , then I can add a build option to compile Python with only required frozen modules. |
@Kumar do you want to tackle this? |
I don't like this approach as it is opposite to what we did to reduce the size of deep-frozen modules to merge them in one file and this approach requires to split it again. |
Do you have an alternative suggestion how to build Python with minimal set of required deepfrozen modules or without any deepfrozen modules at all? A minimal set is not only helpful for Victor's use case. I would also like to use it in WebAssembly builds to reduce the overall file size. |
I don't want to change the default. Keeping fast startup time is a nice goal! I'm asking to make it configurable for my own development workflow: build Python as fast as possible. It is easy to hack Makefile.am.in and Python/frozen.c to freeze less modules. If you want, I can try to work on a patch to make it configurable. Maybe some people want to freeze... *more* modules, rather than less ;-) |
I tried to make the 'FROZEN' variable in freeze_modules.py empty, but it has a bunch of places where this is unexpected. Maybe someone can fix that? |
The bpo-46476 added _Py_Deepfreeze_Fini() and _PyStaticCode_Dealloc() functions: commit c7f810b. If we need to ajust _Py_RefTotal manually, *maybe* it can be done there? I don't understand well how static/immortal code object lead to negative _Py_RefTotal. For me, Py_INCREF() and Py_DECREF() should still be used on these objects, no? |
The problem is in PyImport_ImportFrozenModuleObject -> unmarshal_frozen_code() -> frozen_info.get_code() -> _Py_get_importlib__bootstrap_external_toplevel() call chain. PyImport_ImportFrozenModuleObject() expects unmarshal_frozen_code() to return a strong reference to the code object. However a frozen_info struct with a get_code() function returns a borrowed reference from deepfreeze.c's toplevel code object. # --- test.c
#include <Python.h>
int main(int argc, char *argv[])
{
for (int i=1; i <= 100; i++) {
Py_SetProgramName(L"./_testembed");
Py_Initialize();
Py_Finalize();
printf("Loop #%d: %zd refs, bootstrap refs: %zd\n", i, _Py_RefTotal, Py_REFCNT(_Py_get_importlib__bootstrap_external_toplevel()));
}
}
# $ gcc -IInclude -I. -o test test.c libpython3.11d.a -lm && ./test
Loop #1: -3 refs, bootstrap refs: 999999998
Loop #2: -8 refs, bootstrap refs: 999999997
Loop #3: -13 refs, bootstrap refs: 999999996
Loop #4: -18 refs, bootstrap refs: 999999995
Loop #5: -23 refs, bootstrap refs: 999999994
Loop #6: -28 refs, bootstrap refs: 999999993
Loop #7: -33 refs, bootstrap refs: 999999992
Loop #8: -38 refs, bootstrap refs: 999999991
Loop #9: -43 refs, bootstrap refs: 999999990
Loop #10: -48 refs, bootstrap refs: 999999989 After I changed unmarshal_frozen_code() to "return Py_NewRef(code);", the reference count of the frozen bootstrap module stays stable, but total refcount increases over time: Loop #1: 10 refs, bootstrap refs: 999999999 |
I created a PR #30976 which adjusts _Py_RefTotal and refcnt of immortal codeobjects to account for Py_INCREF/Py_DECREF on codeobjects. With that patch refcnt is 8 and increases by 8 with each initialization of Python. |
Christian's solution seems better to me so I'll close my PR. Christian would you like to create a PR for it ? |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: