Skip to content

Commit

Permalink
Port to Kotlin 1.5 (#268)
Browse files Browse the repository at this point in the history
* Port dependencies to Kotlin 1.5
- Convert AbstractRateLimiter.AbstractRequestToken to a static rather than an inner class due to a compiler bug
- Downgrade kx.ser-json to 1.0.0 to avoid a compiler bug
- Bump other Kotlin dependencies to latest

fixup! Port dependencies to Kotlin 1.5 - Convert AbstractRateLimiter.AbstractRequestToken to a static rather than an inner class due to a compiler bug - Downgrade kx.ser-json to 1.0.0 to avoid a compiler bug - Bump other Kotlin dependencies to latest

* Replace deprecated kotlin.time APIs

* Replace more deprecated APIs & inline classes

* Replace deprecated usage of time API in tests

* Possibly fix test

Co-authored-by: Hope <[email protected]>
  • Loading branch information
DRSchlaubi and HopeBaron authored Apr 27, 2021
1 parent e1ef647 commit e737212
Show file tree
Hide file tree
Showing 18 changed files with 119 additions and 107 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.apache.commons.codec.binary.Base64
buildscript {
repositories {
jcenter()
mavenCentral()
maven(url = "https://plugins.gradle.org/m2/")
}
dependencies {
Expand Down
10 changes: 5 additions & 5 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
object Versions {
const val kotlin = "1.5.0-RC"
const val kotlinxSerialization = "1.1.0"
const val ktor = "1.5.2"
const val kotlinxCoroutines = "1.4.2"
const val kotlin = "1.5.0"
const val kotlinxSerialization = "1.0.0"
const val ktor = "1.5.3"
const val kotlinxCoroutines = "1.4.3"
const val kotlinLogging = "2.0.4"
const val atomicFu = "0.15.1"
const val atomicFu = "0.15.2"
const val binaryCompatibilityValidator = "0.4.0"

//test deps
Expand Down
2 changes: 1 addition & 1 deletion common/src/main/kotlin/ratelimit/BucketRateLimiter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class BucketRateLimiter(

private fun resetState() {
count = 0
nextInterval = clock.millis() + refillInterval.inMilliseconds.toLong()
nextInterval = clock.millis() + refillInterval.inWholeMilliseconds
}

private suspend fun delayUntilNextInterval() {
Expand Down
5 changes: 3 additions & 2 deletions common/src/test/kotlin/ratelimit/BucketRateLimiterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import java.time.ZoneOffset
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.asserter
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.milliseconds

@ExperimentalTime
@ExperimentalCoroutinesApi
class BucketRateLimiterTest {

val interval = 1_000_000.milliseconds
val interval = Duration.milliseconds(1_000_000)
val instant = Instant.now()
val clock = Clock.fixed(instant, ZoneOffset.UTC)
lateinit var rateLimiter: BucketRateLimiter
Expand All @@ -38,7 +39,7 @@ class BucketRateLimiterTest {
rateLimiter.consume()
rateLimiter.consume()

asserter.assertTrue("expected timeout of ${interval.inMilliseconds.toLong()} ms but was $currentTime ms", interval.toLongMilliseconds() == currentTime)
asserter.assertTrue("expected timeout of ${interval.inWholeMilliseconds} ms but was $currentTime ms", interval.inWholeMilliseconds == currentTime)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.coroutineContext
import kotlin.time.Duration
import kotlin.time.TimeMark
import kotlin.time.seconds

/**
* The behavior of a Discord channel that can use messages.
Expand Down Expand Up @@ -199,7 +199,7 @@ interface MessageChannelBehavior : ChannelBehavior, Strategizable {
suspend fun typeUntil(mark: TimeMark) {
while (mark.hasNotPassedNow()) {
type()
delay(8.seconds.toLongMilliseconds()) //bracing ourselves for some network delays
delay(Duration.seconds(8).inWholeMilliseconds) //bracing ourselves for some network delays
}
}

Expand All @@ -212,7 +212,7 @@ interface MessageChannelBehavior : ChannelBehavior, Strategizable {
suspend fun typeUntil(instant: Instant) {
while (instant.isBefore(Instant.now())) {
type()
delay(8.seconds.toLongMilliseconds()) //bracing ourselves for some network delays
delay(Duration.seconds(8).inWholeMilliseconds) //bracing ourselves for some network delays
}
}

Expand Down Expand Up @@ -297,7 +297,7 @@ suspend inline fun <T : MessageChannelBehavior> T.withTyping(block: T.() -> Unit
kord.launch(context = coroutineContext) {
while (typing) {
type()
delay(8.seconds.toLongMilliseconds())
delay(Duration.seconds(8).inWholeMilliseconds)
}
}

Expand Down
9 changes: 5 additions & 4 deletions core/src/main/kotlin/builder/kord/KordBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,19 @@ import kotlin.concurrent.thread
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.time.Duration
import kotlin.time.seconds

operator fun DefaultGateway.Companion.invoke(
resources: ClientResources,
retry: Retry = LinearRetry(2.seconds, 60.seconds, 10)
retry: Retry = LinearRetry(Duration.seconds(2), Duration.seconds(60), 10)
): DefaultGateway {
return DefaultGateway {
url = "wss://gateway.discord.gg/"
client = resources.httpClient
reconnectRetry = retry
sendRateLimiter = BucketRateLimiter(120, 60.seconds)
identifyRateLimiter = BucketRateLimiter(1, 5.seconds)
sendRateLimiter = BucketRateLimiter(120, Duration.seconds(60))
identifyRateLimiter = BucketRateLimiter(1, Duration.seconds(5))
}
}

Expand All @@ -63,7 +64,7 @@ class KordBuilder(val token: String) {
private var shardsBuilder: (recommended: Int) -> Shards = { Shards(it) }
private var gatewayBuilder: (resources: ClientResources, shards: List<Int>) -> List<Gateway> =
{ resources, shards ->
val rateLimiter = BucketRateLimiter(1, 5.seconds)
val rateLimiter = BucketRateLimiter(1, Duration.seconds(5))
shards.map {
DefaultGateway {
client = resources.httpClient
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/kotlin/event/guild/InviteCreateEvent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class InviteCreateEvent(
/**
* How long the invite is valid for (in seconds).
*/
val maxAge: Duration get() = data.maxAge.seconds
val maxAge: Duration get() = Duration.seconds(data.maxAge)

/**
* The maximum number of times the invite can be used.
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/kotlin/gateway/MasterGateway.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ class MasterGateway(
*/
val averagePing
get(): Duration? {
val pings = gateways.values.mapNotNull { it.ping.value?.inMicroseconds }
val pings = gateways.values.mapNotNull { it.ping.value?.inWholeMilliseconds }
if (pings.isEmpty()) return null

return pings.average().microseconds
return Duration.microseconds(pings.average())
}


Expand Down
76 changes: 38 additions & 38 deletions core/src/test/kotlin/performance/KordEventDropTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import dev.kord.rest.request.KtorRequestHandler
import dev.kord.rest.service.RestClient
import io.ktor.client.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.time.Clock
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicInteger
Expand All @@ -25,7 +25,6 @@ import kotlin.coroutines.EmptyCoroutineContext
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.Duration
import kotlin.time.minutes

class KordEventDropTest {

Expand All @@ -47,46 +46,47 @@ class KordEventDropTest {
}

val kord = Kord(
resources = ClientResources("token", Shards(1), HttpClient(), EntitySupplyStrategy.cache, Intents.none),
cache = DataCache.none(),
MasterGateway(mapOf(0 to SpammyGateway)),
RestClient(KtorRequestHandler("token", clock = Clock.systemUTC())),
Snowflake("420"),
MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE),
Dispatchers.Default
resources = ClientResources("token", Shards(1), HttpClient(), EntitySupplyStrategy.cache, Intents.none),
cache = DataCache.none(),
MasterGateway(mapOf(0 to SpammyGateway)),
RestClient(KtorRequestHandler("token", clock = Clock.systemUTC())),
Snowflake("420"),
MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE),
Dispatchers.Default
)

@Test
fun `hammering the gateway does not drop core events`() = runBlocking {
val amount = 1_000

val event = GuildCreate(
DiscordGuild(
Snowflake("1337"),
"discord guild",
afkTimeout = 0,
defaultMessageNotifications = DefaultMessageNotificationLevel.AllMessages,
emojis = emptyList(),
explicitContentFilter = ExplicitContentFilter.AllMembers,
features = emptyList(),
mfaLevel = MFALevel.Elevated,
ownerId = Snowflake("123"),
preferredLocale = "en",
description = "A not really real guild",
premiumTier = PremiumTier.None,
region = "idk",
roles = emptyList(),
verificationLevel = VerificationLevel.High,
icon = null,
afkChannelId = null,
applicationId = null,
systemChannelFlags = SystemChannelFlags(0),
systemChannelId = null,
rulesChannelId = null,
vanityUrlCode = null,
banner = null,
publicUpdatesChannelId = null
), 0)
DiscordGuild(
Snowflake("1337"),
"discord guild",
afkTimeout = 0,
defaultMessageNotifications = DefaultMessageNotificationLevel.AllMessages,
emojis = emptyList(),
explicitContentFilter = ExplicitContentFilter.AllMembers,
features = emptyList(),
mfaLevel = MFALevel.Elevated,
ownerId = Snowflake("123"),
preferredLocale = "en",
description = "A not really real guild",
premiumTier = PremiumTier.None,
region = "idk",
roles = emptyList(),
verificationLevel = VerificationLevel.High,
icon = null,
afkChannelId = null,
applicationId = null,
systemChannelFlags = SystemChannelFlags(0),
systemChannelId = null,
rulesChannelId = null,
vanityUrlCode = null,
banner = null,
publicUpdatesChannelId = null
), 0
)

val counter = AtomicInteger(0)
val countdown = CountDownLatch(amount)
Expand All @@ -99,7 +99,7 @@ class KordEventDropTest {
SpammyGateway.events.emit(event)
}

withTimeout(1.minutes) {
withTimeout(Duration.minutes(1).inWholeMilliseconds) {
countdown.await()
}
assertEquals(amount, counter.get())
Expand Down
7 changes: 4 additions & 3 deletions gateway/src/main/kotlin/DefaultGatewayBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlin.time.Duration
import kotlin.time.seconds

class DefaultGatewayBuilder {
Expand All @@ -32,9 +33,9 @@ class DefaultGatewayBuilder {
install(WebSockets)
install(JsonFeature)
}
val retry = reconnectRetry ?: LinearRetry(2.seconds, 20.seconds, 10)
val sendRateLimiter = sendRateLimiter ?: BucketRateLimiter(120, 60.seconds)
val identifyRateLimiter = identifyRateLimiter ?: BucketRateLimiter(1, 5.seconds)
val retry = reconnectRetry ?: LinearRetry(Duration.seconds(2), Duration.seconds(20), 10)
val sendRateLimiter = sendRateLimiter ?: BucketRateLimiter(120, Duration.seconds(60))
val identifyRateLimiter = identifyRateLimiter ?: BucketRateLimiter(1, Duration.seconds(5))

client.requestPipeline.intercept(HttpRequestPipeline.Render) {
// CIO adds this header even if no extensions are used, which causes it to be empty
Expand Down
11 changes: 4 additions & 7 deletions gateway/src/main/kotlin/retry/LinearRetry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,12 @@ class LinearRetry constructor(
private val maxTries: Int
) : Retry {

constructor(firstBackoffMillis: Long, maxBackoffMillis: Long, maxTries: Int) :
this(firstBackoffMillis.milliseconds, maxBackoffMillis.milliseconds, maxTries)

init {
require(firstBackoff.isPositive()) { "firstBackoff needs to be positive but was ${firstBackoff.toLongMilliseconds()} ms" }
require(maxBackoff.isPositive()) { "maxBackoff needs to be positive but was ${maxBackoff.toLongMilliseconds()} ms" }
require(firstBackoff.isPositive()) { "firstBackoff needs to be positive but was ${firstBackoff.inWholeMilliseconds} ms" }
require(maxBackoff.isPositive()) { "maxBackoff needs to be positive but was ${maxBackoff.inWholeMilliseconds} ms" }
require(
maxBackoff.minus(firstBackoff).isPositive()
) { "maxBackoff ${maxBackoff.toLongMilliseconds()} ms needs to be bigger than firstBackoff ${firstBackoff.toLongMilliseconds()} ms" }
) { "maxBackoff ${maxBackoff.inWholeMilliseconds} ms needs to be bigger than firstBackoff ${firstBackoff.inWholeMilliseconds} ms" }
require(maxTries > 0) { "maxTries needs to be positive but was $maxTries" }
}

Expand All @@ -47,7 +44,7 @@ class LinearRetry constructor(
if (!hasNext) error("max retries exceeded")

tries.incrementAndGet()
var diff = (maxBackoff - firstBackoff).toLongMilliseconds() / maxTries
var diff = (maxBackoff - firstBackoff).inWholeMilliseconds / maxTries
diff *= tries.value
linearRetryLogger.trace { "retry attempt ${tries.value}, delaying for $diff ms" }
delay(diff)
Expand Down
4 changes: 2 additions & 2 deletions gateway/src/test/kotlin/gateway/DefaultGatewayTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import java.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.seconds
import kotlin.time.Duration as KDuration
import kotlin.time.toKotlinDuration

@FlowPreview
Expand All @@ -44,7 +44,7 @@ class DefaultGatewayTest {
}
}

reconnectRetry = LinearRetry(2.seconds, 20.seconds, 10)
reconnectRetry = LinearRetry(KDuration.seconds(2), KDuration.seconds(20), 10)
sendRateLimiter = BucketRateLimiter(120, Duration.ofSeconds(60).toKotlinDuration())
}

Expand Down
3 changes: 2 additions & 1 deletion gateway/src/test/kotlin/json/CommandTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import dev.kord.gateway.*
import kotlinx.serialization.json.*
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.util.*

private val json = Json { encodeDefaults = false }

Expand Down Expand Up @@ -112,7 +113,7 @@ class CommandTest {
put("d", buildJsonObject {
put("since", since)
put("activities", null as String?)
put("status", status.value.toLowerCase())
put("status", status.value.lowercase(Locale.getDefault()))
put("afk", afk)
})
})
Expand Down
Loading

0 comments on commit e737212

Please sign in to comment.