xref: /aosp_15_r20/external/kotlinx.serialization/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt (revision 57b5a4a64c534cf7f27ac9427ceab07f3d8ed3d8)
1 /*
2  * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.serialization.internal
6 
7 import kotlinx.serialization.KSerializer
8 import java.lang.ref.SoftReference
9 import java.util.concurrent.ConcurrentHashMap
10 import kotlin.reflect.KClass
11 import kotlin.reflect.KClassifier
12 import kotlin.reflect.KType
13 import kotlin.reflect.KTypeProjection
14 
15 /*
16  * By default, we use ClassValue-based caches to avoid classloader leaks,
17  * but ClassValue is not available on Android, thus we attempt to check it dynamically
18  * and fallback to ConcurrentHashMap-based cache.
19  */
20 private val useClassValue = try {
21     Class.forName("java.lang.ClassValue")
22     true
23 } catch (_: Throwable) {
24     false
25 }
26 
27 /**
28  * Creates a **strongly referenced** cache of values associated with [Class].
29  * Serializers are computed using provided [factory] function.
30  *
31  * `null` values are not supported, though there aren't any technical limitations.
32  */
createCachenull33 internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> {
34     return if (useClassValue) ClassValueCache(factory) else ConcurrentHashMapCache(factory)
35 }
36 
37 /**
38  * Creates a **strongly referenced** cache of values associated with [Class].
39  * Serializers are computed using provided [factory] function.
40  *
41  * `null` values are not supported, though there aren't any technical limitations.
42  */
createParametrizedCachenull43 internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> {
44     return if (useClassValue) ClassValueParametrizedCache(factory) else ConcurrentHashMapParametrizedCache(factory)
45 }
46 
47 private class ClassValueCache<T>(val compute: (KClass<*>) -> KSerializer<T>?) : SerializerCache<T> {
48     private val classValue = ClassValueReferences<CacheEntry<T>>()
49 
getnull50     override fun get(key: KClass<Any>): KSerializer<T>? {
51         return classValue
52             .getOrSet(key.java) { CacheEntry(compute(key)) }
53             .serializer
54     }
55 }
56 
57 /**
58  * A class that combines the capabilities of ClassValue and SoftReference.
59  * Softly binds the calculated value to the specified class.
60  *
61  * [SoftReference] used to prevent class loaders from leaking,
62  * since the value can transitively refer to an instance of type [Class], this may prevent the loader from
63  * being collected during garbage collection.
64  *
65  * In the first calculation the value is cached, every time [getOrSet] is called, a pre-calculated value is returned.
66  *
67  * However, the value can be collected during garbage collection (thanks to [SoftReference])
68  * - in this case, when trying to call the [getOrSet] function, the value will be calculated again and placed in the cache.
69  *
70  * 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.
71  * In the case of serializers, these should be instances of the same class filled with equivalent values.
72  */
73 @SuppressAnimalSniffer
74 private class ClassValueReferences<T> : ClassValue<MutableSoftReference<T>>() {
computeValuenull75     override fun computeValue(type: Class<*>): MutableSoftReference<T> {
76         return MutableSoftReference()
77     }
78 
getOrSetnull79     inline fun getOrSet(key: Class<*>, crossinline factory: () -> T): T {
80         val ref: MutableSoftReference<T> = get(key)
81 
82         ref.reference.get()?.let { return it }
83 
84         // go to the slow path and create serializer with blocking, also wrap factory block
85         return ref.getOrSetWithLock { factory() }
86     }
87 
88 }
89 
90 /**
91  * Wrapper over `SoftReference`, used  to store a mutable value.
92  */
93 private class MutableSoftReference<T> {
94     // volatile because of situations like https://stackoverflow.com/a/7855774
95     @JvmField
96     @Volatile
97     var reference: SoftReference<T> = SoftReference(null)
98 
99     /*
100     It is important that the monitor for synchronized is the `MutableSoftReference` of a specific class
101     This way access to reference is blocked only for one serializable class, and not for all
102      */
103     @Synchronized
getOrSetWithLocknull104     fun getOrSetWithLock(factory: () -> T): T {
105         // exit function if another thread has already filled in the `reference` with non-null value
106         reference.get()?.let { return it }
107 
108         val value = factory()
109         reference = SoftReference(value)
110         return value
111     }
112 }
113 
114 private class ClassValueParametrizedCache<T>(private val compute: (KClass<Any>, List<KType>) -> KSerializer<T>?) :
115     ParametrizedSerializerCache<T> {
116     private val classValue = ClassValueReferences<ParametrizedCacheEntry<T>>()
117 
getnull118     override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
119         return classValue.getOrSet(key.java) { ParametrizedCacheEntry() }
120             .computeIfAbsent(types) { compute(key, types) }
121     }
122 }
123 
124 /**
125  * We no longer support Java 6, so the only place we use this cache is Android, where there
126  * are no classloader leaks issue, thus we can safely use strong references and do not bother
127  * with WeakReference wrapping.
128  */
129 private class ConcurrentHashMapCache<T>(private val compute: (KClass<*>) -> KSerializer<T>?) : SerializerCache<T> {
130     private val cache = ConcurrentHashMap<Class<*>, CacheEntry<T>>()
131 
getnull132     override fun get(key: KClass<Any>): KSerializer<T>? {
133         return cache.getOrPut(key.java) {
134             CacheEntry(compute(key))
135         }.serializer
136     }
137 }
138 
139 
140 private class ConcurrentHashMapParametrizedCache<T>(private val compute: (KClass<Any>, List<KType>) -> KSerializer<T>?) :
141     ParametrizedSerializerCache<T> {
142     private val cache = ConcurrentHashMap<Class<*>, ParametrizedCacheEntry<T>>()
143 
getnull144     override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
145         return cache.getOrPut(key.java) { ParametrizedCacheEntry() }
146             .computeIfAbsent(types) { compute(key, types) }
147     }
148 }
149 
150 /**
151  * Wrapper for cacheable serializer of some type.
152  * Used to store cached serializer or indicates that the serializer is not cacheable.
153  *
154  * If serializer for type is not cacheable then value of [serializer] is `null`.
155  */
156 private class CacheEntry<T>(@JvmField val serializer: KSerializer<T>?)
157 
158 /**
159  * Workaround of https://youtrack.jetbrains.com/issue/KT-54611 and https://github.com/Kotlin/kotlinx.serialization/issues/2065
160  */
161 private class KTypeWrapper(private val origin: KType) : KType {
162     override val annotations: List<Annotation>
163         get() = origin.annotations
164     override val arguments: List<KTypeProjection>
165         get() = origin.arguments
166     override val classifier: KClassifier?
167         get() = origin.classifier
168     override val isMarkedNullable: Boolean
169         get() = origin.isMarkedNullable
170 
equalsnull171     override fun equals(other: Any?): Boolean {
172         if (other == null) return false
173         if (origin != (other as? KTypeWrapper)?.origin) return false
174 
175         val kClassifier = classifier
176         if (kClassifier is KClass<*>) {
177             val otherClassifier = (other as? KType)?.classifier
178             if (otherClassifier == null || otherClassifier !is KClass<*>) {
179                 return false
180             }
181             return kClassifier.java == otherClassifier.java
182         } else {
183             return false
184         }
185     }
186 
hashCodenull187     override fun hashCode(): Int {
188         return origin.hashCode()
189     }
190 
toStringnull191     override fun toString(): String {
192         return "KTypeWrapper: $origin"
193     }
194 }
195 
196 private class ParametrizedCacheEntry<T> {
197     private val serializers: ConcurrentHashMap<List<KTypeWrapper>, Result<KSerializer<T>?>> = ConcurrentHashMap()
computeIfAbsentnull198     inline fun computeIfAbsent(types: List<KType>, producer: () -> KSerializer<T>?): Result<KSerializer<T>?> {
199         val wrappedTypes = types.map { KTypeWrapper(it) }
200         return serializers.getOrPut(wrappedTypes) {
201             kotlin.runCatching { producer() }
202         }
203     }
204 }
205 
206