KT-57154: Fix JRT-FS contents served for wrong JDK home on JDK 9+

If the compiler runtime JDK is 9+,
it will already contain JrtFileSystemProvider and won't use provided
classloader
In order to fix KT-57154 we need to provide "java.home" argument to
newFileSystem

In order to reduce the severity of the leak in KT-56789 we cache
instances of FileSystem itself forever
Otherwise, each invocation of newFileSystem on JDK 9+ will leak
classloader, which is created deep inside the JDK code

Add unit test for JRT-FS contents served through CoreJrtFs

Add Gradle Integration test to 
test if the daemon correctly reads JDK contents from the specified 
toolchain and not from its runtime JDK

^KT-57154
Regression test for ^KT-57077
This commit is contained in:
Simon Ogorodnik
2023-03-28 10:38:46 +00:00
committed by Space Team
parent 563781a246
commit ae32eff543
7 changed files with 158 additions and 17 deletions
@@ -20,31 +20,18 @@ import com.intellij.openapi.vfs.DeprecatedVirtualFileSystem
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.ConcurrentFactoryMap
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.io.URLUtil
import java.io.File
import java.net.URI
import java.net.URLClassLoader
import java.nio.file.FileSystem
import java.nio.file.FileSystems
// There's JrtFileSystem in idea-full which we can't use in the compiler because it depends on NewVirtualFileSystem, absent in intellij-core
class CoreJrtFileSystem : DeprecatedVirtualFileSystem() {
private val roots =
ConcurrentFactoryMap.createMap<String, CoreJrtVirtualFile?> { jdkHomePath ->
val jdkHome = File(jdkHomePath)
val rootUri = URI.create(StandardFileSystems.JRT_PROTOCOL + ":/")
val jrtFsJar = loadJrtFsJar(jdkHome) ?: return@createMap null
/*
This ClassLoader actually lives as long as current thread due to ThreadLocal leak in jrt-fs,
See https://bugs.openjdk.java.net/browse/JDK-8260621
So that cache allows us to avoid creating too many classloaders for same JDK and reduce severity of that leak
*/
val classLoader = globalJrtFsClassLoaderCache.computeIfAbsent(jrtFsJar) {
URLClassLoader(arrayOf(jrtFsJar.toURI().toURL()), null)
}
val fileSystem = FileSystems.newFileSystem(rootUri, emptyMap<String, Nothing>(), classLoader)
val fileSystem = globalJrtFsCache[jdkHomePath] ?: return@createMap null
CoreJrtVirtualFile(this, jdkHomePath, fileSystem.getPath(""), parent = null)
}
@@ -84,6 +71,26 @@ class CoreJrtFileSystem : DeprecatedVirtualFileSystem() {
return Pair(localPath, pathInJar)
}
private val globalJrtFsClassLoaderCache = ContainerUtil.createConcurrentWeakValueMap<File, URLClassLoader>()
private val globalJrtFsCache = ConcurrentFactoryMap.createMap<String, FileSystem?> { jdkHomePath ->
val jdkHome = File(jdkHomePath)
val jrtFsJar = loadJrtFsJar(jdkHome) ?: return@createMap null
val rootUri = URI.create(StandardFileSystems.JRT_PROTOCOL + ":/")
/*
The ClassLoader, that was used to load JRT FS Provider actually lives as long as current thread due to ThreadLocal leak in jrt-fs,
See https://bugs.openjdk.java.net/browse/JDK-8260621
So that cache allows us to avoid creating too many classloaders for same JDK and reduce severity of that leak
*/
if (isAtLeastJava9()) {
// If the runtime JDK is set to 9+ it has JrtFileSystemProvider,
// but to load proper jrt-fs (one that is pointed by jdkHome) we should provide "java.home" path
FileSystems.newFileSystem(rootUri, mapOf("java.home" to jdkHome.absolutePath))
} else {
val classLoader = URLClassLoader(arrayOf(jrtFsJar.toURI().toURL()), null)
// If the runtime JDK is set to <9, there are no JrtFileSystemProvider,
// we should create classloader with jrt-fs.jar, and DO NOT NEED to pass "java.home" path,
// as otherwise it will incur additional classloader creation
FileSystems.newFileSystem(rootUri, emptyMap<String, Nothing>(), classLoader)
}
}
}
}