mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2024-11-22 08:13:47 +00:00
limiter rework
This commit is contained in:
parent
4fab01b2a2
commit
66b4d06064
@ -5,6 +5,13 @@
|
|||||||
* `Common`:
|
* `Common`:
|
||||||
* `Version`:
|
* `Version`:
|
||||||
* `MicroUtils`: `0.3.0` -> `0.3.1`
|
* `MicroUtils`: `0.3.0` -> `0.3.1`
|
||||||
|
* `Core`:
|
||||||
|
* New type of requests exceptions `TooMuchRequestsException`. In fact it will be rare case when you will get this
|
||||||
|
exception
|
||||||
|
* `EmptyLimiter` has been renamed to `ExceptionsOnlyLimiter` and currently will stop requests after
|
||||||
|
`TooMuchRequestsException` happen until retry time is actual
|
||||||
|
* `AbstractRequestCallFactory` currently will not look at the response and wait if it have `RetryAfter` error. New
|
||||||
|
behaviour aimed on delegating of this work to `RequestsLimiter`
|
||||||
|
|
||||||
## 0.30.2
|
## 0.30.2
|
||||||
|
|
||||||
|
@ -4,8 +4,7 @@ import dev.inmo.micro_utils.coroutines.safely
|
|||||||
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
||||||
import dev.inmo.tgbotapi.bot.Ktor.base.*
|
import dev.inmo.tgbotapi.bot.Ktor.base.*
|
||||||
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
||||||
import dev.inmo.tgbotapi.bot.settings.limiters.EmptyLimiter
|
import dev.inmo.tgbotapi.bot.settings.limiters.*
|
||||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
|
||||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||||
import dev.inmo.tgbotapi.types.Response
|
import dev.inmo.tgbotapi.types.Response
|
||||||
import dev.inmo.tgbotapi.utils.*
|
import dev.inmo.tgbotapi.utils.*
|
||||||
@ -19,7 +18,7 @@ class KtorRequestsExecutor(
|
|||||||
client: HttpClient = HttpClient(),
|
client: HttpClient = HttpClient(),
|
||||||
callsFactories: List<KtorCallFactory> = emptyList(),
|
callsFactories: List<KtorCallFactory> = emptyList(),
|
||||||
excludeDefaultFactories: Boolean = false,
|
excludeDefaultFactories: Boolean = false,
|
||||||
private val requestsLimiter: RequestLimiter = EmptyLimiter,
|
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter,
|
||||||
private val jsonFormatter: Json = nonstrictJsonFormat
|
private val jsonFormatter: Json = nonstrictJsonFormat
|
||||||
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
||||||
private val callsFactories: List<KtorCallFactory> = callsFactories.run {
|
private val callsFactories: List<KtorCallFactory> = callsFactories.run {
|
||||||
|
@ -53,14 +53,6 @@ abstract class AbstractRequestCallFactory : KtorCallFactory {
|
|||||||
|
|
||||||
return (responseObject.result?.let {
|
return (responseObject.result?.let {
|
||||||
jsonFormatter.decodeFromJsonElement(request.resultDeserializer, it)
|
jsonFormatter.decodeFromJsonElement(request.resultDeserializer, it)
|
||||||
} ?: responseObject.parameters?.let {
|
|
||||||
val error = it.error
|
|
||||||
if (error is RetryAfterError) {
|
|
||||||
delay(error.leftToRetry)
|
|
||||||
makeCall(client, urlsKeeper, request, jsonFormatter)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} ?: response.let {
|
} ?: response.let {
|
||||||
throw newRequestException(
|
throw newRequestException(
|
||||||
responseObject,
|
responseObject,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.tgbotapi.bot.exceptions
|
package dev.inmo.tgbotapi.bot.exceptions
|
||||||
|
|
||||||
import dev.inmo.tgbotapi.types.Response
|
import com.soywiz.klock.DateTime
|
||||||
|
import dev.inmo.tgbotapi.types.*
|
||||||
import io.ktor.utils.io.errors.IOException
|
import io.ktor.utils.io.errors.IOException
|
||||||
|
|
||||||
fun newRequestException(
|
fun newRequestException(
|
||||||
@ -16,6 +17,13 @@ fun newRequestException(
|
|||||||
description == "Unauthorized" -> UnauthorizedException(response, plainAnswer, message, cause)
|
description == "Unauthorized" -> UnauthorizedException(response, plainAnswer, message, cause)
|
||||||
description.contains("PHOTO_INVALID_DIMENSIONS") -> InvalidPhotoDimensionsException(response, plainAnswer, message, cause)
|
description.contains("PHOTO_INVALID_DIMENSIONS") -> InvalidPhotoDimensionsException(response, plainAnswer, message, cause)
|
||||||
description.contains("wrong file identifier") -> WrongFileIdentifierException(response, plainAnswer, message, cause)
|
description.contains("wrong file identifier") -> WrongFileIdentifierException(response, plainAnswer, message, cause)
|
||||||
|
description.contains("Too Many Requests") -> TooMuchRequestsException(
|
||||||
|
(response.parameters ?.error as? RetryAfterError) ?: RetryAfterError(60, DateTime.now().unixMillisLong),
|
||||||
|
response,
|
||||||
|
plainAnswer,
|
||||||
|
message,
|
||||||
|
cause
|
||||||
|
)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
} ?: CommonRequestException(response, plainAnswer, message, cause)
|
} ?: CommonRequestException(response, plainAnswer, message, cause)
|
||||||
@ -49,3 +57,6 @@ class InvalidPhotoDimensionsException(response: Response, plainAnswer: String, m
|
|||||||
|
|
||||||
class WrongFileIdentifierException(response: Response, plainAnswer: String, message: String?, cause: Throwable?) :
|
class WrongFileIdentifierException(response: Response, plainAnswer: String, message: String?, cause: Throwable?) :
|
||||||
RequestException(response, plainAnswer, message, cause)
|
RequestException(response, plainAnswer, message, cause)
|
||||||
|
|
||||||
|
class TooMuchRequestsException(val retryAfter: RetryAfterError, response: Response, plainAnswer: String, message: String?, cause: Throwable?) :
|
||||||
|
RequestException(response, plainAnswer, message, cause)
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package dev.inmo.tgbotapi.bot.settings.limiters
|
|
||||||
|
|
||||||
object EmptyLimiter : RequestLimiter {
|
|
||||||
override suspend fun <T> limit(block: suspend () -> T): T = block()
|
|
||||||
}
|
|
@ -0,0 +1,43 @@
|
|||||||
|
package dev.inmo.tgbotapi.bot.settings.limiters
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.safely
|
||||||
|
import dev.inmo.tgbotapi.bot.exceptions.TooMuchRequestsException
|
||||||
|
import dev.inmo.tgbotapi.types.RetryAfterError
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This limiter will limit requests only after getting a [RetryAfterError] from incoming [block]s. Important thing is
|
||||||
|
* that in case if some of block has been blocked, all the others will wait until it will be possible to be called
|
||||||
|
*/
|
||||||
|
object ExceptionsOnlyLimiter : RequestLimiter {
|
||||||
|
private val lockState = MutableStateFlow(false)
|
||||||
|
override suspend fun <T> limit(block: suspend () -> T): T {
|
||||||
|
while (true) {
|
||||||
|
lockState.first { !it }
|
||||||
|
val result = safely({
|
||||||
|
if (it is TooMuchRequestsException) {
|
||||||
|
try {
|
||||||
|
safely {
|
||||||
|
lockState.emit(true)
|
||||||
|
delay(it.retryAfter.leftToRetry)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lockState.emit(false)
|
||||||
|
}
|
||||||
|
Result.failure(it)
|
||||||
|
} else {
|
||||||
|
throw it
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Result.success(block())
|
||||||
|
}
|
||||||
|
if (result.isSuccess) {
|
||||||
|
return result.getOrNull()!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Renamed", ReplaceWith("ExceptionsOnlyLimiter", "dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter"))
|
||||||
|
typealias EmptyLimiter = ExceptionsOnlyLimiter
|
@ -5,7 +5,7 @@ import com.soywiz.klock.DateTime
|
|||||||
sealed class RequestError
|
sealed class RequestError
|
||||||
|
|
||||||
data class RetryAfterError(
|
data class RetryAfterError(
|
||||||
val seconds: Long,
|
val seconds: Seconds,
|
||||||
val startCountingMillis: Long
|
val startCountingMillis: Long
|
||||||
) : RequestError() {
|
) : RequestError() {
|
||||||
val canContinue = (seconds * 1000L) + startCountingMillis
|
val canContinue = (seconds * 1000L) + startCountingMillis
|
||||||
|
@ -8,7 +8,7 @@ data class ResponseParametersRaw(
|
|||||||
@SerialName("migrate_to_chat_id")
|
@SerialName("migrate_to_chat_id")
|
||||||
private val migrateToChatId: ChatId? = null,
|
private val migrateToChatId: ChatId? = null,
|
||||||
@SerialName("retry_after")
|
@SerialName("retry_after")
|
||||||
private val retryAfter: Long? = null
|
private val retryAfter: Seconds? = null
|
||||||
) {
|
) {
|
||||||
@Transient
|
@Transient
|
||||||
private val createTime: Long = DateTime.now().unixMillisLong
|
private val createTime: Long = DateTime.now().unixMillisLong
|
||||||
|
Loading…
Reference in New Issue
Block a user