MLP217: Frame-varying key with use_svg_cache=True grows the process-global SVG cache¶
qual reports MLP217 when an SVG-cached constructor
(SVGMobject, Text, MarkupText, MathTex, SingleStringMathTex,
Tex) runs inside a proven per-frame callback with a provably
frame-varying key (an f-string) while use_svg_cache is provably
enabled: every rendered frame then inserts a new entry — a deep copy of
the whole constructed family — into the process-global SVG cache, which
is never evicted. Memory grows O(F × family) for the life of the
process.
- Default severity:
warning - Minimum confidence:
high - Implementation phase:
3 - Required capabilities:
qualified-calls,cost-facts,local-fork-overlay - Fix: none — pass
use_svg_cache=Falsefor frame-varying keys or hoist the construction; both are suggested in the explanation
Profile gate (inert under upstream_0_20)¶
Per DESIGN 7.3 the rule is enabled only when the selected knowledge
profile declares the process-global SVG cache semantics:
KnowledgeProfile::svg_cache() must return a fact block with
process_global: true and unbounded: true (the
local_0_20_1_4d25c031 overlay curates SVG_HASH_TO_MOB_MAP from
svg_mobject.py, including its key components and copy-on-hit
behavior). The map happens to exist upstream too, but upstream_0_20
declares nothing — the accessor returns None and the rule is fully
inert, so the memory-growth claim only ever rests on curated semantics.
Cache-flag resolution (conservative)¶
The flag is the real use_svg_cache parameter of the fork's
SVGMobject.__init__ (verified in the fork source):
- an explicit literal
use_svg_cache=True/Falsewins for every class; - absent the keyword, the class default applies: the fork's
Textdefaults toFalse, every other cached class forwards toSVGMobject's defaultTrue; - a non-literal flag value or a
**kwargssplat (which can hide the keyword) leaves the flag unknown — silence, never a guess.
Relationship to MLP226 / MLP201¶
The DESIGN 7.3 supersedes table orders neither rule above the other,
because they claim different defects on the same span: MLP226 reports
per-frame construction cost and distinct Text/TeX disk assets
(K_resource ≈ F), MLP201 generic per-frame construction cost, while
MLP217 reports unbounded process-global memory growth. A
frame-varying Text/TeX key with the cache enabled therefore carries
both MLP217 and MLP226; a frame-varying SVGMobject key carries
MLP217 and MLP201. Suppressing one never silences the other defect.
Liveness and quantification¶
Like MLP226, the rule requires at least one play where the callback
provably executes per frame (registration active, host in the scene
family, not suspended by the play's own animations, dynamic wait). The
retained-entry count is bound to real frame numbers summed over those
proven plays only ("may retain ~480 cache entries" for one proven 8 s
play at 60 fps); without literal durations the evidence stays
"per-frame" — never a fabricated number.
Example¶
tracker = ValueTracker(0)
badge = always_redraw(
lambda: SVGMobject(f"badge_{tracker.get_value():.0f}.svg") # MLP217
)
label = always_redraw(
lambda: Text(f"t = {tracker.get_value():.2f}", use_svg_cache=True) # MLP217 + MLP226
)
self.add(badge, label)
self.play(tracker.animate.set_value(1), run_time=8)
Silent near misses: the fork's Text without the explicit flag (its
default is False); use_svg_cache=False; a static key (one cache
entry, reused every frame — that is the cache working); a non-literal
flag or **kwargs splat; any profile that does not declare the
process-global cache semantics.