United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-6640532 Graphics.getFontMetrics() throws NullPointerException
JDK-6640532 : Graphics.getFontMetrics() throws NullPointerException

Details
Type:
Bug
Submit Date:
2007-12-11
Status:
Closed
Updated Date:
2011-03-08
Project Name:
JDK
Resolved Date:
2011-03-08
Component:
client-libs
OS:
linux
Sub-Component:
2d
CPU:
x86
Priority:
P3
Resolution:
Fixed
Affected Versions:
6
Fixed Versions:

Related Reports
Backport:

Sub Tasks

Description
FULL PRODUCT VERSION :
JRE 1.6.0_03-b05

ADDITIONAL OS VERSION INFORMATION :
Linux 2.6.21-1.3194.fc7 #1 SMP Wed May 23 22:35:01 EDT 2007 i686 i686 i386 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
Graphics.getFontMetrics() throws NullPointerException.

Happens on FontMetrics.charsWidth  also.

The problem seems to be in sun.font.FontManager.addToPool(). The following assertion fails.

// is it possible for this to be the same font?
assert fontFileCache[lastPoolIndex] != font;

This can happen when the sun.font.TrueTypeFont.readBlock() was executed, and during execution if the threads were interrupted.  Several threads executing readBlock() needs to be interrupted, for this problem to show up.  In such a scenario, FontManager cache might be containing multiple references to the same TrueType font.  When FontManager.addToPool attempts to close a font file which is same as the one getting added to pool, we get this exception.

One solution would be in the TrueTypeFont.open(), assign the fontfile after adding it to pool.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached testcase and keep selecting entries in  the combo box which contains the list of fonts.  Selecting a entry in the Combox  enables getting the fontmetrics of that font.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Should not throw NPE
ACTUAL -
throws NPE

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "Thread-1" java.lang.NullPointerException
at sun.font.TrueTypeFont.readBlock(Unknown Source)
        at sun.font.TrueTypeFont.readBlock(Unknown Source)
        at sun.font.TrueTypeFont.createScaler(Native Method)
        at sun.font.TrueTypeFont.getScaler(Unknown Source)
        at sun.font.FileFontStrike.<init>(Unknown Source)
        at sun.font.FileFont.createStrike(Unknown Source)
        at sun.font.Font2D.getStrike(Unknown Source)
        at sun.font.Font2D.getStrike(Unknown Source)
        at sun.font.CompositeStrike.getStrikeForSlot(Unknown Source)
        at sun.font.CompositeStrike.getFontMetrics(Unknown Source)
        at sun.font.FontDesignMetrics.initMatrixAndMetrics(Unknown Source)
        at sun.font.FontDesignMetrics.<init>(Unknown Source)
        at sun.font.FontDesignMetrics.getMetrics(Unknown Source)
        at sun.java2d.SunGraphics2D.getFontMetrics(Unknown Source)
        at lab.FontNPETest$2.run(FontNPETest.java:57)
        at java.lang.Thread.run(Unknown Source)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package lab;

import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JComboBox;
import javax.swing.JFrame;

public class FontNPETest {

  private static Font gfont = null;
  
  public static void main(String[] args) {
    final JFrame fr = new JFrame("FontTest");
    final JComboBox box = createCombo();
    fr.getContentPane().add(box);
    fr.pack();
    fr.setVisible(true);
    fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    startFontManipulation(fr);
  }
  
  private static JComboBox createCombo() {
    final JComboBox box = new JComboBox(
        GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts());
    box.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        Font f = (Font)box.getSelectedItem();
        f = f.deriveFont(12f);
        setFont(f);
      }
    });
    return box;
  }
  
  private static synchronized Font getFont() {
    return gfont;
  }
  
  private static synchronized void setFont(Font f) {
    gfont = f;
  }
  
  private static void startFontManipulation(final JFrame fr) {
    Thread t1 = new Thread(new Runnable() {
      public void run() {
          while(true) {
              Font f = getFont();
              if(f != null) {
                Image img = fr.createImage(100, 100);
                Graphics g = img.getGraphics();
                FontMetrics fm = g.getFontMetrics(gfont);
                final Thread thisT = Thread.currentThread();
                Thread t = new Thread(new Runnable() {
                    public void run() {
                        thisT.interrupt();
                    }
                });
                t.start();
                try {
                   fm.charsWidth("ABCDE".toCharArray(), 0, 5);
                } catch(NullPointerException e) {
                  e.printStackTrace();
                }
                g.dispose();
              }
          }
      }
    });
    t1.start();
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Don't interrupt the thread.

                                    

Comments
EVALUATION

If the thread is being interrupted repeatedly, its possible we
end up with the same font filling a number of slots, and since
this code is called only from within an open() call, to register
that the font file is open for reading, its a bad idea to close() it
as happens here, since its destined for almost immediate use :
        // is it possible for this to be the same font?
        assert fontFileCache[lastPoolIndex] != font;
        /* replace with new font,  poolSize is unchanged. */
        fontFileCache[lastPoolIndex].close();
        fontFileCache[lastPoolIndex] = font;

Need to revise this code to detect the addition of duplicates.

I can also see how we can get a deadlock when updating the pool.
Its hard to capture this without fixing the first problem, after
which I was able to see an instance

Thread-A locks font1, then tries to acquire the pool lock

        at sun.font.FontManager.addToPool(FontManager.java:287)
        - waiting to lock <0xf47a6fb8> (a [Lsun.font.FileFont;)
        at sun.font.TrueTypeFont.open(TrueTypeFont.java:282)
        - locked <0xf485cec0> (a sun.font.TrueTypeFont)
        at sun.font.TrueTypeFont.readBlock(TrueTypeFont.java:313)
        - locked <0xf485cec0> (a sun.font.TrueTypeFont)

meanwhile Thread-B first acquires a lock on font2,
then the pool lock, and then needs the lock on font1 to close() it
and hence free it from the pool :

        at sun.font.TrueTypeFont.close(TrueTypeFont.java:304)
        - waiting to lock <0xf485cec0> (a sun.font.TrueTypeFont)
        at sun.font.FontManager.addToPool(FontManager.java:309)
        - locked <0xf47a6fb8> (a [Lsun.font.FileFont;)
        at sun.font.TrueTypeFont.open(TrueTypeFont.java:282)
        - locked <0xf485d5f0> (a sun.font.TrueTypeFont)
        at sun.font.TrueTypeFont.getTableBuffer(TrueTypeFont.java:819)
        - locked <0xf485d5f0> (a sun.font.TrueTypeFont)

The simplest fix here is for close() to be moved outside the synchronised block.
                                     
2007-12-13



Hardware and Software, Engineered to Work Together