Skip to content

Commit 841148c

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 ea68ca2 commit 841148c

File tree

2 files changed

+59
-4
lines changed

2 files changed

+59
-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
@@ -19,6 +19,7 @@
1919
import java.util.Collections;
2020
import java.util.Enumeration;
2121
import java.util.HashMap;
22+
import java.util.HashSet;
2223
import java.util.Iterator;
2324
import java.util.List;
2425
import java.util.Map;
@@ -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 = new HashSet<>();
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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,19 @@
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.Assume.assumeTrue;
1721

22+
import java.time.Duration;
23+
import java.time.Instant;
24+
import java.util.concurrent.atomic.AtomicReference;
25+
26+
import org.eclipse.core.runtime.Platform.OS;
27+
import org.eclipse.jface.resource.FontRegistry;
1828
import org.eclipse.jface.resource.JFaceResources;
29+
import org.eclipse.swt.graphics.Device;
1930
import org.eclipse.swt.graphics.Font;
2031
import org.eclipse.swt.graphics.FontData;
2132
import org.eclipse.swt.widgets.Display;
@@ -41,4 +52,47 @@ public void testBug544026() {
4152
assertArrayEquals(fontData, JFaceResources.getDefaultFont().getFontData());
4253
}
4354

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

0 commit comments

Comments
 (0)