Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port to Kotlin 1.5 #268

Merged
merged 6 commits into from
Apr 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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