From 1b64c6989a573f0f07562a450051bc23282b6675 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Sun, 29 Sep 2024 19:36:12 +0800 Subject: [PATCH 1/2] =?UTF-8?q?longPolling=20=E5=A2=9E=E5=8A=A0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=20`handleRetry`,=20=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=96=B9=E5=BC=8F=E6=94=AF=E6=8C=81=20`retry`=20?= =?UTF-8?q?=E5=92=8C=20`handleRetry`=20=E9=85=8D=E7=BD=AE=20handleRetry=20?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E6=A8=A1=E5=BC=8F=E4=B8=BA=20`TIMEOUT=5FONLY?= =?UTF-8?q?`=20(=E4=B8=8E=E4=B9=8B=E5=89=8D=E8=A1=8C=E4=B8=BA=E4=B8=80?= =?UTF-8?q?=E8=87=B4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../telegram/api/update/GetUpdatesApi.kt | 2 +- .../SerializableTelegramBotConfiguration.kt | 7 +- .../forte/simbot/telegram/stdlib/bot/Bot.kt | 38 +++++++++- .../telegram/stdlib/bot/internal/BotImpl.kt | 74 ++++++++++++++++--- 4 files changed, 107 insertions(+), 14 deletions(-) diff --git a/simbot-component-telegram-api/src/commonMain/kotlin/love/forte/simbot/telegram/api/update/GetUpdatesApi.kt b/simbot-component-telegram-api/src/commonMain/kotlin/love/forte/simbot/telegram/api/update/GetUpdatesApi.kt index e090bb5..e93b5ab 100644 --- a/simbot-component-telegram-api/src/commonMain/kotlin/love/forte/simbot/telegram/api/update/GetUpdatesApi.kt +++ b/simbot-component-telegram-api/src/commonMain/kotlin/love/forte/simbot/telegram/api/update/GetUpdatesApi.kt @@ -151,7 +151,7 @@ public inline fun getUpdateFlow( eachLimit: Int? = null, allowedUpdates: Collection? = null, crossinline onEachResult: (List) -> List = { it }, - crossinline onError: (Throwable) -> List = { + crossinline onError: suspend (Throwable) -> List = { if (it is HttpRequestTimeoutException) emptyList() else throw it }, crossinline requestor: suspend (GetUpdatesApi) -> List diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/SerializableTelegramBotConfiguration.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/SerializableTelegramBotConfiguration.kt index 0760404..8a2177f 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/SerializableTelegramBotConfiguration.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/SerializableTelegramBotConfiguration.kt @@ -68,12 +68,17 @@ public data class SerializableTelegramBotConfiguration( val limit: Int? = null, val timeout: Int? = BotConfiguration.DefaultLongPollingTimeout.inWholeSeconds.toInt(), val allowedUpdates: Collection? = null, + val retry: LongPolling.Retry? = null, + val handleRetry: LongPolling.HandleRetry? = null, ) { public fun toBotLongPolling(): LongPolling = LongPolling( limit = limit, timeout = timeout, allowedUpdates = allowedUpdates, - ) + retry = retry, + ).also { + this.handleRetry?.also { thisHr -> it.handleRetry = thisHr } + } } public fun toBotConfiguration(): TelegramBotConfiguration { diff --git a/simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/Bot.kt b/simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/Bot.kt index b895d3f..8ea6761 100644 --- a/simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/Bot.kt +++ b/simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/Bot.kt @@ -358,6 +358,12 @@ public enum class SubscribeSequence { * The difference is that [timeout] defaults to [DefaultLongPollingTimeout] * instead of `null`. * + * @property retry Retry config. [retry] is based on the Ktor plugin + * [HttpTimeout][io.ktor.client.plugins.HttpRequestRetry] implementation. + * + * @property handleRetry Retry configuration for exception handling for long polling flow. + * Unlike [retry], [handleRetry] is based on `onError` in [getUpdateFlow]. + * * @see getUpdateFlow */ @OptIn(InternalSimbotAPI::class) @@ -366,8 +372,9 @@ public data class LongPolling( val timeout: Int? = DefaultLongPollingTimeout.inWholeSeconds.toInt(), val allowedUpdates: Collection? = null, // retry times on error - val retry: Retry? = null + val retry: Retry? = null, ) { + var handleRetry: HandleRetry = HandleRetry.DEFAULT @Serializable public data class Retry( @@ -375,6 +382,35 @@ public data class LongPolling( val delayMillis: Long = 5000, val isDelayMillisMultiplyByRetryTimes: Boolean = false ) + + @Serializable + public data class HandleRetry( + val strategy: HandleRetryStrategy = HandleRetryStrategy.TIMEOUT_ONLY, + val delayMillis: Long = 5000, + ) { + public companion object { + internal val DEFAULT: HandleRetry = HandleRetry() + } + } + + + public enum class HandleRetryStrategy { + /** + * Throw every exception, no retry. + */ + NONE, + + /** + * Catch [io.ktor.client.plugins.HttpRequestTimeoutException] only. + */ + TIMEOUT_ONLY, + + /** + * Always retry (except [CancellationException]). + */ + ALL, + } + // multiplyBy } diff --git a/simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/internal/BotImpl.kt b/simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/internal/BotImpl.kt index cf8b2e1..1793747 100644 --- a/simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/internal/BotImpl.kt +++ b/simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/internal/BotImpl.kt @@ -294,6 +294,7 @@ internal class BotImpl( } } + @Suppress("ThrowsCount") private suspend fun HttpClient.longPolling( token: String, server: Url?, @@ -309,25 +310,76 @@ internal class BotImpl( eachLimit = limit, allowedUpdates = allowedUpdates, onError = { error -> - when (error) { - is CancellationException -> { - eventLogger.trace("Handle an cancellation error on long polling task: {}", error.message, error) - // throw, and stop the job. + if (error is CancellationException) { + eventLogger.trace("Handle an cancellation error on long polling task: {}", error.message, error) + // throw, and stop the job. + throw error + } + + val handleRetry = longPolling?.handleRetry ?: LongPolling.HandleRetry.DEFAULT + val strategy = handleRetry.strategy + val delay = handleRetry.delayMillis + + when (strategy) { + LongPolling.HandleRetryStrategy.NONE -> { + eventLogger.error( + "Handle an error on long polling task " + + "with handle retry strategy {}: {}, " + + "bot will be shutdown.", + strategy, + error.message, + error + ) + job.cancel(CancellationException("LongPolling on failure", error)) throw error } - is HttpRequestTimeoutException -> { - eventLogger.trace("Handle an timeout error on long polling task: {}", error.message, error) + LongPolling.HandleRetryStrategy.TIMEOUT_ONLY -> { + if (error is HttpRequestTimeoutException) { + eventLogger.debug( + "Handle an timeout error " + + "on long polling task: {}, just re-poll.", + error.message, + error + ) + } else { + eventLogger.error( + "Handle an error on long polling task " + + "with handle retry strategy {}: {}, " + + "bot will be shutdown.", + strategy, + error.message, + error + ) + // throw to Bot + job.cancel(CancellationException("LongPolling on failure", error)) + throw error + } } - else -> { - eventLogger.error("Handle an error on long polling task: {}", error.message, error) - // throw to Bot - job.cancel(CancellationException("LongPolling on failure", error)) - throw error + LongPolling.HandleRetryStrategy.ALL -> { + if (error is HttpRequestTimeoutException) { + eventLogger.debug( + "Handle an timeout error " + + "on long polling task: {}, just re-poll.", + error.message, + error + ) + } else { + eventLogger.debug( + "Handle an error on long polling task " + + "with handle retry strategy {}: {}, " + + "retry in {} ms", + strategy, + error.message, + delay, + error + ) + } } } + delay(delay) emptyList() } ) { api -> From 41d3efd36e68df1ca1e453ca98568195a39826c3 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Sun, 29 Sep 2024 20:11:14 +0800 Subject: [PATCH 2/2] =?UTF-8?q?delay=E7=9A=84=E8=A1=8C=E4=B8=BA=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../love/forte/simbot/telegram/stdlib/bot/internal/BotImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/internal/BotImpl.kt b/simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/internal/BotImpl.kt index 1793747..f0ce93e 100644 --- a/simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/internal/BotImpl.kt +++ b/simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/internal/BotImpl.kt @@ -375,11 +375,11 @@ internal class BotImpl( delay, error ) + delay(delay) } } } - delay(delay) emptyList() } ) { api ->