- OS: Windows 11 Home 25H2
- Talon version: 0.4.0 public
First: Please guarantee that an old Python module object is always quickly garbage-collected. I.e., if you're not already doing this, please call gc.collect() in the context of a script reload. This can, e.g., be important to abort an old thread that the new Python module restarts. See also the todo at the start of my windows_toolkit_talon's reload_resilience.py (fixed commit). (You can read it with wrapped lines by using GitHub's "Raw" button.)
Please consider the two todos at the start of windows_toolkit_talon's /pymod_termination/index.py (fixed commit) that are directed at you, the Talon author.
The "private notes" of the first todo refer to these reproduction steps:
-
Create bug.talon:
debug set event: user.dbg_set_event()
If needed, this allows you to differentiate old module objects.
-
Create bug.py:
Click to view.
import sys
import threading
import weakref
import talon
_mod = talon.Module()
_event = threading.Event()
@_mod.action
def dbg_set_event():
"""..."""
_event.set()
_pymod_name = __name__
weakref.finalize(
sys.modules[_pymod_name],
lambda name=_pymod_name: print(f"Python module `{name}` finalized. Event set: {_event.is_set()}"),
)
_pymod_dict_representative = frozenset() # Something weakly referenceable.
weakref.finalize(
_pymod_dict_representative,
lambda name=_pymod_name: print(f"`__dict__` of module `{name}` finalized."),
)
Note that module finalizers are officially supported as per the bottom of this page.
-
On the first execution, nothing is printed. — Correct, because there was no old module to be finalized.
-
On the second execution (by changing the file), something is printed. — Correct, because the module from the first execution was finalized.
-
Do any number of reloads with the use or omission of the voice command that calls event.set() to get a feeling of what's to be expected.
-
Bug: On any bug.py-related file-remove action ([-] in log)—also by moving or renaming bug.py—the module isn't finalized. Only when a file-add action ([+] in log) for the same file path occurs (even with a zero-length file) does the tardy finalizer run. (When moving the file into and out of a subdirectory, this leads to the finalizers lagging one file-move step behind.)
The memory leak the second todo talks about has to do with the fact that a Python module object's __dict__ is also held by module globals like, e.g., functions and indirectly methods, which reference it in their respective __globals__ attributes. This creates a reference cycle that keeps the module __dict__ (not the module object) of every module version along the track of reloads alive. The above bug.py should demonstrate that `__dict__` of module `….bug` finalized. is never printed, even when running gc.collect().
First: Please guarantee that an old Python module object is always quickly garbage-collected. I.e., if you're not already doing this, please call
gc.collect()in the context of a script reload. This can, e.g., be important to abort an old thread that the new Python module restarts. See also the todo at the start of mywindows_toolkit_talon'sreload_resilience.py(fixed commit). (You can read it with wrapped lines by using GitHub's "Raw" button.)Please consider the two todos at the start of
windows_toolkit_talon's/pymod_termination/index.py(fixed commit) that are directed at you, the Talon author.The "private notes" of the first todo refer to these reproduction steps:
Create
bug.talon:If needed, this allows you to differentiate old module objects.
Create
bug.py:Click to view.
Note that module finalizers are officially supported as per the bottom of this page.
On the first execution, nothing is printed. — Correct, because there was no old module to be finalized.
On the second execution (by changing the file), something is printed. — Correct, because the module from the first execution was finalized.
Do any number of reloads with the use or omission of the voice command that calls
event.set()to get a feeling of what's to be expected.Bug: On any
bug.py-related file-remove action ([-]in log)—also by moving or renamingbug.py—the module isn't finalized. Only when a file-add action ([+]in log) for the same file path occurs (even with a zero-length file) does the tardy finalizer run. (When moving the file into and out of a subdirectory, this leads to the finalizers lagging one file-move step behind.)The memory leak the second todo talks about has to do with the fact that a Python module object's
__dict__is also held by module globals like, e.g., functions and indirectly methods, which reference it in their respective__globals__attributes. This creates a reference cycle that keeps the module__dict__(not the module object) of every module version along the track of reloads alive. The abovebug.pyshould demonstrate that`__dict__` of module `….bug` finalized.is never printed, even when runninggc.collect().