LRUCache

class LRUCache<String, V>(var capacity: Int, val ttl: Long, val onRemove: (key: String, value: V) -> Unit?, val canEvict: (key: String, value: V) -> Boolean? = null) : MutableMap<String, V>

Internally synchronized LRU map.

Every public read or write takes lock for the duration of the operation, so callers can touch the cache from any thread without external locking. This is important for com.xweather.mapsgl.sources.VectorTileSource's tile cache, which is read from the main thread (e.g. refreshCustomStencilMask → replayCustomStencilFeedsFromCachedVectorTiles) at the same time tile-parse worker coroutines call get/set/delete from background threads.

History: the cache used to be lock-free, with iteration via _list.items().map { ... } — a lazy Sequence that walks current.next pointers in the LRU's doubly-linked list. Any concurrent get mutated those pointers (via _bumpAge → _list.unshiftNode), so the iterator's .next pointed back into already-visited territory and .toList() / .count() looped forever. We patched the obvious freezes by snapshotting up front with .toList(), but the snapshot itself is the same lazy walk: a background tile-parse get mid-snapshot would re-trigger the same infinite loop — .toList() keeps add()ing to its ArrayList, growing it until the heap is exhausted (we observed an OOM trying to allocate 118 MB in replayCustomStencilFeedsFromCachedVectorTiles's .toList()).

The fix is to actually make the cache thread-safe rather than try to defend against the data race at every call site. All mutators and iteration helpers now build their results under lock; the iteration helpers return eagerly materialised snapshots, so the returned Sequence no longer touches _list after we release the lock. The _cache.values-based iteration also doesn't depend on .next pointers, so it's robust against the LRU-order shuffling that broke the linked-list walk.

Constructors

Link copied to clipboard
constructor(capacity: Int, ttl: Long, onRemove: (key: String, value: V) -> Unit?, canEvict: (key: String, value: V) -> Boolean? = null)

Properties

Link copied to clipboard
Link copied to clipboard
val canEvict: (key: String, value: V) -> Boolean? = null

Optional veto for capacity eviction. Entries whose work is still in flight (e.g. a tile queued for GPU upload) must not be dropped: the owner still holds the key, so evicting it strands that work and its progress bookkeeping. Eviction walks toward the newest entry until it finds a candidate this predicate allows.

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
val first: V?
Link copied to clipboard
open override val keys: MutableSet<String>
Link copied to clipboard
val last: V?
Link copied to clipboard
val onRemove: (key: String, value: V) -> Unit?
Link copied to clipboard
open override val size: Int
Link copied to clipboard
val ttl: Long
Link copied to clipboard
open override val values: MutableCollection<V>

Functions

Link copied to clipboard
open override fun clear()
Link copied to clipboard
open fun compute(p0: String, p1: BiFunction<in String, in V?, out V?>): V?
Link copied to clipboard
open fun computeIfAbsent(p0: String, p1: Function<in String, out V>): V
Link copied to clipboard
open fun computeIfPresent(p0: String, p1: BiFunction<in String, in V & Any, out V?>): V?
Link copied to clipboard
open override fun containsKey(key: String): Boolean
Link copied to clipboard
open override fun containsValue(value: V): Boolean
Link copied to clipboard
fun delete(key: String): V?
Link copied to clipboard

Like keys but for (key, value) pairs. See keys for the snapshot rationale.

Link copied to clipboard
fun forEach(fn: (entry: Entry<String, V>) -> Unit)
open fun forEach(p0: BiConsumer<in String, in V>)
Link copied to clipboard
open operator override fun get(key: String): V?
Link copied to clipboard
open fun getOrDefault(key: String, defaultValue: V): V
Link copied to clipboard
fun has(key: String): Boolean
Link copied to clipboard
open override fun isEmpty(): Boolean
Link copied to clipboard

Eagerly snapshots all keys under lock and returns a sequence over the snapshot. The returned sequence does NOT touch the live LRU after this call returns, so callers can iterate without holding the lock and without risking the linked-list .next infinite loop that the old lazy _list.items() sequence had.

Link copied to clipboard
open fun merge(p0: String, p1: V & Any, p2: BiFunction<in V & Any, in V & Any, out V?>): V?
Link copied to clipboard
fun peek(key: String): V?
Link copied to clipboard
open override fun put(key: String, value: V): V?
Link copied to clipboard
open override fun putAll(from: Map<out String, V>)
Link copied to clipboard
open fun putIfAbsent(p0: String, p1: V): V?
Link copied to clipboard
open override fun remove(key: String): V?
open fun remove(key: String, value: V): Boolean
Link copied to clipboard
open fun replace(p0: String, p1: V): V?
open fun replace(p0: String, p1: V, p2: V): Boolean
Link copied to clipboard
open fun replaceAll(p0: BiFunction<in String, in V, out V>)
Link copied to clipboard
operator fun set(key: String, value: V)
Link copied to clipboard
fun values(): Sequence<V>

Like keys but for values. See keys for the snapshot rationale.