From 1bdcf3ae6040951f30363d8b67a9fbcc00b9896c Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 27 Mar 2019 09:13:07 +0800 Subject: [PATCH] add CommonLimiter --- CHANGELOG.md | 1 + .../bot/settings/limiters/CommonLimiter.kt | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/CommonLimiter.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index db09522152..c84508da03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ media for present out) ### 0.12.4 UpdatesPoller optimisations * Optimized preparing of media group in `UpdatesPoller` +* Add `CommonLimiter` ## 0.11.0 diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/CommonLimiter.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/CommonLimiter.kt new file mode 100644 index 0000000000..e053a48294 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/CommonLimiter.kt @@ -0,0 +1,66 @@ +package com.github.insanusmokrassar.TelegramBotAPI.bot.settings.limiters + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel + +private fun now(): Long = System.currentTimeMillis() + +class CommonLimiter( + private val lockCount: Int = 10, + private val regenTime: Long = 20 * 1000L // 20 seconds for full regen of opportunity to send message +) : RequestLimiter { + private var doLimit: Boolean = false + + private val counterChannel = Channel(Channel.UNLIMITED) + private val scope = CoroutineScope(Dispatchers.Default) + private val counterJob = scope.launch { + var wasLastSecond = 0 + var lastCountTime = now() + var limitManagementJob: Job? = null + var removeLimitTime: Long = lastCountTime + for (counter in counterChannel) { + val now = now() + if (now - lastCountTime > 1000) { + lastCountTime = now + wasLastSecond = 1 + } else { + wasLastSecond++ + } + if (wasLastSecond >= lockCount) { + removeLimitTime = now + regenTime + if (limitManagementJob == null) { + limitManagementJob = launch { + doLimit = true + var internalNow = now() + while (internalNow < removeLimitTime) { + delay(removeLimitTime - internalNow) + internalNow = now() + } + doLimit = false + } + } + } + if (now > removeLimitTime) { + limitManagementJob = null + } + } + } + + private val quoterChannel = Channel(Channel.CONFLATED) + private val tickerJob = scope.launch { + while (isActive) { + quoterChannel.send(Unit) + delay(1000L) + } + } + + override suspend fun limit(block: suspend () -> T): T { + counterChannel.send(Unit) + return if (!doLimit) { + block() + } else { + quoterChannel.receive() + block() + } + } +}