TTLMap.kt
3.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/*
* Copyright 2025 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.livekit.android.util
import android.os.SystemClock
import kotlin.time.Duration
import kotlin.time.DurationUnit
/**
* @suppress
*/
class TTLMap<K, V>(
val ttl: Duration,
private val clock: () -> Long = { SystemClock.elapsedRealtime() },
) : MutableMap<K, V> {
private data class TTLItem<V>(val value: V, val expiresAt: Long)
private val map = mutableMapOf<K, TTLItem<V>>()
private val lastCleanup = getNow()
override fun get(key: K): V? {
val item = map[key]
if (item == null) {
return null
}
if (item.expiresAt < getNow()) {
map.remove(key)
return null
}
return item.value
}
override val size: Int
get() {
cleanup()
return map.size
}
override fun containsKey(key: K): Boolean = get(key) != null
override fun containsValue(value: V): Boolean = values.contains(value)
override fun isEmpty(): Boolean {
cleanup()
return map.isEmpty()
}
private data class MutableEntry<K, V>(override val key: K, override var value: V) : MutableMap.MutableEntry<K, V> {
override fun setValue(newValue: V): V {
val old = this.value
this.value = newValue
return old
}
}
override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
get() {
cleanup()
return map.entries
.map { (key, value) -> MutableEntry(key, value.value) }
.toMutableSet()
}
override val keys: MutableSet<K>
get() {
cleanup()
return map.keys
}
override val values: MutableCollection<V>
get() {
cleanup()
return map.values
.map { (value, _) -> value }
.toMutableList()
}
override fun clear() {
map.clear()
}
override fun put(key: K, value: V): V? {
val now = getNow()
val ttlMs = ttl.toLong(DurationUnit.MILLISECONDS)
if (now - lastCleanup > ttlMs / 2) {
cleanup()
}
val expiresAt = now + ttlMs
map[key] = TTLItem(value, expiresAt)
return value
}
override fun putAll(from: Map<out K, V>) {
from.iterator().forEach { (key, value) ->
put(key, value)
}
}
override fun remove(key: K): V? {
return map.remove(key)?.value
}
fun cleanup() {
val now = getNow()
val iterator = map.iterator()
while (iterator.hasNext()) {
val (_, entry) = iterator.next()
if (entry.expiresAt < now) {
iterator.remove()
}
}
}
private fun getNow() = clock()
}