karakun / OpenWebStart

Run Web Start based applications after the release of Java 11
https://openwebstart.com
Other
416 stars 48 forks source link

Classloading issue based on EDT internal classloader #201

Open SonicSunset opened 4 years ago

SonicSunset commented 4 years ago

Hi, in our application we use JPopupMenus for context-menus. When using OpenWebstart the popup-menus are not displayed, only very small rectangles (i.e. the borders of a context menu without any content).

However, what is very weird is that the popup-menus are displayed when configuring in itw-settings.exe in the Logging section that the Log-Window should always be shown! This problem is independent of the underlying JRE (in our case Corretto 1.8.0_222 and Oracle 1.8.0_231). When using Oracle Webstart we don't have this problem.

OS: Windows 10 OpenWebstart Version.: 1.1.4 (also on 1.0) Java Versions: Amazon Corretto 1.8.0_222 and Oracle 1.8.0_231

The Context-Menu not shown is of javax.swing.JPopupMenu class

sclassen commented 4 years ago

can you share your application so we can reproduce this issue?

SonicSunset commented 4 years ago

I'll have to ask my boss next week but I think the answer will be "No". Until then, is there any other information I can provide you with in order to reproduce?

janakmulani commented 4 years ago

We tested with Oracle 8 u 172. The Popup Menu in our app works with or without the Log windows open. We need more info about your scenario. Can you share the link for the app? Do you have any VM args specified in your jnlp?

Code:

JPopupMenu popUpMenu = new JPopupMenu();
JMenu buttonMenu = new JMenu("ButtonMenu");
popUpMenu.add(buttonMenu);
popUpMenu.add(new JMenuItem("Menu1"));
popUpMenu.add(new JMenuItem("Menu2"));

JButton checkClassButton = new JButton("Check if class is on classpath");
checkClassButton.setComponentPopupMenu(popUpMenu);
SonicSunset commented 4 years ago

Unfortunately the app is only used in our client's intranet, so I can't provide a link. We have the following VM args provided in our JNLP:

j2se version="1.7+" href="http://java.sun.com/products/autodl/j2se" initial-heap-size="64m" max-heap-size="256m" java-vm-args="-XX:+HeapDumpOnOutOfMemoryError" />

What I have observed though is that when I "hide" the log window this also helps, ie. the JPopups content will be rendered. It's not rendered when disabling the log-Window. It then looks like the thing in the red circle in the screenshot (ie. a frame without any content)

image

With debug window displayed or hidden: image

FrankBusse commented 4 years ago

We observe the same or a similar problem in our application with JComboBox. If the console is enabled, everything works fine. But if the console is disabled, the ComponentUI for javax.swing.JComboBox is not found. By the way, the same problem occurs with the pure icedtea-web.

[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] UIDefaults.getUI() failed: no ComponentUI class for: javax.swing.JComboBox[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=8,maximumSize=,minimumSize=,preferredSize=,isEditable=false,lightWeightPopupEnabled=true,maximumRowCount=8,selectedItemReminder=]
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] java.lang.Error
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020]   at javax.swing.UIDefaults.getUIError(UIDefaults.java:731)
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020]   at javax.swing.MultiUIDefaults.getUIError(MultiUIDefaults.java:130)
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020]   at javax.swing.UIDefaults.getUI(UIDefaults.java:761)
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020]   at javax.swing.UIManager.getUI(UIManager.java:1016)
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020]   at javax.swing.JComboBox.updateUI(JComboBox.java:266)
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020]   at javax.swing.JComboBox.init(JComboBox.java:231)
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020]   at javax.swing.JComboBox.<init>(JComboBox.java:225)
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020]   at com.jidesoft.combobox.AbstractComboBox.createDefaultButton(AbstractComboBox.java:3814)
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020]   at com.jidesoft.combobox.AbstractComboBox.initComponent(AbstractComboBox.java:468)
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020]   at com.jidesoft.combobox.AbstractComboBox.initComponent(AbstractComboBox.java:309)
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020]   at com.jidesoft.combobox.ColorComboBox.<init>(ColorComboBox.java:55)
[ERROR_ALL][Tue Mar 10 15:04:48 CET 2020] 
sclassen commented 4 years ago

Could you share your application so we can try to reproduce the behavior?

SonicSunset commented 4 years ago

We have found out that we don't seem to have this problem anymore after disabling any platform-specific look&feel and using the standard Swing L&F.

Maybe that may serve as a workaround for you?

Originally we have used Jide's

LookAndFeelFactory.installDefaultLookAndFeelAndExtension();

and then tried

UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

and had problems with both.

janakmulani commented 4 years ago

In my application's main method I use:

UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

and I cannot reproduce the issue with oracle 1.8.0_172

You say that in your application (that is started by OWS) you use System or Jide's LnF and you are getting the issue with Amazon 1.8 u 222 and Oracle 1.8 u 231.

OWS itself uses UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

Popup and combo not showing up implies something to do with heavy and light weight components. Need to investigate further...

FrankBusse commented 4 years ago

Could you share your application so we can try to reproduce the behavior?

Sorry, unfortunately that's not possible. The application is quite complex and it needs a server part to run. Furthermore it is closed source.

FrankBusse commented 4 years ago

We have found out that we don't seem to have this problem anymore after disabling any platform-specific look&feel and using the standard Swing L&F.

That's interesting, we are also using Jide and also ran into this problem. I've been playing around with the L&F a bit, but it didn't help. The only thing that helps is turning on the console.

But the problem doesn't seem to be related only to Jide, because we have another class that is not found. This is the WebSocket implementation.

Caused by: java.lang.RuntimeException: Could not find an implementation class.
    at javax.websocket.ContainerProvider.getWebSocketContainer(ContainerProvider.java:73)
    at org.nustaq.kontraktor.remoting.http.JSR356CryptoProxyClientConnector$WSCryptoProxyClientEndpoint.<init>(JSR356CryptoProxyClientConnector.java:161)
    ... 20 more

This error also does not occur when the console is switched on.

I just discovered that there is a similar sounding issue in the IcedTea-Web project. ...apps not working correctly without console...

janakmulani commented 4 years ago

Tested with JGoodies and Jide Demo. Popups in combos, menus, rt-clk menu in file chooser work. See the jnlp files. Please try.

looksdemo.jnlp.txt jide_demo.jnlp.txt

Gr33nbl00d commented 4 years ago

We have a problem which seems to be related to this. We use Synthetica Look and Feel btw.

UIDefaults.getUI() failed: no ComponentUI class for: javax.swing.JRootPane[,0,0,0x0,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=1,maximumSize=,minimumSize=,preferredSize=]
java.lang.Error
    at javax.swing.UIDefaults.getUIError(UIDefaults.java:731)
    at javax.swing.MultiUIDefaults.getUIError(MultiUIDefaults.java:130)
    at javax.swing.UIDefaults.getUI(UIDefaults.java:761)
    at javax.swing.UIManager.getUI(UIManager.java:1016)
    at javax.swing.JRootPane.updateUI(JRootPane.java:484)
    at javax.swing.JRootPane.<init>(JRootPane.java:371)
    at javax.swing.JInternalFrame.createRootPane(JInternalFrame.java:362)
    at javax.swing.JInternalFrame.<init>(JInternalFrame.java:339)
    at javax.swing.JInternalFrame.<init>(JInternalFrame.java:269)
    at de.javasoft.plaf.synthetica.SyntheticaLookAndFeel.installCompatibilityDefaults(SyntheticaLookAndFeel.java:1193)
    at de.javasoft.plaf.synthetica.SyntheticaLookAndFeel.installCompatibilityDefaults(SyntheticaLookAndFeel.java:870)
    at de.javasoft.plaf.synthetica.SyntheticaLookAndFeel.setFont(SyntheticaLookAndFeel.java:1548)
    at de.javasoft.plaf.synthetica.StyleFactory.getStyle(StyleFactory.java:355)
    at de.javasoft.plaf.synthetica.StyleFactory.getStyle(StyleFactory.java:246)
    at javax.swing.plaf.synth.SynthLookAndFeel.getStyle(SynthLookAndFeel.java:235)
    at javax.swing.plaf.synth.SynthLookAndFeel.updateStyle(SynthLookAndFeel.java:256)
    at javax.swing.plaf.synth.SynthButtonUI.updateStyle(SynthButtonUI.java:79)
    at javax.swing.plaf.synth.SynthButtonUI.installDefaults(SynthButtonUI.java:62)
    at javax.swing.plaf.basic.BasicButtonUI.installUI(BasicButtonUI.java:88)
    at javax.swing.JComponent.setUI(JComponent.java:666)
    at javax.swing.AbstractButton.setUI(AbstractButton.java:1810)
    at javax.swing.JButton.updateUI(JButton.java:147)
    at javax.swing.AbstractButton.init(AbstractButton.java:2176)
    at javax.swing.JButton.<init>(JButton.java:137)
    at javax.swing.JButton.<init>(JButton.java:91)
    at de.javasoft.plaf.synthetica.SyntheticaLookAndFeel.installCompatibilityDefaults(SyntheticaLookAndFeel.java:968)
    at 

I could track down the problem that Swing is unable to load a class, the error above is just the result of not finding the class. Usualy the next exception below is not logged or printed i was only able to find it by debugging openwebstart:

Class.forName fails. But the jar containing the class is in the jnlp and also downloaded in the cache. No problem if using oracle javaws with same JNLP

java.lang.ClassNotFoundException: de.javasoft.plaf.synthetica.SyntheticaRootPaneUI
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at javax.swing.SwingUtilities.loadSystemClass(SwingUtilities.java:1879)
    at javax.swing.UIDefaults.getUIClass(UIDefaults.java:685)
    at javax.swing.UIDefaults.getUI(UIDefaults.java:757)
    at javax.swing.UIManager.getUI(UIManager.java:1016)
    at javax.swing.JRootPane.updateUI(JRootPane.java:484)
    at javax.swing.JRootPane.<init>(JRootPane.java:371)
    at javax.swing.JInternalFrame.createRootPane(JInternalFrame.java:362)
    at javax.swing.JInternalFrame.<init>(JInternalFrame.java:339)
    at javax.swing.JInternalFrame.<init>(JInternalFrame.java:269)
    at de.javasoft.plaf.synthetica.SyntheticaLookAndFeel.installCompatibilityDefaults(SyntheticaLookAndFeel.java:1193)
    at de.javasoft.plaf.synthetica.SyntheticaLookAndFeel.installCompatibilityDefaults(SyntheticaLookAndFeel.java:870)
    at de.javasoft.plaf.synthetica.SyntheticaLookAndFeel.setFont(SyntheticaLookAndFeel.java:1548)
    at de.javasoft.plaf.synthetica.StyleFactory.getStyle(StyleFactory.java:355)
    at de.javasoft.plaf.synthetica.StyleFactory.getStyle(StyleFactory.java:246)
    at javax.swing.plaf.synth.SynthLookAndFeel.getStyle(SynthLookAndFeel.java:235)
    at javax.swing.plaf.synth.SynthLookAndFeel.updateStyle(SynthLookAndFeel.java:256)
    at javax.swing.plaf.synth.SynthButtonUI.updateStyle(SynthButtonUI.java:79)
    at javax.swing.plaf.synth.SynthButtonUI.installDefaults(SynthButtonUI.java:62)
    at javax.swing.plaf.basic.BasicButtonUI.installUI(BasicButtonUI.java:88)
    at javax.swing.JComponent.setUI(JComponent.java:666)
    at javax.swing.AbstractButton.setUI(AbstractButton.java:1810)
    at javax.swing.JButton.updateUI(JButton.java:147)
    at javax.swing.AbstractButton.init(AbstractButton.java:2176)
    at javax.swing.JButton.<init>(JButton.java:137)
    at javax.swing.JButton.<init>(JButton.java:91)
    at de.javasoft.plaf.synthetica.SyntheticaLookAndFeel.installCompatibilityDefaults(SyntheticaLookAndFeel.java:968)
    at de.javasoft.plaf.synthetica.SyntheticaLookAndFeel.installCompatibilityDefaults(SyntheticaLookAndFeel.java:870)
sclassen commented 4 years ago

If this looks like it could be classloader related than please try the 3.0.0-alpha1 release. It has a new classloader implementation which fixes many issues

Gr33nbl00d commented 4 years ago

I dont know much about the classloading possibilities. So i am just guessing here. Could this be related to the fact that the eventdispatch thread ist startet before you set some webstart classloader so this thread uses the wrong classloader?

Gr33nbl00d commented 4 years ago

Ok i will check this tomorrow :)

janakmulani commented 4 years ago

I dont know much about the classloading possibilities. So i am just guessing here. Could this be related to the fact that the eventdispatch thread ist startet before you set some webstart classloader so this thread uses the wrong classloader?

Hi,

I would like to reproduce this issue on a simple Swing app. Can you please tell me the exact version of Synthetica lib and the exact name of the LnF class you are uisng from that lib? Thanks..

Gr33nbl00d commented 4 years ago

3.0.0-alpha1 did not fix the problem, however i think i found the reason behind it. The problem is that the eventdispatch queue thread somehow not uses the JNLPClassloader when starting the real application.

The reason is that if no window is open anymore then awt initiates an auto shutdown, stops the event dispatch queue threads (this also explains why it works to let the console open as than no auto shutdown occurs). Later the eventdispatch thread is created again when starting the real application. The problem is that the classloader is stored in a singleton (EventQueue.class, field classLoader). So if the edt is created again it uses the wrong classloader.

I got to it by finding this old bug which is quite similar: https://bugs.openjdk.java.net/browse/JDK-8017776

For reproduction it should be sufficient to add a delay off a few seconds to the main method of the launched application and before the start of the first invokelater or invokeandwait command. So the Autoshutdown is triggered. In the AWTAutoShutdown class the delay seems to be 1 second.

A workarround which worked for me was hack i added to my main method inside my swing applicaiton:

try
{
    // Change context in all future threads
    final Field field = EventQueue.class.getDeclaredField("classLoader");
    field.setAccessible(true);
    final EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
    field.set(eventQueue, Thread.currentThread().getContextClassLoader());
}
catch (Exception e)
{
    e.printStackTrace();
}
Gr33nbl00d commented 4 years ago

Regarding a fix this could be tricky, as the EDT is initialized for use for the splashscreen and dowloadprogess. At that time the classloader is not yet available... But i might have an idea how to fix it anyway. You could create a delegating proxy classloader which first delegates classloading to the normal classloader and than later when the jnlpclassloader is available you could inject the jnlpclassloader into the delegating classloader

janakmulani commented 4 years ago

Can you please provide the exact version of Synthetica LnF lib and the exact name of the LnF class you are using from that lib? Thanks..

hendrikebbers commented 4 years ago

@Gr33nbl00d great work! Your description makers sense. Let me think about a possible solution. I assume a "real" Java proxy would create more problems than it solves. Maybe we can change the class loader hierarchy.

Gr33nbl00d commented 4 years ago

Thank you :) Yes i also thought there has to be a better way than a "real" java proxy as this would also not be good for performance. But here i hand it over to you as you seem to have more knowledge about classloaders and already some idea ;)

FoxyBOA commented 4 years ago

Expected the same strange behavior - the first start works correctly for both (1.1.7 and 3.0 Alpha). Any following starts are leading to UIDefaults.getUI() failed: no ComponentUI class for: javax.swing.JTextArea and similar exceptions.

@Gr33nbl00d Thanks for helpful workarounds

hendrikebbers commented 4 years ago

@FoxyBOA @Gr33nbl00d @FrankBusse @SonicSunset Can anyone of you provide a small sample that we can use to reproduce the error? I created several sample with Classloader and multithreading handling in it and I can not reproduce it :(

Gr33nbl00d commented 4 years ago

@hendrikebbers I created an example project: https://github.com/Gr33nbl00d/openwebstart-example Important is that the debug console is disabled so no awt window is active.

in the main method is a Thread.sleep if it is set to something low for example 50msec than the shutdown does not trigger. The EDT Thread still is alive and using the jnlp classloader.

When using a highter sleep time of 5000 msec The application crashes by a class not found exception because EDT autoshutdown occured and after restart of EDT the edt thread uses wrong classloader.

With 5000 msec bad

With 50 msec good

net.sourceforge.jnlp.LaunchException: Fatal: Launch Error: Could not launch JNLP file. The application has not been initialized, for more information execute javaws/browser from the command line and send a bug report. at net.sourceforge.jnlp.Launcher.launchApplication(Launcher.java:407) at net.sourceforge.jnlp.Launcher.access$200(Launcher.java:69) at net.sourceforge.jnlp.Launcher$TgThread.run(Launcher.java:651) Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at net.sourceforge.jnlp.Launcher.launchApplication(Launcher.java:400) ... 2 more Caused by: java.lang.reflect.InvocationTargetException at java.awt.EventQueue.invokeAndWait(EventQueue.java:1349) at java.awt.EventQueue.invokeAndWait(EventQueue.java:1324) at javax.swing.SwingUtilities.invokeAndWait(SwingUtilities.java:1353) at de.greenblood.openwebstart.example.Hello.main(Hello.java:22) ... 7 more Caused by: java.lang.RuntimeException: Unable to initialize LaF for class name: com.alee.laf.WebLookAndFeel at com.alee.utils.LafUtils.setupLookAndFeel(LafUtils.java:210) at com.alee.utils.LafUtils.setupLookAndFeel(LafUtils.java:193) at com.alee.laf.WebLookAndFeel.install(WebLookAndFeel.java:1120) at com.alee.laf.WebLookAndFeel.install(WebLookAndFeel.java:1099) at com.alee.laf.WebLookAndFeel.install(WebLookAndFeel.java:1087) at de.greenblood.openwebstart.example.Hello$1.run(Hello.java:26) at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:301) at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758) at java.awt.EventQueue.access$500(EventQueue.java:97) at java.awt.EventQueue$3.run(EventQueue.java:709) at java.awt.EventQueue$3.run(EventQueue.java:703) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74) at java.awt.EventQueue.dispatchEvent(EventQueue.java:728) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:205) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93) at java.awt.EventDispatchThread.run(EventDispatchThread.java:82) Caused by: java.lang.ClassNotFoundException: com.alee.laf.WebLookAndFeel at java.net.URLClassLoader.findClass(URLClassLoader.java:382) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348) at javax.swing.SwingUtilities.loadSystemClass(SwingUtilities.java:1879) at javax.swing.UIManager.setLookAndFeel(UIManager.java:582) at com.alee.utils.LafUtils.setupLookAndFeel(LafUtils.java:206) ... 19 more

janakmulani commented 4 years ago

@Gr33nbl00d Thanks for providing this sample. I can reproduce the issue. We will investigate and report.

hendrikebbers commented 4 years ago

We did some tests by checking the stored classloader of the EDT:

public static void debugEventQueue() {
  try {
    final Field classLoaderField = EventQueue.class.getDeclaredField("classLoader");
    classLoaderField.setAccessible(true);
    final EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
    final ClassLoader classLoader = (ClassLoader) classLoaderField.get(eventQueue);
    System.out.println("Classloader '" + classLoader + "' is used in EventQueue '" + eventQueue + "'");
  } catch (final Exception e) {
    e.printStackTrace();
  }
}

Looks like the classloader is always the same instance. Even directly before the exception. This one needs further investigation. Sadly we do not have the resources at the moment to spend much time on it. @Gr33nbl00d help is more than welcome :)

Gr33nbl00d commented 4 years ago

Yes the classloader inside EventQueue is always the same because it is only initialized during startup. it temporarly works because the currently active thread has the right classloader set by the method updating all threads with correct classloader. But after awtautoshutdown a new thread is created with the classloader in EventQueue which is still the normal classloader not the jnlp one.

douglasawh commented 1 year ago

@Gr33nbl00d is this still meant to be left open after https://github.com/AdoptOpenJDK/IcedTea-Web/pull/679 ?