Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Viewer: Synchronize viewport re-creation in EDT
There are some CConn methods that access the viewport object in the RFB thread: - setName(), setCursor(), enableGII(), giiDeviceCreated(), enableQEMUExtKeyEventExt(), setLEDState() * invoked when a specific type of RFB message is received from the VNC server - framebufferUpdateStart() - framebufferUpdateEnd() * if firstUpdate is set and the desktop resizing mode is not "Server" - beginRect() * if the RFB encoding has changed There are also some CConn methods that re-create the viewport directly in the EDT, as opposed to creating it in the RFB thread via SwingUtilities.invokeAndWait(): - getOptions() * if the toolbar is visible and "View only" was toggled in the Options dialog * if the viewer is in windowed mode and "Show toolbar" was toggled in the Options dialog * if the viewer is not in macOS full-screen mode and "Scaling factor" was changed in the Options dialog * if the viewer is not in macOS full-screen mode and "Remote desktop size" was changed to something other than "Server" in the Options dialog * if the viewer is not in macOS full-screen mode and "Span mode" was changed in the Options dialog - zoomIn(), zoomOut(), zoom100(), toggleToolbar(), toggleFullScreen(), toggleViewOnly() In theory, if any operation from the first group coincides with any operation from the second group, then the viewport object might be accessed while it is being re-created. The specific issue I observed was triggered by the following procedure: - Run GLXSpheres with VirtualGL in a TurboVNC session. - Pop up the TurboVNC Viewer Options dialog, choose a remote desktop size that is larger than the client's screen, and click OK. - Pop up the TurboVNC Viewer Options dialog, set the remote desktop size to "Auto", and click OK. Setting the remote desktop size to "Auto" causes CConn.getOptions() to set firstUpdate, which causes CConn.framebufferUpdateEnd() to invoke CConn.sendDesktopSize(), which invokes CConn.computeScreenLayout(). Since framebuffer updates were being received rapidly from the server, it was highly likely (100% reproducible on my systems) that CConn.computeScreenLayout() would be invoked before the new viewport was visible, which triggered an IllegalComponentStateException when CConn.computeScreenLayout() attempted to invoke viewport.getContentPane().getLocationOnScreen(). Since all of the methods in the first group are guarded by the CConn/CConnection monitor lock in CConnection.processMsg(), the solution was to obtain a CConn/CConnection monitor lock for all invocations of CConn.recreateViewport() in the second group. We already know that this won't cause a deadlock, because in several places, CConn.recreateViewport() is invoked via SwingUtilities.invokeAndWait() (which causes it to execute in the EDT) while CConnection.processMsg() owns the CConn/CConnection monitor lock. (However, note that this is also the reason why we can't simply add a 'synchronized' keyword to CConn.recreateViewport().) The specific failure mode that I observed was by far the most likely failure mode, since other possible failure modes would have required very unfortuitous timing. It is also unknown whether other failure modes would have triggered an exception or other visible problems. They might have been silent, leading to unexpected but not necessarily fatal behavior.
- Loading branch information