Browse Source

Revamp the Kotlin Vert.x portions "vertx-web-kotlinx" and "vertx-web-kotlin-coroutines" (#9374)

* Revamp the "vertx-web-kotlinx" portion project

Changes:
1. bump the Gradle versions and the dependency versions to the latest
1. bump the JVM version to 21 with the new `jvmToolchain` DSL
1. resolve deprecations
1. update the dockerfile to use the `installDist` Gradle task so the time for archiving and unarchiving is saved

* Run the revamped benchmark

There are no build or runtime errors. Single query performance seems to have been improved by 2% with the bumped versions (I think most likely due to Java 21). Vagrant reduces performance by about 20% compared to running directly with Docker.

* Enable io_uring and run the benchmark

The single query performance is improved by 5% - 10%.

* Try fixing the performance issues of the "vertx-web-kotlinx" portion in the "single query" and "JSON serialization" tests in the [Continuous Benchmarking results](https://tfb-status.techempower.com/) by using static kotlinx.serialization serializers

The "vertx-web-kotlinx" results in the Continuous Benchmarking results are much lower than those of the "vertx-web-kotlin-coroutines" portion. See [the latest results](https://www.techempower.com/benchmarks/#section=test&runid=592cab59-a9db-463b-a9c9-33d2f9484e92&hw=ph&test=db) for example.

Looking at the "single query" results, I first suspected that it was caused by there being not enough memory for the JVM runtime, so I added some logging code that prints the memory usage using `Runtime.totalMemory` and `Runtime.maxMemory`. It showed that there was about 7 GB max memory available during the benchmark runs, and the program only used 400 MB to 1 GB. I then tried allocating a 4 GB array during the run to ensure that the memory was usable and it worked with no problem.

Then looking at the "JSON serialization" results again, I saw that "vertx-web-kotlinx" performs a lot worse in this test too, and decided that this is more likely to be the bottleneck. Therefore, the static serializers are provided explicitly and the performance is improved slightly as tested on my machine. (Also, see commit 315b4e359cde7909810a6a842ae3c7d49233291f for an attempt before.) I then copied the "JSON serialization" test code from "vertx-web-kotlin-coroutines" and ran the benchmark to see if there were other factors, such as project configuration differences, affecting the performance, and the answer was no. On my machine, the "JSON serialization" performance of "vertx-web-kotlinx" is about 80% - 85% of that of "vertx-web-kotlin-coroutines". And I think the bottleneck possibly lies in kotlinx.serialization serializing an object to a byte array first and then copying it to a Vert.x buffer.

Remove the broken tag in "vertx-web-kotlin-coroutines" BTW, which was added in commit e53e0260e522106b316d8fce2e378a056d866356, for the benchmark runs without problems now as I tested.

* Update README.md correspondingly

* Add `--no-daemon` to the Gradle command in the Dockerfiles

* Update the "Connection reset" exception to the io_uring one that's ignored in logging

* Use `encodeToBufferedSink` in "kotlinx-serialization-json-okio" with a custom-implemented `VertxBufferSink` in JSON serialization

The "JSON serialization" performance is on par with using `io.vertx.core.json.Json.encodeToBuffer` as tested on my machine after this change.

* Replace Okio with kotlinx-io and remove unneeded code

The "JSON serialization" performance seems to be slightly less.

* Rename 2 classes to clarify

* Use to the new Vert.x coroutine APIs introduced in https://github.com/vert-x3/vertx-lang-kotlin/pull/253 / https://github.com/vert-x3/vertx-lang-kotlin/commit/e841975ef50b6be9b516d27498821096a3ad462e

There is no noticeable performance degradation in the "plaintext" and "JSON serialization" tests.

* Simply the code introduced in the previous commit by making `MainVerticle` implement `CoroutineRouterSupport`

I didn't go through [the docs](https://vertx.io/docs/vertx-lang-kotlin-coroutines/kotlin/#_vert_x_web) thoroughly before implementing this.

* Revamp the "vertx-web-kotlin-coroutines" portion project too following the changes made to the "vertx-web-kotlinx" portion

The "gradlew" script somehow had incorrect access permissions and is fixed by bumping the Gradle wrapper.

To keep the dependencies consistent with the "vert-web" portion, some dependencies are not updated to the latest versions.

Remove 2 useless `COPY`s in "vertx-web-kotlin-coroutines-postgres.dockerfile" BTW.

* Remove unneeded and incorrect comments

Wrapping something as a Vert.x `Buffer` by implementing the `Buffer` interface is not viable because `BufferImpl` contains casts from a `Buffer` to a `BufferImpl`.
Shreck Ye 9 months ago
parent
commit
fed744bee1
21 changed files with 231 additions and 130 deletions
  1. 1 2
      frameworks/Kotlin/vertx-web-kotlin-coroutines/benchmark_config.json
  2. 13 12
      frameworks/Kotlin/vertx-web-kotlin-coroutines/build.gradle.kts
  3. BIN
      frameworks/Kotlin/vertx-web-kotlin-coroutines/gradle/wrapper/gradle-wrapper.jar
  4. 2 1
      frameworks/Kotlin/vertx-web-kotlin-coroutines/gradle/wrapper/gradle-wrapper.properties
  5. 21 13
      frameworks/Kotlin/vertx-web-kotlin-coroutines/gradlew
  6. 12 10
      frameworks/Kotlin/vertx-web-kotlin-coroutines/gradlew.bat
  7. 1 3
      frameworks/Kotlin/vertx-web-kotlin-coroutines/vertx-web-kotlin-coroutines-postgres.dockerfile
  8. 1 1
      frameworks/Kotlin/vertx-web-kotlin-coroutines/vertx-web-kotlin-coroutines.dockerfile
  9. 2 1
      frameworks/Kotlin/vertx-web-kotlinx/README.md
  10. 15 12
      frameworks/Kotlin/vertx-web-kotlinx/build.gradle.kts
  11. BIN
      frameworks/Kotlin/vertx-web-kotlinx/gradle/wrapper/gradle-wrapper.jar
  12. 2 1
      frameworks/Kotlin/vertx-web-kotlinx/gradle/wrapper/gradle-wrapper.properties
  13. 21 13
      frameworks/Kotlin/vertx-web-kotlinx/gradlew
  14. 12 10
      frameworks/Kotlin/vertx-web-kotlinx/gradlew.bat
  15. 47 0
      frameworks/Kotlin/vertx-web-kotlinx/src/main/kotlin/KotlinxIo.kt
  16. 3 3
      frameworks/Kotlin/vertx-web-kotlinx/src/main/kotlin/Main.kt
  17. 63 37
      frameworks/Kotlin/vertx-web-kotlinx/src/main/kotlin/MainVerticle.kt
  18. 7 0
      frameworks/Kotlin/vertx-web-kotlinx/src/main/kotlin/Serializers.kt
  19. 2 3
      frameworks/Kotlin/vertx-web-kotlinx/src/main/kotlin/VertxCoroutine.kt
  20. 3 4
      frameworks/Kotlin/vertx-web-kotlinx/vertx-web-kotlinx-postgresql.dockerfile
  21. 3 4
      frameworks/Kotlin/vertx-web-kotlinx/vertx-web-kotlinx.dockerfile

+ 1 - 2
frameworks/Kotlin/vertx-web-kotlin-coroutines/benchmark_config.json

@@ -18,8 +18,7 @@
       "database_os": "Linux",
       "display_name": "vertx-web-kotlin-coroutines",
       "notes": "",
-      "versus": "vertx-web",
-      "tags": ["broken"]
+      "versus": "vertx-web"
     },
     "postgres": {
       "db_url": "/db",

+ 13 - 12
frameworks/Kotlin/vertx-web-kotlin-coroutines/build.gradle.kts

@@ -1,34 +1,37 @@
 import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
-import org.jetbrains.kotlin.gradle.dsl.JvmTarget
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 tasks.wrapper {
     distributionType = Wrapper.DistributionType.ALL
 }
 
 plugins {
-    kotlin("jvm") version "1.8.10"
+    kotlin("jvm") version "2.0.21"
     application
     id("nu.studer.rocker") version "3.0.4"
-    id("com.github.johnrengelman.shadow") version "7.1.2"
+    id("com.gradleup.shadow") version "8.3.4"
 }
 
 group = "io.vertx"
-version = "4.3.8"
+version = "4.3.8" // 4.5.10 is available, but this is not updated to be kept consistent with the "vert-web" portion
 
 repositories {
     mavenCentral()
 }
 
 dependencies {
-    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
+    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
     implementation(platform("io.vertx:vertx-stack-depchain:$version"))
     implementation("io.vertx:vertx-core")
-    implementation("com.fasterxml.jackson.module:jackson-module-blackbird:2.14.2")
+    implementation("com.fasterxml.jackson.module:jackson-module-blackbird:2.14.2") // 2.18.1 is available, but this is not updated to be kept consistent with the "vert-web" portion
     implementation("io.vertx:vertx-web")
     implementation("io.vertx:vertx-pg-client")
     implementation("io.vertx:vertx-web-templ-rocker")
-    implementation("io.netty", "netty-transport-native-epoll", classifier = "linux-x86_64")
+    runtimeOnly("io.netty", "netty-transport-native-epoll", classifier = "linux-x86_64")
+    // not used to make the project consistent with the "vert-web" portion
+    /*
+    runtimeOnly("io.vertx:vertx-io_uring-incubator")
+    runtimeOnly("io.netty.incubator:netty-incubator-transport-native-io_uring:0.0.25.Final:linux-x86_64")
+    */
     implementation("io.vertx:vertx-lang-kotlin")
     implementation("io.vertx:vertx-lang-kotlin-coroutines")
 }
@@ -38,14 +41,12 @@ rocker {
         create("main") {
             templateDir.set(file("src/main/resources"))
             optimize.set(true)
-            javaVersion.set("17")
+            javaVersion.set("17") // kept consistent with the "vert-web" portion
         }
     }
 }
 
-tasks.withType<KotlinCompile> {
-    compilerOptions.jvmTarget.set(JvmTarget.JVM_17)
-}
+kotlin.jvmToolchain(17) // kept consistent with the "vert-web" portion
 
 
 // content below copied from the project generated by the app generator

BIN
frameworks/Kotlin/vertx-web-kotlin-coroutines/gradle/wrapper/gradle-wrapper.jar


+ 2 - 1
frameworks/Kotlin/vertx-web-kotlin-coroutines/gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,7 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
 networkTimeout=10000
+validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

+ 21 - 13
frameworks/Kotlin/vertx-web-kotlin-coroutines/gradlew

@@ -15,6 +15,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+# SPDX-License-Identifier: Apache-2.0
+#
 
 ##############################################################################
 #
@@ -55,7 +57,7 @@
 #       Darwin, MinGW, and NonStop.
 #
 #   (3) This script is generated from the Groovy template
-#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
 #       within the Gradle project.
 #
 #       You can find Gradle at https://github.com/gradle/gradle/.
@@ -83,10 +85,9 @@ done
 # This is normally unused
 # shellcheck disable=SC2034
 APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
+' "$PWD" ) || exit
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 MAX_FD=maximum
@@ -133,10 +134,13 @@ location of your Java installation."
     fi
 else
     JAVACMD=java
-    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+    if ! command -v java >/dev/null 2>&1
+    then
+        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 
 Please set the JAVA_HOME variable in your environment to match the
 location of your Java installation."
+    fi
 fi
 
 # Increase the maximum file descriptors if we can.
@@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
     case $MAX_FD in #(
       max*)
         # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC3045 
+        # shellcheck disable=SC2039,SC3045
         MAX_FD=$( ulimit -H -n ) ||
             warn "Could not query maximum file descriptor limit"
     esac
@@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
       '' | soft) :;; #(
       *)
         # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC3045 
+        # shellcheck disable=SC2039,SC3045
         ulimit -n "$MAX_FD" ||
             warn "Could not set maximum file descriptor limit to $MAX_FD"
     esac
@@ -197,11 +201,15 @@ if "$cygwin" || "$msys" ; then
     done
 fi
 
-# Collect all arguments for the java command;
-#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-#     shell script including quotes and variable substitutions, so put them in
-#     double quotes to make sure that they get re-expanded; and
-#   * put everything else in single quotes, so that it's not re-expanded.
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+#     and any embedded shellness will be escaped.
+#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+#     treated as '${Hostname}' itself on the command line.
 
 set -- \
         "-Dorg.gradle.appname=$APP_BASE_NAME" \

+ 12 - 10
frameworks/Kotlin/vertx-web-kotlin-coroutines/gradlew.bat

@@ -13,6 +13,8 @@
 @rem See the License for the specific language governing permissions and
 @rem limitations under the License.
 @rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
 
 @if "%DEBUG%"=="" @echo off
 @rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
 %JAVA_EXE% -version >NUL 2>&1
 if %ERRORLEVEL% equ 0 goto execute
 
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
 
 goto fail
 
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 
 if exist "%JAVA_EXE%" goto execute
 
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
 
 goto fail
 

+ 1 - 3
frameworks/Kotlin/vertx-web-kotlin-coroutines/vertx-web-kotlin-coroutines-postgres.dockerfile

@@ -1,10 +1,8 @@
-FROM gradle:7.6-jdk17 as gradle
+FROM gradle:8.10.2-jdk17 as gradle
 WORKDIR /vertx-web-kotlin-coroutines
-COPY gradle gradle
 COPY src src
 COPY build.gradle.kts build.gradle.kts
 COPY gradle.properties gradle.properties
-COPY gradlew gradlew
 COPY settings.gradle.kts settings.gradle.kts
 RUN gradle shadowJar
 

+ 1 - 1
frameworks/Kotlin/vertx-web-kotlin-coroutines/vertx-web-kotlin-coroutines.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:7.6-jdk17 as gradle
+FROM gradle:8.10.2-jdk17 as gradle
 WORKDIR /vertx-web-kotlin-coroutines
 COPY src src
 COPY build.gradle.kts build.gradle.kts

+ 2 - 1
frameworks/Kotlin/vertx-web-kotlinx/README.md

@@ -2,7 +2,7 @@
 
 Vert.x-Web in Kotlin with request handling implemented as much with official kotlinx libraries as possible.
 
-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, 17.
+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.
 
 ## Test Type Implementation Source Code
 
@@ -27,6 +27,7 @@ The tests were run with:
 * [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/)
 * [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines)
 * [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization)
+* [kotlinx-io](https://github.com/Kotlin/kotlinx-io)
 * [kotlinx.html](https://github.com/Kotlin/kotlinx.html)
 
 ## Test URLs

+ 15 - 12
frameworks/Kotlin/vertx-web-kotlinx/build.gradle.kts

@@ -1,12 +1,9 @@
-import org.jetbrains.kotlin.gradle.dsl.JvmTarget
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
 tasks.wrapper {
     distributionType = Wrapper.DistributionType.ALL
 }
 
 plugins {
-    val kotlinVersion = "1.8.10"
+    val kotlinVersion = "2.0.21"
     kotlin("jvm") version kotlinVersion
     kotlin("plugin.serialization") version kotlinVersion
     application
@@ -16,7 +13,8 @@ repositories {
     mavenCentral()
 }
 
-val vertxVersion = "4.3.8"
+val vertxVersion = "4.5.10"
+val kotlinxSerializationVersion = "1.7.3"
 dependencies {
     implementation(platform("io.vertx:vertx-stack-depchain:$vertxVersion"))
     implementation("io.vertx:vertx-web")
@@ -24,15 +22,20 @@ dependencies {
     implementation("io.netty", "netty-transport-native-epoll", classifier = "linux-x86_64")
     implementation("io.vertx:vertx-lang-kotlin")
     implementation("io.vertx:vertx-lang-kotlin-coroutines")
+    runtimeOnly("io.vertx:vertx-io_uring-incubator")
+    // This dependency has to be added for io_uring to work.
+    runtimeOnly("io.netty.incubator:netty-incubator-transport-native-io_uring:0.0.25.Final:linux-x86_64")
 
-    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
-    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
-    implementation("org.jetbrains.kotlinx:kotlinx-html:0.8.0")
-    //implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
-}
+    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
 
-tasks.withType<KotlinCompile> {
-    compilerOptions.jvmTarget.set(JvmTarget.JVM_17)
+    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializationVersion")
+    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-io:$kotlinxSerializationVersion")
+    implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.5.4")
+
+    implementation("org.jetbrains.kotlinx:kotlinx-html:0.11.0")
+    //implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") // the latest version is 0.6.1
 }
 
+kotlin.jvmToolchain(21)
+
 application.mainClass.set("MainKt")

BIN
frameworks/Kotlin/vertx-web-kotlinx/gradle/wrapper/gradle-wrapper.jar


+ 2 - 1
frameworks/Kotlin/vertx-web-kotlinx/gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,7 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
 networkTimeout=10000
+validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

+ 21 - 13
frameworks/Kotlin/vertx-web-kotlinx/gradlew

@@ -15,6 +15,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+# SPDX-License-Identifier: Apache-2.0
+#
 
 ##############################################################################
 #
@@ -55,7 +57,7 @@
 #       Darwin, MinGW, and NonStop.
 #
 #   (3) This script is generated from the Groovy template
-#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
 #       within the Gradle project.
 #
 #       You can find Gradle at https://github.com/gradle/gradle/.
@@ -83,10 +85,9 @@ done
 # This is normally unused
 # shellcheck disable=SC2034
 APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
+' "$PWD" ) || exit
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 MAX_FD=maximum
@@ -133,10 +134,13 @@ location of your Java installation."
     fi
 else
     JAVACMD=java
-    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+    if ! command -v java >/dev/null 2>&1
+    then
+        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 
 Please set the JAVA_HOME variable in your environment to match the
 location of your Java installation."
+    fi
 fi
 
 # Increase the maximum file descriptors if we can.
@@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
     case $MAX_FD in #(
       max*)
         # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC3045
+        # shellcheck disable=SC2039,SC3045
         MAX_FD=$( ulimit -H -n ) ||
             warn "Could not query maximum file descriptor limit"
     esac
@@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
       '' | soft) :;; #(
       *)
         # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC3045
+        # shellcheck disable=SC2039,SC3045
         ulimit -n "$MAX_FD" ||
             warn "Could not set maximum file descriptor limit to $MAX_FD"
     esac
@@ -197,11 +201,15 @@ if "$cygwin" || "$msys" ; then
     done
 fi
 
-# Collect all arguments for the java command;
-#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-#     shell script including quotes and variable substitutions, so put them in
-#     double quotes to make sure that they get re-expanded; and
-#   * put everything else in single quotes, so that it's not re-expanded.
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+#     and any embedded shellness will be escaped.
+#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+#     treated as '${Hostname}' itself on the command line.
 
 set -- \
         "-Dorg.gradle.appname=$APP_BASE_NAME" \

+ 12 - 10
frameworks/Kotlin/vertx-web-kotlinx/gradlew.bat

@@ -13,6 +13,8 @@
 @rem See the License for the specific language governing permissions and
 @rem limitations under the License.
 @rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
 
 @if "%DEBUG%"=="" @echo off
 @rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
 %JAVA_EXE% -version >NUL 2>&1
 if %ERRORLEVEL% equ 0 goto execute
 
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
 
 goto fail
 
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 
 if exist "%JAVA_EXE%" goto execute
 
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
 
 goto fail
 

+ 47 - 0
frameworks/Kotlin/vertx-web-kotlinx/src/main/kotlin/KotlinxIo.kt

@@ -0,0 +1,47 @@
+import io.vertx.core.streams.WriteStream
+import io.vertx.kotlin.coroutines.coAwait
+import kotlinx.coroutines.runBlocking
+import kotlinx.io.RawSink
+import kotlinx.io.readByteArray
+import io.vertx.core.buffer.Buffer as VertxBuffer
+import kotlinx.io.Buffer as KotlinxIoBuffer
+
+@Suppress("NOTHING_TO_INLINE")
+private inline fun Long.toIntOrThrow(): Int {
+    require(this in Int.MIN_VALUE.toLong()..Int.MAX_VALUE.toLong())
+    return toInt()
+}
+
+@JvmInline
+value class VertxBufferWriteStreamRawSink(val writeStream: WriteStream<VertxBuffer>) : RawSink {
+    override fun write(source: KotlinxIoBuffer, byteCount: Long) {
+        runBlocking {
+            writeStream.write(VertxBuffer.buffer(source.readByteArray(byteCount.toIntOrThrow()))).coAwait()
+        }
+    }
+
+    override fun flush() {}
+
+    override fun close() {
+        writeStream.end()
+    }
+}
+
+// not used currently
+fun WriteStream<VertxBuffer>.toRawSink(): RawSink =
+    VertxBufferWriteStreamRawSink(this)
+
+
+@JvmInline
+value class VertxBufferRawSink(val vertxBuffer: VertxBuffer) : RawSink {
+    override fun write(source: KotlinxIoBuffer, byteCount: Long) {
+        vertxBuffer.appendBytes(source.readByteArray(byteCount.toIntOrThrow()))
+    }
+
+    override fun flush() {}
+
+    override fun close() {}
+}
+
+fun VertxBuffer.toRawSink(): RawSink =
+    VertxBufferRawSink(this)

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

@@ -2,10 +2,10 @@ import io.vertx.core.Vertx
 import io.vertx.core.impl.cpu.CpuCoreSensor
 import io.vertx.kotlin.core.deploymentOptionsOf
 import io.vertx.kotlin.core.vertxOptionsOf
-import io.vertx.kotlin.coroutines.await
+import io.vertx.kotlin.coroutines.coAwait
 import java.util.logging.Logger
 
-const val SERVER_NAME = "Vert.x-Web Kotlinx Benchmark"
+const val SERVER_NAME = "Vert.x-Web Kotlinx Benchmark server"
 
 val logger = Logger.getLogger("Vert.x-Web Kotlinx Benchmark")
 suspend fun main(args: Array<String>) {
@@ -19,6 +19,6 @@ suspend fun main(args: Array<String>) {
         it.printStackTrace()
     }
     vertx.deployVerticle({ MainVerticle(hasDb) }, deploymentOptionsOf(instances = CpuCoreSensor.availableProcessors()))
-        .await()
+        .coAwait()
     logger.info("$SERVER_NAME started.")
 }

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

@@ -1,4 +1,5 @@
 import io.netty.channel.unix.Errors.NativeIoException
+import io.vertx.core.buffer.Buffer
 import io.vertx.core.http.HttpHeaders
 import io.vertx.core.http.HttpServer
 import io.vertx.core.http.HttpServerRequest
@@ -7,39 +8,28 @@ import io.vertx.ext.web.Route
 import io.vertx.ext.web.Router
 import io.vertx.ext.web.RoutingContext
 import io.vertx.kotlin.core.http.httpServerOptionsOf
+import io.vertx.kotlin.coroutines.CoroutineRouterSupport
 import io.vertx.kotlin.coroutines.CoroutineVerticle
-import io.vertx.kotlin.coroutines.await
+import io.vertx.kotlin.coroutines.coAwait
 import io.vertx.kotlin.pgclient.pgConnectOptionsOf
 import io.vertx.pgclient.PgConnection
 import io.vertx.sqlclient.PreparedQuery
 import io.vertx.sqlclient.Row
 import io.vertx.sqlclient.RowSet
 import io.vertx.sqlclient.Tuple
-import kotlinx.coroutines.*
+import kotlinx.coroutines.Dispatchers
 import kotlinx.html.*
 import kotlinx.html.stream.appendHTML
+import kotlinx.io.buffered
+import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.Serializable
-import kotlinx.serialization.encodeToString
+import kotlinx.serialization.SerializationStrategy
 import kotlinx.serialization.json.Json
-import java.net.SocketException
+import kotlinx.serialization.json.io.encodeToSink
 import java.time.ZonedDateTime
 import java.time.format.DateTimeFormatter
 
-class MainVerticle(val hasDb: Boolean) : CoroutineVerticle() {
-    inline fun Route.checkedCoroutineHandlerUnconfined(crossinline requestHandler: suspend (RoutingContext) -> Unit): Route =
-        handler { ctx ->
-            /* Some conclusions from the Plaintext test results with trailing `await()`s:
-               1. `launch { /*...*/ }` < `launch(start = CoroutineStart.UNDISPATCHED) { /*...*/ }` < `launch(Dispatchers.Unconfined) { /*...*/ }`.
-               1. `launch { /*...*/ }` without `context` or `start` lead to `io.netty.channel.StacklessClosedChannelException` and `io.netty.channel.unix.Errors$NativeIoException: sendAddress(..) failed: Connection reset by peer`. */
-            launch(Dispatchers.Unconfined) {
-                try {
-                    requestHandler(ctx)
-                } catch (t: Throwable) {
-                    ctx.fail(t)
-                }
-            }
-        }
-
+class MainVerticle(val hasDb: Boolean) : CoroutineVerticle(), CoroutineRouterSupport {
     // `PgConnection`s as used in the "vertx" portion offers better performance than `PgPool`s.
     lateinit var pgConnection: PgConnection
     lateinit var date: String
@@ -68,7 +58,7 @@ class MainVerticle(val hasDb: Boolean) : CoroutineVerticle() {
                     cachePreparedStatements = true,
                     pipeliningLimit = 100000
                 )
-            ).await()
+            ).coAwait()
 
             selectWorldQuery = pgConnection.preparedQuery(SELECT_WORLD_SQL)
             selectFortuneQuery = pgConnection.preparedQuery(SELECT_FORTUNE_SQL)
@@ -81,15 +71,19 @@ class MainVerticle(val hasDb: Boolean) : CoroutineVerticle() {
             .requestHandler(Router.router(vertx).apply { routes() })
             .exceptionHandler {
                 // wrk resets the connections when benchmarking is finished.
-                if ((it is NativeIoException && it.message == "recvAddress(..) failed: Connection reset by peer")
-                    || (it is SocketException && it.message == "Connection reset")
+                if (
+                // for epoll
+                /*(it is NativeIoException && it.message == "recvAddress(..) failed: Connection reset by peer")
+                || (it is SocketException && it.message == "Connection reset")*/
+                // for io_uring
+                    it is NativeIoException && it.message == "io_uring read(..) failed: Connection reset by peer"
                 )
                     return@exceptionHandler
 
                 logger.info("Exception in HttpServer: $it")
                 it.printStackTrace()
             }
-            .listen().await()
+            .listen().coAwait()
     }
 
 
@@ -110,14 +104,46 @@ class MainVerticle(val hasDb: Boolean) : CoroutineVerticle() {
         putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
     }
 
-    inline fun <reified T : Any> Route.jsonResponseHandler(crossinline requestHandler: suspend (RoutingContext) -> @Serializable T) =
-        checkedCoroutineHandlerUnconfined {
+
+    fun Route.coHandlerUnconfined(requestHandler: suspend (RoutingContext) -> Unit): Route =
+        /* Some conclusions from the Plaintext test results with trailing `await()`s:
+           1. `launch { /*...*/ }` < `launch(start = CoroutineStart.UNDISPATCHED) { /*...*/ }` < `launch(Dispatchers.Unconfined) { /*...*/ }`.
+           1. `launch { /*...*/ }` without `context` or `start` lead to `io.netty.channel.StacklessClosedChannelException` and `io.netty.channel.unix.Errors$NativeIoException: sendAddress(..) failed: Connection reset by peer`. */
+        coHandler(Dispatchers.Unconfined, requestHandler)
+
+    inline fun <reified T : Any> Route.jsonResponseCoHandler(
+        serializer: SerializationStrategy<T>,
+        crossinline requestHandler: suspend (RoutingContext) -> @Serializable T
+    ) =
+        coHandlerUnconfined {
             it.response().run {
                 putJsonResponseHeader()
-                end(Json.encodeToString(requestHandler(it)))/*.await()*/
+
+                /*
+                // approach 1
+                end(Json.encodeToString(serializer, requestHandler(it)))/*.coAwait()*/
+                */
+
+                /*
+                // approach 2
+                // 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.
+                toRawSink().buffered().use { bufferedSink ->
+                    @OptIn(ExperimentalSerializationApi::class)
+                    Json.encodeToSink(serializer, requestHandler(it), bufferedSink)
+                }
+                */
+
+                // approach 3
+                end(Buffer.buffer().apply {
+                    toRawSink().buffered().use { bufferedSink ->
+                        @OptIn(ExperimentalSerializationApi::class)
+                        Json.encodeToSink(serializer, requestHandler(it), bufferedSink)
+                    }
+                })
             }
         }
 
+
     suspend fun selectRandomWorlds(queries: Int): List<World> {
         val rowSets = List(queries) {
             selectWorldQuery.execute(Tuple.of(randomIntBetween1And10000()))
@@ -126,23 +152,23 @@ class MainVerticle(val hasDb: Boolean) : CoroutineVerticle() {
     }
 
     fun Router.routes() {
-        get("/json").jsonResponseHandler {
+        get("/json").jsonResponseCoHandler(Serializers.message) {
             jsonSerializationMessage
         }
 
-        get("/db").jsonResponseHandler {
-            val rowSet = selectWorldQuery.execute(Tuple.of(randomIntBetween1And10000())).await()
+        get("/db").jsonResponseCoHandler(Serializers.world) {
+            val rowSet = selectWorldQuery.execute(Tuple.of(randomIntBetween1And10000())).coAwait()
             rowSet.single().toWorld()
         }
 
-        get("/queries").jsonResponseHandler {
+        get("/queries").jsonResponseCoHandler(Serializers.worlds) {
             val queries = it.request().getQueries()
             selectRandomWorlds(queries)
         }
 
-        get("/fortunes").checkedCoroutineHandlerUnconfined {
+        get("/fortunes").coHandlerUnconfined {
             val fortunes = mutableListOf<Fortune>()
-            selectFortuneQuery.execute().await()
+            selectFortuneQuery.execute().coAwait()
                 .mapTo(fortunes) { it.toFortune() }
 
             fortunes.add(Fortune(0, "Additional fortune added at request time."))
@@ -173,11 +199,11 @@ class MainVerticle(val hasDb: Boolean) : CoroutineVerticle() {
             it.response().run {
                 putCommonHeaders()
                 putHeader(HttpHeaders.CONTENT_TYPE, "text/html; charset=utf-8")
-                end(htmlString)/*.await()*/
+                end(htmlString)/*.coAwait()*/
             }
         }
 
-        get("/updates").jsonResponseHandler {
+        get("/updates").jsonResponseCoHandler(Serializers.worlds) {
             val queries = it.request().getQueries()
             val worlds = selectRandomWorlds(queries)
             val updatedWorlds = worlds.map { it.copy(randomNumber = randomIntBetween1And10000()) }
@@ -185,7 +211,7 @@ class MainVerticle(val hasDb: Boolean) : CoroutineVerticle() {
             // Approach 1
             // The updated worlds need to be sorted first to avoid deadlocks.
             updateWordQuery
-                .executeBatch(updatedWorlds.sortedBy { it.id }.map { Tuple.of(it.randomNumber, it.id) }).await()
+                .executeBatch(updatedWorlds.sortedBy { it.id }.map { Tuple.of(it.randomNumber, it.id) }).coAwait()
 
             /*
             // Approach 2, worse performance
@@ -197,11 +223,11 @@ class MainVerticle(val hasDb: Boolean) : CoroutineVerticle() {
             updatedWorlds
         }
 
-        get("/plaintext").checkedCoroutineHandlerUnconfined {
+        get("/plaintext").coHandlerUnconfined {
             it.response().run {
                 putCommonHeaders()
                 putHeader(HttpHeaders.CONTENT_TYPE, "text/plain")
-                end("Hello, World!")/*.await()*/
+                end("Hello, World!")/*.coAwait()*/
             }
         }
     }

+ 7 - 0
frameworks/Kotlin/vertx-web-kotlinx/src/main/kotlin/Serializers.kt

@@ -0,0 +1,7 @@
+import kotlinx.serialization.serializer
+
+object Serializers {
+    val message = serializer<Message>()
+    val world = serializer<World>()
+    val worlds = serializer<List<World>>()
+}

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

@@ -1,6 +1,5 @@
-import io.vertx.core.CompositeFuture
 import io.vertx.core.Future
-import io.vertx.kotlin.coroutines.await
+import io.vertx.kotlin.coroutines.coAwait
 
 suspend fun <T> List<Future<T>>.awaitAll(): List<T> =
-    CompositeFuture.all(this).await().list()
+    Future.all(this).coAwait().list()

+ 3 - 4
frameworks/Kotlin/vertx-web-kotlinx/vertx-web-kotlinx-postgresql.dockerfile

@@ -1,12 +1,11 @@
-FROM gradle:8.0-jdk17
+FROM gradle:8.10.2-jdk21
 
 WORKDIR /vertx-web-kotlinx
 COPY build.gradle.kts build.gradle.kts
 COPY settings.gradle.kts settings.gradle.kts
 COPY gradle.properties gradle.properties
 COPY src src
-RUN gradle assembleDist
-RUN tar -xf build/distributions/vertx-web-kotlinx-benchmark.tar
+RUN gradle --no-daemon installDist
 
 EXPOSE 8080
 
@@ -25,4 +24,4 @@ CMD export JAVA_OPTS=" \
     -Dio.netty.buffer.checkBounds=false \
     -Dio.netty.buffer.checkAccessible=false \
     " && \
-    vertx-web-kotlinx-benchmark/bin/vertx-web-kotlinx-benchmark true
+    build/install/vertx-web-kotlinx-benchmark/bin/vertx-web-kotlinx-benchmark true

+ 3 - 4
frameworks/Kotlin/vertx-web-kotlinx/vertx-web-kotlinx.dockerfile

@@ -1,12 +1,11 @@
-FROM gradle:8.0-jdk17
+FROM gradle:8.10.2-jdk21
 
 WORKDIR /vertx-web-kotlinx
 COPY build.gradle.kts build.gradle.kts
 COPY settings.gradle.kts settings.gradle.kts
 COPY gradle.properties gradle.properties
 COPY src src
-RUN gradle assembleDist
-RUN tar -xf build/distributions/vertx-web-kotlinx-benchmark.tar
+RUN gradle --no-daemon installDist
 
 EXPOSE 8080
 
@@ -25,4 +24,4 @@ CMD export JAVA_OPTS=" \
     -Dio.netty.buffer.checkBounds=false \
     -Dio.netty.buffer.checkAccessible=false \
     " && \
-    vertx-web-kotlinx-benchmark/bin/vertx-web-kotlinx-benchmark false
+    build/install/vertx-web-kotlinx-benchmark/bin/vertx-web-kotlinx-benchmark false