diff --git a/CHANGELOG.md b/CHANGELOG.md index da2d5e260..cd505abba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,15 @@ Open cycle — bug-fix / housekeeping. Entries land here as they merge. large table this removes the dominant per-cell layout allocation. No public API or behaviour change. +- **Process-wide line-metrics cache stops inserting instead of flushing when full.** + The static line-metrics cache `clear()`-ed every entry once it passed 50,000 + distinct styles — a full flush whose non-atomic check-then-clear is a + thundering-herd recompute under concurrent rendering. It now stops inserting at + the cap and keeps the existing entries (distinct styles are few in real use, so + this is only a pathological-explosion guard; it runs on a cache miss, never on + the per-measurement path). **Measured line metrics are unchanged.** No public API + or behaviour change. + ### Tests / tooling - **Benchmark regression gate and measurement probe (benchmarks module, not part diff --git a/src/main/java/com/demcha/compose/engine/measurement/FontLibraryTextMeasurementSystem.java b/src/main/java/com/demcha/compose/engine/measurement/FontLibraryTextMeasurementSystem.java index b94e3f29b..500e219d5 100644 --- a/src/main/java/com/demcha/compose/engine/measurement/FontLibraryTextMeasurementSystem.java +++ b/src/main/java/com/demcha/compose/engine/measurement/FontLibraryTextMeasurementSystem.java @@ -100,10 +100,16 @@ private GlobalPdfStyleKey globalPdfStyleKey(PdfFont font, TextStyle style) { } private static void cacheGlobalLineMetrics(GlobalPdfStyleKey key, LineMetrics metrics) { - if (GLOBAL_PDF_LINE_METRICS_CACHE.size() > GLOBAL_LINE_METRICS_CACHE_LIMIT) { - GLOBAL_PDF_LINE_METRICS_CACHE.clear(); + // Safety cap on the process-wide line-metrics cache. Distinct styles are + // few in real use (a handful of font/size/decoration combos); this only + // guards a pathological style explosion. Stop inserting once full instead + // of clear()-ing: the old full flush wiped every hot entry under + // concurrent rendering (a thundering-herd recompute), so keeping the + // existing entries is strictly better. This runs on a cache miss only, + // never on the per-measurement get() path. + if (GLOBAL_PDF_LINE_METRICS_CACHE.size() < GLOBAL_LINE_METRICS_CACHE_LIMIT) { + GLOBAL_PDF_LINE_METRICS_CACHE.putIfAbsent(key, metrics); } - GLOBAL_PDF_LINE_METRICS_CACHE.putIfAbsent(key, metrics); } @Override