LRUCache
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
Properties
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.
Functions
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.