simplified InMemoryCache implementation

This commit is contained in:
2025-03-03 09:44:37 +08:00
parent d64f7f4f27
commit 31ce34cddb

View File

@@ -6,11 +6,11 @@ import net.woggioni.rbcs.api.CacheValueMetadata
import net.woggioni.rbcs.common.createLogger import net.woggioni.rbcs.common.createLogger
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
import java.util.PriorityQueue
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.PriorityBlockingQueue
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.withLock
private class CacheKey(private val value: ByteArray) { private class CacheKey(private val value: ByteArray) {
override fun equals(other: Any?) = if (other is CacheKey) { override fun equals(other: Any?) = if (other is CacheKey) {
@@ -34,15 +34,17 @@ class InMemoryCache(
private val log = createLogger<InMemoryCache>() private val log = createLogger<InMemoryCache>()
} }
private val size = AtomicLong() private var mapSize : Long = 0
private val map = ConcurrentHashMap<CacheKey, CacheEntry>() private val map = HashMap<CacheKey, CacheEntry>()
private val lock = ReentrantReadWriteLock()
private val cond = lock.writeLock().newCondition()
private class RemovalQueueElement(val key: CacheKey, val value: CacheEntry, val expiry: Instant) : private class RemovalQueueElement(val key: CacheKey, val value: CacheEntry, val expiry: Instant) :
Comparable<RemovalQueueElement> { Comparable<RemovalQueueElement> {
override fun compareTo(other: RemovalQueueElement) = expiry.compareTo(other.expiry) override fun compareTo(other: RemovalQueueElement) = expiry.compareTo(other.expiry)
} }
private val removalQueue = PriorityBlockingQueue<RemovalQueueElement>() private val removalQueue = PriorityQueue<RemovalQueueElement>()
@Volatile @Volatile
private var running = true private var running = true
@@ -51,20 +53,27 @@ class InMemoryCache(
init { init {
Thread.ofVirtual().name("in-memory-cache-gc").start { Thread.ofVirtual().name("in-memory-cache-gc").start {
try { try {
while (running) { lock.writeLock().withLock {
val el = removalQueue.poll(1, TimeUnit.SECONDS) ?: continue while (running) {
val value = el.value val el = removalQueue.poll()
val now = Instant.now() if(el == null) {
if (now > el.expiry) { cond.await(1000, TimeUnit.MILLISECONDS)
val removed = map.remove(el.key, value) continue
if (removed) { }
updateSizeAfterRemoval(value.content) val value = el.value
//Decrease the reference count for map val now = Instant.now()
value.content.release() if (now > el.expiry) {
val removed = map.remove(el.key, value)
if (removed) {
updateSizeAfterRemoval(value.content)
//Decrease the reference count for map
value.content.release()
}
} else {
removalQueue.offer(el)
val interval = minOf(Duration.between(now, el.expiry), Duration.ofSeconds(1))
cond.await(interval.toMillis(), TimeUnit.MILLISECONDS)
} }
} else {
removalQueue.put(el)
Thread.sleep(minOf(Duration.between(now, el.expiry), Duration.ofSeconds(1)))
} }
} }
complete(null) complete(null)
@@ -77,7 +86,7 @@ class InMemoryCache(
fun removeEldest(): Long { fun removeEldest(): Long {
while (true) { while (true) {
val el = removalQueue.take() val el = removalQueue.poll() ?: return mapSize
val value = el.value val value = el.value
val removed = map.remove(el.key, value) val removed = map.remove(el.key, value)
if (removed) { if (removed) {
@@ -90,18 +99,22 @@ class InMemoryCache(
} }
private fun updateSizeAfterRemoval(removed: ByteBuf): Long { private fun updateSizeAfterRemoval(removed: ByteBuf): Long {
return size.updateAndGet { currentSize: Long -> mapSize -= removed.readableBytes()
currentSize - removed.readableBytes() return mapSize
}
} }
override fun asyncClose() : CompletableFuture<Void> { override fun asyncClose() : CompletableFuture<Void> {
running = false running = false
lock.writeLock().withLock {
cond.signal()
}
return closeFuture return closeFuture
} }
fun get(key: ByteArray) = map[CacheKey(key)]?.run { fun get(key: ByteArray) = lock.readLock().withLock {
CacheEntry(metadata, content.retainedDuplicate()) map[CacheKey(key)]?.run {
CacheEntry(metadata, content.retainedDuplicate())
}
} }
fun put( fun put(
@@ -109,18 +122,18 @@ class InMemoryCache(
value: CacheEntry, value: CacheEntry,
) { ) {
val cacheKey = CacheKey(key) val cacheKey = CacheKey(key)
val oldSize = map.put(cacheKey, value)?.let { old -> lock.writeLock().withLock {
val result = old.content.readableBytes() val oldSize = map.put(cacheKey, value)?.let { old ->
old.content.release() val result = old.content.readableBytes()
result old.content.release()
} ?: 0 result
val delta = value.content.readableBytes() - oldSize } ?: 0
var newSize = size.updateAndGet { currentSize: Long -> val delta = value.content.readableBytes() - oldSize
currentSize + delta mapSize += delta
} removalQueue.offer(RemovalQueueElement(cacheKey, value, Instant.now().plus(maxAge)))
removalQueue.put(RemovalQueueElement(cacheKey, value, Instant.now().plus(maxAge))) while (mapSize > maxSize) {
while (newSize > maxSize) { removeEldest()
newSize = removeEldest() }
} }
} }
} }