mirror of https://github.com/usbharu/Hideout.git
feat: メモリリークの原因となるキャッシュの開放忘れを修正
This commit is contained in:
parent
3e7d05a128
commit
6aabc7e98d
|
@ -1,27 +1,40 @@
|
||||||
package dev.usbharu.hideout.service.ap.resource
|
package dev.usbharu.hideout.service.ap.resource
|
||||||
|
|
||||||
import dev.usbharu.hideout.domain.model.ap.Object
|
import dev.usbharu.hideout.domain.model.ap.Object
|
||||||
|
import dev.usbharu.hideout.util.LruCache
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class InMemoryCacheManager : CacheManager {
|
class InMemoryCacheManager : CacheManager {
|
||||||
private val cacheKey = mutableSetOf<String>()
|
private val cacheKey = LruCache<String, Long>(15)
|
||||||
private val valueStore = mutableMapOf<String, Object>()
|
private val valueStore = mutableMapOf<String, Object>()
|
||||||
private val keyMutex = Mutex()
|
private val keyMutex = Mutex()
|
||||||
|
|
||||||
override suspend fun putCache(key: String, block: suspend () -> Object) {
|
override suspend fun putCache(key: String, block: suspend () -> Object) {
|
||||||
val hasCache: Boolean
|
val needRunBlock: Boolean
|
||||||
keyMutex.withLock {
|
keyMutex.withLock {
|
||||||
hasCache = cacheKey.contains(key)
|
cacheKey.filter { Instant.ofEpochMilli(it.value).plusSeconds(300) <= Instant.now() }
|
||||||
cacheKey.add(key)
|
|
||||||
|
val cached = cacheKey.get(key)
|
||||||
|
if (cached == null) {
|
||||||
|
needRunBlock = true
|
||||||
|
cacheKey[key] = Instant.now().toEpochMilli()
|
||||||
|
|
||||||
|
valueStore.remove(key)
|
||||||
|
} else {
|
||||||
|
needRunBlock = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (hasCache.not()) {
|
if (needRunBlock) {
|
||||||
val processed = block()
|
val processed = block()
|
||||||
|
|
||||||
valueStore[key] = processed
|
if (cacheKey.containsKey(key)) {
|
||||||
|
valueStore[key] = processed
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +42,9 @@ class InMemoryCacheManager : CacheManager {
|
||||||
override suspend fun getOrWait(key: String): Object {
|
override suspend fun getOrWait(key: String): Object {
|
||||||
|
|
||||||
while (valueStore.contains(key).not()) {
|
while (valueStore.contains(key).not()) {
|
||||||
|
if (cacheKey.containsKey(key).not()) {
|
||||||
|
throw IllegalStateException("Invalid cache key.")
|
||||||
|
}
|
||||||
delay(1)
|
delay(1)
|
||||||
}
|
}
|
||||||
return valueStore.getValue(key)
|
return valueStore.getValue(key)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package dev.usbharu.hideout.util
|
||||||
|
|
||||||
|
import java.io.Serial
|
||||||
|
|
||||||
|
class LruCache<K, V>(private val maxSize: Int) : LinkedHashMap<K, V>(15, 0.75f, true) {
|
||||||
|
|
||||||
|
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?): Boolean = size > maxSize
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Serial
|
||||||
|
private const val serialVersionUID: Long = -6446947260925053191L
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.usbharu.hideout.service.ap.resource
|
package dev.usbharu.hideout.service.ap.resource
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Object
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
||||||
import dev.usbharu.hideout.repository.UserRepository
|
import dev.usbharu.hideout.repository.UserRepository
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
|
@ -52,7 +53,7 @@ class APResourceResolveServiceImplTest {
|
||||||
val apResourceResolveService =
|
val apResourceResolveService =
|
||||||
APResourceResolveServiceImpl(httpClient, userRepository, InMemoryCacheManager(), objectMapper)
|
APResourceResolveServiceImpl(httpClient, userRepository, InMemoryCacheManager(), objectMapper)
|
||||||
|
|
||||||
apResourceResolveService.resolve("https", 0)
|
apResourceResolveService.resolve<Object>("https", 0)
|
||||||
|
|
||||||
assertEquals(1, count)
|
assertEquals(1, count)
|
||||||
}
|
}
|
||||||
|
@ -87,10 +88,10 @@ class APResourceResolveServiceImplTest {
|
||||||
val apResourceResolveService =
|
val apResourceResolveService =
|
||||||
APResourceResolveServiceImpl(httpClient, userRepository, InMemoryCacheManager(), objectMapper)
|
APResourceResolveServiceImpl(httpClient, userRepository, InMemoryCacheManager(), objectMapper)
|
||||||
|
|
||||||
apResourceResolveService.resolve("https", 0)
|
apResourceResolveService.resolve<Object>("https", 0)
|
||||||
apResourceResolveService.resolve("https", 0)
|
apResourceResolveService.resolve<Object>("https", 0)
|
||||||
apResourceResolveService.resolve("https", 0)
|
apResourceResolveService.resolve<Object>("https", 0)
|
||||||
apResourceResolveService.resolve("https", 0)
|
apResourceResolveService.resolve<Object>("https", 0)
|
||||||
|
|
||||||
assertEquals(1, count)
|
assertEquals(1, count)
|
||||||
}
|
}
|
||||||
|
@ -127,17 +128,17 @@ class APResourceResolveServiceImplTest {
|
||||||
|
|
||||||
repeat(10) {
|
repeat(10) {
|
||||||
awaitAll(
|
awaitAll(
|
||||||
async { apResourceResolveService.resolve("https", 0) },
|
async { apResourceResolveService.resolve<Object>("https", 0) },
|
||||||
async { apResourceResolveService.resolve("https", 0) },
|
async { apResourceResolveService.resolve<Object>("https", 0) },
|
||||||
async { apResourceResolveService.resolve("https", 0) },
|
async { apResourceResolveService.resolve<Object>("https", 0) },
|
||||||
async { apResourceResolveService.resolve("https", 0) },
|
async { apResourceResolveService.resolve<Object>("https", 0) },
|
||||||
async { apResourceResolveService.resolve("https", 0) },
|
async { apResourceResolveService.resolve<Object>("https", 0) },
|
||||||
async { apResourceResolveService.resolve("https", 0) },
|
async { apResourceResolveService.resolve<Object>("https", 0) },
|
||||||
async { apResourceResolveService.resolve("https", 0) },
|
async { apResourceResolveService.resolve<Object>("https", 0) },
|
||||||
async { apResourceResolveService.resolve("https", 0) },
|
async { apResourceResolveService.resolve<Object>("https", 0) },
|
||||||
async { apResourceResolveService.resolve("https", 0) },
|
async { apResourceResolveService.resolve<Object>("https", 0) },
|
||||||
async { apResourceResolveService.resolve("https", 0) },
|
async { apResourceResolveService.resolve<Object>("https", 0) },
|
||||||
async { apResourceResolveService.resolve("https", 0) },
|
async { apResourceResolveService.resolve<Object>("https", 0) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,9 +175,9 @@ class APResourceResolveServiceImplTest {
|
||||||
val apResourceResolveService =
|
val apResourceResolveService =
|
||||||
APResourceResolveServiceImpl(httpClient, userRepository, InMemoryCacheManager(), objectMapper)
|
APResourceResolveServiceImpl(httpClient, userRepository, InMemoryCacheManager(), objectMapper)
|
||||||
|
|
||||||
apResourceResolveService.resolve("abcd", 0)
|
apResourceResolveService.resolve<Object>("abcd", 0)
|
||||||
apResourceResolveService.resolve("1234", 0)
|
apResourceResolveService.resolve<Object>("1234", 0)
|
||||||
apResourceResolveService.resolve("aaaa", 0)
|
apResourceResolveService.resolve<Object>("aaaa", 0)
|
||||||
|
|
||||||
assertEquals(3, count)
|
assertEquals(3, count)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue