Skip to content

Commit fe7b163

Browse files
authored
fix memory leak in builders: they should not hold on to the original collection (#193)
1 parent c60eb51 commit fe7b163

File tree

5 files changed

+104
-62
lines changed

5 files changed

+104
-62
lines changed

core/commonMain/src/implementations/immutableList/PersistentVectorBuilder.kt

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,52 @@ import kotlinx.collections.immutable.internal.MutabilityOwnership
1212
import kotlinx.collections.immutable.internal.assert
1313
import kotlinx.collections.immutable.internal.modCount
1414

15-
internal class PersistentVectorBuilder<E>(private var vector: PersistentList<E>,
16-
private var vectorRoot: Array<Any?>?,
17-
private var vectorTail: Array<Any?>,
15+
internal class PersistentVectorBuilder<E>(vector: PersistentList<E>,
16+
vectorRoot: Array<Any?>?,
17+
vectorTail: Array<Any?>,
1818
internal var rootShift: Int) : AbstractMutableList<E>(), PersistentList.Builder<E> {
19+
20+
private var builtVector: PersistentList<E>? = vector
1921
private var ownership = MutabilityOwnership()
22+
2023
internal var root = vectorRoot
21-
private set
24+
private set (value) {
25+
if (value !== field) {
26+
builtVector = null
27+
field = value
28+
}
29+
}
30+
2231
internal var tail = vectorTail
23-
private set
32+
private set (value) {
33+
if (value !== field) {
34+
builtVector = null
35+
field = value
36+
}
37+
}
38+
2439
override var size = vector.size
2540
private set
2641

2742
internal fun getModCount() = modCount
2843

2944
override fun build(): PersistentList<E> {
30-
vector = if (root === vectorRoot && tail === vectorTail) {
31-
vector
32-
} else {
45+
return builtVector ?: run {
46+
val root = root
47+
val tail = tail
3348
ownership = MutabilityOwnership()
34-
vectorRoot = root
35-
vectorTail = tail
36-
if (root == null) {
49+
val newlyBuiltVector: PersistentList<E> = if (root == null) {
3750
if (tail.isEmpty()) {
3851
persistentVectorOf()
3952
} else {
4053
SmallPersistentVector(tail.copyOf(size))
4154
}
4255
} else {
43-
PersistentVector(root!!, tail, size, rootShift)
56+
PersistentVector(root, tail, size, rootShift)
4457
}
58+
builtVector = newlyBuiltVector
59+
newlyBuiltVector
4560
}
46-
return vector
4761
}
4862

4963
private fun rootSize(): Int {

core/commonMain/src/implementations/immutableMap/PersistentHashMapBuilder.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,18 @@ import kotlinx.collections.immutable.internal.DeltaCounter
1212
import kotlinx.collections.immutable.internal.MapImplementation
1313
import kotlinx.collections.immutable.internal.MutabilityOwnership
1414

15-
internal class PersistentHashMapBuilder<K, V>(private var map: PersistentHashMap<K, V>) : PersistentMap.Builder<K, V>, AbstractMutableMap<K, V>() {
15+
internal class PersistentHashMapBuilder<K, V>(map: PersistentHashMap<K, V>) : PersistentMap.Builder<K, V>, AbstractMutableMap<K, V>() {
16+
internal var builtMap: PersistentHashMap<K, V>? = map
17+
private set
1618
internal var ownership = MutabilityOwnership()
1719
private set
1820
internal var node = map.node
21+
set(value) {
22+
if (value !== field) {
23+
field = value
24+
builtMap = null
25+
}
26+
}
1927
internal var operationResult: V? = null
2028
internal var modCount = 0
2129

@@ -27,13 +35,12 @@ internal class PersistentHashMapBuilder<K, V>(private var map: PersistentHashMap
2735
}
2836

2937
override fun build(): PersistentHashMap<K, V> {
30-
map = if (node === map.node) {
31-
map
32-
} else {
38+
return builtMap ?: run {
39+
val newlyBuiltMap = PersistentHashMap(node, size)
40+
builtMap = newlyBuiltMap
3341
ownership = MutabilityOwnership()
34-
PersistentHashMap(node, size)
42+
newlyBuiltMap
3543
}
36-
return map
3744
}
3845

3946
override val entries: MutableSet<MutableMap.MutableEntry<K, V>>

core/commonMain/src/implementations/immutableSet/PersistentHashSetBuilder.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ import kotlinx.collections.immutable.PersistentSet
99
import kotlinx.collections.immutable.internal.DeltaCounter
1010
import kotlinx.collections.immutable.internal.MutabilityOwnership
1111

12-
internal class PersistentHashSetBuilder<E>(private var set: PersistentHashSet<E>) : AbstractMutableSet<E>(), PersistentSet.Builder<E> {
12+
internal class PersistentHashSetBuilder<E>(set: PersistentHashSet<E>) : AbstractMutableSet<E>(), PersistentSet.Builder<E> {
13+
private var builtSet: PersistentHashSet<E>? = set
1314
internal var ownership = MutabilityOwnership()
1415
private set
1516
internal var node = set.node
16-
private set
17+
private set (value) {
18+
if (value !== field) {
19+
builtSet = null
20+
field = value
21+
}
22+
}
1723
internal var modCount = 0
1824
private set
1925

@@ -25,13 +31,12 @@ internal class PersistentHashSetBuilder<E>(private var set: PersistentHashSet<E>
2531
}
2632

2733
override fun build(): PersistentHashSet<E> {
28-
set = if (node === set.node) {
29-
set
30-
} else {
34+
return builtSet ?: run {
35+
val newlyBuiltSet = PersistentHashSet(node, size)
3136
ownership = MutabilityOwnership()
32-
PersistentHashSet(node, size)
37+
builtSet = newlyBuiltSet
38+
newlyBuiltSet
3339
}
34-
return set
3540
}
3641

3742
override fun contains(element: E): Boolean {

core/commonMain/src/implementations/persistentOrderedMap/PersistentOrderedMapBuilder.kt

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import kotlinx.collections.immutable.internal.EndOfChain
1212
import kotlinx.collections.immutable.internal.MapImplementation
1313
import kotlinx.collections.immutable.internal.assert
1414

15-
internal class PersistentOrderedMapBuilder<K, V>(private var map: PersistentOrderedMap<K, V>) : AbstractMutableMap<K, V>(), PersistentMap.Builder<K, V> {
15+
internal class PersistentOrderedMapBuilder<K, V>(map: PersistentOrderedMap<K, V>) : AbstractMutableMap<K, V>(), PersistentMap.Builder<K, V> {
16+
private var builtMap: PersistentOrderedMap<K, V>? = map
17+
1618
internal var firstKey = map.firstKey
1719
private set
1820

@@ -23,15 +25,17 @@ internal class PersistentOrderedMapBuilder<K, V>(private var map: PersistentOrde
2325
override val size: Int get() = hashMapBuilder.size
2426

2527
override fun build(): PersistentMap<K, V> {
26-
val newHashMap = hashMapBuilder.build()
27-
map = if (newHashMap === map.hashMap) {
28+
return builtMap?.also { map ->
29+
assert(hashMapBuilder.builtMap != null)
2830
assert(firstKey === map.firstKey)
2931
assert(lastKey === map.lastKey)
30-
map
31-
} else {
32-
PersistentOrderedMap(firstKey, lastKey, newHashMap)
32+
} ?: run {
33+
assert(hashMapBuilder.builtMap == null)
34+
val newHashMap = hashMapBuilder.build()
35+
val newOrdered = PersistentOrderedMap(firstKey, lastKey, newHashMap)
36+
builtMap = newOrdered
37+
newOrdered
3338
}
34-
return map
3539
}
3640

3741
override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
@@ -55,34 +59,36 @@ internal class PersistentOrderedMapBuilder<K, V>(private var map: PersistentOrde
5559

5660
override fun put(key: K, value: @UnsafeVariance V): V? {
5761
val links = hashMapBuilder[key]
58-
if (links != null) {
62+
return if (links != null) {
5963
if (links.value === value) {
60-
return value
64+
value
65+
} else {
66+
builtMap = null
67+
hashMapBuilder[key] = links.withValue(value)
68+
links.value
6169
}
62-
hashMapBuilder[key] = links.withValue(value)
63-
return links.value
64-
}
65-
66-
if (isEmpty()) { // isEmpty
67-
firstKey = key
68-
lastKey = key
69-
hashMapBuilder[key] = LinkedValue(value)
70-
return null
70+
} else {
71+
builtMap = null
72+
if (isEmpty()) {
73+
firstKey = key
74+
lastKey = key
75+
hashMapBuilder[key] = LinkedValue(value)
76+
} else {
77+
@Suppress("UNCHECKED_CAST")
78+
val lastKey = lastKey as K
79+
val lastLinks = hashMapBuilder[lastKey]!!
80+
assert(!lastLinks.hasNext)
81+
hashMapBuilder[lastKey] = lastLinks.withNext(key)
82+
hashMapBuilder[key] = LinkedValue(value, previous = lastKey)
83+
this.lastKey = key
84+
}
85+
null
7186
}
72-
@Suppress("UNCHECKED_CAST")
73-
val lastKey = lastKey as K
74-
val lastLinks = hashMapBuilder[lastKey]!!
75-
assert(!lastLinks.hasNext)
76-
77-
hashMapBuilder[lastKey] = lastLinks.withNext(key)
78-
hashMapBuilder[key] = LinkedValue(value, previous = lastKey)
79-
this.lastKey = key
80-
return null
8187
}
8288

8389
override fun remove(key: K): V? {
8490
val links = hashMapBuilder.remove(key) ?: return null
85-
91+
builtMap = null
8692
if (links.hasPrevious) {
8793
val previousLinks = hashMapBuilder[links.previous]!!
8894
// assert(previousLinks.next == key)
@@ -115,6 +121,9 @@ internal class PersistentOrderedMapBuilder<K, V>(private var map: PersistentOrde
115121
}
116122

117123
override fun clear() {
124+
if (hashMapBuilder.isNotEmpty()) {
125+
builtMap = null
126+
}
118127
hashMapBuilder.clear()
119128
firstKey = EndOfChain
120129
lastKey = EndOfChain

core/commonMain/src/implementations/persistentOrderedSet/PersistentOrderedSetBuilder.kt

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import kotlinx.collections.immutable.PersistentSet
99
import kotlinx.collections.immutable.internal.EndOfChain
1010
import kotlinx.collections.immutable.internal.assert
1111

12-
internal class PersistentOrderedSetBuilder<E>(private var set: PersistentOrderedSet<E>) : AbstractMutableSet<E>(), PersistentSet.Builder<E> {
12+
internal class PersistentOrderedSetBuilder<E>(set: PersistentOrderedSet<E>) : AbstractMutableSet<E>(), PersistentSet.Builder<E> {
13+
private var builtSet: PersistentOrderedSet<E>? = set
1314
internal var firstElement = set.firstElement
1415
private var lastElement = set.lastElement
1516
internal val hashMapBuilder = set.hashMap.builder()
@@ -18,15 +19,17 @@ internal class PersistentOrderedSetBuilder<E>(private var set: PersistentOrdered
1819
get() = hashMapBuilder.size
1920

2021
override fun build(): PersistentSet<E> {
21-
val newMap = hashMapBuilder.build()
22-
set = if (newMap === set.hashMap) {
22+
return builtSet?.also { set ->
23+
assert(hashMapBuilder.builtMap != null)
2324
assert(firstElement === set.firstElement)
2425
assert(lastElement === set.lastElement)
25-
set
26-
} else {
27-
PersistentOrderedSet(firstElement, lastElement, newMap)
26+
} ?: run {
27+
assert(hashMapBuilder.builtMap == null)
28+
val newMap = hashMapBuilder.build()
29+
val newSet = PersistentOrderedSet(firstElement, lastElement, newMap)
30+
builtSet = newSet
31+
newSet
2832
}
29-
return set
3033
}
3134

3235
override fun contains(element: E): Boolean {
@@ -37,6 +40,7 @@ internal class PersistentOrderedSetBuilder<E>(private var set: PersistentOrdered
3740
if (hashMapBuilder.containsKey(element)) {
3841
return false
3942
}
43+
builtSet = null
4044
if (isEmpty()) {
4145
firstElement = element
4246
lastElement = element
@@ -56,7 +60,7 @@ internal class PersistentOrderedSetBuilder<E>(private var set: PersistentOrdered
5660

5761
override fun remove(element: E): Boolean {
5862
val links = hashMapBuilder.remove(element) ?: return false
59-
63+
builtSet = null
6064
if (links.hasPrevious) {
6165
val previousLinks = hashMapBuilder[links.previous]!!
6266
// assert(previousLinks.next == element)
@@ -78,6 +82,9 @@ internal class PersistentOrderedSetBuilder<E>(private var set: PersistentOrdered
7882
}
7983

8084
override fun clear() {
85+
if (hashMapBuilder.isNotEmpty()) {
86+
builtSet = null
87+
}
8188
hashMapBuilder.clear()
8289
firstElement = EndOfChain
8390
lastElement = EndOfChain

0 commit comments

Comments
 (0)