fixed race condition in InMemoryCache

This commit is contained in:
2025-02-01 10:06:39 +08:00
parent a2a40ab60f
commit 89153b60f8
5 changed files with 30 additions and 48 deletions

View File

@@ -1,7 +1,9 @@
package net.woggioni.gbcs.common package net.woggioni.gbcs.common
import net.woggioni.jwo.JWO
import java.net.URI import java.net.URI
import java.net.URL import java.net.URL
import java.security.MessageDigest
object GBCS { object GBCS {
fun String.toUrl() : URL = URL.of(URI(this), null) fun String.toUrl() : URL = URL.of(URI(this), null)
@@ -9,4 +11,19 @@ object GBCS {
const val GBCS_NAMESPACE_URI: String = "urn:net.woggioni.gbcs.server" const val GBCS_NAMESPACE_URI: String = "urn:net.woggioni.gbcs.server"
const val GBCS_PREFIX: String = "gbcs" const val GBCS_PREFIX: String = "gbcs"
const val XML_SCHEMA_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema-instance" const val XML_SCHEMA_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema-instance"
fun digest(
data: ByteArray,
md: MessageDigest = MessageDigest.getInstance("MD5")
): ByteArray {
md.update(data)
return md.digest()
}
fun digestString(
data: ByteArray,
md: MessageDigest = MessageDigest.getInstance("MD5")
): String {
return JWO.bytesToHex(digest(data, md))
}
} }

View File

@@ -1,21 +0,0 @@
package net.woggioni.gbcs.server.cache
import net.woggioni.jwo.JWO
import java.security.MessageDigest
object CacheUtils {
fun digest(
data: ByteArray,
md: MessageDigest = MessageDigest.getInstance("MD5")
): ByteArray {
md.update(data)
return md.digest()
}
fun digestString(
data: ByteArray,
md: MessageDigest = MessageDigest.getInstance("MD5")
): String {
return JWO.bytesToHex(digest(data, md))
}
}

View File

@@ -1,8 +1,8 @@
package net.woggioni.gbcs.server.cache package net.woggioni.gbcs.server.cache
import net.woggioni.gbcs.api.Cache import net.woggioni.gbcs.api.Cache
import net.woggioni.gbcs.common.GBCS.digestString
import net.woggioni.gbcs.common.contextLogger import net.woggioni.gbcs.common.contextLogger
import net.woggioni.gbcs.server.cache.CacheUtils.digestString
import net.woggioni.jwo.LockFile import net.woggioni.jwo.LockFile
import java.nio.channels.Channels import java.nio.channels.Channels
import java.nio.channels.FileChannel import java.nio.channels.FileChannel

View File

@@ -1,18 +1,15 @@
package net.woggioni.gbcs.server.cache package net.woggioni.gbcs.server.cache
import net.woggioni.gbcs.api.Cache import net.woggioni.gbcs.api.Cache
import net.woggioni.gbcs.server.cache.CacheUtils.digestString import net.woggioni.gbcs.common.GBCS.digestString
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.nio.channels.Channels import java.nio.channels.Channels
import java.security.MessageDigest import java.security.MessageDigest
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.PriorityBlockingQueue import java.util.concurrent.PriorityBlockingQueue
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
import java.util.zip.Deflater import java.util.zip.Deflater
import java.util.zip.DeflaterOutputStream import java.util.zip.DeflaterOutputStream
import java.util.zip.Inflater import java.util.zip.Inflater
@@ -25,33 +22,27 @@ class InMemoryCache(
val compressionLevel: Int val compressionLevel: Int
) : Cache { ) : Cache {
private val map = ConcurrentHashMap<String, MapValue>() private val map = ConcurrentHashMap<String, ByteArray>()
private class MapValue(val rc: AtomicInteger, val payload : AtomicReference<ByteArray>) private class RemovalQueueElement(val key: String, val value : ByteArray, val expiry : Instant) : Comparable<RemovalQueueElement> {
override fun compareTo(other: RemovalQueueElement) = expiry.compareTo(other.expiry)
private class RemovalQueueElement(val key: String, val expiry : Instant) : Comparable<RemovalQueueElement> {
override fun compareTo(other: RemovalQueueElement)= expiry.compareTo(other.expiry)
} }
private val removalQueue = PriorityBlockingQueue<RemovalQueueElement>() private val removalQueue = PriorityBlockingQueue<RemovalQueueElement>()
private var running = true private var running = true
private val garbageCollector = Thread({ private val garbageCollector = Thread {
while(true) { while(true) {
val el = removalQueue.take() val el = removalQueue.take()
val now = Instant.now() val now = Instant.now()
if(now > el.expiry) { if(now > el.expiry) {
val value = map[el.key] ?: continue map.remove(el.key, el.value)
val rc = value.rc.decrementAndGet()
if(rc == 0) {
map.remove(el.key)
}
} else { } else {
removalQueue.put(el) removalQueue.put(el)
Thread.sleep(minOf(Duration.between(now, el.expiry), Duration.ofSeconds(1))) Thread.sleep(minOf(Duration.between(now, el.expiry), Duration.ofSeconds(1)))
} }
} }
}).apply { }.apply {
start() start()
} }
@@ -68,8 +59,6 @@ class InMemoryCache(
} ?: key } ?: key
).let { digest -> ).let { digest ->
map[digest] map[digest]
?.let(MapValue::payload)
?.let(AtomicReference<ByteArray>::get)
?.let { value -> ?.let { value ->
if (compressionEnabled) { if (compressionEnabled) {
val inflater = Inflater() val inflater = Inflater()
@@ -96,11 +85,8 @@ class InMemoryCache(
} else { } else {
content content
} }
val mapValue = map.computeIfAbsent(digest) { map[digest] = value
MapValue(AtomicInteger(0), AtomicReference()) removalQueue.put(RemovalQueueElement(digest, value, Instant.now().plus(maxAge)))
}
mapValue.payload.set(value)
removalQueue.put(RemovalQueueElement(digest, Instant.now().plus(maxAge)))
} }
} }
} }

View File

@@ -2,9 +2,9 @@ org.gradle.configuration-cache=false
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.caching=true org.gradle.caching=true
gbcs.version = 0.0.11 gbcs.version = 0.1.1
lys.version = 2025.01.25 lys.version = 2025.01.31
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
docker.registry.url=gitea.woggioni.net docker.registry.url=gitea.woggioni.net