fix: stop image map area ids leaking, without an unbounded obarray - #16
Open
sudo-human wants to merge 1 commit into
Open
fix: stop image map area ids leaking, without an unbounded obarray#16sudo-human wants to merge 1 commit into
sudo-human wants to merge 1 commit into
Conversation
Author
|
Hey, this is a vibe coded fix with claude. I don't really have much elsip knowledge apart from write my config 😅 |
`svg-margin--cell-area' tagged each clickable/help hot-spot with a fresh
`make-symbol' area id on every render. Emacs looks images up in its cache
with `equal', and two uninterned symbols of the same name are never `equal',
so a byte-identical image rebuilt on the next render is a cache MISS and is
retained for `image-cache-eviction-delay' (300s by default).
On a buffer with a `:help'/`:action' indicator on many lines -- e.g. diff-hl
on a merge-conflict file, where nearly every line is a hunk -- a frequent
re-render (idle timer, flydiff, after-change) regenerates hundreds of
never-deduped SVG images repeatedly and the image cache grows until eviction,
which reads as a memory leak.
The area id has three hard constraints, which is what makes this fiddly:
1. It must be a symbol -- Emacs composes the click event by combining the
id with the mouse event (e.g. [ID mouse-1]).
2. It must be distinct per cell -- a shared id makes mouse-highlight treat
two hot-spots as one and stop re-firing help-echo between them.
3. It must be the SAME across renders of the same cell -- otherwise the
composite image is never `equal' and the cache never hits (the bug).
A fresh `make-symbol' satisfies 1 and 2 but not 3; interning the name (even in
a private obarray) satisfies all three but, because the name embeds the buffer
position, accumulates a permanent symbol for every position ever drawn -- so a
long session that shifts positions (typing above the indicators, a growing
file) leaks without bound instead.
Memoize an UNINTERNED symbol per cell in a small buffer-local table that is
rotated each render: a cell still present reuses its symbol from the previous
generation (cache hit), a cell that disappears is dropped at the next rotation
and garbage-collected. The live set is bounded by the cells of the last two
renders, so nothing accumulates.
Tests assert the id is a symbol, uninterned in the global obarray, distinct
per cell, stable across renders, and bounded under position churn.
sudo-human
force-pushed
the
fix/image-cache-leak-area-ids
branch
from
July 9, 2026 11:50
02ee941 to
9d7c15d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
svg-margin--cell-areatags each clickable/help hot-spot with a freshmake-symbolarea id on every render. Emacs looks images up in its cache withequal, and two uninterned symbols of the same name are neverequal— so a byte-identical composite image rebuilt on the next render is a cache miss and is retained forimage-cache-eviction-delay(300s by default) instead of reused.On a buffer with a
:help/:actionindicator on many lines — e.g.diff-hlon a merge-conflict file, where nearly every line is a hunk — a frequent re-render (idle timer, flydiff,after-change) regenerates hundreds of never-deduped SVG images repeatedly. The image cache climbs steadily until eviction, which presents as a memory leak.Why it's fiddly — three conflicting constraints on the area id
[ID mouse-1], per Image Descriptors in the Elisp manual).help-echobetween them.equaland the cache never hits (the bug above).make-symbolsatisfies (1) and (2) but not (3). Interning the name — even in a private obarray — satisfies all three, but because the name embeds the buffer position, it accumulates a permanent symbol for every position ever drawn. A long session that shifts positions (typing above the indicators, or a growing file) then leaks without bound instead. Interned symbols are never GC'd.Fix
Memoize an uninterned symbol per cell in a small buffer-local table that is rotated each render (
svg-margin--area-rotate,svg-margin--area-id):equal→ cache hit.This keeps all three constraints: symbol type (clicks work), distinct per cell (hover works), stable across renders (cache hits), and now bounded (no leak either way).
Verification
Measured the live area-symbol set across editing patterns (before → after):
make-symbolTests
Added tests asserting the id is a symbol, uninterned in the global obarray, distinct per cell, stable across renders (including across a rotation), and bounded under position churn.
Full suite: 60 passed, 1 skipped (
image-builds, needs a GUI/librsvg), 0 unexpected. Byte-compiles with no warnings;checkdocclean.