Retrieving the GWT SDK version is incredibly slow in large projects
See original GitHub issueBackground: We have a very large code base that consists of more than 150 sub projects, with roughly 70% of them being either directly or transitively referenced by a distribution project that ties everything together. The whole code base is a Gradle project, and we use the Buildship plugin to import everything into Eclipse. We also have some Gradle build scripts to set the correct GWT settings (enabling the GWT nature on the distribution project, assigning the SDK and configuring other project-specific options) before the import starts.
Problems: We noticed that having the GWT nature enabled on the project has a severe negative impact on the performance of Eclipse in general:
- Without the GWT nature, the import takes roughly 15 minutes (on an SSD). With it, that time almost doubles.
- Validating the GWT project with the
GWTProjectValidator
implementation has a 4-5 minute wind-up time before it even starts doing work. Since this validation is performed during the import and also before any launch (either the application itself, a Gradle task or a unit test), we end up with a lot of idle waiting time. - The UI menu for launch configurations freezes completely and refuses to subsequently open after switching to the overview for the GWT launch configuration.
- Eclipse is often waiting a long time for background work to complete, which delays many user actions like saving of a file and rebuilding the workspace.
Cause:
Since this is pretty much a show stopper that makes Eclipse nearly unusable for us, we started investigating the cause and found it in ProjectBoundSdk.createClassLoader()
(in GwtSdk.java
), which is called when the getVersion()
method is used to retrieve a string for the SDK’s version number. This apparently searches the entire classpath of the project and all its referenced subprojects for a specific class in order to call a method on it via reflection.
To be even more precise, the call to JavaRuntime.computeDefaultRuntimeClassPath(javaProject)
in that method is what’s taking several minutes to return. This call causes Buildship to resolve its classpath container, which apparently involves many calls to File.getCanonicalPath()
(aka slow native file system operations). I don’t know if this is something that’s specific to Gradle Buildship or if it is done the same way in other project types.
getVersion()
is called in many places:
- The
GWTProjectValidator
. - The
LaunchConfigurationUpdater
calls this several times, which in turn gets activated whenever theLaunchConfigAffectingChangesListener
detects changes to the classpath. (This also runs during the initial project import.) equals()
andhashCode()
implementation ofAbstractSdk
. All of these are likely responsible for the problems mentioned above.
createClassLoader()
apparently also has been identified as performance critical in the past, as the TODO comments there indicate:
/**
* Returns a {@link ClassLoader} that is backed by the project's runtime classpath.
*
* TODO: This returns a classloader which contains ALL of the jars of the project. Lookups on
* this thing are going to be SLOW. Can we optimize this? We could create a classloader that
* just contains the jars that GWT requires. Maybe caching is the right solution here.
*
* TODO: Why can't we just delegate to {@link #getClasspathEntries()} when generating the
* classloader URLs? Why do we have to add every URL that is part of the project? That would
* certainly speed up lookups on this classloader. Maybe we cannot do this because project-bound
* sdks handle the case of source-based runtimes, and in that case, we need all of the
* dependencies as part of the classloader.
*/
Workaround: For our own needs, we added a workaround where we simply read the SDK version string from the plugin’s project preferences, which we write there ourselves (via the Gradle build script). If it’s missing, we fall back to whole classpath lookup thing, but that case shouldn’t ever happen again for us if the project is set up correctly. We could confirm that this change solves the perceived problems.
Whether such a hack is suited for the “official” release is up to you to decide. Perhaps you can think of a more elegant solution as well. I am aware that this is probably not as easy to reproduce, since it requires quite a large project.
Issue Analytics
- State:
- Created 5 years ago
- Comments:17 (11 by maintainers)
Sounds good. I have two long weekends in a row, so it might take me 3 weeks to get back to this. Although I have some dedicated time road mapped and coming to work on this plugin coming. I’m getting closer to that time.
Your solution is indeed better for the average user, since it does not involve any manual work (and is also not just a dirty hack like mine).
It is not as optimal for us, however, since the properties key that stores cached version depends strongly on the installation directory and SDK location of the end user. (It looks like this in my case:
gwtVersion_E:\\Dev\\eclipse_photon\\plugins\\com.gwtplugins.gwt.eclipse.sdkbundle.gwt27_2.7.0.201808101238\\gwt-2.7.0
) This makes it rather difficult to set this property in advance via a Gradle plugin, since that plugin won’t know about the installation path (and has no reason to, especially if it runs in an environment without Eclipse). And without setting it in advance, we are still giving away 4-5 minutes that could be saved otherwise. I am not asking you to do anything about that. If it really bothers us too much, we’ll just have to keep using our forked fix. Just wanted to point it out.(Also, not sure if the GWT version is ever retrieved by more than one thread, in which case it could start multiple classpath resolves before finally having the version in its cache.)