diff --git a/api/src/main/kotlin/com/few/api/config/ApiConfig.kt b/api/src/main/kotlin/com/few/api/config/ApiConfig.kt index 18cc18f23..39a7771a7 100644 --- a/api/src/main/kotlin/com/few/api/config/ApiConfig.kt +++ b/api/src/main/kotlin/com/few/api/config/ApiConfig.kt @@ -4,6 +4,7 @@ import com.few.api.repo.config.ApiRepoConfig import com.few.batch.config.BatchConfig import com.few.storage.document.config.DocumentStorageConfig import com.few.storage.image.config.ImageStorageConfig +import org.springframework.boot.context.properties.ConfigurationPropertiesScan import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import @@ -15,6 +16,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc @Import(ApiRepoConfig::class, BatchConfig::class, ImageStorageConfig::class, DocumentStorageConfig::class) @EnableWebMvc @EnableAsync +@ConfigurationPropertiesScan(basePackages = [ApiConfig.BASE_PACKAGE]) class ApiConfig { companion object { const val BASE_PACKAGE = "com.few.api" diff --git a/api/src/main/kotlin/com/few/api/config/ApiThreadPoolConfig.kt b/api/src/main/kotlin/com/few/api/config/ApiThreadPoolConfig.kt new file mode 100644 index 000000000..10425f1ae --- /dev/null +++ b/api/src/main/kotlin/com/few/api/config/ApiThreadPoolConfig.kt @@ -0,0 +1,39 @@ +package com.few.api.config + +import com.few.api.config.properties.ThreadPoolProperties +import org.apache.juli.logging.LogFactory +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor + +@Configuration +class ApiThreadPoolConfig { + + private val log = LogFactory.getLog(ApiThreadPoolConfig::class.java) + + companion object { + const val DISCORD_HOOK_EVENT_POOL = "discord-task-" + } + + @Bean + @ConfigurationProperties(prefix = "discord.thread-pool") + fun disCordThreadPoolProperties(): ThreadPoolProperties { + return ThreadPoolProperties() + } + + @Bean(DISCORD_HOOK_EVENT_POOL) + fun discordHookThreadPool() = ThreadPoolTaskExecutor().apply { + val properties = disCordThreadPoolProperties() + corePoolSize = properties.getCorePoolSize() + maxPoolSize = properties.getMaxPoolSize() + queueCapacity = properties.getQueueCapacity() + setWaitForTasksToCompleteOnShutdown(properties.getWaitForTasksToCompleteOnShutdown()) + setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds()) + setThreadNamePrefix("discordHookThreadPool-") + setRejectedExecutionHandler { r, _ -> + log.warn("Discord Hook Event Task Rejected: $r") + } + initialize() + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/config/properties/ThreadPoolProperties.kt b/api/src/main/kotlin/com/few/api/config/properties/ThreadPoolProperties.kt new file mode 100644 index 000000000..2ce84d036 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/config/properties/ThreadPoolProperties.kt @@ -0,0 +1,31 @@ +package com.few.api.config.properties + +import com.few.api.exception.properties.NotSetPropertyException + +data class ThreadPoolProperties( + var corePoolSize: Int? = null, + var maxPoolSize: Int? = null, + var queueCapacity: Int? = null, + var waitForTasksToCompleteOnShutdown: Boolean? = null, + var awaitTerminationSeconds: Int? = null +) { + fun getCorePoolSize(): Int { + return corePoolSize ?: throw NotSetPropertyException("core pool size") + } + + fun getMaxPoolSize(): Int { + return maxPoolSize ?: throw NotSetPropertyException("max pool size") + } + + fun getQueueCapacity(): Int { + return queueCapacity ?: throw NotSetPropertyException("queue capacity") + } + + fun getWaitForTasksToCompleteOnShutdown(): Boolean { + return waitForTasksToCompleteOnShutdown ?: throw NotSetPropertyException("waitForTasksToCompleteOnShutdown") + } + + fun getAwaitTerminationSeconds(): Int { + return awaitTerminationSeconds ?: throw NotSetPropertyException("awaitTerminationSeconds") + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/event/WorkbookSubscriptionEventListener.kt b/api/src/main/kotlin/com/few/api/domain/subscription/event/WorkbookSubscriptionEventListener.kt index 36a6f401b..ad6a7f0f0 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/event/WorkbookSubscriptionEventListener.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/event/WorkbookSubscriptionEventListener.kt @@ -2,6 +2,7 @@ package com.few.api.domain.subscription.event import com.few.api.client.subscription.SubscriptionClient import com.few.api.client.subscription.dto.WorkbookSubscriptionArgs +import com.few.api.config.ApiThreadPoolConfig.Companion.DISCORD_HOOK_EVENT_POOL import com.few.api.domain.subscription.event.dto.WorkbookSubscriptionEvent import com.few.api.domain.subscription.service.WorkbookService import com.few.api.domain.subscription.service.dto.ReadWorkbookTitleInDto @@ -18,7 +19,7 @@ class WorkbookSubscriptionEventListener( private val workbookService: WorkbookService ) { - @Async + @Async(value = DISCORD_HOOK_EVENT_POOL) @EventListener fun handleWorkbookSubscriptionEvent(event: WorkbookSubscriptionEvent) { val title = ReadWorkbookTitleInDto(event.workbookId).let { dto -> diff --git a/api/src/main/kotlin/com/few/api/exception/properties/NotSetPropertyException.kt b/api/src/main/kotlin/com/few/api/exception/properties/NotSetPropertyException.kt new file mode 100644 index 000000000..2be9cff32 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/exception/properties/NotSetPropertyException.kt @@ -0,0 +1,3 @@ +package com.few.api.exception.properties + +class NotSetPropertyException(property: String) : RuntimeException("$property is not set") \ No newline at end of file diff --git a/api/src/main/resources/application-client-local.yml b/api/src/main/resources/application-client-local.yml index 3808a6abb..6d13c3012 100644 --- a/api/src/main/resources/application-client-local.yml +++ b/api/src/main/resources/application-client-local.yml @@ -5,3 +5,11 @@ client: webhook: discord: "localhost:8080/webhook/discord/unused" + +discord: + thread-pool: + core-pool-size: 5 + max-pool-size: 15 + queue-capacity: 30 + wait-for-tasks-to-complete-on-shutdown: true + await-termination-seconds: 60 diff --git a/api/src/main/resources/application-client-prd.yml b/api/src/main/resources/application-client-prd.yml index 042942ce2..2b009b3ce 100644 --- a/api/src/main/resources/application-client-prd.yml +++ b/api/src/main/resources/application-client-prd.yml @@ -5,3 +5,11 @@ client: webhook: discord: ${WEBHOOK_DISCORD} + +discord: + thread-pool: + core-pool-size: ${DISCORD_THREAD_POOL_CORE_POOL_SIZE:5} + max-pool-size: ${DISCORD_THREAD_POOL_MAX_POOL_SIZE:15} + queue-capacity: ${DISCORD_THREAD_POOL_QUEUE_CAPACITY:30} + wait-for-tasks-to-complete-on-shutdown: ${DISCORD_THREAD_POOL_WAIT_FOR_TASKS_TO_COMPLETE_ON_SHUTDOWN:true} + await-termination-seconds: ${DISCORD_THREAD_POOL_AWAIT_TERMINATION_SECONDS:60} diff --git a/api/src/test/resources/application-test.yml b/api/src/test/resources/application-test.yml index c387cea4c..948332566 100644 --- a/api/src/test/resources/application-test.yml +++ b/api/src/test/resources/application-test.yml @@ -48,3 +48,11 @@ client: webhook: discord: https://discord.com/api/webhooks/1234567890/abcdefg + +discord: + thread-pool: + core-pool-size: 5 + max-pool-size: 15 + queue-capacity: 30 + wait-for-tasks-to-complete-on-shutdown: true + await-termination-seconds: 60