JDK-7027300 : Unsynchronized HashMap access causes endless loop
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 6,6u10
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS:
    linux_suse_sles_11,solaris_10,windows_7 linux_suse_sles_11,solaris_10,windows_7
  • CPU: x86
  • Submitted: 2011-03-14
  • Updated: 2019-04-24
  • Resolved: 2012-06-27
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.
6u33Fixed 7u6Fixed 8 b45Fixed
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Relates :  
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)

Solaris 10 10/09 s10x_u8wos_08a X86
SunOS 5.10 Generic_142901-06 i86pc i386 i86pc

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)

difficult to reproduce, but we think the code tells enough to tell you about the thread safety.

This bug can be reproduced rarely.

Functional test has not failed since it was added to repo.

RULE 2D_Font/standalone/EndlessLoopTest Crash any

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); } }

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

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

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