JDK-8095280 : Thread classloader gets lost through the JFX launch process
  • Type: Bug
  • Component: javafx
  • Sub-Component: application-lifecycle
  • Affected Version: 8u20
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2014-10-08
  • Updated: 2015-06-12
  • Resolved: 2014-10-28
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.
JDK 8
8u40Fixed
Related Reports
Blocks :  
Relates :  
Relates :  
Description
Thread.currentThread().setContextClassLoader(a);
Application.launch(args);
Thread.currentThread().getContextClassLoader() != not a


This is only a minor inconvenience for my apps (which use the UpdateFX updates framework) because you can always just run

Thread.currentThread().setContextClassLoader(ExampleApp.class.getClassLoader());

as the first line in start(Stage) ... but it'd be nice if it wasn't necessary. I poked through the code but it seems starting the JFX app thread is done possibly inside native code.


Comments
http://hg.openjdk.java.net/openjfx/8u-dev/rt/rev/a4cc2cb233ab
28-10-2014

Regression tests have been added to cover this case (and ensure that existing cases still run). Unit tests: launchertest/MainLauncherTest.java
28-10-2014

I know that runnable run in the order they were queued. Someone can come along later and change the code to run a runnable before yours runs. I am ok with the code how it is right now and was unaware that setting a class loader from another thread was not protected inside of the Java class libraries. +1 ... release the Kraken!
28-10-2014

It's the former, but setting the CCL for another thread is a bad idea if that other thread can be running. Using runLater is the best way to ensure that the thread is in the right state (it would be a more intrusive change to do otherwise). In any case, the contract of runLater is that the runnables are run in order, so it won't be a problem. The CCL will be set before the subsequent runLaters that are used to construct the preloader class (if used) or app class.
28-10-2014

Is the runLater() needed to ensure that the current thread is the UI thread or is it actually necessary to run the code on the UI thread? The only reason I ask this is that if another runLater() can somehow run before this one, then the correct class loader won't be set.
28-10-2014

Here is the link for review: http://cr.openjdk.java.net/~kcr/RT-38936/webrev.00/
28-10-2014

Actually, option #3 turns out to be only slightly more code than the brute force fix (the above unacceptable option #2) and is very safe. I will send out a webrev shortly.
28-10-2014

Here is the background behind this, and the reason for the current behavior. In JDK 8, we made the following intentional changes: *) jfxrt.jar is now loaded by default and available to all applications without needing to set the classpath or have a special classloader (the ext classloader loads JavaFX classes) *) The Java 8 launcher was modified to recognize JavaFX application classes (a class that extends javafx.application.Application) and launch it directly rather than calling main() *) RT-28755 : For compatibility with IDEs and with applications that expect main to be called, we changed the FX launcher entry point used by the Java 8 launcher to look for main() and call that if it exists, otherwise we launch the app directly. *) RT-28754 : In order to make applications less prone to certain types of threading issues we changed FX startup to initlialize the FX toolkit prior to initializing the app class. The change for RT-28754 is the direct cause of this bug. The launcher does the following for classes that extend javafx.application.Application: A) [main thread] The Java launcher initializes the FX toolkit and spins up the FX app thread. The CCL is propagated from the main thread to the FX app thread. B) [FX app thread] the main thread calls runLater to load and initializes the application class in the app thread; this runs the static init blocks in the application class. Changes in the CCL in the static init will be effective. C) [main thread] if there is a main(String[]) method in the loaded app class, it is called on the launcher thread. Changes to the CCL in the main() method will affect only the main thread (and, later, the launcher thread), but will not be propagated to the FX app thread. D) [main thread] the application calls Application.launch() to run the FX application life-cycle. The launch method calls runLater to construct an instance of the application on the FX app thread; it then calls the init() method on the launcher thread; finally it calls runLater to construct the primary stage and execute the start() method. To implement option the above option #3 we would save the CCL just prior to calling the main() method in step C, and then check in Application.launch() whether it has been changed. If it is the same then we do nothing. If it has been changed, then Application.launch() will pass the CCL from the main thread to the FX app thread.
28-10-2014

A strong argument against #2 is that main is never called in WebStart or Applet mode, so fixing this bug incompatibly would mean a difference in behavior between launching the same application in standalone mode versus webstart mode. I am not sure that the complexity of #3 is worth the effort.
28-10-2014

I added several tests for CCL as part of RT-32949 including a commented-out test for this case (since it doesn't run). There is a complication with the fix for this bug. I'll provide more details tomorrow, but the short version is that fixing this would require an incompatible behavior change versus FX 8; it will break the case where the app sets the CCL in the static init block rather than the main() method. If this bug is fixed, then the CCL that is set in the static init block will be overwritten at Application.launch() time. There are three choices that I can see: 1) Don't fix this bug, but instead document the current FX 8 behavior, which is a direct result of the changes in Java 8 launcher behavior. 2) Fix this bug in 8u40, breaking compatibility with FX 8 (and 8u releases prior to 8u40) 3) Determine whether the app has set the CCL to something other than the default in the static init block of its application class (if possible). Then use that fact to qualify whether to forward the CCL of the main thread to the application thread at Application.launch time. This would fix the bug while preserving compatibility with FX 8. I favor #1 for simplicity, or maybe #3 if it is feasible. I am opposed to #2.
28-10-2014

Another, and probably better, workaround is to set the CCL in the static init block rather than in the main() method.
28-10-2014

Another workaround is to bypass the Java 8 launcher and factor out your main method into a separate class that has as its only reference to FX the Application.launch() method. For example: MyApp.java ---------------- // FX application without a main method public class MyApp extends Application { ... } Main.java ------------- public class Main { public static void main(String[] args) throws Exception { Thread.currentThread().setContextClassLoader(...); Application.launch(MyApp.class, args); } }
23-10-2014

Here is a complete test program (which will form the basis for the unit test) that shows the bug: import java.net.URL; import java.net.URLClassLoader; import javafx.application.Application; import javafx.application.Platform; import javafx.stage.Stage; public class CCLTest extends Application { private static ClassLoader ccl = null; @Override public void init() { System.out.println("[init] CCL = " + Thread.currentThread().getContextClassLoader()); } @Override public void start(Stage stage) throws Exception { System.out.println("[start] CCL = " + Thread.currentThread().getContextClassLoader()); if (ccl != Thread.currentThread().getContextClassLoader()) { System.err.println("***** ClassLoader not set as expected!"); } Platform.exit(); } public static void main(String[] args) throws Exception { ccl = new URLClassLoader(new URL[] { new URL("file:.") }); Thread.currentThread().setContextClassLoader(ccl); System.out.println("[main] CCL = " + Thread.currentThread().getContextClassLoader()); Application.launch(args); } }
23-10-2014

As part of this, I will port the currently-disabled ClassLoaderTest (RT-32949) to the launcher test framework. Since these test won't catch this specific bug, I will add new tests for this case.
23-10-2014

The fix should be simple: in addition to setting the CCL at FX toolkit init time (which is still needed for applet and WebStart cases) we need to set the CCL from Application.launch()
23-10-2014

This bug was introduced by the fix for RT-28754 which requires us to initialize the FX toolkit before calling main().
23-10-2014

The context class loader should be the same as the one that launches the application. In JDK 8 with the Java launcher changes, we now initialize FX prior to running the main() method, so we may have broken this in FX 8.
08-10-2014