United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-7027300 Unsynchronized HashMap access causes endless loop
JDK-7027300 : Unsynchronized HashMap access causes endless loop

Details
Type:
Bug
Submit Date:
2011-03-14
Status:
Closed
Updated Date:
2013-11-08
Project Name:
JDK
Resolved Date:
2012-06-27
Component:
client-libs
OS:
solaris_10,linux_suse_sles_11,windows_7
Sub-Component:
2d
CPU:
x86
Priority:
P2
Resolution:
Fixed
Affected Versions:
6,6u10
Fixed Versions:

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

Sub Tasks

Description
FULL PRODUCT VERSION :
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
Java HotSpot(TM) Server VM (build 16.3-b01, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Solaris 10 10/09 s10x_u8wos_08a X86
SunOS 5.10 Generic_142901-06 i86pc i386 i86pc

A DESCRIPTION OF THE PROBLEM :
Using one of the polymorphic methods java.awt.Graphics2D.drawString() leads to an endless loop when accessing this method in a multi threaded environment. For example, we use a 3rd party library jcaptcha (http://jcaptcha.sourceforge.net/) to generate images within our web application.
We discussed the problem and came to the conclusion, that not the implementation of the jcaptcha is the problem und must be implemented in a thread safe manner. Rather the implementation of the sdk code within sun.font.SunLayoutEngine.getEngine(LayoutEngineKey) should be thread safe. The read access to the hashmap is not synchronized and leads to endless loops for the threads which want to get the LayoutEngine. Unfortunatelly the thread which writes (hashmap.put()) has passed the code point and does not appear within our stack trace at all.

java.lang.Thread.State: RUNNABLE
        at java.util.HashMap.get(HashMap.java:303)
        at sun.font.SunLayoutEngine.getEngine(SunLayoutEngine.java:115)
        at sun.font.GlyphLayout$EngineRecord.init(GlyphLayout.java:642)
        at sun.font.GlyphLayout.nextEngineRecord(GlyphLayout.java:494)
        at sun.font.GlyphLayout.layout(GlyphLayout.java:417)
        at sun.font.ExtendedTextSourceLabel.createGV(ExtendedTextSourceLabel.java:308)
        at sun.font.ExtendedTextSourceLabel.getGV(ExtendedTextSourceLabel.java:294)
        at sun.font.ExtendedTextSourceLabel.createLogicalBounds(ExtendedTextSourceLabel.java:208)
        at sun.font.ExtendedTextSourceLabel.getAdvance(ExtendedTextSourceLabel.java:117)
        at java.awt.font.TextLine.init(TextLine.java:264)
        at java.awt.font.TextLine.<init>(TextLine.java:110)
        at java.awt.font.TextLine.fastCreateTextLine(TextLine.java:952)
        at java.awt.font.TextLayout.fastInit(TextLayout.java:585)
        at java.awt.font.TextLayout.<init>(TextLayout.java:511)
        at sun.java2d.SunGraphics2D.drawString(SunGraphics2D.java:2804)
 

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
difficult to reproduce, but we think the code tells enough to tell you about the thread safety.


REPRODUCIBILITY :
This bug can be reproduced rarely.

                                    

Comments
EVALUATION

I have, on other occasions, seen infinite loops in Hashmap when there's a
concurrent update so the submitter's suggestion seems very plausible.

We previously fixed in 6u10:
6611637: sun.font.GlyphLayout not threadsafe causing NullPointerException 
But it sounds like that just unmasks this other problem, since he is using 6u20
                                     
2011-05-19
EVALUATION

"an infinite loop in a Hashmap with concurrent updates"
from the previous comment
led me to the SunLayoutEngine.getEngine() method
where an unsynchronized HashMap may be used simultaniously from different threads.

I found that sun.font.GlyphLayout uses ConcurrentHashMap,
after changing HashMap to ConcurrentHashMap in SunLayoutEngine.getEngine()
the bug is not reproducible on my machine
                                     
2012-06-09
EVALUATION

I also confirm that with ConcurrentHashMap instead of HashMap the issue is not reproducible.
                                     
2012-06-09
EVALUATION

ConcurrentHashMap fixes the problem.

A reliable test progam was attached to 
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6611637
attempting to show a differnet bug but its of some manual use to test
this one too. Its somewhat dependent on the #CPUs to show a hang.
 
I've pasted it here although I'm not sure its an ideal regression test.

import java.awt.Color;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;

import java.util.Random;

/**
 * attempt at a reproducible test case for bug
 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6367148
 */
public class GlyphBug implements Runnable {
	private static final FontRenderContext LINE_BREAK_FONT_RENDER_CONTEXT = new FontRenderContext(null, true, true);
	private static final int RUNS = 50;
	private static final int THREADS = 5;
	
	private static boolean RUNNING = true;
	
	/**
	 * @return true if the Main method is still starting or waiting on threads.
	 */
	public static boolean isRunning() {
		return RUNNING;
	}
	private static String[] FONT_NAMES = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();

	/**
	 * @param args optional - first is # of threads, second # of iterations per thread.
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		Thread thread = null;
		int threads = THREADS;
		int runs = RUNS;
		
		if (args != null) {
			if (args.length >= 1) threads = Integer.parseInt(args[0]);
			if (args.length >= 2) runs = Integer.parseInt(args[1]);
		}
//		Thread churn = new Thread(new Churn(), "Byte array GC churn thread");
//		churn.start();
		System.out.println("Starting " + threads + " threads with " + runs + " measurement iterations...");
		System.out.println("Randomly selecting from " + FONT_NAMES.length + " font families...");
		for (int t = 0; t < threads; t++) {
			thread = new Thread(new GlyphBug(runs), "GlyphLayout Thread " + (t+1));
			thread.start();
		}
		// wait for last thread - I know, may not be last to finish, but we don't care that much.
		if (threads > 0) thread.join();
		synchronized (GlyphBug.class) {
			RUNNING = false;
		}
//		churn.join();
		System.out.println("Done.");
	}

	private int _runs;
	private RandomStringFactory _stringFactory = new RandomStringFactory();
	
	/**
	 * @param runs 
	 */
	public GlyphBug(int runs) {
		_runs = runs;
	}
	
	/**
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		for (int r = 0; r < _runs; r++) {
			AttributedString formattedString = new AttributedString(_stringFactory.getRandomUnicodeString(255));
			formattedString.addAttribute(TextAttribute.FONT, new Font(FONT_NAMES[r % FONT_NAMES.length], Font.BOLD, 12));
			formattedString.addAttribute(TextAttribute.BACKGROUND, Color.RED);
			formattedString.addAttribute(TextAttribute.FOREGROUND, Color.WHITE);

			AttributedCharacterIterator text = formattedString.getIterator();
			LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(text, LINE_BREAK_FONT_RENDER_CONTEXT);
			while (lineMeasurer.getPosition() < text.getEndIndex()) {
				// this is the call that hits the bug under load on multiprocessor systems
				lineMeasurer.nextLayout(100.0f);
			}
		}
		System.out.println(Thread.currentThread().getName() + " done.");
	}

}

class RandomStringFactory {
	/**
	 * array of all valid non-control Unicode characters
	 */
	private static char[] CHARACTERS;
	
	static {
		StringBuilder b = new StringBuilder(Character.MAX_VALUE);
		for (int i=Character.MIN_CODE_POINT+1; i <= Character.MAX_VALUE; i++) {
			if (Character.isLetterOrDigit(i) || Character.isWhitespace(i) ) b.append((char) i);
		}
		CHARACTERS = new char[b.length()];
		b.getChars(0, b.length(), CHARACTERS, 0);
		System.out.println("Using a pool of " + CHARACTERS.length + " Unicode characters");
	}
	
	private Random _rand;
	
	/**
	 * default constructor - creates a default {@link Random} instance
	 */
	public RandomStringFactory() {
		_rand = new Random();
	}
	
	/**
	 * Creates a new {@link Random} with the given seed, to produce a well-defined, evenly distributed series.
	 * @param seed
	 */
	public RandomStringFactory(long seed) {
		_rand = new Random(seed);
	}
	
	/**
	 * @param length
	 * @return random string from all Unicode letter, number, and whitespace characters with spaces every 10th character
	 */
	public String getRandomUnicodeString(int length) {
		char[] chars = new char[length];
		int x = 0;
		while (x < chars.length) {
			if (x % 10 == 0) chars[x++] = ' ';
			else chars[x++] = CHARACTERS[_rand.nextInt(CHARACTERS.length)];
		}
		return new String(chars, 0, chars.length);
	}
}
                                     
2012-06-12
RULE 2D_Font/standalone/EndlessLoopTest Crash any
                                     
2013-06-21
Functional test has not failed since it was added to repo.
                                     
2013-11-08



Hardware and Software, Engineered to Work Together