5
5
package kotlinx.serialization.internal
6
6
7
7
import kotlinx.serialization.KSerializer
8
+ import java.lang.ref.SoftReference
8
9
import java.util.concurrent.ConcurrentHashMap
9
10
import kotlin.reflect.KClass
10
11
import kotlin.reflect.KClassifier
@@ -40,38 +41,80 @@ internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KTyp
40
41
return if (useClassValue) ClassValueParametrizedCache (factory) else ConcurrentHashMapParametrizedCache (factory)
41
42
}
42
43
43
- private class ClassValueCache <T >(compute : (KClass <* >) -> KSerializer <T >? ) : SerializerCache<T> {
44
- private val classValue = ClassValueWrapper (compute )
44
+ private class ClassValueCache <T >(val compute : (KClass <* >) -> KSerializer <T >? ) : SerializerCache<T> {
45
+ private val classValue = ClassValueReferences < CacheEntry < T >>( )
45
46
46
- override fun get (key : KClass <Any >): KSerializer <T >? = classValue[key.java].serializer
47
+ override fun get (key : KClass <Any >): KSerializer <T >? {
48
+ return classValue
49
+ .getOrSet(key.java) { CacheEntry (compute(key)) }
50
+ .serializer
51
+ }
47
52
}
48
53
54
+ /* *
55
+ * A class that combines the capabilities of ClassValue and SoftReference.
56
+ * Softly binds the calculated value to the specified class.
57
+ *
58
+ * [SoftReference] used to prevent class loaders from leaking,
59
+ * since the value can transitively refer to an instance of type [Class], this may prevent the loader from
60
+ * being collected during garbage collection.
61
+ *
62
+ * In the first calculation the value is cached, every time [getOrSet] is called, a pre-calculated value is returned.
63
+ *
64
+ * However, the value can be collected during garbage collection (thanks to [SoftReference])
65
+ * - in this case, when trying to call the [getOrSet] function, the value will be calculated again and placed in the cache.
66
+ *
67
+ * 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.
68
+ * In the case of serializers, these should be instances of the same class filled with equivalent values.
69
+ */
49
70
@SuppressAnimalSniffer
50
- private class ClassValueWrapper <T >(private val compute : (KClass <* >) -> KSerializer <T >? ): ClassValue<CacheEntry<T>>() {
51
- /*
52
- * Since during the computing of the value for the `ClassValue` entry, we do not know whether a nullable
53
- * serializer is needed, so we may need to differentiate nullable/non-null caches by a level higher
54
- */
55
- override fun computeValue (type : Class <* >): CacheEntry <T > {
56
- return CacheEntry (compute(type.kotlin))
71
+ private class ClassValueReferences <T > : ClassValue <MutableSoftReference <T >>() {
72
+ override fun computeValue (type : Class <* >): MutableSoftReference <T > {
73
+ return MutableSoftReference ()
57
74
}
58
- }
59
75
60
- private class ClassValueParametrizedCache <T >(private val compute : (KClass <Any >, List <KType >) -> KSerializer <T >? ) : ParametrizedSerializerCache<T> {
61
- private val classValue = ParametrizedClassValueWrapper <T >()
76
+ inline fun getOrSet (key : Class <* >, crossinline factory : () -> T ): T {
77
+ val ref: MutableSoftReference <T > = get(key)
78
+
79
+ ref.reference.get()?.let { return it }
80
+
81
+ // go to the slow path and create serializer with blocking, also wrap factory block
82
+ return ref.getOrSetWithLock { factory() }
83
+ }
62
84
63
- override fun get (key : KClass <Any >, types : List <KType >): Result <KSerializer <T >? > =
64
- classValue[key.java].computeIfAbsent(types) { compute(key, types) }
65
85
}
66
86
67
- @SuppressAnimalSniffer
68
- private class ParametrizedClassValueWrapper <T > : ClassValue <ParametrizedCacheEntry <T >>() {
87
+ /* *
88
+ * Wrapper over `SoftReference`, used to store a mutable value.
89
+ */
90
+ private class MutableSoftReference <T > {
91
+ // volatile because of situations like https://stackoverflow.com/a/7855774
92
+ @JvmField
93
+ @Volatile
94
+ var reference: SoftReference <T > = SoftReference (null )
95
+
69
96
/*
70
- * Since during the computing of the value for the `ClassValue` entry, we do not know whether a nullable
71
- * serializer is needed, so we may need to differentiate nullable/non-null caches by a level higher
72
- */
73
- override fun computeValue (type : Class <* >): ParametrizedCacheEntry <T > {
74
- return ParametrizedCacheEntry ()
97
+ It is important that the monitor for synchronized is the `MutableSoftReference` of a specific class
98
+ This way access to reference is blocked only for one serializable class, and not for all
99
+ */
100
+ @Synchronized
101
+ fun getOrSetWithLock (factory : () -> T ): T {
102
+ // exit function if another thread has already filled in the `reference` with non-null value
103
+ reference.get()?.let { return it }
104
+
105
+ val value = factory()
106
+ reference = SoftReference (value)
107
+ return value
108
+ }
109
+ }
110
+
111
+ private class ClassValueParametrizedCache <T >(private val compute : (KClass <Any >, List <KType >) -> KSerializer <T >? ) :
112
+ ParametrizedSerializerCache <T > {
113
+ private val classValue = ClassValueReferences <ParametrizedCacheEntry <T >>()
114
+
115
+ override fun get (key : KClass <Any >, types : List <KType >): Result <KSerializer <T >? > {
116
+ return classValue.getOrSet(key.java) { ParametrizedCacheEntry () }
117
+ .computeIfAbsent(types) { compute(key, types) }
75
118
}
76
119
}
77
120
@@ -91,8 +134,8 @@ private class ConcurrentHashMapCache<T>(private val compute: (KClass<*>) -> KSer
91
134
}
92
135
93
136
94
-
95
- private class ConcurrentHashMapParametrizedCache < T >( private val compute : ( KClass < Any >, List < KType >) -> KSerializer < T > ? ) : ParametrizedSerializerCache<T> {
137
+ private class ConcurrentHashMapParametrizedCache < T >( private val compute : ( KClass < Any >, List < KType >) -> KSerializer < T > ? ) :
138
+ ParametrizedSerializerCache <T > {
96
139
private val cache = ConcurrentHashMap <Class <* >, ParametrizedCacheEntry <T >>()
97
140
98
141
override fun get (key : KClass <Any >, types : List <KType >): Result <KSerializer <T >? > {
@@ -101,6 +144,12 @@ private class ConcurrentHashMapParametrizedCache<T>(private val compute: (KClass
101
144
}
102
145
}
103
146
147
+ /* *
148
+ * Wrapper for cacheable serializer of some type.
149
+ * Used to store cached serializer or indicates that the serializer is not cacheable.
150
+ *
151
+ * If serializer for type is not cacheable then value of [serializer] is `null`.
152
+ */
104
153
private class CacheEntry <T >(@JvmField val serializer : KSerializer <T >? )
105
154
106
155
/* *
0 commit comments