diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskAddedEvent.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskEvents.kt similarity index 67% rename from komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskAddedEvent.kt rename to komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskEvents.kt index 20fdcdedb..59673128d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskAddedEvent.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskEvents.kt @@ -1,3 +1,4 @@ package org.gotson.komga.application.tasks class TaskAddedEvent +class TaskPoolSizeChangedEvent diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskProcessor.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskProcessor.kt index 64e5d70f4..3603953a9 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskProcessor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskProcessor.kt @@ -1,6 +1,7 @@ package org.gotson.komga.application.tasks import mu.KotlinLogging +import org.gotson.komga.infrastructure.configuration.KomgaSettingsProvider import org.springframework.beans.factory.InitializingBean import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.boot.task.TaskExecutorBuilder @@ -14,12 +15,13 @@ private val logger = KotlinLogging.logger {} class TaskProcessor( private val tasksRepository: TasksRepository, private val taskHandler: TaskHandler, + private val settingsProvider: KomgaSettingsProvider, taskExecutorBuilder: TaskExecutorBuilder, ) : InitializingBean { val executor: ThreadPoolTaskExecutor = taskExecutorBuilder .threadNamePrefix("taskProcessor-") - .corePoolSize(8) + .corePoolSize(settingsProvider.taskPoolSize) .build() .apply { initialize() } @@ -32,11 +34,18 @@ class TaskProcessor( processTasks = true } + @EventListener(TaskPoolSizeChangedEvent::class) + fun taskPoolSizeChanged() { + executor.corePoolSize = settingsProvider.taskPoolSize + } + @EventListener(TaskAddedEvent::class, ApplicationReadyEvent::class) fun processAvailableTask() { if (processTasks) { + logger.debug { "Active count: ${executor.activeCount}, Core Pool Size: ${executor.corePoolSize}, Pool Size: ${executor.poolSize}" } + if (executor.corePoolSize == 1) executor.execute { takeAndProcess() } // fan out while threads are available - while (tasksRepository.hasAvailable() && executor.activeCount < executor.corePoolSize) + else while (tasksRepository.hasAvailable() && executor.activeCount < executor.corePoolSize) executor.execute { takeAndProcess() } } else { logger.debug { "Not processing tasks" } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaSettingsProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaSettingsProvider.kt index 427768256..0537ceffb 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaSettingsProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaSettingsProvider.kt @@ -1,8 +1,10 @@ package org.gotson.komga.infrastructure.configuration import org.apache.commons.lang3.RandomStringUtils +import org.gotson.komga.application.tasks.TaskPoolSizeChangedEvent import org.gotson.komga.domain.model.ThumbnailSize import org.gotson.komga.infrastructure.jooq.main.ServerSettingsDao +import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Service import kotlin.time.Duration import kotlin.time.Duration.Companion.days @@ -10,6 +12,7 @@ import kotlin.time.Duration.Companion.days @Service class KomgaSettingsProvider( private val serverSettingsDao: ServerSettingsDao, + private val eventPublisher: ApplicationEventPublisher, ) { var deleteEmptyCollections: Boolean = serverSettingsDao.getSettingByKey(Settings.DELETE_EMPTY_COLLECTIONS.name, Boolean::class.java) ?: false @@ -54,6 +57,14 @@ class KomgaSettingsProvider( serverSettingsDao.saveSetting(Settings.THUMBNAIL_SIZE.name, value.name) field = value } + + var taskPoolSize: Int = + serverSettingsDao.getSettingByKey(Settings.TASK_POOL_SIZE.name, Int::class.java) ?: 8 + set(value) { + serverSettingsDao.saveSetting(Settings.TASK_POOL_SIZE.name, value) + field = value + eventPublisher.publishEvent(TaskPoolSizeChangedEvent()) + } } private enum class Settings { @@ -62,4 +73,5 @@ private enum class Settings { REMEMBER_ME_KEY, REMEMBER_ME_DURATION, THUMBNAIL_SIZE, + TASK_POOL_SIZE, } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SettingsController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SettingsController.kt index 764bbe274..852c768f8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SettingsController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SettingsController.kt @@ -32,6 +32,7 @@ class SettingsController( komgaSettingsProvider.deleteEmptyReadLists, komgaSettingsProvider.rememberMeDuration.inWholeDays, komgaSettingsProvider.thumbnailSize.toDto(), + komgaSettingsProvider.taskPoolSize, ) @PatchMapping @@ -45,5 +46,6 @@ class SettingsController( newSettings.rememberMeDurationDays?.let { komgaSettingsProvider.rememberMeDuration = it.days } if (newSettings.renewRememberMeKey == true) komgaSettingsProvider.renewRememberMeKey() newSettings.thumbnailSize?.let { komgaSettingsProvider.thumbnailSize = it.toDomain() } + newSettings.taskPoolSize?.let { komgaSettingsProvider.taskPoolSize = it } } } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SettingsDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SettingsDto.kt index daddd1d81..38a755331 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SettingsDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SettingsDto.kt @@ -5,4 +5,5 @@ data class SettingsDto( val deleteEmptyReadLists: Boolean, val rememberMeDurationDays: Long, val thumbnailSize: ThumbnailSizeDto, + val taskPoolSize: Int, ) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SettingsUpdateDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SettingsUpdateDto.kt index cb81d2dd3..e6d6075f5 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SettingsUpdateDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SettingsUpdateDto.kt @@ -9,4 +9,6 @@ data class SettingsUpdateDto( val rememberMeDurationDays: Long? = null, val renewRememberMeKey: Boolean? = null, val thumbnailSize: ThumbnailSizeDto? = null, + @get:Positive + val taskPoolSize: Int? = null, ) diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SettingsControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SettingsControllerTest.kt index f2d916ad3..fec76a11a 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SettingsControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SettingsControllerTest.kt @@ -53,6 +53,7 @@ class SettingsControllerTest( komgaSettingsProvider.deleteEmptyReadLists = false komgaSettingsProvider.rememberMeDuration = 5.days komgaSettingsProvider.thumbnailSize = ThumbnailSize.LARGE + komgaSettingsProvider.taskPoolSize = 4 mockMvc.get("/api/v1/settings") .andExpect { @@ -61,6 +62,7 @@ class SettingsControllerTest( jsonPath("deleteEmptyReadLists") { value(false) } jsonPath("rememberMeDurationDays") { value(5) } jsonPath("thumbnailSize") { value("LARGE") } + jsonPath("taskPoolSize") { value(4) } } } @@ -71,6 +73,7 @@ class SettingsControllerTest( komgaSettingsProvider.deleteEmptyReadLists = true komgaSettingsProvider.rememberMeDuration = 5.days komgaSettingsProvider.thumbnailSize = ThumbnailSize.LARGE + komgaSettingsProvider.taskPoolSize = 4 val rememberMeKey = komgaSettingsProvider.rememberMeKey @@ -80,7 +83,8 @@ class SettingsControllerTest( "deleteEmptyCollections": false, "rememberMeDurationDays": 15, "renewRememberMeKey": true, - "thumbnailSize": "MEDIUM" + "thumbnailSize": "MEDIUM", + "taskPoolSize": 8 } """.trimIndent() @@ -97,6 +101,7 @@ class SettingsControllerTest( assertThat(komgaSettingsProvider.rememberMeDuration).isEqualTo(15.days) assertThat(komgaSettingsProvider.rememberMeKey).isNotEqualTo(rememberMeKey) assertThat(komgaSettingsProvider.thumbnailSize).isEqualTo(ThumbnailSize.MEDIUM) + assertThat(komgaSettingsProvider.taskPoolSize).isEqualTo(8) } @ParameterizedTest @@ -107,6 +112,8 @@ class SettingsControllerTest( """{"rememberMeDurationDays": 0}""", //language=JSON """{"thumbnailSize": "HUGE"}""", + """{"taskPoolSize": 0}""", + """{"taskPoolSize": -15}""", ], ) fun `given admin user when updating with invalid settings then returns bad request`(jsonString: String) {