feat(api): configure number of task processing threads

This commit is contained in:
Gauthier Roebroeck 2023-10-20 16:50:02 +08:00
parent 528eddb94c
commit 9ef319b703
7 changed files with 37 additions and 3 deletions

View file

@ -1,3 +1,4 @@
package org.gotson.komga.application.tasks
class TaskAddedEvent
class TaskPoolSizeChangedEvent

View file

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

View file

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

View file

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

View file

@ -5,4 +5,5 @@ data class SettingsDto(
val deleteEmptyReadLists: Boolean,
val rememberMeDurationDays: Long,
val thumbnailSize: ThumbnailSizeDto,
val taskPoolSize: Int,
)

View file

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

View file

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