jvmMain.kotlinx.serialization.internal.Caching.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-jupyter-script-classpath-shadowed Show documentation
Show all versions of kotlin-jupyter-script-classpath-shadowed Show documentation
Kotlin Jupyter kernel script classpath with all dependencies inside one artifact
/*
* Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.serialization.internal
import kotlinx.serialization.KSerializer
import java.lang.ref.SoftReference
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KClass
import kotlin.reflect.KClassifier
import kotlin.reflect.KType
import kotlin.reflect.KTypeProjection
/*
* By default, we use ClassValue-based caches to avoid classloader leaks,
* but ClassValue is not available on Android, thus we attempt to check it dynamically
* and fallback to ConcurrentHashMap-based cache.
*/
private val useClassValue = try {
Class.forName("java.lang.ClassValue")
true
} catch (_: Throwable) {
false
}
/**
* Creates a **strongly referenced** cache of values associated with [Class].
* Serializers are computed using provided [factory] function.
*
* `null` values are not supported, though there aren't any technical limitations.
*/
internal actual fun createCache(factory: (KClass<*>) -> KSerializer?): SerializerCache {
return if (useClassValue) ClassValueCache(factory) else ConcurrentHashMapCache(factory)
}
/**
* Creates a **strongly referenced** cache of values associated with [Class].
* Serializers are computed using provided [factory] function.
*
* `null` values are not supported, though there aren't any technical limitations.
*/
internal actual fun createParametrizedCache(factory: (KClass, List) -> KSerializer?): ParametrizedSerializerCache {
return if (useClassValue) ClassValueParametrizedCache(factory) else ConcurrentHashMapParametrizedCache(factory)
}
private class ClassValueCache(val compute: (KClass<*>) -> KSerializer?) : SerializerCache {
private val classValue = ClassValueReferences>()
override fun get(key: KClass): KSerializer? {
return classValue
.getOrSet(key.java) { CacheEntry(compute(key)) }
.serializer
}
}
/**
* A class that combines the capabilities of ClassValue and SoftReference.
* Softly binds the calculated value to the specified class.
*
* [SoftReference] used to prevent class loaders from leaking,
* since the value can transitively refer to an instance of type [Class], this may prevent the loader from
* being collected during garbage collection.
*
* In the first calculation the value is cached, every time [getOrSet] is called, a pre-calculated value is returned.
*
* However, the value can be collected during garbage collection (thanks to [SoftReference])
* - in this case, when trying to call the [getOrSet] function, the value will be calculated again and placed in the cache.
*
* An important requirement for a function generating a value is that it must be stable, so that each time it is called for the same class, the function returns similar values.
* In the case of serializers, these should be instances of the same class filled with equivalent values.
*/
@SuppressAnimalSniffer
private class ClassValueReferences : ClassValue>() {
override fun computeValue(type: Class<*>): MutableSoftReference {
return MutableSoftReference()
}
inline fun getOrSet(key: Class<*>, crossinline factory: () -> T): T {
val ref: MutableSoftReference = get(key)
ref.reference.get()?.let { return it }
// go to the slow path and create serializer with blocking, also wrap factory block
return ref.getOrSetWithLock { factory() }
}
}
/**
* Wrapper over `SoftReference`, used to store a mutable value.
*/
private class MutableSoftReference {
// volatile because of situations like https://stackoverflow.com/a/7855774
@JvmField
@Volatile
var reference: SoftReference = SoftReference(null)
/*
It is important that the monitor for synchronized is the `MutableSoftReference` of a specific class
This way access to reference is blocked only for one serializable class, and not for all
*/
@Synchronized
fun getOrSetWithLock(factory: () -> T): T {
// exit function if another thread has already filled in the `reference` with non-null value
reference.get()?.let { return it }
val value = factory()
reference = SoftReference(value)
return value
}
}
private class ClassValueParametrizedCache(private val compute: (KClass, List) -> KSerializer?) :
ParametrizedSerializerCache {
private val classValue = ClassValueReferences>()
override fun get(key: KClass, types: List): Result?> {
return classValue.getOrSet(key.java) { ParametrizedCacheEntry() }
.computeIfAbsent(types) { compute(key, types) }
}
}
/**
* We no longer support Java 6, so the only place we use this cache is Android, where there
* are no classloader leaks issue, thus we can safely use strong references and do not bother
* with WeakReference wrapping.
*/
private class ConcurrentHashMapCache(private val compute: (KClass<*>) -> KSerializer?) : SerializerCache {
private val cache = ConcurrentHashMap, CacheEntry>()
override fun get(key: KClass): KSerializer? {
return cache.getOrPut(key.java) {
CacheEntry(compute(key))
}.serializer
}
}
private class ConcurrentHashMapParametrizedCache(private val compute: (KClass, List) -> KSerializer?) :
ParametrizedSerializerCache {
private val cache = ConcurrentHashMap, ParametrizedCacheEntry>()
override fun get(key: KClass, types: List): Result?> {
return cache.getOrPut(key.java) { ParametrizedCacheEntry() }
.computeIfAbsent(types) { compute(key, types) }
}
}
/**
* Wrapper for cacheable serializer of some type.
* Used to store cached serializer or indicates that the serializer is not cacheable.
*
* If serializer for type is not cacheable then value of [serializer] is `null`.
*/
private class CacheEntry(@JvmField val serializer: KSerializer?)
/**
* Workaround of https://youtrack.jetbrains.com/issue/KT-54611 and https://github.com/Kotlin/kotlinx.serialization/issues/2065
*/
private class KTypeWrapper(private val origin: KType) : KType {
override val annotations: List
get() = origin.annotations
override val arguments: List
get() = origin.arguments
override val classifier: KClassifier?
get() = origin.classifier
override val isMarkedNullable: Boolean
get() = origin.isMarkedNullable
override fun equals(other: Any?): Boolean {
if (other == null) return false
if (origin != (other as? KTypeWrapper)?.origin) return false
val kClassifier = classifier
if (kClassifier is KClass<*>) {
val otherClassifier = (other as? KType)?.classifier
if (otherClassifier == null || otherClassifier !is KClass<*>) {
return false
}
return kClassifier.java == otherClassifier.java
} else {
return false
}
}
override fun hashCode(): Int {
return origin.hashCode()
}
override fun toString(): String {
return "KTypeWrapper: $origin"
}
}
private class ParametrizedCacheEntry {
private val serializers: ConcurrentHashMap, Result?>> = ConcurrentHashMap()
inline fun computeIfAbsent(types: List, producer: () -> KSerializer?): Result?> {
val wrappedTypes = types.map { KTypeWrapper(it) }
return serializers.getOrPut(wrappedTypes) {
kotlin.runCatching { producer() }
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy