JDK-6611637 : sun.font.GlyphLayout not threadsafe causing NullPointerException
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 6
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2007-10-01
  • Updated: 2011-03-08
  • 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 Availability Release.

To download the current JDK release, click here.
JDK 6 JDK 7
6u10Fixed 7 b28Fixed
Related Reports
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.6.0_02"
Java(TM) SE Runtime Environment (build 1.6.0_02-b05)
Java HotSpot(TM) Client VM (build 1.6.0_02-b05, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
We have a multithreaded batch generation process based on the jasperreports java library, which runs once per night and generates around 4000 pdf reports. On each batch processing run, we have 2-4

NullPointerExceptions with the following stack trace:
	at sun.font.GlyphLayout$EngineRecord.init(GlyphLayout.java:565)
	at sun.font.GlyphLayout.nextEngineRecord(GlyphLayout.java:439)
	at sun.font.GlyphLayout.layout(GlyphLayout.java:362)
	at sun.font.ExtendedTextSourceLabel.createGV(ExtendedTextSourceLabel.java:267)
	at sun.font.ExtendedTextSourceLabel.getGV(ExtendedTextSourceLabel.java:252)
	at sun.font.ExtendedTextSourceLabel.createCharinfo(ExtendedTextSourceLabel.java:522)
	at sun.font.ExtendedTextSourceLabel.getCharinfo(ExtendedTextSourceLabel.java:451)
	at sun.font.ExtendedTextSourceLabel.getLineBreakIndex(ExtendedTextSourceLabel.java:397)
	at java.awt.font.TextMeasurer.calcLineBreak(TextMeasurer.java:313)
	at java.awt.font.TextMeasurer.getLineBreakIndex(TextMeasurer.java:548)
	at java.awt.font.LineBreakMeasurer.nextOffset(LineBreakMeasurer.java:340)
	at java.awt.font.LineBreakMeasurer.nextLayout(LineBreakMeasurer.java:422)
	at java.awt.font.LineBreakMeasurer.nextLayout(LineBreakMeasurer.java:395)
	at net.sf.jasperreports.engine.fill.TextMeasurer.renderParagraph(TextMeasurer.java:294)
	at net.sf.jasperreports.engine.fill.TextMeasurer.measure(TextMeasurer.java:249)
	at net.sf.jasperreports.engine.fill.JRFillTextElement.chopTextElement(JRFillTextElement.java:535)
	at net.sf.jasperreports.engine.fill.JRFillStaticText.prepare(JRFillStaticText.java:193)
	at net.sf.jasperreports.engine.fill.JRFillElementContainer.prepareElements(JRFillElementContainer.java:343)
	at net.sf.jasperreports.engine.fill.JRFillBand.fill(JRFillBand.java:323)
	at net.sf.jasperreports.engine.fill.JRFillBand.fill(JRFillBand.java:282)
	at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillTitle(JRVerticalFiller.java:313)
	at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillReportStart(JRVerticalFiller.java:247)
	at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillReport(JRVerticalFiller.java:113)
	at net.sf.jasperreports.engine.fill.JRBaseFiller.fill(JRBaseFiller.java:791)
	at net.sf.jasperreports.engine.fill.JRBaseFiller.fill(JRBaseFiller.java:714)
	at net.sf.jasperreports.engine.fill.JRFiller.fillReport(JRFiller.java:89)
	at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:601)

this is due to the fact that the static cache variable declared at line 74

    // cached glyph layout data for reuse
    private static GlyphLayout cache;  // reusable

is not declared as volatile, and in the following method (line 191) it is uninitialized in an unsynchronized block:

    public static void done(GlyphLayout gl) {
        gl._lef = null;
        cache = gl; // object reference assignment is thread safe, it says here...
    }
 
what probably happens here is that since the cache variable is not volatile nor its deinitialization is enclosed in a synchronized block the JVM reorganizes the order of the operations so that in the first thread (A) the gl._lef=null is actually executed after the cache=gl line.

This makes so that another thread (let's call it B) coming to the get method with a call like the following:

    GlyphLayout layout = GlyphLayout.get( null );

in the get(LayoutEngineFactory) implementation at line 175 can see a non null cache and grab the same cache that A has not yet completely uninitialized because of the inversion in code,
            
            if (cache != null) {
                result = cache;
                cache = null;
            }

and uses the one just left by the other thread (BEFORE the gl._lef=null occurs). Apparently it may happen that the B thread arrives until the line 184 (return result;) before the uninitialization occurs, and then context is swapped to A, which finally executes the code at line 192:

     gl._lef = null;

so making the object reference used by B an invalid one because of the null _lef: this will cause the NPE at line 565, when B tries to use the object.

The original programmer thought that the nullify operation on line 193 is thread safe (see the comment in code), but did not consider that in multi-core systems there may be effects coming from code reordering.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Since the bug occurs in multi-thread programs, in code that is very low-level, it is difficult to write a program allowing to reproduce it. Looking at the stack trace one can deduce that the error occurs while jasperreports is rendering a paragraph.

That the error is reproducible may be deduced by the fact that a simple google search reveals multiple different occurrences of the problem reported by different people.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No NPE
ACTUAL -
NPE occurs

REPRODUCIBILITY :
This bug can be reproduced always.

CUSTOMER SUBMITTED WORKAROUND :
The only successful workaround we could apply has been to patch the GlyphLayout class by declaring the cache variable as volatile and then executing our batch with that class in the bootclasspath: this has completely stopped our problems. We have nevertheless no workaround that is appliable without patching the JDK.

Comments
EVALUATION I believe the submitter is correct. This was previously reported as : 6367148: java.lang.NullPointerException in sun.font.GlyphLayout$EngineRecord.init but the cause wasn't identified. An alternative to making cache volatile is make the done() method synchronized.
03-10-2007