ENT-3000 Start pooling classpath scanning and related fixes (#4664)

* Start pooling classpath scanning

Quickly patch synchronisation of attachment class loader cache.  Needs a revisit but more complicated due to DJVM.

Annotate away for DJVM

Take ClassGraph utils into their own file so we can exclude for DJVM

Clean up a little

* Daemonize the threads

* Seems to be some concurrency problems with use of ClassGraph.  Using a mutex for now to work around.
This commit is contained in:
Rick Parker 2019-01-30 10:35:20 +00:00 committed by GitHub
parent 9ade410109
commit 7dc7313fb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 30 additions and 19 deletions

View File

@ -0,0 +1,19 @@
@file:DeleteForDJVM
package net.corda.core.internal
import co.paralleluniverse.strands.concurrent.ReentrantLock
import io.github.classgraph.ClassGraph
import io.github.classgraph.ScanResult
import net.corda.core.DeleteForDJVM
import kotlin.concurrent.withLock
private val pooledScanMutex = ReentrantLock()
/**
* Use this rather than the built in implementation of [scan] on [ClassGraph]. The built in implementation of [scan] creates
* a thread pool every time resulting in too many threads. This one uses a mutex to restrict concurrency.
*/
fun ClassGraph.pooledScan(): ScanResult {
return pooledScanMutex.withLock { this@pooledScan.scan() }
}

View File

@ -19,7 +19,7 @@ import net.corda.core.StubOutForDJVM
fun <T: Any> createInstancesOfClassesImplementing(classloader: ClassLoader, clazz: Class<T>): Set<T> {
return ClassGraph().addClassLoader(classloader)
.enableClassInfo()
.scan()
.pooledScan()
.use {
it.getClassesImplementing(clazz.name)
.filterNot { it.isAbstract }
@ -38,4 +38,4 @@ fun <T: Any?> executeWithThreadContextClassLoader(classloader: ClassLoader, fn:
Thread.currentThread().contextClassLoader = threadClassLoader
}
}
}

View File

@ -2,7 +2,6 @@ package net.corda.core.serialization.internal
import net.corda.core.CordaException
import net.corda.core.KeepForDJVM
import net.corda.core.internal.createInstancesOfClassesImplementing
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.TransactionVerificationException.OverlappingAttachmentsException
@ -10,10 +9,6 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.targetPlatformVersion
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.*
import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.toUrl
import net.corda.core.utilities.contextLogger
@ -187,7 +182,7 @@ internal object AttachmentsClassLoaderBuilder {
private const val CACHE_SIZE = 1000
// This runs in the DJVM so it can't use caffeine.
private val cache: MutableMap<Set<SecureHash>, SerializationContext> = createSimpleCache(CACHE_SIZE)
private val cache: MutableMap<Set<SecureHash>, SerializationContext> = createSimpleCache<Set<SecureHash>, SerializationContext>(CACHE_SIZE).toSynchronised()
fun <T> withAttachmentsClassloaderContext(attachments: List<Attachment>, block: (ClassLoader) -> T): T {
val attachmentIds = attachments.map { it.id }.toSet()

View File

@ -6,6 +6,7 @@ import net.corda.behave.scenarios.api.StepsBlock
import net.corda.behave.scenarios.api.StepsProvider
import net.corda.behave.scenarios.steps.*
import net.corda.core.internal.objectOrNewInstance
import net.corda.core.internal.pooledScan
import net.corda.core.utilities.contextLogger
@Suppress("KDocMissingDocumentation")
@ -18,7 +19,7 @@ class StepsContainer(val state: ScenarioState) : En {
ClassGraph()
.addClassLoader(this::class.java.classLoader)
.enableAllInfo()
.scan()
.pooledScan()
.use { it.getClassesImplementing(StepsProvider::class.java.name).loadClasses(StepsProvider::class.java) }
.map { it.kotlin.objectOrNewInstance() }
}

View File

@ -9,7 +9,6 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.internal.*
import org.slf4j.LoggerFactory
import java.io.InputStream
import java.net.URLClassLoader
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
@ -28,7 +27,7 @@ class ContractsJarFile(private val file: Path) : ContractsJar {
override val hash: SecureHash by lazy(LazyThreadSafetyMode.NONE, file::hash)
override fun scan(): List<ContractClassName> {
val scanResult = ClassGraph().overrideClasspath(singleton(file)).enableClassInfo().scan()
val scanResult = ClassGraph().overrideClasspath(singleton(file)).enableClassInfo().pooledScan()
return scanResult.use { result ->
coreContractClasses

View File

@ -280,7 +280,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult {
logger.info("Scanning CorDapp in ${cordappJarPath.url}")
return cachedScanResult.computeIfAbsent(cordappJarPath) {
val scanResult = ClassGraph().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).enableAllInfo().scan()
val scanResult = ClassGraph().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).enableAllInfo().pooledScan()
RestrictedScanResult(scanResult, cordappJarPath.qualifiedNamePrefix)
}
}

View File

@ -7,10 +7,7 @@ import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.StubOutForDJVM
import net.corda.core.cordapp.Cordapp
import net.corda.core.internal.isAbstractClass
import net.corda.core.internal.objectOrNewInstance
import net.corda.core.internal.toSynchronised
import net.corda.core.internal.uncheckedCast
import net.corda.core.internal.*
import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.contextLogger
@ -90,7 +87,7 @@ abstract class AbstractAMQPSerializationScheme(
.whitelistPackages(scanSpec)
.addClassLoader(cl)
.enableAllInfo()
.scan()
.pooledScan()
.use {
val serializerClass = SerializationCustomSerializer::class.java
it.getClassesImplementing(serializerClass.name).loadClasses(serializerClass)

View File

@ -52,7 +52,7 @@ data class CustomCordapp(
val scanResult = classGraph
.whitelistPackages(*packages.toTypedArray())
.whitelistClasses(*classes.map { it.name }.toTypedArray())
.scan()
.pooledScan()
scanResult.use {
JarOutputStream(file.outputStream()).use { jos ->

View File

@ -55,7 +55,7 @@ data class TestCordappImpl(val scanPackage: String, override val config: Map<Str
return packageToRootPaths.computeIfAbsent(scanPackage) {
ClassGraph()
.whitelistPackages(scanPackage)
.scan()
.pooledScan()
.use { it.allResources }
.asSequence()
.map { it.classpathElementFile.toPath() }