Skip to content

Commit 975a93d

Browse files
authored
Keep the vertx-web-kotlinx portion updated (#10383)
* Bump all dependencies and toolchains to the latest * Migrate to Vert.x 5 APIs and bump Kotlin to 2.3.0-RC2 to support Java 25 * Update catching the "connection" reset exceptions * Try setting `date` with kotlinx-datetime but it doesn't work as expected Got `java.lang.IllegalStateException: Field offsetHours is not set`. * Remove the kotlinx-datetime dependency and update the comment See commit 9cf28f1 for a failed attempt to try using kotlinx-datetime. * Get setting `date` with `kotlinx-datetime` to work ref: huanshankeji#1 * Review and port some of the changes in `FrameworkBenchmarks/frameworks/Java/vertx/` from commit 057c25b to commit 1838aa5, and fix some typos Some changes are not ported. There is about 5% performance improvement in the JSON test as tested on my device. * Increase the io_uring completion queue size to resolve warnings and improve the plaintext test performance ``` io.netty.channel.uring.IoUringIoHandler processCompletionsAndHandleOverflow vertx: WARNING: CompletionQueue overflow detected, consider increasing size: 4096 ``` This is still one warning when the value is set to 8192 as tested on my device, instead of many for the default 4096. * Benchmark Approach 1 and Approach 3 in `jsonResponseCoHandler` again and fix capitalization BTW The "Requests/sec" result of Approach 1 is only 40% of Approach 3 as tested on my device. * Remove an outdated comment as found by Gemini Code Assist * Use the "connection reset" error code from Netty instead of hard-coding it and also catch Java NIO `SocketException` for developing on other OSs such as macOS and Windows Tested with the `tfb` command on both macOS and Linux. * Fix some errors and keep things updated in benchmark_config.json and README.md
1 parent 761a47a commit 975a93d

File tree

12 files changed

+100
-74
lines changed

12 files changed

+100
-74
lines changed

frameworks/Kotlin/vertx-web-kotlinx/README.md

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Vert.x-Web in Kotlin with request handling implemented as much with official kotlinx libraries as possible.
44

5-
Code is written from scratch to be as concise as possible with common code extracted into common (possibly inline) functions. SQL client implementation details and JVM Options are adapted referring to [the vertx-web portion](../../Java/vertx-web) and [the vertx portion](../../Java/vertx). All requests are handled in coroutines and suspend `await`s are used instead of future compositions. Compared to [the vertx-web-kotlin-coroutines portion](../vertx-web-kotlin-coroutines), besides adopting the Kotlinx libraries, this project simplifies the code by using more built-in Coroutine functions and avoids mutability as much as possible. JSON serialization is implemented with kotlinx.serialization and Fortunes with kotlinx.html. The benchmark is run on the latest LTS version of JVM, 21.
5+
Code is written from scratch to be as concise as possible with common code extracted into common (possibly inline) functions. SQL client implementation details and JVM Options are adapted referring to [the vertx-web portion](../../Java/vertx-web) and [the vertx portion](../../Java/vertx). All requests are handled in coroutines and suspend `await`s are used instead of future compositions. Compared to [the vertx-web-kotlin-coroutines portion](../vertx-web-kotlin-coroutines), besides adopting the Kotlinx libraries, this project simplifies the code by using more built-in Coroutine functions and avoids mutability as much as possible. JSON serialization is implemented with kotlinx.serialization and Fortunes with kotlinx.html. The benchmark is run on the latest LTS version of JVM, 25.
66

77
## Test Type Implementation Source Code
88

@@ -13,7 +13,6 @@ Code is written from scratch to be as concise as possible with common code extra
1313
* [PLAINTEXT](src/main/kotlin/MainVerticle.kt)
1414
* [DB](src/main/kotlin/MainVerticle.kt)
1515
* [QUERY](src/main/kotlin/MainVerticle.kt)
16-
* [CACHED QUERY](src/main/kotlin/MainVerticle.kt)
1716
* [UPDATE](src/main/kotlin/MainVerticle.kt)
1817
* [FORTUNES](src/main/kotlin/MainVerticle.kt)
1918

@@ -48,10 +47,6 @@ http://localhost:8080/db
4847

4948
http://localhost:8080/query?queries=
5049

51-
### CACHED QUERY
52-
53-
http://localhost:8080/cached_query?queries=
54-
5550
### UPDATE
5651

5752
http://localhost:8080/update?queries=

frameworks/Kotlin/vertx-web-kotlinx/benchmark_config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"database_os": "Linux",
4141
"display_name": "vertx-web-kotlinx-postgresql",
4242
"notes": "",
43-
"versus": "vertx-web"
43+
"versus": "vertx-web-postgres"
4444
}
4545
}
4646
]

frameworks/Kotlin/vertx-web-kotlinx/build.gradle.kts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ tasks.wrapper {
33
}
44

55
plugins {
6-
val kotlinVersion = "2.0.21"
6+
val kotlinVersion = "2.3.0-RC2"
77
kotlin("jvm") version kotlinVersion
88
kotlin("plugin.serialization") version kotlinVersion
99
application
@@ -13,29 +13,27 @@ repositories {
1313
mavenCentral()
1414
}
1515

16-
val vertxVersion = "4.5.10"
17-
val kotlinxSerializationVersion = "1.7.3"
16+
val vertxVersion = "5.0.5"
17+
val kotlinxSerializationVersion = "1.9.0"
1818
dependencies {
1919
implementation(platform("io.vertx:vertx-stack-depchain:$vertxVersion"))
2020
implementation("io.vertx:vertx-web")
2121
implementation("io.vertx:vertx-pg-client")
22-
implementation("io.netty", "netty-transport-native-epoll", classifier = "linux-x86_64")
22+
//implementation("io.netty", "netty-transport-native-epoll", classifier = "linux-x86_64")
23+
implementation("io.netty", "netty-transport-native-io_uring", classifier = "linux-x86_64")
2324
implementation("io.vertx:vertx-lang-kotlin")
2425
implementation("io.vertx:vertx-lang-kotlin-coroutines")
25-
runtimeOnly("io.vertx:vertx-io_uring-incubator")
26-
// This dependency has to be added for io_uring to work.
27-
runtimeOnly("io.netty.incubator:netty-incubator-transport-native-io_uring:0.0.25.Final:linux-x86_64")
2826

29-
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
27+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
3028

3129
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializationVersion")
3230
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-io:$kotlinxSerializationVersion")
33-
implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.5.4")
31+
implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.8.2")
3432

35-
implementation("org.jetbrains.kotlinx:kotlinx-html:0.11.0")
36-
//implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") // the latest version is 0.6.1
33+
implementation("org.jetbrains.kotlinx:kotlinx-html:0.12.0")
34+
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1")
3735
}
3836

39-
kotlin.jvmToolchain(21)
37+
kotlin.jvmToolchain(25)
4038

4139
application.mainClass.set("MainKt")
Binary file not shown.

frameworks/Kotlin/vertx-web-kotlinx/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-all.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME

frameworks/Kotlin/vertx-web-kotlinx/gradlew

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/sh
22

33
#
4-
# Copyright © 2015-2021 the original authors.
4+
# Copyright © 2015 the original authors.
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -86,8 +86,7 @@ done
8686
# shellcheck disable=SC2034
8787
APP_BASE_NAME=${0##*/}
8888
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89-
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
90-
' "$PWD" ) || exit
89+
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
9190

9291
# Use the maximum available, or set MAX_FD != -1 to use that value.
9392
MAX_FD=maximum
@@ -115,7 +114,6 @@ case "$( uname )" in #(
115114
NONSTOP* ) nonstop=true ;;
116115
esac
117116

118-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
119117

120118

121119
# Determine the Java command to use to start the JVM.
@@ -173,7 +171,6 @@ fi
173171
# For Cygwin or MSYS, switch paths to Windows format before running java
174172
if "$cygwin" || "$msys" ; then
175173
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
176-
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
177174

178175
JAVACMD=$( cygpath --unix "$JAVACMD" )
179176

@@ -206,15 +203,14 @@ fi
206203
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
207204

208205
# Collect all arguments for the java command:
209-
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
206+
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
210207
# and any embedded shellness will be escaped.
211208
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
212209
# treated as '${Hostname}' itself on the command line.
213210

214211
set -- \
215212
"-Dorg.gradle.appname=$APP_BASE_NAME" \
216-
-classpath "$CLASSPATH" \
217-
org.gradle.wrapper.GradleWrapperMain \
213+
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
218214
"$@"
219215

220216
# Stop when "xargs" is not available.

frameworks/Kotlin/vertx-web-kotlinx/gradlew.bat

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,10 @@ goto fail
7070
:execute
7171
@rem Setup the command line
7272

73-
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
7473

7574

7675
@rem Execute Gradle
77-
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76+
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
7877

7978
:end
8079
@rem End local scope for the variables with windows NT shell

frameworks/Kotlin/vertx-web-kotlinx/src/main/kotlin/Main.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,30 @@ import io.vertx.core.impl.cpu.CpuCoreSensor
33
import io.vertx.kotlin.core.deploymentOptionsOf
44
import io.vertx.kotlin.core.vertxOptionsOf
55
import io.vertx.kotlin.coroutines.coAwait
6+
import java.util.function.Supplier
67
import java.util.logging.Logger
78

89
const val SERVER_NAME = "Vert.x-Web Kotlinx Benchmark server"
10+
val numProcessors = CpuCoreSensor.availableProcessors()
911

1012
val logger = Logger.getLogger("Vert.x-Web Kotlinx Benchmark")
1113
suspend fun main(args: Array<String>) {
1214
val hasDb = args.getOrNull(0)?.toBooleanStrictOrNull()
1315
?: throw IllegalArgumentException("Specify the first `hasDb` Boolean argument")
1416

1517
logger.info("$SERVER_NAME starting...")
16-
val vertx = Vertx.vertx(vertxOptionsOf(preferNativeTransport = true))
18+
val vertx = Vertx.vertx(
19+
vertxOptionsOf(
20+
eventLoopPoolSize = numProcessors, preferNativeTransport = true, disableTCCL = true
21+
)
22+
)
1723
vertx.exceptionHandler {
1824
logger.info("Vertx exception caught: $it")
1925
it.printStackTrace()
2026
}
21-
vertx.deployVerticle({ MainVerticle(hasDb) }, deploymentOptionsOf(instances = CpuCoreSensor.availableProcessors()))
22-
.coAwait()
27+
vertx.deployVerticle(
28+
Supplier { MainVerticle(hasDb) },
29+
deploymentOptionsOf(instances = numProcessors)
30+
).coAwait()
2331
logger.info("$SERVER_NAME started.")
2432
}

frameworks/Kotlin/vertx-web-kotlinx/src/main/kotlin/MainVerticle.kt

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import io.netty.channel.unix.Errors
12
import io.netty.channel.unix.Errors.NativeIoException
3+
import io.vertx.core.MultiMap
24
import io.vertx.core.buffer.Buffer
35
import io.vertx.core.http.HttpHeaders
46
import io.vertx.core.http.HttpServer
@@ -18,6 +20,9 @@ import io.vertx.sqlclient.Row
1820
import io.vertx.sqlclient.RowSet
1921
import io.vertx.sqlclient.Tuple
2022
import kotlinx.coroutines.Dispatchers
23+
import kotlinx.datetime.UtcOffset
24+
import kotlinx.datetime.format.DateTimeComponents
25+
import kotlinx.datetime.format.format
2126
import kotlinx.html.*
2227
import kotlinx.html.stream.appendHTML
2328
import kotlinx.io.buffered
@@ -26,23 +31,31 @@ import kotlinx.serialization.Serializable
2631
import kotlinx.serialization.SerializationStrategy
2732
import kotlinx.serialization.json.Json
2833
import kotlinx.serialization.json.io.encodeToSink
29-
import java.time.ZonedDateTime
30-
import java.time.format.DateTimeFormatter
34+
import java.net.SocketException
35+
import kotlin.time.Clock
3136

3237
class MainVerticle(val hasDb: Boolean) : CoroutineVerticle(), CoroutineRouterSupport {
38+
object HttpHeaderValues {
39+
val vertxWeb = HttpHeaders.createOptimized("Vert.x-Web")
40+
val applicationJson = HttpHeaders.createOptimized("application/json")
41+
val textHtmlCharsetUtf8 = HttpHeaders.createOptimized("text/html; charset=utf-8")
42+
val textPlain = HttpHeaders.createOptimized("text/plain")
43+
}
44+
3345
// `PgConnection`s as used in the "vertx" portion offers better performance than `PgPool`s.
3446
lateinit var pgConnection: PgConnection
3547
lateinit var date: String
3648
lateinit var httpServer: HttpServer
3749

3850
lateinit var selectWorldQuery: PreparedQuery<RowSet<Row>>
3951
lateinit var selectFortuneQuery: PreparedQuery<RowSet<Row>>
40-
lateinit var updateWordQuery: PreparedQuery<RowSet<Row>>
52+
lateinit var updateWorldQuery: PreparedQuery<RowSet<Row>>
4153

4254
fun setCurrentDate() {
43-
// kotlinx-datetime doesn't support the format yet.
44-
//date = Clock.System.now().toString()
45-
date = DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now())
55+
date = DateTimeComponents.Formats.RFC_1123.format {
56+
// We don't need a more complicated system `TimeZone` here (whose offset depends dynamically on the actual time due to DST) since UTC works.
57+
setDateTimeOffset(Clock.System.now(), UtcOffset.ZERO)
58+
}
4659
}
4760

4861
override suspend fun start() {
@@ -56,27 +69,25 @@ class MainVerticle(val hasDb: Boolean) : CoroutineVerticle(), CoroutineRouterSup
5669
user = "benchmarkdbuser",
5770
password = "benchmarkdbpass",
5871
cachePreparedStatements = true,
59-
pipeliningLimit = 100000
72+
pipeliningLimit = 256
6073
)
6174
).coAwait()
6275

6376
selectWorldQuery = pgConnection.preparedQuery(SELECT_WORLD_SQL)
6477
selectFortuneQuery = pgConnection.preparedQuery(SELECT_FORTUNE_SQL)
65-
updateWordQuery = pgConnection.preparedQuery(UPDATE_WORLD_SQL)
78+
updateWorldQuery = pgConnection.preparedQuery(UPDATE_WORLD_SQL)
6679
}
6780

6881
setCurrentDate()
6982
vertx.setPeriodic(1000) { setCurrentDate() }
70-
httpServer = vertx.createHttpServer(httpServerOptionsOf(port = 8080))
83+
httpServer = vertx.createHttpServer(
84+
httpServerOptionsOf(port = 8080, http2ClearTextEnabled = false, strictThreadMode = true)
85+
)
7186
.requestHandler(Router.router(vertx).apply { routes() })
7287
.exceptionHandler {
7388
// wrk resets the connections when benchmarking is finished.
74-
if (
75-
// for epoll
76-
/*(it is NativeIoException && it.message == "recvAddress(..) failed: Connection reset by peer")
77-
|| (it is SocketException && it.message == "Connection reset")*/
78-
// for io_uring
79-
it is NativeIoException && it.message == "io_uring read(..) failed: Connection reset by peer"
89+
if ((/* for native transport */it is NativeIoException && it.expectedErr() == Errors.ERRNO_ECONNRESET_NEGATIVE) ||
90+
(/* for Java NIO */ it is SocketException && it.message == "Connection reset")
8091
)
8192
return@exceptionHandler
8293

@@ -93,15 +104,17 @@ class MainVerticle(val hasDb: Boolean) : CoroutineVerticle(), CoroutineRouterSup
93104
}
94105

95106
@Suppress("NOTHING_TO_INLINE")
96-
inline fun HttpServerResponse.putCommonHeaders() {
97-
putHeader(HttpHeaders.SERVER, "Vert.x-Web")
98-
putHeader(HttpHeaders.DATE, date)
107+
inline fun MultiMap.addCommonHeaders() {
108+
add(HttpHeaders.SERVER, HttpHeaderValues.vertxWeb)
109+
add(HttpHeaders.DATE, date)
99110
}
100111

101112
@Suppress("NOTHING_TO_INLINE")
102-
inline fun HttpServerResponse.putJsonResponseHeader() {
103-
putCommonHeaders()
104-
putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
113+
inline fun HttpServerResponse.addJsonResponseHeaders() {
114+
headers().run {
115+
addCommonHeaders()
116+
add(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.applicationJson)
117+
}
105118
}
106119

107120

@@ -117,23 +130,23 @@ class MainVerticle(val hasDb: Boolean) : CoroutineVerticle(), CoroutineRouterSup
117130
) =
118131
coHandlerUnconfined {
119132
it.response().run {
120-
putJsonResponseHeader()
133+
addJsonResponseHeaders()
121134

122135
/*
123-
// approach 1
136+
// Approach 1
124137
end(Json.encodeToString(serializer, requestHandler(it)))/*.coAwait()*/
125138
*/
126139

127140
/*
128-
// approach 2
141+
// Approach 2
129142
// java.lang.IllegalStateException: You must set the Content-Length header to be the total size of the message body BEFORE sending any data if you are not using HTTP chunked encoding.
130143
toRawSink().buffered().use { bufferedSink ->
131144
@OptIn(ExperimentalSerializationApi::class)
132145
Json.encodeToSink(serializer, requestHandler(it), bufferedSink)
133146
}
134147
*/
135148

136-
// approach 3
149+
// Approach 3
137150
end(Buffer.buffer().apply {
138151
toRawSink().buffered().use { bufferedSink ->
139152
@OptIn(ExperimentalSerializationApi::class)
@@ -197,20 +210,23 @@ class MainVerticle(val hasDb: Boolean) : CoroutineVerticle(), CoroutineRouterSup
197210
}
198211

199212
it.response().run {
200-
putCommonHeaders()
201-
putHeader(HttpHeaders.CONTENT_TYPE, "text/html; charset=utf-8")
213+
headers().run {
214+
addCommonHeaders()
215+
add(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.textHtmlCharsetUtf8)
216+
}
202217
end(htmlString)/*.coAwait()*/
203218
}
204219
}
205220

221+
// Some changes to this part in the `vertx` portion in #9142 are not ported.
206222
get("/updates").jsonResponseCoHandler(Serializers.worlds) {
207223
val queries = it.request().getQueries()
208224
val worlds = selectRandomWorlds(queries)
209225
val updatedWorlds = worlds.map { it.copy(randomNumber = randomIntBetween1And10000()) }
210226

211227
// Approach 1
212228
// The updated worlds need to be sorted first to avoid deadlocks.
213-
updateWordQuery
229+
updateWorldQuery
214230
.executeBatch(updatedWorlds.sortedBy { it.id }.map { Tuple.of(it.randomNumber, it.id) }).coAwait()
215231

216232
/*
@@ -225,8 +241,10 @@ class MainVerticle(val hasDb: Boolean) : CoroutineVerticle(), CoroutineRouterSup
225241

226242
get("/plaintext").coHandlerUnconfined {
227243
it.response().run {
228-
putCommonHeaders()
229-
putHeader(HttpHeaders.CONTENT_TYPE, "text/plain")
244+
headers().run {
245+
addCommonHeaders()
246+
add(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.textPlain)
247+
}
230248
end("Hello, World!")/*.coAwait()*/
231249
}
232250
}

frameworks/Kotlin/vertx-web-kotlinx/src/main/kotlin/VertxCoroutine.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ import io.vertx.core.Future
22
import io.vertx.kotlin.coroutines.coAwait
33

44
suspend fun <T> List<Future<T>>.awaitAll(): List<T> =
5-
Future.all(this).coAwait().list()
5+
Future.all<T>(this).coAwait().list()

0 commit comments

Comments
 (0)