Skip to content

Commit 607d44f

Browse files
authored
Merge pull request #60 from simple-robot/dev/retry-config
longPolling 增加配置 `handleRetry`, 配置文件方式支持 `retry` 和 `handleRetry` 配置
2 parents e318783 + 41d3efd commit 607d44f

File tree

4 files changed

+107
-14
lines changed
  • simbot-component-telegram-api/src/commonMain/kotlin/love/forte/simbot/telegram/api/update
  • simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot
  • simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot

4 files changed

+107
-14
lines changed

simbot-component-telegram-api/src/commonMain/kotlin/love/forte/simbot/telegram/api/update/GetUpdatesApi.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public inline fun getUpdateFlow(
151151
eachLimit: Int? = null,
152152
allowedUpdates: Collection<String>? = null,
153153
crossinline onEachResult: (List<Update>) -> List<Update> = { it },
154-
crossinline onError: (Throwable) -> List<Update> = {
154+
crossinline onError: suspend (Throwable) -> List<Update> = {
155155
if (it is HttpRequestTimeoutException) emptyList() else throw it
156156
},
157157
crossinline requestor: suspend (GetUpdatesApi) -> List<Update>

simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/SerializableTelegramBotConfiguration.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,17 @@ public data class SerializableTelegramBotConfiguration(
6868
val limit: Int? = null,
6969
val timeout: Int? = BotConfiguration.DefaultLongPollingTimeout.inWholeSeconds.toInt(),
7070
val allowedUpdates: Collection<String>? = null,
71+
val retry: LongPolling.Retry? = null,
72+
val handleRetry: LongPolling.HandleRetry? = null,
7173
) {
7274
public fun toBotLongPolling(): LongPolling = LongPolling(
7375
limit = limit,
7476
timeout = timeout,
7577
allowedUpdates = allowedUpdates,
76-
)
78+
retry = retry,
79+
).also {
80+
this.handleRetry?.also { thisHr -> it.handleRetry = thisHr }
81+
}
7782
}
7883

7984
public fun toBotConfiguration(): TelegramBotConfiguration {

simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/Bot.kt

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,12 @@ public enum class SubscribeSequence {
359359
* The difference is that [timeout] defaults to [DefaultLongPollingTimeout]
360360
* instead of `null`.
361361
*
362+
* @property retry Retry config. [retry] is based on the Ktor plugin
363+
* [HttpTimeout][io.ktor.client.plugins.HttpRequestRetry] implementation.
364+
*
365+
* @property handleRetry Retry configuration for exception handling for long polling flow.
366+
* Unlike [retry], [handleRetry] is based on `onError` in [getUpdateFlow].
367+
*
362368
* @see getUpdateFlow
363369
*/
364370
@OptIn(InternalSimbotAPI::class)
@@ -367,15 +373,45 @@ public data class LongPolling(
367373
val timeout: Int? = DefaultLongPollingTimeout.inWholeSeconds.toInt(),
368374
val allowedUpdates: Collection<String>? = null,
369375
// retry times on error
370-
val retry: Retry? = null
376+
val retry: Retry? = null,
371377
) {
378+
var handleRetry: HandleRetry = HandleRetry.DEFAULT
372379

373380
@Serializable
374381
public data class Retry(
375382
val maxRetries: Int = 3,
376383
val delayMillis: Long = 5000,
377384
val isDelayMillisMultiplyByRetryTimes: Boolean = false
378385
)
386+
387+
@Serializable
388+
public data class HandleRetry(
389+
val strategy: HandleRetryStrategy = HandleRetryStrategy.TIMEOUT_ONLY,
390+
val delayMillis: Long = 5000,
391+
) {
392+
public companion object {
393+
internal val DEFAULT: HandleRetry = HandleRetry()
394+
}
395+
}
396+
397+
398+
public enum class HandleRetryStrategy {
399+
/**
400+
* Throw every exception, no retry.
401+
*/
402+
NONE,
403+
404+
/**
405+
* Catch [io.ktor.client.plugins.HttpRequestTimeoutException] only.
406+
*/
407+
TIMEOUT_ONLY,
408+
409+
/**
410+
* Always retry (except [CancellationException]).
411+
*/
412+
ALL,
413+
}
414+
379415
// multiplyBy
380416
}
381417

simbot-component-telegram-stdlib/src/commonMain/kotlin/love/forte/simbot/telegram/stdlib/bot/internal/BotImpl.kt

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ internal class BotImpl(
303303
}
304304
}
305305

306+
@Suppress("ThrowsCount")
306307
private suspend fun HttpClient.longPolling(
307308
token: String,
308309
server: Url?,
@@ -318,22 +319,73 @@ internal class BotImpl(
318319
eachLimit = limit,
319320
allowedUpdates = allowedUpdates,
320321
onError = { error ->
321-
when (error) {
322-
is CancellationException -> {
323-
eventLogger.trace("Handle an cancellation error on long polling task: {}", error.message, error)
324-
// throw, and stop the job.
322+
if (error is CancellationException) {
323+
eventLogger.trace("Handle an cancellation error on long polling task: {}", error.message, error)
324+
// throw, and stop the job.
325+
throw error
326+
}
327+
328+
val handleRetry = longPolling?.handleRetry ?: LongPolling.HandleRetry.DEFAULT
329+
val strategy = handleRetry.strategy
330+
val delay = handleRetry.delayMillis
331+
332+
when (strategy) {
333+
LongPolling.HandleRetryStrategy.NONE -> {
334+
eventLogger.error(
335+
"Handle an error on long polling task " +
336+
"with handle retry strategy {}: {}, " +
337+
"bot will be shutdown.",
338+
strategy,
339+
error.message,
340+
error
341+
)
342+
job.cancel(CancellationException("LongPolling on failure", error))
325343
throw error
326344
}
327345

328-
is HttpRequestTimeoutException -> {
329-
eventLogger.trace("Handle an timeout error on long polling task: {}", error.message, error)
346+
LongPolling.HandleRetryStrategy.TIMEOUT_ONLY -> {
347+
if (error is HttpRequestTimeoutException) {
348+
eventLogger.debug(
349+
"Handle an timeout error " +
350+
"on long polling task: {}, just re-poll.",
351+
error.message,
352+
error
353+
)
354+
} else {
355+
eventLogger.error(
356+
"Handle an error on long polling task " +
357+
"with handle retry strategy {}: {}, " +
358+
"bot will be shutdown.",
359+
strategy,
360+
error.message,
361+
error
362+
)
363+
// throw to Bot
364+
job.cancel(CancellationException("LongPolling on failure", error))
365+
throw error
366+
}
330367
}
331368

332-
else -> {
333-
eventLogger.error("Handle an error on long polling task: {}", error.message, error)
334-
// throw to Bot
335-
job.cancel(CancellationException("LongPolling on failure", error))
336-
throw error
369+
LongPolling.HandleRetryStrategy.ALL -> {
370+
if (error is HttpRequestTimeoutException) {
371+
eventLogger.debug(
372+
"Handle an timeout error " +
373+
"on long polling task: {}, just re-poll.",
374+
error.message,
375+
error
376+
)
377+
} else {
378+
eventLogger.debug(
379+
"Handle an error on long polling task " +
380+
"with handle retry strategy {}: {}, " +
381+
"retry in {} ms",
382+
strategy,
383+
error.message,
384+
delay,
385+
error
386+
)
387+
delay(delay)
388+
}
337389
}
338390
}
339391

0 commit comments

Comments
 (0)