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 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
|