Skip to content

Commit 4a9b6cf

Browse files
committed
Properly dispose font in FontRegistry upon display disposal
When retrieving a font from a FontRegistry instance that is not cached in the registry yet, it will be created on the current display. In order to dispose the fonts of a display upon the display's disposal, the FontRegistry registers a dispose hook at the display instance. The current implementation only does this for the first display that is instantiated. For every further display, which may be created on Windows systems, no hook is added. Thus, if an additional display is disposed, its fonts remain in the FontRegistry, referring to a disposed display. This change improved the FontRegistry implementation to properly register a dispose hook to every display for which it contains fonts. It also adds an according regression test.
1 parent f0e5533 commit 4a9b6cf

File tree

2 files changed

+61
-4
lines changed

2 files changed

+61
-4
lines changed

bundles/org.eclipse.jface/src/org/eclipse/jface/resource/FontRegistry.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.MissingResourceException;
2626
import java.util.ResourceBundle;
2727
import java.util.Set;
28+
import java.util.concurrent.ConcurrentHashMap;
2829

2930
import org.eclipse.core.runtime.Assert;
3031
import org.eclipse.jface.util.Policy;
@@ -207,7 +208,7 @@ void addAllocatedFontsToStale(Font defaultFont) {
207208
*/
208209
protected Runnable displayRunnable = this::clearCaches;
209210

210-
private boolean displayDisposeHooked;
211+
private final Set<Display> displayDisposeHooked = ConcurrentHashMap.newKeySet();
211212

212213
private final boolean cleanOnDisplayDisposal;
213214

@@ -492,7 +493,7 @@ private FontRecord createFont(String symbolicName, FontData[] fonts) {
492493
if (display == null) {
493494
return null;
494495
}
495-
if (cleanOnDisplayDisposal && !displayDisposeHooked) {
496+
if (cleanOnDisplayDisposal && !displayDisposeHooked.contains(display)) {
496497
hookDisplayDispose(display);
497498
}
498499

@@ -718,7 +719,7 @@ protected void clearCaches() {
718719
stringToFontRecord.clear();
719720
staleFonts.clear();
720721

721-
displayDisposeHooked = false;
722+
displayDisposeHooked.remove(Display.getCurrent());
722723
}
723724

724725
/**
@@ -736,7 +737,7 @@ private void disposeFonts(Iterator<Font> iterator) {
736737
* Hook a dispose listener on the SWT display.
737738
*/
738739
private void hookDisplayDispose(Display display) {
739-
displayDisposeHooked = true;
740+
displayDisposeHooked.add(display);
740741
display.disposeExec(displayRunnable);
741742
}
742743

tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/resources/FontRegistryTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,20 @@
1414
package org.eclipse.jface.tests.resources;
1515

1616
import static org.junit.Assert.assertArrayEquals;
17+
import static org.junit.Assert.assertEquals;
18+
import static org.junit.Assert.assertFalse;
19+
import static org.junit.Assert.assertNotEquals;
20+
import static org.junit.Assert.assertTrue;
21+
import static org.junit.Assume.assumeTrue;
1722

23+
import java.time.Duration;
24+
import java.time.Instant;
25+
import java.util.concurrent.atomic.AtomicReference;
26+
27+
import org.eclipse.core.runtime.Platform.OS;
28+
import org.eclipse.jface.resource.FontRegistry;
1829
import org.eclipse.jface.resource.JFaceResources;
30+
import org.eclipse.swt.graphics.Device;
1931
import org.eclipse.swt.graphics.Font;
2032
import org.eclipse.swt.graphics.FontData;
2133
import org.eclipse.swt.widgets.Display;
@@ -41,4 +53,48 @@ public void testBug544026() {
4153
assertArrayEquals(fontData, JFaceResources.getDefaultFont().getFontData());
4254
}
4355

56+
@Test
57+
public void multipleDisplayDispose() {
58+
assumeTrue("multiple Display instance only allowed on Windows", OS.isWindows());
59+
60+
FontRegistry fontRegistry = new FontRegistry();
61+
Display secondDisplay = initializeDisplayInSeparateThread();
62+
Font fontOnSecondDisplay = secondDisplay.syncCall(fontRegistry::defaultFont);
63+
64+
Font fontOnThisDisplayBeforeSecondDisplayDispose = fontRegistry.defaultFont();
65+
Device displayOfFontOnSecondDisplay = fontOnSecondDisplay.getDevice();
66+
// font registry returns same font for every display
67+
assertEquals(secondDisplay, displayOfFontOnSecondDisplay);
68+
assertEquals(fontOnThisDisplayBeforeSecondDisplayDispose, fontOnSecondDisplay);
69+
70+
// after disposing font's display, registry should reinitialize the font
71+
secondDisplay.syncExec(secondDisplay::dispose);
72+
assertTrue(fontOnSecondDisplay.isDisposed());
73+
Font fontOnThisDisplayAfterSecondDisplayDispose = fontRegistry.defaultFont();
74+
assertNotEquals(fontOnThisDisplayAfterSecondDisplayDispose, fontOnSecondDisplay);
75+
}
76+
77+
private static Display initializeDisplayInSeparateThread() {
78+
AtomicReference<Display> displayReference = new AtomicReference<>();
79+
new Thread(() -> {
80+
Display display = new Display();
81+
displayReference.set(display);
82+
while (!display.isDisposed()) {
83+
if (!display.readAndDispatch()) {
84+
display.sleep();
85+
}
86+
}
87+
}, "async display creation").start();
88+
waitForDisplayInstantiation(displayReference);
89+
return displayReference.get();
90+
}
91+
92+
private static void waitForDisplayInstantiation(AtomicReference<Display> displayReference) {
93+
Instant maximumEndTime = Instant.now().plus(Duration.ofSeconds(10));
94+
while (displayReference.get() == null) {
95+
assertFalse("display was not instantiated in time", Instant.now().isAfter(maximumEndTime));
96+
Thread.yield();
97+
}
98+
}
99+
44100
}

0 commit comments

Comments
 (0)