diff --git a/build.gradle.kts b/build.gradle.kts index af9d60f7..a1912f3b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -143,7 +143,6 @@ dependencies { implementation("org.drewcarlson:kjob-core:0.6.0") implementation("org.drewcarlson:kjob-mongo:0.6.0") - testImplementation("org.slf4j:slf4j-simple:2.0.7") detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.1") } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/resource/APResourceResolveServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/resource/APResourceResolveServiceImpl.kt index 55d1faa2..3402aff4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/resource/APResourceResolveServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/resource/APResourceResolveServiceImpl.kt @@ -8,17 +8,14 @@ import dev.usbharu.hideout.repository.UserRepository import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* -import kotlinx.coroutines.delay import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Service -import java.time.Instant -import java.util.* -import java.util.concurrent.ConcurrentHashMap @Service class APResourceResolveServiceImpl( private val httpClient: HttpClient, private val userRepository: UserRepository, + private val cacheManager: CacheManager, @Qualifier("activitypub") private val objectMapper: ObjectMapper ) : APResourceResolveService { @@ -34,31 +31,19 @@ class APResourceResolveServiceImpl( private suspend fun internalResolve(url: String, singerId: Long?): Object { val key = genCacheKey(url, singerId) - val ifAbsent = cacheKey.putIfAbsent(key, Instant.now().toEpochMilli()) - if (ifAbsent == null) { - val resolve = runResolve(url, singerId?.let { userRepository.findById(it) }) - valueStore.putIfAbsent(key, resolve) - return resolve + + cacheManager.putCache(key) { + runResolve(url, singerId?.let { userRepository.findById(it) }) } - return wait(key) + return cacheManager.getOrWait(key) } private suspend fun internalResolve(url: String, singer: User?): Object { val key = genCacheKey(url, singer?.id) - val ifAbsent = cacheKey.putIfAbsent(key, Instant.now().toEpochMilli()) - if (ifAbsent == null) { - val resolve = runResolve(url, singer) - valueStore.putIfAbsent(key, resolve) - return resolve + cacheManager.putCache(key) { + runResolve(url, singer) } - return wait(key) - } - - private suspend fun wait(key: String): Object { - while (valueStore.containsKey(key).not()) { - delay(1) - } - return valueStore.getValue(key) as Object + return cacheManager.getOrWait(key) } private suspend fun runResolve(url: String, singer: User?): Object { @@ -72,7 +57,4 @@ class APResourceResolveServiceImpl( } return url } - - private val cacheKey = ConcurrentHashMap() - private val valueStore = Collections.synchronizedMap(mutableMapOf()) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/resource/CacheManager.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/resource/CacheManager.kt new file mode 100644 index 00000000..909e7c62 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/resource/CacheManager.kt @@ -0,0 +1,9 @@ +package dev.usbharu.hideout.service.ap.resource + +import dev.usbharu.hideout.domain.model.ap.Object + +interface CacheManager { + + suspend fun putCache(key: String, block: suspend () -> Object) + suspend fun getOrWait(key: String): Object +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/resource/InMemoryCacheManager.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/resource/InMemoryCacheManager.kt new file mode 100644 index 00000000..be0abde5 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/resource/InMemoryCacheManager.kt @@ -0,0 +1,37 @@ +package dev.usbharu.hideout.service.ap.resource + +import dev.usbharu.hideout.domain.model.ap.Object +import kotlinx.coroutines.delay +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.springframework.stereotype.Service + +@Service +class InMemoryCacheManager : CacheManager { + private val cacheKey = mutableSetOf() + private val valueStore = mutableMapOf() + private val keyMutex = Mutex() + + override suspend fun putCache(key: String, block: suspend () -> Object) { + val hasCache: Boolean + keyMutex.withLock { + hasCache = cacheKey.contains(key) + cacheKey.add(key) + } + if (hasCache.not()) { + val processed = block() + + valueStore[key] = processed + + } + } + + override suspend fun getOrWait(key: String): Object { + + while (valueStore.contains(key).not()) { + delay(1) + } + return valueStore.getValue(key) + + } +} diff --git a/src/test/kotlin/dev/usbharu/hideout/service/ap/resource/APResourceResolveServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/ap/resource/APResourceResolveServiceImplTest.kt index 226da282..16b171ff 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/ap/resource/APResourceResolveServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/ap/resource/APResourceResolveServiceImplTest.kt @@ -49,7 +49,8 @@ class APResourceResolveServiceImplTest { ) ) - val apResourceResolveService = APResourceResolveServiceImpl(httpClient, userRepository, objectMapper) + val apResourceResolveService = + APResourceResolveServiceImpl(httpClient, userRepository, InMemoryCacheManager(), objectMapper) apResourceResolveService.resolve("https", 0) @@ -83,7 +84,8 @@ class APResourceResolveServiceImplTest { ) ) - val apResourceResolveService = APResourceResolveServiceImpl(httpClient, userRepository, objectMapper) + val apResourceResolveService = + APResourceResolveServiceImpl(httpClient, userRepository, InMemoryCacheManager(), objectMapper) apResourceResolveService.resolve("https", 0) apResourceResolveService.resolve("https", 0) @@ -120,7 +122,8 @@ class APResourceResolveServiceImplTest { ) ) - val apResourceResolveService = APResourceResolveServiceImpl(httpClient, userRepository, objectMapper) + val apResourceResolveService = + APResourceResolveServiceImpl(httpClient, userRepository, InMemoryCacheManager(), objectMapper) repeat(10) { awaitAll( @@ -168,7 +171,8 @@ class APResourceResolveServiceImplTest { ) ) - val apResourceResolveService = APResourceResolveServiceImpl(httpClient, userRepository, objectMapper) + val apResourceResolveService = + APResourceResolveServiceImpl(httpClient, userRepository, InMemoryCacheManager(), objectMapper) apResourceResolveService.resolve("abcd", 0) apResourceResolveService.resolve("1234", 0)