Do not use ClassLoader.getResourceAsStream in reflection
Use ClassLoader.getResource + openStream instead, to workaround an issue in URLClassLoader. Also set useCaches to false because kotlin-reflect only reads builtins metadata once per class loader, and doesn't need it to be cached. Using caches here might also lead to the problem of closed input streams when protobuf is read in parallel. The test doesn't check exactly this, though (it seems to succeed even if cached connections are used). Note that BuiltInsResourceLoader has a JDK 9+ specialization at libraries/reflect/api/src/java9, but that implementation does not need any changes because it uses Module.getResourceAsStream which is not affected by this issue in URLClassLoader. #KT-18277 Fixed
This commit is contained in:
@@ -11,7 +11,12 @@ import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
||||
import org.jetbrains.kotlin.test.util.KtTestUtil
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class ReflectionIntegrationTest : KtUsefulTestCase() {
|
||||
// This test checks that we use the correct class loader to load .kotlin_builtins resource files.
|
||||
@@ -49,4 +54,45 @@ class ReflectionIntegrationTest : KtUsefulTestCase() {
|
||||
process.waitFor(1, TimeUnit.MINUTES)
|
||||
assertEquals(0, process.exitValue())
|
||||
}
|
||||
|
||||
// This test checks that simultaneous access to kotlin-reflect from different threads works, in case the URLClassLoader instance is
|
||||
// being closed in one of the threads. It creates two threads that for several seconds continuously load kotlin-reflect in a new
|
||||
// class loader, call something from it, and close the class loader.
|
||||
fun testParallelAccess() {
|
||||
val urls = arrayOf(
|
||||
ForTestCompileRuntime.reflectJarForTests().toURI().toURL(),
|
||||
ForTestCompileRuntime.runtimeJarForTests().toURI().toURL(),
|
||||
)
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
val error = AtomicReference<Throwable?>()
|
||||
repeat(2) {
|
||||
thread {
|
||||
while (latch.count == 1L) {
|
||||
try {
|
||||
val classLoader = URLClassLoader(urls, null)
|
||||
|
||||
// Invoke KClass.primaryConstructor on String::class reflectively.
|
||||
// Among other things, it leads to reading a resource kotlin/kotlin.kotlin_builtins from the JAR file.
|
||||
val getPrimaryConstructor = classLoader.loadClass("kotlin.reflect.full.KClasses")
|
||||
.getDeclaredMethod("getPrimaryConstructor", classLoader.loadClass(KClass::class.java.name))
|
||||
val createKClass = classLoader.loadClass("kotlin.jvm.internal.Reflection")
|
||||
.getDeclaredMethod("getOrCreateKotlinClass", Class::class.java)
|
||||
getPrimaryConstructor(null, createKClass(null, String::class.java))
|
||||
|
||||
// Close URLClassLoader to verify that it won't affect reading the class loader resources from the other thread(s).
|
||||
classLoader.close()
|
||||
} catch (e: Throwable) {
|
||||
error.set(e)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
latch.await(5L, TimeUnit.SECONDS)
|
||||
latch.countDown()
|
||||
|
||||
error.get()?.let { throw it }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user