Details
-
Bug
-
Resolution: Fixed
-
P4
-
11, 15, 16
-
b24
-
generic
-
generic
Description
A DESCRIPTION OF THE PROBLEM :
Calling fromRGB or toRGB (or any other conversion functions) isn't thread safe in ICC_ColorSpace. There is a one-time init at the start of each function that isn't guarded by any lock. The code almost gets away with it by using transients, but the transient lookup table is set before a call to setComponentScaling which occasionally causes NullPointerExceptions when calling conversion functions from multiple threads.
An easy solution would be to mark ICC_ColorSpace as not-thread-safe, but the issue is that java.awt.color.ColorSpace.getInstance returns lazily-initialized singletons of this class. It seems like the current design requires either returning new instances to each caller or just synchronizing the ColorSpace class.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
You can run the repro
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Prints infinitely
Try 1
OK
Try 2
OK
ACTUAL -
Try 43
java.lang.NullPointerException
at java.desktop/java.awt.color.ICC_ColorSpace.toRGB(ICC_ColorSpace.java:183)
at Repro.lambda$test$0(Repro.java:20)
at java.base/java.lang.Thread.run(Thread.java:832)
In less than 10 seconds or so.
---------- BEGIN SOURCE ----------
import java.awt.color.*;
import java.util.concurrent.*;
public final class Repro {
public static final void main(String[] args) throws Throwable {
for (int i = 0; true; i++) {
System.err.println("Try " + i);
test(new ICC_ColorSpace(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
}
}
public static void test(final ColorSpace cs) throws Throwable {
Thread[] ts = new Thread[10];
final CountDownLatch latch = new CountDownLatch(ts.length);
for (int i = 0; i < ts.length; i++) {
ts[i] =
new Thread(
() -> {
latch.countDown();
try {
latch.await();
} catch (InterruptedException ex) {
}
try {
cs.toRGB(new float[3]);
} catch (Throwable t) {
t.printStackTrace();
System.exit(1);
}
});
}
for (Thread t : ts) {
t.start();
}
for (Thread t : ts) {
t.join();
}
System.err.println("OK");
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Since these are static inits, you could just call fromRGB/toRGB/etc in a synchronized context before using the color spaces - or alternatively, always synchronize calls to conversion routines yourself.
FREQUENCY : rarely
Calling fromRGB or toRGB (or any other conversion functions) isn't thread safe in ICC_ColorSpace. There is a one-time init at the start of each function that isn't guarded by any lock. The code almost gets away with it by using transients, but the transient lookup table is set before a call to setComponentScaling which occasionally causes NullPointerExceptions when calling conversion functions from multiple threads.
An easy solution would be to mark ICC_ColorSpace as not-thread-safe, but the issue is that java.awt.color.ColorSpace.getInstance returns lazily-initialized singletons of this class. It seems like the current design requires either returning new instances to each caller or just synchronizing the ColorSpace class.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
You can run the repro
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Prints infinitely
Try 1
OK
Try 2
OK
ACTUAL -
Try 43
java.lang.NullPointerException
at java.desktop/java.awt.color.ICC_ColorSpace.toRGB(ICC_ColorSpace.java:183)
at Repro.lambda$test$0(Repro.java:20)
at java.base/java.lang.Thread.run(Thread.java:832)
In less than 10 seconds or so.
---------- BEGIN SOURCE ----------
import java.awt.color.*;
import java.util.concurrent.*;
public final class Repro {
public static final void main(String[] args) throws Throwable {
for (int i = 0; true; i++) {
System.err.println("Try " + i);
test(new ICC_ColorSpace(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
}
}
public static void test(final ColorSpace cs) throws Throwable {
Thread[] ts = new Thread[10];
final CountDownLatch latch = new CountDownLatch(ts.length);
for (int i = 0; i < ts.length; i++) {
ts[i] =
new Thread(
() -> {
latch.countDown();
try {
latch.await();
} catch (InterruptedException ex) {
}
try {
cs.toRGB(new float[3]);
} catch (Throwable t) {
t.printStackTrace();
System.exit(1);
}
});
}
for (Thread t : ts) {
t.start();
}
for (Thread t : ts) {
t.join();
}
System.err.println("OK");
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Since these are static inits, you could just call fromRGB/toRGB/etc in a synchronized context before using the color spaces - or alternatively, always synchronize calls to conversion routines yourself.
FREQUENCY : rarely