feat: メモリリークの原因となるキャッシュの開放忘れを修正

This commit is contained in:
usbharu 2023-10-12 17:53:02 +09:00
parent 3e7d05a128
commit 6aabc7e98d
3 changed files with 56 additions and 25 deletions

View File

@ -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)

View File

@ -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
}
}

View File

@ -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)
} }