Fix massive amount of memleaks on exit in BPY.

Essentially, our current code would not properly remove (dereference)
its python objects matching various RNA data created during execution.

Some cases are fairly trivial to understand (like the lack of handling
of unregstering for our 'startup' operators and UI), other were more
subtle (like unregistered PropertyGroups who would remove/free their RNA
struct definition, without releasing first the potential matching python
object).

Co-authored-by: Campbell Barton <campbell@blender.org>

Pull Request: https://projects.blender.org/blender/blender/pulls/128899
This commit is contained in:
Bastien Montagne
2024-10-11 18:33:29 +02:00
committed by Gitea
parent 649dfb8abc
commit 1e55d034a1
12 changed files with 101 additions and 58 deletions

View File

@@ -201,6 +201,31 @@ _registered_module_names = []
import bpy_types as _bpy_types
def _register_module_call(mod):
register = getattr(mod, "register", None)
if register:
try:
register()
except Exception:
import traceback
traceback.print_exc()
else:
print(
"\nWarning! {!r} has no register function, "
"this is now a requirement for registerable scripts".format(mod.__file__)
)
def _unregister_module_call(mod):
unregister = getattr(mod, "unregister", None)
if unregister:
try:
unregister()
except Exception:
import traceback
traceback.print_exc()
def load_scripts(*, reload_scripts=False, refresh_scripts=False, extensions=True):
"""
Load scripts and run each modules register function.
@@ -234,29 +259,6 @@ def load_scripts(*, reload_scripts=False, refresh_scripts=False, extensions=True
for addon_module_name in [ext.module for ext in _preferences.addons]:
_addon_utils.disable(addon_module_name)
def register_module_call(mod):
register = getattr(mod, "register", None)
if register:
try:
register()
except Exception:
import traceback
traceback.print_exc()
else:
print(
"\nWarning! {!r} has no register function, "
"this is now a requirement for registerable scripts".format(mod.__file__)
)
def unregister_module_call(mod):
unregister = getattr(mod, "unregister", None)
if unregister:
try:
unregister()
except Exception:
import traceback
traceback.print_exc()
def test_reload(mod):
import importlib
# reloading this causes internal errors
@@ -281,7 +283,7 @@ def load_scripts(*, reload_scripts=False, refresh_scripts=False, extensions=True
mod = test_reload(mod)
if mod:
register_module_call(mod)
_register_module_call(mod)
_registered_module_names.append(mod.__name__)
if reload_scripts:
@@ -304,7 +306,7 @@ def load_scripts(*, reload_scripts=False, refresh_scripts=False, extensions=True
# Loop over and unload all scripts.
for mod in registered_modules:
unregister_module_call(mod)
_unregister_module_call(mod)
for mod in registered_modules:
test_reload(mod)
@@ -356,6 +358,22 @@ def load_scripts(*, reload_scripts=False, refresh_scripts=False, extensions=True
print("Warning, unregistered class: {:s}({:s})".format(subcls.__name__, cls.__name__))
# Internal only, called on exit by `WM_exit_ex`.
def _on_exit():
# Disable all add-ons.
_addon_utils.disable_all()
# Call `unregister` function on internal startup module.
# Must only be used as part of Blender 'exit' process.
from bpy_restrict_state import RestrictBlend
with RestrictBlend():
for mod_name in reversed(_registered_module_names):
if (mod := _sys.modules.get(mod_name)) is None:
print("Warning: module", repr(mod_name), "not found in sys.modules")
continue
_unregister_module_call(mod)
def load_scripts_extensions(*, reload_scripts=False):
"""
Load extensions scripts (add-ons and app-templates)