QuantumRenderer modifies buffer in use by JavaFX Application Thread
Component
Type: Bug
Product/Category: JavaFX
Subcategory: Graphics
Release: 8 Update 112 (1.8.0_112)
Operating System: Ubuntu - 64 bit
Details
More details about the problem (★ indicates required fields).
★ Synopsis:
QuantumRenderer modifies buffer in use by JavaFX Application Thread
Full OS version:
Ubuntu 14.04.5 LTS with Linux kernel version: Linux ws490 4.4.0-45-generic #66~14.04.1-Ubuntu SMP Wed Oct 19 15:05:38 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
.
Additional Configuration Information:
The Java VM is Oracle JDK 8 Update 112 with the OpenJFX 8 x86egl overlay bundle built from sources on October 27, 2016. The test machine is a Dell Precision 490 with 8 GB of RAM and 8 logical processors (two 3.0 GHz Intel Xeon 5050 dual-core processors with hyper-threading enabled).
Development Kit or Runtime version:
java version "1.8.0_112"
Java(TM) SE Runtime Environment (build 1.8.0_112-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.112-b15, mixed mode)
★ Description:
The JavaFX Application Thread calls the put(IntBuffer)
method in java.nio.IntBuffer
to copy its frame buffer, shown below with comments added.
public IntBuffer put(IntBuffer src) {
if (src == this)
throw new IllegalArgumentException();
if (isReadOnly())
throw new ReadOnlyBufferException();
// BEGIN critical section for buffer position
int n = src.remaining();
if (n > remaining())
throw new BufferOverflowException();
for (int i = 0; i < n; i++)
put(src.get()); // Line #771 in stack trace
// END critical section for buffer position
return this;
}
At the same time, the QuantumRenderer can call the getPixels()
method in com.sun.glass.ui.Pixels
, shown below, which sets the buffer's position to zero by rewinding it.
The QuantumRenderer calls the getPixels()
method while trying to find a buffer that's not in use, yet in doing so it can inadvertently modify a buffer that's in use by the JavaFX Application Thread.
public final Buffer getPixels() {
if (this.bytes != null) {
this.bytes.rewind();
return this.bytes;
} else if (this.ints != null) {
this.ints.rewind(); // Line #164 in stack trace
return this.ints;
} else {
throw new RuntimeException("Unexpected Pixels state.");
}
}
The call stacks of both threads when the problem occurs are shown below.
"JavaFX Application Thread"
at java.nio.IntBuffer.put(IntBuffer.java:771)
at com.sun.glass.ui.monocle.Framebuffer.composePixels(Framebuffer.java:168)
at com.sun.glass.ui.monocle.HeadlessScreen.uploadPixels(HeadlessScreen.java:118)
at com.sun.glass.ui.monocle.MonocleView._uploadPixels(MonocleView.java:95)
at com.sun.glass.ui.View.uploadPixels(View.java:771)
at com.sun.prism.PresentableState.uploadPixels(PresentableState.java:295)
at com.sun.javafx.tk.quantum.SceneState.access$001(SceneState.java:40)
at com.sun.javafx.tk.quantum.SceneState.lambda$uploadPixels$0(SceneState.java:123)
at com.sun.javafx.tk.quantum.SceneState$$Lambda$105.1914611323.run
at com.sun.glass.ui.monocle.RunnableProcessor.runLoop(RunnableProcessor.java:92)
at com.sun.glass.ui.monocle.RunnableProcessor.run(RunnableProcessor.java:51)
at java.lang.Thread.run(Thread.java:745)
"QuantumRenderer-0"
at com.sun.glass.ui.Pixels.getPixels(Pixels.java:164)
at com.sun.prism.impl.QueuedPixelSource.usesSameBuffer(QueuedPixelSource.java:103)
at com.sun.prism.impl.QueuedPixelSource.getUnusedPixels(QueuedPixelSource.java:129)
at com.sun.javafx.tk.quantum.UploadingPainter.run(UploadingPainter.java:147)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:125)
at java.lang.Thread.run(Thread.java:745)
★ Frequency:
☐ Always
☒ Often
☐ Occasionally
☐ Rarely
Regression:
Unknown.
Steps to Reproduce:
The JavaFX application WavingApp can reproduce the problem in two ways, depending on the Monocle platform:
- The Monocle VNC platform lets you see the problem on screen.
- The Monocle Headless platform lets you trap the problem in the debugger.
See the NetBeans project at jfxtest for the predefined run configurations described below.
The VM options for the Monocle VNC platform are: -Dglass.platform=Monocle -Dmonocle.platform=VNC -Dprism.order=sw
.
The application parameters are: width=1024, height=600
.
Run with this configuration and connect with a VNC client to see the problem on screen.
The VM options for the Monocle Headless platform are: -Dglass.platform=Monocle -Dmonocle.platform=Headless -Dprism.order=sw -Xint
.
The application parameters are: width=1280, height=800
.
The VM option -Xint
(interpreted mode execution only) is not required, but it helps reproduce the problem by slowing down the program's execution.
Set the breakpoint defined below to trap the error and stop all threads, and debug the test application using this run configuration.
The debugger traps the error within seconds on my computer.
File: /com/sun/glass/ui/Pixels.java
Line number: 164
Condition: ints.position() > 0 && ints.position() < (1280 * 800)
Suspend: All threads
Expected Result:
The animated GIF below is a screencast of the expected result using the default run configuration and captured with the Byzanz screencast creator. Notice that Duke is always centered in the window.
Actual Result:
The animated GIF below is a screencast of the actual result using the Monocle VNC run configuration and the Remmina remote desktop client with a VNC connection to the local host. Notice that Duke jumps around in the window when the buffer's position is reset while it's being copied.
The three images below are screenshots of the actual results using the Monocle Headless run configuration and captured with the debugger. Notice that Duke is not centered in the frame.
Error Message(s)/Crash Logs:
To see the buffer position while debugging with the Monocle Headless platform, set the following watch variable:
ints.toString()
When the program stops at the breakpoint Pixels.java:164
, you will see values for the watch variable like the following:
java.nio.DirectIntBufferU[pos=5172 lim=1024000 cap=1024000]
java.nio.DirectIntBufferU[pos=852 lim=1024000 cap=1024000]
java.nio.DirectIntBufferU[pos=711 lim=1024000 cap=1024000]
The value of pos
shows the source buffer's position being used by the JavaFX Application Thread to copy the buffer just before the QuantumRenderer rewinds it, setting its position back to zero.
Source code for an executable test case:
The source code is in WavingApp.java, a simple JavaFX test application that displays an animated GIF of Duke waving at 8⅓ frames per second. You can build the application with the NetBeans project at jfxtest.
Workaround:
The workaround is to use a port of OpenJFX whose NativeScreen implementation calls ByteBuffer.allocateDirect(int)
for the frame buffer instead of ByteBuffer.allocate(int)
.
I was unable to reproduce the problem when using a direct byte buffer with the put(IntBuffer)
method in java.nio.DirectIntBufferU
, shown below.
public IntBuffer put(IntBuffer src) {
if (src instanceof DirectIntBufferU) {
if (src == this)
throw new IllegalArgumentException();
DirectIntBufferU sb = (DirectIntBufferU)src;
int spos = sb.position(); // Get buffer position
int slim = sb.limit();
assert (spos <= slim);
int srem = (spos <= slim ? slim - spos : 0);
int pos = position();
int lim = limit();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (srem > rem)
throw new BufferOverflowException();
unsafe.copyMemory(sb.ix(spos), ix(pos), (long)srem << 2);
sb.position(spos + srem);
position(pos + srem);
} else if (src.hb != null) {
// ...
} else {
// ...
}
return this;
}
★ Severity:
The problem can occur for any Monocle platform that uses a non-direct byte buffer to create its frame buffer, such as the Monocle Headless and Monocle VNC platforms.
☐ Can not make any progress until this bug is resolved.
☐ Difficult to make even minimal progress without resolving this bug.
☒ Some progress is possible without resolving this bug.
☐ No Impact.
User Information
★ Your Name: John Neffenger
★ Company: Status Six Communications
★ Email: john@status6.com