Leak report: stay quiet when a patcher dies whose entry holds no model
ComfyUI builds a lookup entry per patcher on every model load and drops it when the model is already loaded. Its hand-over still fires when the patcher is freed, and was logged as "a dead patcher died; its parent is gone" although nothing can be lost there. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.2
parent
9c415f81ee
commit
db1602c6e6
+3
-1
@@ -130,12 +130,14 @@ def watch_switch(mm) -> bool:
|
||||
try:
|
||||
parent = self._parent_model()
|
||||
real = self.real_model() if callable(self.real_model) else None
|
||||
if real is None: # a lookup entry ComfyUI built and dropped, or one whose model is freed: nothing to lose
|
||||
return original(self)
|
||||
_seen[0] += 1
|
||||
if parent is None or _seen[0] <= 40: # a hand-over with no parent always shows; the others for the first 40 of the session
|
||||
depth, p = 0, parent
|
||||
while p is not None and getattr(p, "parent", None) is not None and depth < 50:
|
||||
p, depth = p.parent, depth + 1
|
||||
log.info("FunUI Leak", f"hand-over {_seen[0]}: a {type(real).__name__ if real is not None else 'dead'} patcher died; its parent is "
|
||||
log.info("FunUI Leak", f"hand-over {_seen[0]}: a {type(real).__name__} patcher died; its parent is "
|
||||
f"{'gone' if parent is None else type(parent).__name__ + f' with {depth} more above it'}; Python's gc running: {_in_gc[0]}")
|
||||
except Exception as exc: # noqa: BLE001 -- a diagnostic must never stop a run
|
||||
log.failed("leak report", exc)
|
||||
|
||||
@@ -148,7 +148,7 @@ def test_a_hand_over_with_no_parent_is_logged_after_the_first_forty(monkeypatch)
|
||||
mm = type("MM", (), {})()
|
||||
|
||||
class LM:
|
||||
def __init__(self): self._parent_model = lambda: None; self.real_model = lambda: None
|
||||
def __init__(self): self._parent_model = lambda: None; self.real_model = lambda: object()
|
||||
def _switch_parent(self): pass
|
||||
mm.LoadedModel = LM
|
||||
leak_report.watch_switch(mm)
|
||||
@@ -175,10 +175,33 @@ def test_the_first_forty_hand_overs_with_a_parent_are_logged_and_the_forty_first
|
||||
mm = type("MM", (), {})()
|
||||
|
||||
class LM:
|
||||
def __init__(self): self._parent_model = lambda: object(); self.real_model = lambda: None
|
||||
def __init__(self): self._parent_model = lambda: object(); self.real_model = lambda: object()
|
||||
def _switch_parent(self): pass
|
||||
mm.LoadedModel = LM
|
||||
leak_report.watch_switch(mm)
|
||||
for _ in range(45):
|
||||
LM()._switch_parent()
|
||||
assert len(lines) == 40
|
||||
|
||||
|
||||
def test_a_throwaway_entry_that_holds_no_model_is_not_reported(comfyui, capsys):
|
||||
"""Each time models are loaded ComfyUI builds a fresh entry per patcher to look it up, and drops it when the model is already
|
||||
loaded. Such an entry holds no model, yet its hand-over still fires when the patcher dies: nothing can be lost there."""
|
||||
import comfy.model_management as mm
|
||||
import comfy.model_patcher as mp
|
||||
original = mm.LoadedModel._switch_parent
|
||||
try:
|
||||
leak_report.watch_switch(mm)
|
||||
base = torch.nn.Linear(2, 2)
|
||||
base.model_config = type("C", (), {})()
|
||||
parent = mp.ModelPatcher(base, load_device=torch.device("cpu"), offload_device=torch.device("cpu"))
|
||||
child = parent.clone()
|
||||
child.myself = child # a cycle, so only Python's gc can free the pair: it clears the link to the parent first
|
||||
mm.LoadedModel(child) # the lookup entry, never loaded and never tracked
|
||||
del parent, child
|
||||
gc.collect()
|
||||
assert "hand-over" not in capsys.readouterr().err
|
||||
finally:
|
||||
mm.LoadedModel._switch_parent = original
|
||||
if leak_report._gc_flag in gc.callbacks:
|
||||
gc.callbacks.remove(leak_report._gc_flag)
|
||||
|
||||
Reference in New Issue
Block a user