Make FontRegistry's table of font data thread safe - #4266
Open
HeikoKlare wants to merge 1 commit into
Open
Conversation
FontRegistry keeps its symbolic-name-to-FontData mapping in a plain HashMap that is read and written from arbitrary threads: - put(String, FontData[]) is public and carries no UI-thread restriction, unlike the methods that hand out Font instances, - getFontData(), getDescriptor(), getKeySet() and hasValueFor() read it and are likewise unrestricted, with getKeySet() even handing out a live view of the table, and - createFont() writes to it (via the internal put() overload) from whichever thread realizes a font, i.e. from any SWT Display's thread. Unsynchronized HashMap mutation from several threads can corrupt the table itself, not merely produce stale reads. Use a ConcurrentHashMap instead, so concurrent access is safe and getKeySet() returns a weakly-consistent view rather than one that may fail arbitrarily while being iterated. Also fold the internal put()'s read-compare-write of that table into a single atomic Map#put: the previous get()/put() pair could interleave so that two threads both concluded the mapping was unchanged, or that the mapping they replaced was one that had already been overwritten. Storing the given array when it is content-equal to the existing one is a no-op for every reader, so this does not change behavior. The table of realized FontRecords is deliberately left alone here: it is guarded by the documented UI-thread restriction of the methods returning Font instances. Assisted-by: Claude Opus 5 <noreply@anthropic.com>
HeikoKlare
force-pushed
the
fontregistry-multidisplay-step1c
branch
from
August 21, 2026 15:08
fc60749 to
81ae8d9
Compare
There was a problem hiding this comment.
Pull request overview
This PR makes FontRegistry’s font-data mapping safe for concurrent access.
Changes:
- Replaces
HashMapwithConcurrentHashMap. - Uses atomic map replacement.
- Preserves weakly consistent key-set iteration.
- A critical issue remains: cache invalidation can still concurrently mutate unsynchronized font-record and stale-font structures.
Suppressed comments (2)
bundles/org.eclipse.jface/src/org/eclipse/jface/resource/FontRegistry.java:821
- This replaces an equal
FontData[]with the caller's array, unlike the previous get-then-put sequence. BecausegetFontData()returns the stored array directly (line 612), a caller that retained and later mutates the previous array now mutates an orphaned array instead of the registry; array identity is also observable. Preserve the existing array when the contents are equal while retaining atomic update semantics.
FontData[] existing = stringToFontData.put(symbolicName, fontData);
bundles/org.eclipse.jface/src/org/eclipse/jface/resource/FontRegistry.java:199
ConcurrentHashMaprejects null keys, so this changes the existing public behavior ofhasValueFor(null)(andgetKeySet().contains(null)) from returningfalsewith the oldHashMapto throwingNullPointerException. NeitherhasValueFornorResourceRegistrydeclares a non-null precondition, andputalready prevents null keys, so these queries should retain the old false result by guarding null before querying the map.
private final Map<String, FontData[]> stringToFontData = new ConcurrentHashMap<>(7);
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Contributor
HeikoKlare
marked this pull request as ready for review
August 21, 2026 15:39
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.
FontRegistrykeeps its symbolic-name-to-FontDatamapping in a plainHashMapthat is read and written from arbitrary threads:put(String, FontData[])is public and carries no UI-thread restriction, unlike the methods handing outFontinstances.getFontData(),getDescriptor(),getKeySet()andhasValueFor()read it and are likewise unrestricted —getKeySet()even hands out a live view.createFont()writes to it from whichever thread realizes a font, i.e. from anyDisplay's thread.Unsynchronized
HashMapmutation from several threads can corrupt the table itself, not merely produce stale reads. Use aConcurrentHashMap, so concurrent access is safe andgetKeySet()returns a weakly-consistent view rather than one that may fail arbitrarily while being iterated.Also folds the internal
put()'s read-compare-write of that table into a single atomicMap#put: the previousget()/put()pair could interleave so that two threads both concluded the mapping was unchanged, or that the mapping they replaced had already been overwritten. Storing the given array when it is content-equal to the existing one is a no-op for every reader, so behaviour is unchanged.The table of realized
FontRecords and the stale-font list are deliberately left alone. They are not fully protected either —put()mutates them and carries no UI-thread restriction — but that race is pre-existing and unchanged here. It is addressed in follow-up changes that this one prepares for, where those structures get restructured anyway.🤖 Generated with Claude Code