From 8c76283db50485180054777110b66ddd0df0394d Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 13 Apr 2020 12:40:14 +0600 Subject: [PATCH] suspend inline function handleSafely was added --- CHANGELOG.md | 3 + .../extensions/api/updates/UpdatesPolling.kt | 69 +++++++------- .../bot/Ktor/KtorRequestsExecutor.kt | 95 ++++++++++--------- .../TelegramBotAPI/utils/HandleSafely.kt | 21 ++++ 4 files changed, 108 insertions(+), 80 deletions(-) create mode 100644 TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/HandleSafely.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index ad49ff4a32..ae543f5dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,9 @@ case will be created `UnknownUpdateType` * `UnknownUpdateType$rawJson` value now is included (`JsonElement`) * **EXPERIMENTALLY** `BaseEditMessageUpdate#data` now is `CommonMessage<*>` + * Suspend inline function `handleSafely` was added + * `KtorRequestsExecutor` now use `handleSafely` instead of `try` with `supervisorScope` + * `UpdatesPolling` now use `handleSafely` instead of `try` with `supervisorScope` ### 0.26.2 diff --git a/TelegramBotAPI-extensions-api/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/api/updates/UpdatesPolling.kt b/TelegramBotAPI-extensions-api/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/api/updates/UpdatesPolling.kt index d74b77c559..d39bd17fa4 100644 --- a/TelegramBotAPI-extensions-api/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/api/updates/UpdatesPolling.kt +++ b/TelegramBotAPI-extensions-api/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/api/updates/UpdatesPolling.kt @@ -11,6 +11,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update import com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.* import com.github.insanusmokrassar.TelegramBotAPI.utils.PreviewFeature +import com.github.insanusmokrassar.TelegramBotAPI.utils.handleSafely import io.ktor.client.features.HttpRequestTimeoutException import kotlinx.coroutines.* @@ -24,42 +25,44 @@ fun RequestsExecutor.startGettingOfUpdates( var lastUpdateIdentifier: UpdateIdentifier? = null while (isActive) { - try { - supervisorScope { - val updates = getUpdates( - offset = lastUpdateIdentifier?.plus(1), - timeout = timeoutSeconds, - allowed_updates = allowedUpdates - ).let { originalUpdates -> - val converted = originalUpdates.convertWithMediaGroupUpdates() - /** - * Dirty hack for cases when the media group was retrieved not fully: - * - * We are throw out the last media group and will reretrieve it again in the next get updates - * and it will guarantee that it is full - */ - if (originalUpdates.size == getUpdatesLimit.last && converted.last() is SentMediaGroupUpdate) { - converted - converted.last() - } else { - converted - } - } - - supervisorScope { - for (update in updates) { - updatesReceiver(update) - - lastUpdateIdentifier = update.lastUpdateIdentifier() + handleSafely( + { e -> + when (e) { + is HttpRequestTimeoutException -> exceptionsHandler ?.invoke(e) + is RequestException -> { + exceptionsHandler ?.invoke(e) + delay(1000L) } + else -> exceptionsHandler ?.invoke(e) + } + } + ) { + val updates = getUpdates( + offset = lastUpdateIdentifier?.plus(1), + timeout = timeoutSeconds, + allowed_updates = allowedUpdates + ).let { originalUpdates -> + val converted = originalUpdates.convertWithMediaGroupUpdates() + /** + * Dirty hack for cases when the media group was retrieved not fully: + * + * We are throw out the last media group and will reretrieve it again in the next get updates + * and it will guarantee that it is full + */ + if (originalUpdates.size == getUpdatesLimit.last && converted.last() is SentMediaGroupUpdate) { + converted - converted.last() + } else { + converted + } + } + + handleSafely { + for (update in updates) { + updatesReceiver(update) + + lastUpdateIdentifier = update.lastUpdateIdentifier() } } - } catch (e: HttpRequestTimeoutException) { - exceptionsHandler ?.invoke(e) // it is ok due to mechanism of long polling - } catch (e: RequestException) { - exceptionsHandler ?.invoke(e) // it is not ok, but in most cases it will mean that there is some limit for requests count - delay(1000L) - } catch (e: Exception) { - exceptionsHandler ?.invoke(e) } } } diff --git a/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt b/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt index ae1f2a4407..5f8bc97d5f 100644 --- a/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt +++ b/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt @@ -9,7 +9,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.bot.settings.limiters.RequestL import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request import com.github.insanusmokrassar.TelegramBotAPI.types.Response import com.github.insanusmokrassar.TelegramBotAPI.types.RetryAfterError -import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper +import com.github.insanusmokrassar.TelegramBotAPI.utils.* import com.github.insanusmokrassar.TelegramBotAPI.utils.nonstrictJsonFormat import io.ktor.client.HttpClient import io.ktor.client.call.receive @@ -17,7 +17,6 @@ import io.ktor.client.features.* import io.ktor.client.statement.HttpStatement import io.ktor.client.statement.readText import kotlinx.coroutines.delay -import kotlinx.coroutines.supervisorScope import kotlinx.serialization.json.Json class KtorRequestsExecutor( @@ -43,54 +42,56 @@ class KtorRequestsExecutor( } override suspend fun execute(request: Request): T { - return try { - supervisorScope { - requestsLimiter.limit { - var statement: HttpStatement? = null - for (factory in callsFactories) { - statement = factory.prepareCall( - client, - telegramAPIUrlsKeeper.commonAPIUrl, - request - ) - if (statement != null) { - break - } - } - - val response = statement?.execute() ?: throw IllegalArgumentException("Can't execute request: $request") - val content = response.receive() + return handleSafely( + { e -> + throw if (e is ClientRequestException) { + val content = e.response.readText() val responseObject = jsonFormatter.parse(Response.serializer(), content) - - (responseObject.result?.let { - jsonFormatter.fromJson(request.resultDeserializer, it) - } ?: responseObject.parameters?.let { - val error = it.error - if (error is RetryAfterError) { - delay(error.leftToRetry) - execute(request) - } else { - null - } - } ?: response.let { - throw newRequestException( - responseObject, - content, - "Can't get result object from $content" - ) - }) + newRequestException( + responseObject, + content, + "Can't get result object from $content" + ) + } else { + e } } - } catch (e: ClientRequestException) { - val content = e.response.readText() - val responseObject = jsonFormatter.parse(Response.serializer(), content) - throw newRequestException( - responseObject, - content, - "Can't get result object from $content" - ) - } catch (e: Exception) { - throw e + ) { + requestsLimiter.limit { + var statement: HttpStatement? = null + for (factory in callsFactories) { + statement = factory.prepareCall( + client, + telegramAPIUrlsKeeper.commonAPIUrl, + request + ) + if (statement != null) { + break + } + } + + val response = statement?.execute() ?: throw IllegalArgumentException("Can't execute request: $request") + val content = response.receive() + val responseObject = jsonFormatter.parse(Response.serializer(), content) + + (responseObject.result?.let { + jsonFormatter.fromJson(request.resultDeserializer, it) + } ?: responseObject.parameters?.let { + val error = it.error + if (error is RetryAfterError) { + delay(error.leftToRetry) + execute(request) + } else { + null + } + } ?: response.let { + throw newRequestException( + responseObject, + content, + "Can't get result object from $content" + ) + }) + } } } diff --git a/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/HandleSafely.kt b/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/HandleSafely.kt new file mode 100644 index 0000000000..6e8deeaae8 --- /dev/null +++ b/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/HandleSafely.kt @@ -0,0 +1,21 @@ +package com.github.insanusmokrassar.TelegramBotAPI.utils + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.supervisorScope + +/** + * It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions + * + * @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this + * exception will be available for catching + */ +suspend inline fun handleSafely( + noinline onException: suspend (Exception) -> T = { throw it }, + noinline block: suspend CoroutineScope.() -> T +): T { + return try { + supervisorScope(block) + } catch (e: Exception) { + onException(e) + } +}