JDK-4665132 : Webstart not returning ContextClassLoader consistently under jre 1.4.0
  • Type: Bug
  • Component: deploy
  • Sub-Component: webstart
  • Affected Version: 1.0.1,1.4.0
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_98,windows_nt
  • CPU: x86
  • Submitted: 2002-04-09
  • Updated: 2003-04-12
  • Resolved: 2002-11-19
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
1.4.2 mantisFixed
Description
RATION" section under "Steps to Reproduce")

This bug can be reproduced always.

---------- BEGIN SOURCE ----------
TEST FILES
We developed the following test program 'JAWSConsoleBug.java' to demonstrate
this bug. It should be compiled and the resulting classes added to a JAR with
the included manifest 'JAWSConsoleBug.mf'. The JAR should be
named 'JAWSConsoleBug.jar', and it should be signed using the JDK's 'jarsigner'
tool for use as a signed Java Web Start application. The resulting signed JAR
should be launched via Java Web Start using the included JNLP application
descriptor 'JAWSConsoleBug.jnlp'. Note that the 'codebase' attribute of
the '<jnlp>' tag in this file must be modified so that it is correct for the
test system. Note also that each time a change is made to the source of the
test class, the code must be recompiled and the JAR must be updated and re-
signed before it is re-executed. Logging should be enabled in the Java Web
Start Application Manager in order to capture the debug output produced by the
program.

-------- Start of JAWSConsoleBug.java
import java.awt.EventQueue;
import java.awt.event.*;
import javax.swing.*;

public class JAWSConsoleBug
{
	private static void dumpClassLoaders(Thread currThread, Class
currClass) {
		System.out.println("Current thread's context classloader
delegation tree:");
		ClassLoader childLoader = currThread.getContextClassLoader();
		System.out.println(childLoader);
		ClassLoader parentLoader = childLoader.getParent();
		while(parentLoader != null) {
			System.out.println(parentLoader);
			parentLoader = parentLoader.getParent();
		}
		System.out.println("(null) bootstrap class loader");

		System.out.println("\nCurrent class's classloader delegation
tree:");
		childLoader = currClass.getClassLoader();
		System.out.println(childLoader);
		parentLoader = childLoader.getParent();
		while(parentLoader != null) {
			System.out.println(parentLoader);
			parentLoader = parentLoader.getParent();
		}
		System.out.println("(null) bootstrap class loader");
	}

	private static class TestAction extends AbstractAction
	{
		public TestAction() {
			super("Test");
			putValue(Action.MNEMONIC_KEY, new Integer
(KeyEvent.VK_T));
		}

		public void actionPerformed(ActionEvent e) {
			System.out.println("\nMENU EVENT");
			dumpClassLoaders(Thread.currentThread(), this.getClass
());
		}
	}

	public static void main(String[] args) {
		try {
/////////////////// code block #1
//			Thread.currentThread().sleep(5000);
			
/////////////////// code block #2
//			final ClassLoader classLoader =
JAWSConsoleBug.class.getClassLoader();
//			EventQueue.invokeLater(new Runnable() {
//				public void run() {
//					Thread.currentThread
().setContextClassLoader(classLoader);
//				}
//			});

/////////////////// code block #3
//			Thread.currentThread().sleep(5000);

			System.out.println("\nMAIN THREAD");
			dumpClassLoaders(Thread.currentThread(),
JAWSConsoleBug.class);

			final JFrame frame = new JFrame("JAWS Console Bug
Demo");

			JMenuBar menuBar = new JMenuBar();
			JMenu menu = new JMenu("File");
			menu.setMnemonic(KeyEvent.VK_F);
			menu.add(new TestAction());
			menuBar.add(menu);
			frame.setJMenuBar(menuBar);

			JButton button = new JButton("Test");
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					System.out.println("\nBUTTON EVENT");
					dumpClassLoaders(Thread.currentThread
(), this.getClass());
				}
			});
			frame.getContentPane().add(button);

			frame.addWindowListener(new WindowAdapter() {
				public void windowClosing(WindowEvent evt) {
					frame.setVisible(false);
					frame.dispose();
					System.exit(0);
				}
			});
			frame.pack();
			frame.setVisible(true);
		} catch (Exception e) {
			e.printStackTrace(System.out);
			System.exit(1);
		}
	}
}
-------- End of JAWSConsoleBug.java

-------- Start of JAWSConsoleBug.mf
Manifest-Version: 1.0
Created-By: (Electronic Label Technology Inc.)
Main-Class: JAWSConsoleBug
-------- End of JAWSConsoleBug.mf

-------- Start of JAWSConsoleBug.jnlp
<?xml version="1.0" encoding="UTF-8"?>

<jnlp spec="1.0+" codebase="file:///temp/JAWS Console Bug/"
href="JAWSConsoleBug.jnlp">

    <information locale="en">
        <title>JAWS Console Bug</title>
        <vendor>Electronic Label Technology</vendor>
        <description>JAWS Console Bug</description>
	<description kind="short">Demonstrates a Java Web Start classloader
bug.</description>
    </information>
    
    <security><all-permissions/></security>

    <resources>
        <j2se version="1.4+"/>
        <jar href="JAWSConsoleBug.jar" main="true"/>
    </resources>

    <application-desc main-class="JAWSConsoleBug"/>

</jnlp>
-------- End of JAWSConsoleBug.jnlp

---------- END SOURCE ----------

CUSTOMER WORKAROUND :
Nothing definite. The problem *seems* to vanish for us in
most cases if you apply the AWT event dispatch thread
classloader patch recommended for Java Web Start 1.0, *and*
if you insist on having the Java Web Start console enabled.
Alternatively, if the patch is applied late enough in the
main thread (after any startup delays and just before
displaying the GUI), the problem seems to be avoided
regardless of the state of the Java Web Start console.
However, this problem is inconsistent in the symptoms that
it produces, so these approaches may work under some
conditions and not others.
(Review ID: 145754)
======================================================================


Name: nt126004			Date: 04/09/2002


FULL PRODUCT VERSION :
java version "1.4.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-b92)
Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)

FULL OPERATING SYSTEM VERSION :
Windows NT Version 4.0

ADDITIONAL OPERATING SYSTEMS :
Windows 2000


EXTRA RELEVANT SYSTEM CONFIGURATION :
500mhz PIII machine with 256mb of RAM


A DESCRIPTION OF THE PROBLEM :
There seems to be a regression problem with the latest j2se
1.4.0 and its bundled version of webstart. Basically its
presents as being unable to load any external resources
from any jars, icons and even getting a xerces xml schema
parser fail. I've tracked what I think is the cause down to
the method of getting hold of a valid classloader to load
these resources. Under certain circumstances it appears
that calling Thread.currentThread().getContextClassLoader()
(the preferred method mentioned in the sun webstart faq,
and used internally by xerces) returned an invalid system
classloader, rather than the jnlp classloader. This rogue
classloader can only return classes and resources from the
system path, not the supplied jnlp jar/classpath resources.

I've managed to simulate the problem by chopping down our
application to a minimumal level. I've placed the code here
on one of our internal machines http://213.48.113.194/tmp/
and a webstart enabled version of the test here
http://213.48.113.194/tmp/bugtest_ctcl.jnlp.

This proves to be a difficult bug to reproduce effectively
(although our full blown application fails, repeatedly each
an every time).. Sometimes it will only fail on the initial
execution, sometimes it fails intermittently after that or
maybe the sleep() value may need tweaking to provoke any
failures.. its awkward to reproduce but very real.
You MUST have the webstart console turned off as this seems
to override the normal restrictions on classloaders,
Console must be off, but logging to a file should be on in
order to see any debug messages..

The key problem seems to be the delay between webstart
loading the application, and the application running. I
simulate the delay with a sleep(5000) in the test; where
our application is actually loading resource bundles,
setting up xerces and logging etc.. and usually a splash
screen is shown.

However the key point is that after this delay calls to
getContextClassLoader() return some internal
sun.misc.Launcher$AppClassLoader rather than the expected
com.sun.jnlp.JNLPClassLoader.. The dump from the execution
on my machine is as follows:

<code>
Java Web Start Console, started Tue Mar 19 17:30:23 GMT 2002
Java 2 Runtime Environment: Version 1.4.0 by Sun
Microsystems Inc.
Logging to file: c:\temp\webstart.log
*ToolsetLogin this.class.classloader:
com.sun.jnlp.JNLPClassLoader@6614e7
*ToolsetLogin currentThread.ContextClassLoader:
com.sun.jnlp.JNLPClassLoader@6614e7
*ToolsetFrame this.class.classloader:
com.sun.jnlp.JNLPClassLoader@6614e7
*ToolsetFrame currentThread.ContextClassLoader:
sun.misc.Launcher$AppClassLoader@bac748
java.lang.NullPointerException
</code>

Where the call to getContextClassLoader in the ToolsetFrame
class returns the invalid classloader; running the same
under 1.3.1 or under 1.4 from the command line (rather than
webstart) all the returned classloaders are the same -- as
expected, and the application doesn't fail.

This test case is a bit of a silly mockup of the problem,
in reality it's made our large swing application unusable
under 1.4.0 due to its now totally unpredictable nature; in
its ability to access resources and classes from external
jars.. Effectively meaning it can only be accessed from the
commandline, which is of no use to any of our webstart
clients.


REGRESSION.  Last worked in version 1.3.1

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Download the code example, compile, pack them into a jar,
sign the jar, put the jar onto a webstart enabled server
etc.. (or use the suppiled ant build script).



EXPECTED VERSUS ACTUAL BEHAVIOR :
Expect mockup version of our login prompt to appear after a
short five second delay (the delay seems to be key).. press
OK and a new frame should appear with a duke icon in it.

When the bug takes hold after pressing OK you will instead
be presented with the dreadded Nullpointer exception and a
empty frame..

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Nullpointer Exception

This bug can be reproduced often.

---------- BEGIN SOURCE ----------
Beacuse this is a combination of webstart and jvm problems the code exmple is a
little more complex than usual; the exmple code in on one of our internal
servers (http://213.48.113.194/tmp/)

should be here
http://213.48.113.194/tmp/bugtest_ctcl.zip

..and an ready to run webstart compiled test (much easier all round) should be
here
http://213.48.113.194/tmp/bugtest_ctcl.jnlp
---------- END SOURCE ----------
(Review ID: 144406) 


CUSTOMER WORKAROUND :
we have an effective workaround that hasn't failed us yet (line
4):

1       public void actionPerformed(ActionEvent e) {
2               if (e.getActionCommand().equals(LOGIN_COMMAND)) {
3                       Thread doLoginThread = new DoLoginThread();
4
doLoginThread.setContextClassLoader(getClass().getClassLoader());
5                       doLoginThread.start();
6               } 
7               else ...
8       }
======================================================================

Name: nt126004			Date: 05/16/2002


FULL PRODUCT VERSION :
java version "1.4.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-b92)
Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)


FULL OPERATING SYSTEM VERSION :
Windows 98 [Version 4.10.1998]

A DESCRIPTION OF THE PROBLEM :
BACKGROUND
We believe this bug may be related to the bug that
originally existed in Java Web Start 1.0 that prevented the
JNLP classloader from being correctly set as the context
classloader of the AWT event dispatch thread in client
applications. After modifying our applications to add the
recommended patch code that set the context class loader of
the AWT event dispatch thread to match the context
classloader of the primordial thread, we did not see the
problem again (everything worked fine under JDK 1.3.1)
until after upgrading to JDK 1.4. This new bug seems to be
similar to that original Java Web Start 1.0 bug, which was
listed as fixed in a subsequent release but seems to have
reappeared now in a slightly different guise.

This new bug appears to be specific to JDK/JRE 1.4 FCS
(1.4.0-b92) and the bundled Java Web Start product
(1.0.1_02 build b03). We also believe it is related
(possibly even identical) to the following existing bug in
progress:

    Bug ID 4665132 Webstart not returning
ContextClassLoader consistently under jre 1.4.0
    
http://developer.java.sun.com/developer/bugParade/bugs/4665132.html

DESCRIPTION
We run our client programs as signed Java Web Start
applications with full security permissions. The specific
behavior we see is that Java Web Start appears to set the
context class loader of the AWT event dispatch thread
incorrectly at some point *after* the client application
has been started, *unless* an AWT event occurs *before*
some small delay (~5s or so) has elapsed. The standard
patch recommended for Java Web Start 1.0 to correct the
context class loader for the AWT event dispatch thread, if
applied late enough in the execution sequence (after a
small delay, or after the first AWT event occurs), does
seem to fix the problem. However, if it is applied too
early (before the delay or the first AWT event), it only
fixes the problem for executions during which the Java Web
Start console is enabled; the problem still occurs in that
case for executions in which the Java Web Start console is
disabled. If not applied at all, the problem either occurs
a)regardless of the state of the Java Web Start console, or
b)it occurs only when the console is enabled. Behavior b)
is most common, but both behaviors have been observed.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
DEMONSTRATION
We have developed a test program to demonstrate this bug
(see TEST FILES below). This program displays a 'JFrame'
with a single 'Test' button and a single 'File->Test' menu
option. When you click the button or select the menu
option, the associated event handler logs classloader
information for the current class and thread context to
stdout. As noted below, logging should be enabled in the
Java Web Start Application Manager in order to capture the
debug output produced by the program. These tests were run
on a 450 MHz machine under Windows 98 (1st edition).

In looking at the source for the test class, you will note
that there are 3 commented and numbered code blocks in the
main() method. For the first test, these code blocks should
all be commented out and the Java Web Start console in the
Application Manager should be disabled. Launch the program
and wait for the GUI to appear. As soon as it does, click
the button or select the menu option. At this point, you
can check the Java Web Start log and see the debug output.
The first part of this output should look similar to the
following:

    MAIN THREAD
    Current thread's context classloader delegation tree:
    com.sun.jnlp.JNLPClassLoader@3c0b53
    sun.misc.Launcher$AppClassLoader@209f4e
    sun.misc.Launcher$ExtClassLoader@bac748
    (null) bootstrap class loader

    Current class's classloader delegation tree:
    com.sun.jnlp.JNLPClassLoader@3c0b53
    sun.misc.Launcher$AppClassLoader@209f4e
    sun.misc.Launcher$ExtClassLoader@bac748
    (null) bootstrap class loader

Here we can see that the classloader delegation trees for
the main class and primoridal thread are both just as we
expect them to be for a Java Web Start application;
specifically, they include an entry for
the 'JNLPClassLoader'. The second part of the debug output
should look something like this:

    BUTTON EVENT
    Current thread's context classloader delegation tree:
    com.sun.jnlp.JNLPClassLoader@3c0b53
    sun.misc.Launcher$AppClassLoader@209f4e
    sun.misc.Launcher$ExtClassLoader@bac748
    (null) bootstrap class loader

    Current class's classloader delegation tree:
    com.sun.jnlp.JNLPClassLoader@3c0b53
    sun.misc.Launcher$AppClassLoader@209f4e
    sun.misc.Launcher$ExtClassLoader@bac748
    (null) bootstrap class loader

Here we can see that the classloader delegation trees for
the button action class and the AWT event dispatch thread
are also just as we expect them to be. Similar output is
produced if the test program's menu option is selected
instead of clicking the button. So far, so good.

Next, run the test again, but this time enable the Java Web
Start console in the Application Manager. You should get
the same output for the main thread, but the output for the
AWT event should be similar to the following:

    BUTTON EVENT
    Current thread's context classloader delegation tree:
    sun.misc.Launcher$AppClassLoader@209f4e
    sun.misc.Launcher$ExtClassLoader@bac748
    (null) bootstrap class loader

    Current class's classloader delegation tree:
    com.sun.jnlp.JNLPClassLoader@3c0b53
    sun.misc.Launcher$AppClassLoader@209f4e
    sun.misc.Launcher$ExtClassLoader@bac748
    (null) bootstrap class loader

Here, we can see that the classloader delegation tree for
the AWT event dispatch thread is missing an entry
for 'JNLPClassLoader'. This will cause a problem if we
attempt to load any new classes known only to that class
loader from within this thread or its child threads. This
is the most basic problem symptom we see, and it typically
results in 'ClassNotFoundException' errors in our client
applications when running under Java Web Start. From time
to time, the problem occurs inconsistently, but we can
always reproduce it at some point by following the steps in
this demonstration.

Next, uncomment code block #3 in the test class. This
introduces a 5-second delay at the beginning of the main
thread. Running the test this time should show that all the
expected 'JNLPClassLoader' entries are again present.
Disabling the Java Web Start console and running the test
again should show that the AWT event dispatch thread is
again missing an entry for 'JNLPClassLoader'.

Next, uncomment code block #2 in the test class, so that
code blocks #2 and #3 are in effect. This introduces the
patch recommended for Java Web Start 1.0 to set the context
classloader of the AWT event dispatch thread appropriately.
Presumably, this patch is no longer necessary, but it seems
a good place to start. Note that the patch is introduced
*prior* to the 5-second delay. This time, running the test
should show that the AWT event dispatch thread is still
missing an entry for 'JNLPClassLoader'. Enabling the Java
Web Start console and running the test again should show
that the mysterious disappearing classloader has returned.

Next, comment out code block #3 and uncomment code block
#1, so that code blocks #1 and #2 are in effect. This
repositions the classloader patch for the AWT event
dispatch thread so that it occurs *after* the 5-second
delay. This time, running the test should show that all the
expected 'JNLPClassLoader' entries are present, regardless
of whether the Java Web Start console is enabled or
disabled.

CONCLUSIONS
So, what we seem to have is a flaky classloader delegation
tree associated with the AWT event dispatch thread under
Java Web Start. Sometimes the delegation tree includes the
correct entry for 'JNLPClassLoader', sometimes it doesn't.
Whether or not it does seems to depend on a combination of
factors: whether or not there is a certain amount of delay
between application startup and the initial AWT events,
whether or not the classloader patch has been applied, at
what point in the main thread the patch occurs, and whether
or the Java Web Start console has been enabled.

Although it seems as though we can hack around on this and
get it to work somewhat consistently in our applications,
it isn't really feasible for us to deploy via Java Web
Start until we can be reasonably sure of the correct
solution to this problem. We believe this behavior to be a
bug in Java Web Start itself, and that it has something to
do with establishing the correct classloader delegation
tree for all threads at client launch time. We ask that
this problem be investigated, and a code patch or
workaround be recommended as soon as possible, with a
complete fix in the next maintenance release.


EXPECTED VERSUS ACTUAL BEHAVIOR :
We expected that the JNLP classloader would be present in
the classloader delegation tree of the AWT event dispatch
thread for any Java Web Start client. We observed with our
test program that this was not in fact always the case, and
that whether or not it was depended on several seemingly-
unrelated factors. As a result, our production applications
exhibit 'ClassNotFoundException' errors under Java Web
Start that we cannot reliably fix.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
(see the "DEMONST

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: mantis mantis-b02 FIXED IN: mantis mantis-b02 INTEGRATED IN: mantis mantis-b02
31-08-2004

EVALUATION Need further investigation for mantis ###@###.### 2002-05-09 -------------------------------- The problem is that 1.) EventQueue.java has static initialization code that records getContextClassLoader() at the time EventQueue is loaded. 2.) In Java Web Start, setContextClassLoader() is called after the first awt component is created (which causes the static initialization of EventQueue). 3.) Java Web Start then sets the ContextClassLoader of the current awt EventDispatchThread(). 4.) When running java 1.3.x, or 1.4.x with any awt component showing (such as the Java Web Start console) this EventDispatchThread keeps running. 5.) When Running 1.4.x w/o any component showing, this EventDispatchThread dies, and a new one is created when an event is posted. 6.) This new EventDispatchThread dosn't have the right ContextClassLoader. We can fix this by setting the ContextClassLoader sooner, before any awt component is created. ###@###.### 2002-07-24 ------------------------------
24-07-2002

SUGGESTED FIX split the functionality of JNLPClassLoader.createClassLoader( into two functions. The first part with no args: JNLPClassLoader.createClassLoader() can construct the object unitialized, and can be called in main before the first awt component is created (this is currently happening when constructing the JAuthentacator) The second part: createClassLoader(LaunchDesc ld, AppPolicy appPolicy) can be the same as the current implementation (except not creating a new instance if one allready exists), and can be used to innitialize the loader from the jnlp file contents. ###@###.### 2002-07-24 -------------------------------
24-07-2002