JDK-7007299 : FileFontStrike appears not to be threadsafe?
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 6u23,7
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: linux_redhat_5.0,windows_vista
  • CPU: x86
  • Submitted: 2010-12-16
  • Updated: 2013-09-12
  • Resolved: 2011-03-08
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availabitlity Release.

To download the current JDK release, click here.
JDK 6 JDK 7
6u27-revFixed 7 b126Fixed
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.6.0_23"
Java(TM) SE Runtime Environment (build 1.6.0_23-b05)
Java HotSpot(TM) 64-Bit Server VM (build 19.0-b09, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Linux bud8 2.6.18-194.17.1.el5 #1 SMP Wed Sep 29 12:50:31 EDT 2010 x86_64 x86_64 x86_64 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
If you use render text from multiple threads, you seem to be guaranteed to get a NullPointerException eventually.

Three other users report similar symptoms in comments at the end of bug 6359722, but that bug (a) appears to have originated as a different issue, and (b) is already closed.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached test case.  It may take some minutes, but will inevitably throw an NPE similar to the one below.


ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.lang.NullPointerException
	at sun.font.FileFontStrike.getCachedGlyphPtr(FileFontStrike.java:470)
	at sun.font.FileFontStrike.getSlot0GlyphImagePtrs(FileFontStrike.java:436)
	at sun.font.CompositeStrike.getGlyphImagePtrs(CompositeStrike.java:97)
	at sun.font.GlyphList.mapChars(GlyphList.java:254)
	at sun.font.GlyphList.setFromString(GlyphList.java:226)
	at sun.java2d.pipe.GlyphListPipe.drawString(GlyphListPipe.java:53)
	at sun.java2d.pipe.ValidatePipe.drawString(ValidatePipe.java:147)
	at sun.java2d.SunGraphics2D.drawString(SunGraphics2D.java:2753)
	at com.test.FileFontStrikeNPE.renderText(FileFontStrikeNPE.java:47)
	at com.test.FileFontStrikeNPE.run(FileFontStrikeNPE.java:20)
	at java.lang.Thread.run(Thread.java:662)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;

public class FileFontStrikeNPE implements Runnable {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new FileFontStrikeNPE(), "Thread_" + i).start();
        }
    }

    public void run() {
        try {
            renderText();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    private void renderText() {
        BufferedImage image = new BufferedImage(200, 200, BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics = image.createGraphics();
        int fontSize = (int) (Math.random() * 20) + 1;
        graphics.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, fontSize));
        graphics.translate(100, 100);

        double rotation = 0.0;
        String label = "Hello world!";

        while (rotation < 100000) {
            FontMetrics fontMetrics = graphics.getFontMetrics(graphics.getFont());
            int width = fontMetrics.stringWidth(label);
            int y = fontMetrics.getAscent() / 2;

            AffineTransform originalTransform = graphics.getTransform();
            Shape originalClip = graphics.getClip();

            graphics.rotate(rotation);
            graphics.translate(-width / 2, y);
            graphics.drawString(label, 0, 0);
            graphics.setTransform(originalTransform);
            graphics.setClip(originalClip);
            rotation += 1.0;
        }
    }
}
---------- END SOURCE ----------

Comments
EVALUATION http://hg.openjdk.java.net/jdk7/build/jdk/rev/b0f9760f3103
2011-01-17

EVALUATION I see the problem. The implementation will defer the creation of the arrays to hold the glyph references until the first actual use of the strike object. This is a performance optimisation first introduced in the JDK 1.5 fix for 5016898: Glyph image caches allocated even if not used i However I think that code was OK and it was the subsequent refactoring in JDK 1.6 under the combined fix for : 6420804: REGRESSION: Rotating graphics2D to draw CJK string causes OutOfMemoryError and 6408162: REGRESSION: Performance Problem for Asian Fonts Larger than 19 pt in Java 1.5 The issue is that whilst initGlyphCache() is called only from a synchronised method, it sets the field 'glyphCacheFormat' *before* it initialises the array itself. Since the code keys off glyphCacheFormat to determine which array to use, then another thread can easily run into a case where it gets to run and sees one field initialised, but not the other. The easiest fix is to move the initGlyphCache() call into the constructor but that will cause the premature array allocation and performance problems the earlier changes were designed to avoid. Instead I think we should initialise the arrays first and only after that set the flag that indicates they are initialised. Since the initialisation happens in a synchronized context there shouldn't be multiple threads allocating these arrays. However there remains the problem that other threads running on other cores aren't guaranteed to see the changes in order. The solution is to make glyphCacheFormat volatile. The Java Memory Model guarantees that all variable writes in program order before a volatile write are seen before a read of that volatile variable by other threads. On Intel and SPARC volatile reads are essentially free, so this should not affect MT scaleability.
2010-12-22