diff --git a/CHANGELOG.md b/CHANGELOG.md index ce38b36bd1..1739c326ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## 30.0.1 +* `Core`: + * Potential fix of [#989](https://github.com/InsanusMokrassar/ktgbotapi/issues/989) by: + * In long polling have been added check for causing by unresolved address exception + * Add `TelegramBotPipelinesHandler.onRequestExceptionInLimiter` which will be triggered in ANY exception during + request execution + ## 30.0.0 **THIS UPDATE MAY CONTAINS BREAKING CHANGES** diff --git a/gradle.properties b/gradle.properties index bb71de3d5d..3bd2b64eea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,4 +9,4 @@ kotlin.incremental.js=true ksp.useKSP2=false library_group=dev.inmo -library_version=30.0.1 +library_version=30.0.1-rc3 diff --git a/tgbotapi.core/api/tgbotapi.core.api b/tgbotapi.core/api/tgbotapi.core.api index 0b1720de73..818fd9d732 100644 --- a/tgbotapi.core/api/tgbotapi.core.api +++ b/tgbotapi.core/api/tgbotapi.core.api @@ -397,6 +397,7 @@ public abstract interface class dev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesH public fun onBeforeCallFactoryMakeCall (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onBeforeSearchCallFactory (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestException (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun onRequestExceptionInLimiter (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestResultAbsent (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestResultPresented (Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestReturnResult-3t6e044 (Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -407,6 +408,7 @@ public final class dev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler$Compan public fun onBeforeCallFactoryMakeCall (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onBeforeSearchCallFactory (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestException (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun onRequestExceptionInLimiter (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestResultAbsent (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestResultPresented (Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestReturnResult-3t6e044 (Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -417,6 +419,7 @@ public final class dev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler$Defaul public static fun onBeforeCallFactoryMakeCall (Ldev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun onBeforeSearchCallFactory (Ldev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun onRequestException (Ldev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun onRequestExceptionInLimiter (Ldev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun onRequestResultAbsent (Ldev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun onRequestResultPresented (Ldev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler;Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun onRequestReturnResult-3t6e044 (Ldev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler;Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -475,13 +478,14 @@ public final class dev/inmo/tgbotapi/bot/ktor/base/SimpleRequestCallFactory : de public class dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware : dev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler { public static final field Companion Ldev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware$Companion; public fun ()V - public fun (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Ljava/lang/String;)V - public synthetic fun (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Ljava/lang/String;)V + public synthetic fun (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getId ()Ljava/lang/String; public fun onAfterCallFactoryMakeCall (Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onBeforeCallFactoryMakeCall (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onBeforeSearchCallFactory (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestException (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun onRequestExceptionInLimiter (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestResultAbsent (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestResultPresented (Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestReturnResult-3t6e044 (Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -503,6 +507,7 @@ public final class dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareB public final fun doOnBeforeCallFactoryMakeCall (Lkotlin/jvm/functions/Function3;)V public final fun doOnBeforeSearchCallFactory (Lkotlin/jvm/functions/Function3;)V public final fun doOnRequestException (Lkotlin/jvm/functions/Function3;)V + public final fun doOnRequestExceptionInLimiter (Lkotlin/jvm/functions/Function3;)V public final fun doOnRequestResultAbsent (Lkotlin/jvm/functions/Function3;)V public final fun doOnRequestResultPresented (Lkotlin/jvm/functions/Function5;)V public final fun doOnRequestReturnResult (Lkotlin/jvm/functions/Function4;)V @@ -511,6 +516,7 @@ public final class dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareB public final fun getOnBeforeCallFactoryMakeCall ()Lkotlin/jvm/functions/Function3; public final fun getOnBeforeSearchCallFactory ()Lkotlin/jvm/functions/Function3; public final fun getOnRequestException ()Lkotlin/jvm/functions/Function3; + public final fun getOnRequestExceptionInLimiter ()Lkotlin/jvm/functions/Function3; public final fun getOnRequestResultAbsent ()Lkotlin/jvm/functions/Function3; public final fun getOnRequestResultPresented ()Lkotlin/jvm/functions/Function5; public final fun getOnRequestReturnResult ()Lkotlin/jvm/functions/Function4; @@ -519,6 +525,7 @@ public final class dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareB public final fun setOnBeforeCallFactoryMakeCall (Lkotlin/jvm/functions/Function3;)V public final fun setOnBeforeSearchCallFactory (Lkotlin/jvm/functions/Function3;)V public final fun setOnRequestException (Lkotlin/jvm/functions/Function3;)V + public final fun setOnRequestExceptionInLimiter (Lkotlin/jvm/functions/Function3;)V public final fun setOnRequestResultAbsent (Lkotlin/jvm/functions/Function3;)V public final fun setOnRequestResultPresented (Lkotlin/jvm/functions/Function5;)V public final fun setOnRequestReturnResult (Lkotlin/jvm/functions/Function4;)V @@ -537,6 +544,7 @@ public final class dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewares public fun onBeforeCallFactoryMakeCall (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onBeforeSearchCallFactory (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestException (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun onRequestExceptionInLimiter (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestResultAbsent (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestResultPresented (Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onRequestReturnResult-3t6e044 (Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -31903,6 +31911,16 @@ public final class dev/inmo/tgbotapi/utils/CausedByCancellationKt { public static final fun isCausedByCancellation (Ljava/lang/Throwable;)Z } +public final class dev/inmo/tgbotapi/utils/CausedByKt { + public static final fun causedBy (Ljava/lang/Throwable;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)Ljava/lang/Throwable; + public static synthetic fun causedBy$default (Ljava/lang/Throwable;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Throwable; +} + +public final class dev/inmo/tgbotapi/utils/CausedByUnresolvedAddressExceptionKt { + public static final fun causedUnresolvedAddressException (Ljava/lang/Throwable;)Ljava/nio/channels/UnresolvedAddressException; + public static final fun isCausedUnresolvedAddressException (Ljava/lang/Throwable;)Z +} + public final class dev/inmo/tgbotapi/utils/DefaultKSLogKt { public static final fun SetDefaultKTgBotAPIKSLog (ZLkotlin/jvm/functions/Function1;)V public static synthetic fun SetDefaultKTgBotAPIKSLog$default (ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler.kt index 22bbb57a4e..107533bdf3 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler.kt @@ -3,9 +3,24 @@ package dev.inmo.tgbotapi.bot.ktor import dev.inmo.tgbotapi.requests.abstracts.Request interface TelegramBotPipelinesHandler { + /** + * Will be called when any exception will happen due to the [request] handling inside of limiter block. This method + * will be called for each exception happened during call factory call + */ + suspend fun onRequestExceptionInLimiter( + request: Request, + t: Throwable + ): T? = null + /** * Will be called when any exception will happen due to the [request] handling. If returns value - that value - * will be returned from [dev.inmo.tgbotapi.bot.RequestsExecutor.execute] instead + * will be returned from [dev.inmo.tgbotapi.bot.RequestsExecutor.execute] instead. In difference with + * [onRequestExceptionInLimiter], this method will be called only AFTER + * [dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter] will pass result of call factory execution outside of + * its [dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter.limit] function + * + * @see dev.inmo.tgbotapi.bot.ktor.base.DefaultKtorRequestsExecutor + * @see dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter */ suspend fun onRequestException( request: Request, diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DefaultKtorRequestsExecutor.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DefaultKtorRequestsExecutor.kt index 40ca65a261..2a48236013 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DefaultKtorRequestsExecutor.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DefaultKtorRequestsExecutor.kt @@ -53,29 +53,33 @@ class DefaultKtorRequestsExecutor internal constructor( logger.v { "Start request $request" } pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories) requestsLimiter.limit(request) { - var result: T? = null - lateinit var factoryHandledRequest: KtorCallFactory - for (potentialFactory in callsFactories) { - pipelineStepsHolder.onBeforeCallFactoryMakeCall(request, potentialFactory) - logger.v { "Trying factory $potentialFactory for $request" } - val resultFromFactory = potentialFactory.makeCall( - client, - telegramAPIUrlsKeeper, - request, - jsonFormatter - ) - logger.v { "Result of factory $potentialFactory handling $request: $resultFromFactory" } - result = pipelineStepsHolder.onAfterCallFactoryMakeCall(resultFromFactory, request, potentialFactory) - logger.v { "Result of pipeline $pipelineStepsHolder handling $resultFromFactory: $result" } - if (result != null) { - factoryHandledRequest = potentialFactory - break + runCatching { + var result: T? = null + lateinit var factoryHandledRequest: KtorCallFactory + for (potentialFactory in callsFactories) { + pipelineStepsHolder.onBeforeCallFactoryMakeCall(request, potentialFactory) + logger.v { "Trying factory $potentialFactory for $request" } + val resultFromFactory = potentialFactory.makeCall( + client, + telegramAPIUrlsKeeper, + request, + jsonFormatter + ) + logger.v { "Result of factory $potentialFactory handling $request: $resultFromFactory" } + result = pipelineStepsHolder.onAfterCallFactoryMakeCall(resultFromFactory, request, potentialFactory) + logger.v { "Result of pipeline $pipelineStepsHolder handling $resultFromFactory: $result" } + if (result != null) { + factoryHandledRequest = potentialFactory + break + } } - } - result ?.let { - pipelineStepsHolder.onRequestResultPresented(it, request, factoryHandledRequest, callsFactories) - } ?: pipelineStepsHolder.onRequestResultAbsent(request, callsFactories) ?: error("Can't execute request: $request") + result ?.let { + pipelineStepsHolder.onRequestResultPresented(it, request, factoryHandledRequest, callsFactories) + } ?: pipelineStepsHolder.onRequestResultAbsent(request, callsFactories) ?: error("Can't execute request: $request") + }.onFailure { e -> + pipelineStepsHolder.onRequestExceptionInLimiter(request, e) ?.let { return@let it } ?: throw e + }.getOrThrow() } }.let { val result = it.exceptionOrNull() ?.let { e -> diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware.kt index 8d76032fbe..56f4dfd357 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware.kt @@ -31,9 +31,16 @@ open class TelegramBotMiddleware( internal val onRequestResultPresented: (suspend (result: Any, request: Request<*>, resultCallFactory: KtorCallFactory, callsFactories: List) -> Any?)? = null, internal val onRequestResultAbsent: (suspend (request: Request<*>, callsFactories: List) -> Any?)? = null, internal val onRequestReturnResult: (suspend (result: Result<*>, request: Request<*>, callsFactories: List) -> Result?)? = null, + internal val onRequestExceptionInLimiter: (suspend (request: Request<*>, t: Throwable?) -> Any?)? = null, val id: String = uuid4().toString() ) : TelegramBotPipelinesHandler { object ResultAbsence : Throwable() + + override suspend fun onRequestExceptionInLimiter(request: Request, t: Throwable): T? { + @Suppress("UNCHECKED_CAST") + return onRequestExceptionInLimiter ?.invoke(request, t) as? T + } + override suspend fun onRequestException(request: Request, t: Throwable): T? { @Suppress("UNCHECKED_CAST") return onRequestException ?.invoke(request, t) as? T diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareBuilder.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareBuilder.kt index 83c64bb8ac..17f0abd390 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareBuilder.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareBuilder.kt @@ -8,6 +8,7 @@ import dev.inmo.tgbotapi.requests.abstracts.Request @Warning("This API is experimental and subject of changes") class TelegramBotMiddlewareBuilder { + var onRequestExceptionInLimiter: (suspend (request: Request<*>, t: Throwable?) -> Any?)? = null var onRequestException: (suspend (request: Request<*>, t: Throwable?) -> Any?)? = null var onBeforeSearchCallFactory: (suspend (request: Request<*>, callsFactories: List) -> Unit)? = null var onBeforeCallFactoryMakeCall: (suspend (request: Request<*>, potentialFactory: KtorCallFactory) -> Unit)? = null @@ -17,6 +18,12 @@ class TelegramBotMiddlewareBuilder { var onRequestReturnResult: (suspend (result: Result<*>, request: Request<*>, callsFactories: List) -> Result?)? = null var id: String = uuid4().toString() + /** + * Useful way to set [onRequestException] + */ + fun doOnRequestExceptionInLimiter(block: suspend (request: Request<*>, t: Throwable?) -> Any?) { + onRequestExceptionInLimiter = block + } /** * Useful way to set [onRequestException] */ @@ -63,6 +70,7 @@ class TelegramBotMiddlewareBuilder { @Warning("This API is experimental and subject of changes") fun build(): TelegramBotMiddleware { return TelegramBotMiddleware( + onRequestExceptionInLimiter = onRequestExceptionInLimiter, onRequestException = onRequestException, onBeforeSearchCallFactory = onBeforeSearchCallFactory, onBeforeCallFactoryMakeCall = onBeforeCallFactoryMakeCall, @@ -78,6 +86,7 @@ class TelegramBotMiddlewareBuilder { @Warning("This API is experimental and subject of changes") fun from(middleware: TelegramBotMiddleware, additionalSetup: TelegramBotMiddlewareBuilder.() -> Unit): TelegramBotMiddleware { return TelegramBotMiddlewareBuilder().apply { + onRequestExceptionInLimiter = middleware.onRequestExceptionInLimiter onRequestException = middleware.onRequestException onBeforeSearchCallFactory = middleware.onBeforeSearchCallFactory onBeforeCallFactoryMakeCall = middleware.onBeforeCallFactoryMakeCall diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewaresPipelinesHandler.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewaresPipelinesHandler.kt index 37818510d1..39d0f92711 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewaresPipelinesHandler.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewaresPipelinesHandler.kt @@ -10,6 +10,12 @@ import dev.inmo.tgbotapi.requests.abstracts.Request class TelegramBotMiddlewaresPipelinesHandler( private val middlewares: List = emptyList() ) : TelegramBotPipelinesHandler { + override suspend fun onRequestExceptionInLimiter(request: Request, t: Throwable): T? { + return middlewares.firstNotNullOfOrNull { + it.onRequestExceptionInLimiter(request, t) + } ?: super.onRequestExceptionInLimiter(request, t) + } + override suspend fun onRequestException(request: Request, t: Throwable): T? { return middlewares.firstNotNullOfOrNull { it.onRequestException(request, t) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/settings/limiters/ExceptionsOnlyLimiter.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/settings/limiters/ExceptionsOnlyLimiter.kt index 0bf389386b..91c7d95a78 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/settings/limiters/ExceptionsOnlyLimiter.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/settings/limiters/ExceptionsOnlyLimiter.kt @@ -1,6 +1,8 @@ package dev.inmo.tgbotapi.bot.settings.limiters import dev.inmo.tgbotapi.bot.exceptions.TooMuchRequestsException +import dev.inmo.tgbotapi.utils.isCausedUnresolvedAddressException +import io.ktor.util.network.UnresolvedAddressException import kotlinx.coroutines.delay /** @@ -13,10 +15,10 @@ object ExceptionsOnlyLimiter : RequestLimiter { result = runCatching { block() }.onFailure { - if (it is TooMuchRequestsException) { - delay(it.retryAfter.leftToRetry) - } else { - throw it + when { + it.isCausedUnresolvedAddressException() -> delay(1000L) + it is TooMuchRequestsException -> delay(it.retryAfter.leftToRetry) + else -> throw it } } } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/CausedBy.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/CausedBy.kt new file mode 100644 index 0000000000..5d83955b6a --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/CausedBy.kt @@ -0,0 +1,15 @@ +package dev.inmo.tgbotapi.utils + +import kotlin.reflect.KClass + +fun Throwable.causedBy(kclass: KClass, additionalFilterOnHappened: (T) -> T? = { it }): T? { + var current = this + while (kclass.isInstance(current) == false) { + when { + kclass.isInstance(current) -> return additionalFilterOnHappened(current as T) + else -> current = current.cause ?: return null + } + } + + return current as T +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/CausedByCancellation.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/CausedByCancellation.kt index 08c921dffc..9b91f54836 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/CausedByCancellation.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/CausedByCancellation.kt @@ -14,16 +14,9 @@ import kotlinx.coroutines.CancellationException * @return the first [CancellationException] found in the cause chain, or `null` if none present */ fun Throwable.causedCancellationException(): CancellationException? { - var current = this - while (current !is CancellationException) { - when { - // It is possible, that API will be changed and cancellation will be caused by something else - current is CancellationException && current.cause == null -> return current - else -> current = current.cause ?: return null - } + return causedBy(CancellationException::class) { + it.takeIf { it.cause == null } } - - return current } /** diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/CausedByUnresolvedAddressException.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/CausedByUnresolvedAddressException.kt new file mode 100644 index 0000000000..8197dac0fd --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/CausedByUnresolvedAddressException.kt @@ -0,0 +1,9 @@ +package dev.inmo.tgbotapi.utils + +import io.ktor.util.network.UnresolvedAddressException + +fun Throwable.causedUnresolvedAddressException(): UnresolvedAddressException? { + return causedBy(UnresolvedAddressException::class) +} + +fun Throwable.isCausedUnresolvedAddressException(): Boolean = causedUnresolvedAddressException() != null diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPolling.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPolling.kt index b59dc5a4c0..148747dbc1 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPolling.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPolling.kt @@ -18,8 +18,11 @@ import dev.inmo.tgbotapi.updateshandlers.* import dev.inmo.tgbotapi.utils.DefaultKTgBotAPIKSLog import dev.inmo.tgbotapi.utils.causedCancellationException import dev.inmo.tgbotapi.utils.isCausedByCancellation +import dev.inmo.tgbotapi.utils.isCausedUnresolvedAddressException import dev.inmo.tgbotapi.utils.subscribeWithBotLogger +import io.ktor.client.network.sockets.ConnectTimeoutException import io.ktor.client.plugins.HttpRequestTimeoutException +import io.ktor.util.network.UnresolvedAddressException import io.ktor.utils.io.CancellationException import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -127,12 +130,16 @@ fun TelegramBot.longPollingFlow( }.onFailure { e -> runCatchingLogging(logger = Log) { val isHttpRequestTimeoutException = - e is HttpRequestTimeoutException || (e is CommonBotException && e.cause is HttpRequestTimeoutException) + e is ConnectTimeoutException || e is HttpRequestTimeoutException || (e is CommonBotException && e.cause is HttpRequestTimeoutException) if (isHttpRequestTimeoutException && autoSkipTimeoutExceptions) { return@onFailure } + exceptionsHandler?.invoke(e) - if (e is RequestException) { + + // It seems some problems with internet connection. See https://github.com/InsanusMokrassar/ktgbotapi/issues/989 + val isUnresolvedAddressException = e.isCausedUnresolvedAddressException() + if (e is RequestException || isUnresolvedAddressException) { delay(1000L) } }