High memory usage after prolonged use — TextMate grammar data accumulates to ~350 MB
Generated by AI and I reviewed by myself
Summary
After running SourceGit (AOT build) for ~6 days with 4–5 repositories open, the process RSS grows to ~1.15 GB. Analysis of /proc/<pid>/smaps and anonymous memory segment dumps reveals that TextMate syntax highlighting grammar data alone accounts for ~345 MB (30%) of total RSS, making it the single largest memory consumer.
Environment
| Item |
Value |
| Build |
AOT (self-contained) |
| Runtime |
~6 days |
| OS |
Linux 6.18 (deepin) |
| GPU |
Intel HD Graphics 630, Mesa 24.3.0 |
| .NET SDK |
10.0.300 |
| Open repos |
4–5 |
Observed memory breakdown
Category RSS Share
──────────────────────────────────────────────
TextMate grammar segments 345 MB 30%
.NET LOH segments 172 MB 15%
.NET SOH segments 138 MB 12%
Other GC segments 204 MB 18%
GPU / Skia cache 61 MB 5%
Native heap 55 MB 5%
Other 175 MB 15%
──────────────────────────────────────────────
Total 1150 MB 100%
Memory mapping analysis
90% of RSS is anonymous memory (heap allocations), not file-backed mappings. Among the 307 anonymous rw-p segments, ~105 segments in the 5–7 MB range each contain a full set of TextMate grammar definitions — compiled regular expressions, scope names (e.g. keyword.preprocessor.error.cs, meta.function-call.parameters.r), and rule structures (beginCaptures, endCaptures, patterns). Each such segment holds roughly 460–480 distinct TextMate scope keywords.
This points to redundant, non-shared TextMate grammar registries being created independently for each editor/viewer instance.
Suspected root cause
TextMateHelper.CreateForEditor() creates a new RegistryOptionsWrapper (and therefore a new RegistryOptions) for every call:
// src/Models/TextMateHelper.cs
public static TextMate.Installation CreateForEditor(TextEditor editor)
{
return editor.InstallTextMate(
Application.Current?.ActualThemeVariant == ThemeVariant.Dark
? new RegistryOptionsWrapper(ThemeName.DarkPlus) // new instance every time
: new RegistryOptionsWrapper(ThemeName.LightPlus));
}
RegistryOptions internally caches loaded grammars (compiled regex objects, scope maps, etc.), but each instance maintains its own independent cache. This method is called from at least 7 different views — TextDiffView, Blame, CommandLogContentPresenter, MergeConflictEditor, AIAssistant, SelfUpdate, and RevisionFileContentViewer — each creating a separate registry.
Although _textMate.Dispose() is called when views are destroyed, the grammar objects previously cached in each RegistryOptions instance may survive on the GC heap until collected. When multiple views' lifetimes overlap (e.g. switching between diffs of different file types), several independent copies of the same grammar data coexist in managed memory simultaneously.
Over hours/days of usage viewing diffs across many file types, this accumulates to an estimated 200–350 MB of retained grammar data.
Suggested fix
Share a single RegistryOptionsWrapper per theme (dark/light) across all editors, so grammar definitions are loaded and cached only once:
private static RegistryOptionsWrapper s_darkRegistry;
private static RegistryOptionsWrapper s_lightRegistry;
public static TextMate.Installation CreateForEditor(TextEditor editor)
{
var isDark = Application.Current?.ActualThemeVariant == ThemeVariant.Dark;
var registry = isDark == true
? (s_darkRegistry ??= new RegistryOptionsWrapper(ThemeName.DarkPlus))
: (s_lightRegistry ??= new RegistryOptionsWrapper(ThemeName.LightPlus));
return editor.InstallTextMate(registry);
}
Note: per-view state like LastScope should be moved out of the shared registry to avoid cross-editor interference.
High memory usage after prolonged use — TextMate grammar data accumulates to ~350 MB
Summary
After running SourceGit (AOT build) for ~6 days with 4–5 repositories open, the process RSS grows to ~1.15 GB. Analysis of
/proc/<pid>/smapsand anonymous memory segment dumps reveals that TextMate syntax highlighting grammar data alone accounts for ~345 MB (30%) of total RSS, making it the single largest memory consumer.Environment
Observed memory breakdown
Memory mapping analysis
90% of RSS is anonymous memory (heap allocations), not file-backed mappings. Among the 307 anonymous
rw-psegments, ~105 segments in the 5–7 MB range each contain a full set of TextMate grammar definitions — compiled regular expressions, scope names (e.g.keyword.preprocessor.error.cs,meta.function-call.parameters.r), and rule structures (beginCaptures,endCaptures,patterns). Each such segment holds roughly 460–480 distinct TextMate scope keywords.This points to redundant, non-shared TextMate grammar registries being created independently for each editor/viewer instance.
Suspected root cause
TextMateHelper.CreateForEditor()creates a newRegistryOptionsWrapper(and therefore a newRegistryOptions) for every call:RegistryOptionsinternally caches loaded grammars (compiled regex objects, scope maps, etc.), but each instance maintains its own independent cache. This method is called from at least 7 different views —TextDiffView,Blame,CommandLogContentPresenter,MergeConflictEditor,AIAssistant,SelfUpdate, andRevisionFileContentViewer— each creating a separate registry.Although
_textMate.Dispose()is called when views are destroyed, the grammar objects previously cached in eachRegistryOptionsinstance may survive on the GC heap until collected. When multiple views' lifetimes overlap (e.g. switching between diffs of different file types), several independent copies of the same grammar data coexist in managed memory simultaneously.Over hours/days of usage viewing diffs across many file types, this accumulates to an estimated 200–350 MB of retained grammar data.
Suggested fix
Share a single
RegistryOptionsWrapperper theme (dark/light) across all editors, so grammar definitions are loaded and cached only once:Note: per-view state like
LastScopeshould be moved out of the shared registry to avoid cross-editor interference.