United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-7007299 FileFontStrike appears not to be threadsafe?
JDK-7007299 : FileFontStrike appears not to be threadsafe?

Details
Type:
Bug
Submit Date:
2010-12-16
Status:
Closed
Updated Date:
2013-04-20
Project Name:
JDK
Resolved Date:
2011-03-08
Component:
client-libs
OS:
windows_vista,linux_redhat_5.0
Sub-Component:
2d
CPU:
x86
Priority:
P2
Resolution:
Fixed
Affected Versions:
6u23,7
Fixed Versions:

Related Reports
Backport:
Backport:
Backport:
Backport:
Duplicate:

Sub Tasks

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

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
EVALUATION

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



Hardware and Software, Engineered to Work Together