Sfoglia il codice sorgente

Merge pull request #9487 from inemtsev/ktor-r2dbc

[Kotlin] Added r2dbc to Ktor suite
Mike Smith 7 mesi fa
parent
commit
640cd71cb9
28 ha cambiato i file con 709 aggiunte e 50 eliminazioni
  1. 23 0
      frameworks/Kotlin/ktor/benchmark_config.json
  2. 10 5
      frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts
  3. 1 1
      frameworks/Kotlin/ktor/ktor-asyncdb/gradle.properties
  4. 1 2
      frameworks/Kotlin/ktor/ktor-asyncdb/gradle/wrapper/gradle-wrapper.properties
  5. 0 14
      frameworks/Kotlin/ktor/ktor-asyncdb/settings.gradle
  6. 1 0
      frameworks/Kotlin/ktor/ktor-asyncdb/settings.gradle.kts
  7. 3 3
      frameworks/Kotlin/ktor/ktor-cio.dockerfile
  8. 1 1
      frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts
  9. 1 1
      frameworks/Kotlin/ktor/ktor-exposed/gradle/wrapper/gradle-wrapper.properties
  10. 6 4
      frameworks/Kotlin/ktor/ktor-jasync.dockerfile
  11. 3 3
      frameworks/Kotlin/ktor/ktor-jetty.dockerfile
  12. 3 3
      frameworks/Kotlin/ktor/ktor-pgclient.dockerfile
  13. 10 7
      frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts
  14. 1 1
      frameworks/Kotlin/ktor/ktor-pgclient/gradle/wrapper/gradle-wrapper.properties
  15. 13 0
      frameworks/Kotlin/ktor/ktor-r2dbc.dockerfile
  16. 50 0
      frameworks/Kotlin/ktor/ktor-r2dbc/README.md
  17. 174 0
      frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml
  18. 29 0
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/assembly/netty-bundle.xml
  19. 186 0
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt
  20. 123 0
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/NioClientEventLoopResources.java
  21. 6 0
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Fortune.kt
  22. 6 0
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Message.kt
  23. 6 0
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/World.kt
  24. 26 0
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/application.conf
  25. 21 0
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/logback.xml
  26. 3 3
      frameworks/Kotlin/ktor/ktor/README.md
  27. 1 1
      frameworks/Kotlin/ktor/ktor/pom.xml
  28. 1 1
      frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt

+ 23 - 0
frameworks/Kotlin/ktor/benchmark_config.json

@@ -25,6 +25,29 @@
         "notes": "http://ktor.io/",
         "versus": "netty"
       },
+      "r2dbc": {
+        "plaintext_url": "/plaintext",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "fortune_url": "/fortunes",
+
+        "port": 9090,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "ktor",
+        "language": "Kotlin",
+        "orm": "Raw",
+        "platform": "Netty",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "ktor-netty-r2dbc",
+        "notes": "http://ktor.io/",
+        "versus": "netty"
+      },
       "jetty": {
         "plaintext_url": "/plaintext",
         "json_url": "/json",

+ 10 - 5
frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts

@@ -1,6 +1,6 @@
 plugins {
     application
-    kotlin("jvm") version "1.9.22"
+    kotlin("jvm") version "2.0.21"
     kotlin("plugin.serialization") version "2.0.0"
     id("com.github.johnrengelman.shadow") version "8.1.0"
 }
@@ -17,8 +17,7 @@ application {
 }
 
 val ktor_version = "2.3.12"
-val kotlinx_serialization_version = "1.6.3"
-val vertx_pg_client = "4.5.8"
+val kotlinx_serialization_version = "1.7.3"
 
 dependencies {
     implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinx_serialization_version")
@@ -26,11 +25,17 @@ dependencies {
     implementation("io.ktor:ktor-server-netty:$ktor_version")
     implementation("io.ktor:ktor-server-default-headers:$ktor_version")
     implementation("io.ktor:ktor-server-html-builder:$ktor_version")
-    implementation("com.github.jasync-sql:jasync-postgresql:2.2.0")
+    implementation("com.github.jasync-sql:jasync-postgresql:2.2.4")
+}
+
+java {
+    toolchain {
+        languageVersion = JavaLanguageVersion.of(21)
+    }
 }
 
 tasks.shadowJar {
-    archiveBaseName.set("bench")
+    archiveBaseName.set("ktor-asyncdb")
     archiveClassifier.set("")
     archiveVersion.set("")
 }

+ 1 - 1
frameworks/Kotlin/ktor/ktor-asyncdb/gradle.properties

@@ -1,4 +1,4 @@
 kotlin.code.style=official
 
-kotlin_version=1.9.22
+kotlin_version=2.0.21
 ktor_version=2.3.12

+ 1 - 2
frameworks/Kotlin/ktor/ktor-asyncdb/gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,5 @@
-#Fri Dec 07 21:01:17 MST 2018
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip

+ 0 - 14
frameworks/Kotlin/ktor/ktor-asyncdb/settings.gradle

@@ -1,14 +0,0 @@
-pluginManagement {
-    resolutionStrategy {
-        eachPlugin {
-            if (requested.id.id == "org.jetbrains.kotlin.jvm") {
-                useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
-            }
-            if (requested.id.id == "kotlinx-serialization") {
-                useModule("org.jetbrains.kotlin:kotlin-serialization:$kotlin_version")
-            }
-        }
-    }
-}
-
-rootProject.name = 'tech-empower-framework-benchmark'

+ 1 - 0
frameworks/Kotlin/ktor/ktor-asyncdb/settings.gradle.kts

@@ -0,0 +1 @@
+rootProject.name = "tech-empower-framework-benchmark"

+ 3 - 3
frameworks/Kotlin/ktor/ktor-cio.dockerfile

@@ -1,13 +1,13 @@
-FROM maven:3.9.7-amazoncorretto-17-debian as maven
+FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven
 WORKDIR /ktor
 COPY ktor/pom.xml pom.xml
 COPY ktor/src src
 RUN mvn clean package -q
 
-FROM amazoncorretto:17.0.11-al2023-headless
+FROM amazoncorretto:21-al2023-headless
 WORKDIR /ktor
 COPY --from=maven /ktor/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-cio-bundle.jar app.jar
 
 EXPOSE 9090
 
-CMD ["java", "-jar", "app.jar"]
+CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "app.jar"]

+ 1 - 1
frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts

@@ -9,7 +9,7 @@ repositories {
     mavenCentral()
 }
 
-val ktorVersion = "3.0.1"
+val ktorVersion = "3.0.3"
 val kotlinxSerializationVersion = "1.7.3"
 val exposedVersion = "0.56.0"
 

+ 1 - 1
frameworks/Kotlin/ktor/ktor-exposed/gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
 networkTimeout=10000
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

+ 6 - 4
frameworks/Kotlin/ktor/ktor-jasync.dockerfile

@@ -1,13 +1,15 @@
-FROM maven:3.9.7-amazoncorretto-17-debian
+FROM gradle:jdk21 as build
 WORKDIR /app
 COPY ktor-asyncdb/gradle gradle
 COPY ktor-asyncdb/build.gradle.kts build.gradle.kts
-COPY ktor-asyncdb/gradle.properties gradle.properties
 COPY ktor-asyncdb/gradlew gradlew
-COPY ktor-asyncdb/settings.gradle settings.gradle
 COPY ktor-asyncdb/src src
 RUN /app/gradlew --no-daemon shadowJar
 
+FROM amazoncorretto:21-al2023-headless
+WORKDIR /app
+COPY --from=build /app/build/libs/ktor-asyncdb.jar ktor-asyncdb.jar
+
 EXPOSE 9090
 
-CMD ["java", "-server", "-XX:+UseParallelGC", "-Xms2G","-Xmx2G", "-jar", "/app/build/libs/bench.jar", "jasync-sql"]
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "ktor-asyncdb.jar", "jasync-sql"]

+ 3 - 3
frameworks/Kotlin/ktor/ktor-jetty.dockerfile

@@ -1,13 +1,13 @@
-FROM maven:3.9.7-amazoncorretto-17-debian as maven
+FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven
 WORKDIR /ktor
 COPY ktor/pom.xml pom.xml
 COPY ktor/src src
 RUN mvn clean package -q
 
-FROM amazoncorretto:17.0.11-al2023-headless
+FROM amazoncorretto:21-al2023-headless
 WORKDIR /ktor
 COPY --from=maven /ktor/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-jetty-bundle.jar app.jar
 
 EXPOSE 9090
 
-CMD ["java", "-jar", "app.jar"]
+CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "app.jar"]

+ 3 - 3
frameworks/Kotlin/ktor/ktor-pgclient.dockerfile

@@ -1,4 +1,4 @@
-FROM maven:3.9.7-amazoncorretto-17-debian as build
+FROM gradle:jdk21 as build
 WORKDIR /app
 COPY ktor-pgclient/gradle gradle
 COPY ktor-pgclient/build.gradle.kts build.gradle.kts
@@ -6,10 +6,10 @@ COPY ktor-pgclient/gradlew gradlew
 COPY ktor-pgclient/src src
 RUN /app/gradlew --no-daemon shadowJar
 
-FROM amazoncorretto:17.0.11-al2023-headless
+FROM amazoncorretto:21-al2023-headless
 WORKDIR /app
 COPY --from=build /app/build/libs/ktor-pgclient.jar ktor-pgclient.jar
 
 EXPOSE 8080
 
-CMD ["java", "-server", "-XX:MaxRAMFraction=1", "-XX:-UseBiasedLocking", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "ktor-pgclient.jar"]
+CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "ktor-pgclient.jar"]

+ 10 - 7
frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts

@@ -1,6 +1,6 @@
 plugins {
     application
-    kotlin("jvm") version "1.9.22"
+    kotlin("jvm") version "2.0.21"
     kotlin("plugin.serialization") version "2.0.0"
     id("com.github.johnrengelman.shadow") version "8.1.0"
 }
@@ -17,19 +17,22 @@ application {
 }
 
 val ktor_version = "2.3.12"
+val vertx_version = "4.5.11"
 
 dependencies {
-    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
+    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
     implementation("io.ktor:ktor-server-netty:$ktor_version")
     implementation("io.ktor:ktor-server-html-builder-jvm:$ktor_version")
     implementation("io.ktor:ktor-server-default-headers-jvm:$ktor_version")
-    implementation("io.vertx:vertx-pg-client:4.5.8")
-    implementation("io.vertx:vertx-lang-kotlin:4.5.8")
-    implementation("io.vertx:vertx-lang-kotlin-coroutines:4.5.8")
+    implementation("io.vertx:vertx-pg-client:$vertx_version")
+    implementation("io.vertx:vertx-lang-kotlin:$vertx_version")
+    implementation("io.vertx:vertx-lang-kotlin-coroutines:$vertx_version")
 }
 
-tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
-    kotlinOptions.jvmTarget = "17"
+java {
+    toolchain {
+        languageVersion = JavaLanguageVersion.of(21)
+    }
 }
 
 tasks.shadowJar {

+ 1 - 1
frameworks/Kotlin/ktor/ktor-pgclient/gradle/wrapper/gradle-wrapper.properties

@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

+ 13 - 0
frameworks/Kotlin/ktor/ktor-r2dbc.dockerfile

@@ -0,0 +1,13 @@
+FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven
+WORKDIR /ktor-r2dbc
+COPY ktor-r2dbc/pom.xml pom.xml
+COPY ktor-r2dbc/src src
+RUN mvn clean package -q
+
+FROM amazoncorretto:21-al2023-headless
+WORKDIR /ktor-r2dbc
+COPY --from=maven /ktor-r2dbc/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-netty-bundle.jar app.jar
+
+EXPOSE 9090
+
+CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "app.jar"]

+ 50 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/README.md

@@ -0,0 +1,50 @@
+# Ktor
+
+Ktor is a framework for building servers and clients in connected systems using Kotlin programming language.
+More information is available at [ktor.io](http://ktor.io). 
+
+# Setup
+
+* Java 21
+* Postgres server
+
+# Requirements
+
+* Maven 3
+* JDK 21
+* Kotlin
+* ktor
+* netty
+* R2DBC
+
+Maven is downloaded automatically via Maven Wrapper script (`mvnw`), add dependencies are specified in `pom.xml` so will be downloaded automatically from maven central and jcenter repositories.
+
+# Deployment
+
+Run maven to build a bundle
+
+```bash
+./mvnw package
+```
+
+Once bundle build complete and mysql server is running you can launch the application
+
+```bash
+java -jar target/tech-empower-framework-benchmark-1.0-SNAPSHOT.jar
+```
+
+Please note that the server holds tty so you may need nohup. See `setup.sh` for details.
+
+# Contact
+
+[Leonid Stashevsky](https://github.com/e5l)
+
+[Sergey Mashkov](https://github.com/cy6erGn0m)
+
+[Ilya Ryzhenkov](https://github.com/orangy)
+
+[Ilya Nemtsev](https://github.com/inemtsev)
+
+Slack ktor channel https://kotlinlang.slack.com/messages/ktor (you need an [invite](http://slack.kotlinlang.org/) to join)
+
+

+ 174 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml

@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.jetbrains.ktor</groupId>
+    <artifactId>tech-empower-framework-benchmark</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <name>org.jetbrains.ktor tech-empower-framework-benchmark</name>
+
+    <properties>
+        <kotlin.version>2.0.21</kotlin.version>
+        <kotlin.coroutines.version>1.10.1</kotlin.coroutines.version>
+        <ktor.version>3.0.3</ktor.version>
+        <serialization.version>1.7.3</serialization.version>
+        <kotlinx.html.version>0.11.0</kotlinx.html.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <logback.version>1.5.12</logback.version>
+        <reactor.version>3.7.1</reactor.version>
+        <postgresql.version>42.7.4</postgresql.version>
+        <r2dbc.version>1.0.7.RELEASE</r2dbc.version>
+        <r2dbc.pool.version>1.0.2.RELEASE</r2dbc.pool.version>
+
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-reflect</artifactId>
+            <version>${kotlin.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-serialization-core</artifactId>
+            <version>${serialization.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-serialization-json</artifactId>
+            <version>${serialization.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-html-jvm</artifactId>
+            <version>${kotlinx.html.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-coroutines-core</artifactId>
+            <version>${kotlin.coroutines.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-coroutines-reactor</artifactId>
+            <version>${kotlin.coroutines.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.ktor</groupId>
+            <artifactId>ktor-server-default-headers-jvm</artifactId>
+            <version>${ktor.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.ktor</groupId>
+            <artifactId>ktor-server-html-builder-jvm</artifactId>
+            <version>${ktor.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>r2dbc-postgresql</artifactId>
+            <version>${r2dbc.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.r2dbc</groupId>
+            <artifactId>r2dbc-pool</artifactId>
+            <version>${r2dbc.pool.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-core</artifactId>
+            <version>${reactor.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>${logback.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.ktor</groupId>
+            <artifactId>ktor-server-netty-jvm</artifactId>
+            <version>${ktor.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <sourceDirectory>src/main/kotlin</sourceDirectory>
+
+        <plugins>
+            <plugin>
+                <groupId>org.jetbrains.kotlin</groupId>
+                <artifactId>kotlin-maven-plugin</artifactId>
+                <version>${kotlin.version}</version>
+                <executions>
+                    <execution>
+                        <id>compile</id>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>compile</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>test-compile</id>
+                        <phase>test-compile</phase>
+                        <goals>
+                            <goal>test-compile</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <compilerPlugins>
+                        <plugin>kotlinx-serialization</plugin>
+                    </compilerPlugins>
+                </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.jetbrains.kotlin</groupId>
+                        <artifactId>kotlin-maven-serialization</artifactId>
+                        <version>${kotlin.version}</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+            <plugin>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>default-jar</id>
+                        <phase>none</phase>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.0.0</version>
+
+                <executions>
+                    <execution>
+                        <id>netty</id>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+
+                        <phase>package</phase>
+
+                        <configuration>
+                            <descriptors>
+                                <descriptor>src/main/assembly/netty-bundle.xml</descriptor>
+                            </descriptors>
+                            <archive>
+                                <manifest>
+                                    <mainClass>io.ktor.server.netty.EngineMain</mainClass>
+                                </manifest>
+                            </archive>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 29 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/assembly/netty-bundle.xml

@@ -0,0 +1,29 @@
+<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
+    <id>netty-bundle</id>
+
+    <formats>
+        <format>jar</format>
+    </formats>
+
+    <includeBaseDirectory>false</includeBaseDirectory>
+
+    <dependencySets>
+        <dependencySet>
+            <unpack>true</unpack>
+            <scope>runtime</scope>
+
+            <excludes>
+                <exclude>*:ktor-server-jetty</exclude>
+                <exclude>*:ktor-server-cio</exclude>
+            </excludes>
+        </dependencySet>
+    </dependencySets>
+
+    <fileSets>
+        <fileSet>
+            <directory>${project.build.outputDirectory}</directory>
+            <outputDirectory>/</outputDirectory>
+        </fileSet>
+    </fileSets>
+</assembly>

+ 186 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt

@@ -0,0 +1,186 @@
+package org.jetbrains.ktor.benchmarks
+
+import io.ktor.http.*
+import io.ktor.http.content.*
+import io.ktor.server.application.*
+import io.ktor.server.config.*
+import io.ktor.server.html.*
+import io.ktor.server.plugins.defaultheaders.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import io.r2dbc.pool.ConnectionPool
+import io.r2dbc.pool.ConnectionPoolConfiguration
+import io.r2dbc.postgresql.PostgresqlConnectionConfiguration
+import io.r2dbc.postgresql.PostgresqlConnectionFactory
+import io.r2dbc.postgresql.client.SSLMode
+import io.r2dbc.spi.Connection
+import io.r2dbc.spi.ConnectionFactory
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.reactive.awaitFirst
+import kotlinx.coroutines.reactive.awaitFirstOrNull
+import kotlinx.html.*
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import org.jetbrains.ktor.benchmarks.Constants.DB_ROWS
+import org.jetbrains.ktor.benchmarks.Constants.FORTUNES_QUERY
+import org.jetbrains.ktor.benchmarks.Constants.UPDATE_QUERY
+import org.jetbrains.ktor.benchmarks.Constants.WORLD_QUERY
+import org.jetbrains.ktor.benchmarks.models.Fortune
+import org.jetbrains.ktor.benchmarks.models.Message
+import org.jetbrains.ktor.benchmarks.models.World
+import reactor.core.publisher.Flux
+import reactor.core.publisher.Mono
+import kotlin.random.Random
+
+fun Application.main() {
+    val config = ApplicationConfig("application.conf")
+    val dbConnFactory = configurePostgresR2DBC(config)
+
+    install(DefaultHeaders)
+
+    val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain)
+
+    routing {
+        get("/plaintext") {
+            call.respond(helloWorldContent)
+        }
+
+        get("/json") {
+            call.respondText(Json.encodeToString(Message("Hello, world!")), ContentType.Application.Json)
+        }
+
+        get("/db") {
+            val random = Random.Default
+            val request = getWorld(dbConnFactory, random)
+            val result = request.awaitFirstOrNull()
+
+            call.respondText(Json.encodeToString(result), ContentType.Application.Json)
+        }
+
+        fun selectWorlds(queries: Int, random: Random): Flow<World> = flow {
+            repeat(queries) {
+                emit(getWorld(dbConnFactory, random).awaitFirst())
+            }
+        }
+
+        get("/queries") {
+            val queries = call.queries()
+            val random = Random.Default
+
+            val result = buildList {
+                selectWorlds(queries, random).collect {
+                    add(it)
+                }
+            }
+
+            call.respondText(Json.encodeToString(result), ContentType.Application.Json)
+        }
+
+        get("/fortunes") {
+            val result = mutableListOf<Fortune>()
+
+            val request = Flux.usingWhen(dbConnFactory.create(), { connection ->
+                Flux.from(connection.createStatement(FORTUNES_QUERY).execute()).flatMap { r ->
+                    Flux.from(r.map { row, _ ->
+                        Fortune(
+                            row.get(0, Int::class.java)!!, row.get(1, String::class.java)!!
+                        )
+                    })
+                }
+            }, { connection -> connection.close() })
+
+            request.collectList().awaitFirstOrNull()?.let { result.addAll(it) }
+
+            result.add(Fortune(0, "Additional fortune added at request time."))
+            result.sortBy { it.message }
+            call.respondHtml {
+                head { title { +"Fortunes" } }
+                body {
+                    table {
+                        tr {
+                            th { +"id" }
+                            th { +"message" }
+                        }
+                        for (fortune in result) {
+                            tr {
+                                td { +fortune.id.toString() }
+                                td { +fortune.message }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        get("/updates") {
+            val queries = call.queries()
+            val random = Random.Default
+
+            val worlds = selectWorlds(queries, random)
+
+            val worldsUpdated = buildList {
+                worlds.collect { world ->
+                    world.randomNumber = random.nextInt(DB_ROWS) + 1
+                    add(world)
+
+                    Mono.usingWhen(dbConnFactory.create(), { connection ->
+                        Mono.from(
+                            connection.createStatement(UPDATE_QUERY)
+                                .bind(0, world.randomNumber)
+                                .bind(1, world.id)
+                                .execute()
+                        ).flatMap { Mono.from(it.rowsUpdated) }
+                    }, Connection::close).awaitFirstOrNull()
+                }
+            }
+
+            call.respondText(Json.encodeToString(worldsUpdated), ContentType.Application.Json)
+        }
+    }
+}
+
+private fun getWorld(
+    dbConnFactory: ConnectionFactory, random: Random
+): Mono<World> = Mono.usingWhen(dbConnFactory.create(), { connection ->
+    Mono.from(connection.createStatement(WORLD_QUERY).bind(0, random.nextInt(DB_ROWS) + 1).execute()).flatMap { r ->
+        Mono.from(r.map { row, _ ->
+            World(
+                row.get(0, Int::class.java)!!, row.get(1, Int::class.java)!!
+            )
+        })
+    }
+}, Connection::close)
+
+private fun configurePostgresR2DBC(config: ApplicationConfig): ConnectionFactory {
+    val cfo = PostgresqlConnectionConfiguration.builder()
+        .host(config.property("db.host").getString())
+        .port(config.property("db.port").getString().toInt())
+        .database(config.property("db.database").getString())
+        .username(config.property("db.username").getString())
+        .password(config.property("db.password").getString())
+        .loopResources { NioClientEventLoopResources(Runtime.getRuntime().availableProcessors()).cacheLoops() }
+        .sslMode(SSLMode.DISABLE)
+        .tcpKeepAlive(true)
+        .tcpNoDelay(true)
+        .build()
+
+    val cf = PostgresqlConnectionFactory(cfo)
+
+    val cp = ConnectionPoolConfiguration.builder(cf)
+        .initialSize(config.property("db.initPoolSize").getString().toInt())
+        .maxSize(config.property("db.maxPoolSize").getString().toInt())
+        .build()
+
+    return ConnectionPool(cp)
+}
+
+private fun ApplicationCall.queries() = request.queryParameters["queries"]?.toIntOrNull()?.coerceIn(1, 500) ?: 1
+
+
+object Constants {
+    const val WORLD_QUERY = "SELECT id, randomnumber FROM world WHERE id = $1"
+    const val FORTUNES_QUERY = "SELECT id, message FROM fortune"
+    const val UPDATE_QUERY = "UPDATE world SET randomnumber = $1 WHERE id = $2"
+    const val DB_ROWS = 10000
+}

+ 123 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/NioClientEventLoopResources.java

@@ -0,0 +1,123 @@
+package org.jetbrains.ktor.benchmarks;
+
+import io.netty.channel.Channel;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.DatagramChannel;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioDatagramChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.util.concurrent.DefaultThreadFactory;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.ThreadPerTaskExecutor;
+import reactor.core.publisher.Mono;
+import reactor.netty.FutureMono;
+import reactor.netty.resources.LoopResources;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Copied from GitHub issue comment: https://github.com/r2dbc/r2dbc-pool/issues/190#issuecomment-1566845190
+ */
+public class NioClientEventLoopResources implements LoopResources {
+    public static final String THREAD_PREFIX = "prefix-";
+    final int threads;
+    final AtomicReference<EventLoopGroup> loops = new AtomicReference<>();
+    final AtomicBoolean running;
+
+    NioClientEventLoopResources(int threads) {
+        this.running = new AtomicBoolean(true);
+        this.threads = threads;
+    }
+
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Mono<Void> disposeLater(Duration quietPeriod, Duration timeout) {
+        return Mono.defer(() -> {
+        long quietPeriodMillis = quietPeriod.toMillis();
+        long timeoutMillis = timeout.toMillis();
+        EventLoopGroup serverLoopsGroup = loops.get();
+        Mono<?> slMono = Mono.empty();
+        if (running.compareAndSet(true, false)) {
+            if (serverLoopsGroup != null) {
+                slMono = FutureMono.from((Future) serverLoopsGroup.shutdownGracefully(
+                        quietPeriodMillis, timeoutMillis, TimeUnit.MILLISECONDS));
+            }
+        }
+        return Mono.when(slMono);
+    });
+    }
+
+    @Override
+    public boolean isDisposed() {
+        return !running.get();
+    }
+
+    @Override
+    public EventLoopGroup onClient(boolean useNative) {
+        return cacheLoops();
+    }
+
+    @Override
+    public EventLoopGroup onServer(boolean useNative) {
+        throw new UnsupportedOperationException("This event loop is designed only for client DB calls.");
+    }
+
+    @Override
+    public EventLoopGroup onServerSelect(boolean useNative) {
+        throw new UnsupportedOperationException("This event loop is designed only for client DB calls.");
+    }
+
+    @Override
+    public <CHANNEL extends Channel> CHANNEL onChannel(Class<CHANNEL> channelType, EventLoopGroup group) {
+        if (channelType.equals(SocketChannel.class)) {
+                return (CHANNEL) new NioSocketChannel();
+            }
+            if (channelType.equals(ServerSocketChannel.class)) {
+                    return (CHANNEL) new NioServerSocketChannel();
+                }
+                if (channelType.equals(DatagramChannel.class)) {
+                        return (CHANNEL) new NioDatagramChannel();
+                    }
+                    throw new IllegalArgumentException("Unsupported channel type: " + channelType.getSimpleName());
+    }
+
+    @Override
+    public <CHANNEL extends Channel> Class<? extends CHANNEL> onChannelClass(Class<CHANNEL> channelType,
+        EventLoopGroup group) {
+        if (channelType.equals(SocketChannel.class)) {
+                return (Class<? extends CHANNEL>) NioSocketChannel.class;
+            }
+            if (channelType.equals(ServerSocketChannel.class)) {
+                    return (Class<? extends CHANNEL>) NioServerSocketChannel.class;
+                }
+                if (channelType.equals(DatagramChannel.class)) {
+                        return (Class<? extends CHANNEL>) NioDatagramChannel.class;
+                    }
+                    throw new IllegalArgumentException("Unsupported channel type: " + channelType.getSimpleName());
+    }
+
+    @SuppressWarnings("FutureReturnValueIgnored")
+    EventLoopGroup cacheLoops() {
+        EventLoopGroup eventLoopGroup = loops.get();
+        if (null == eventLoopGroup) {
+            EventLoopGroup newEventLoopGroup = createNewEventLoopGroup();
+            if (!loops.compareAndSet(null, newEventLoopGroup)) {
+                //"FutureReturnValueIgnored" this is deliberate
+                newEventLoopGroup.shutdownGracefully(0, 0, TimeUnit.MILLISECONDS);
+            }
+            eventLoopGroup = cacheLoops();
+        }
+        return eventLoopGroup;
+    }
+
+    private NioEventLoopGroup createNewEventLoopGroup() {
+        return new NioEventLoopGroup(threads, new ThreadPerTaskExecutor(new DefaultThreadFactory(THREAD_PREFIX)));
+    }
+}

+ 6 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Fortune.kt

@@ -0,0 +1,6 @@
+package org.jetbrains.ktor.benchmarks.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class Fortune(val id: Int, var message: String)

+ 6 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Message.kt

@@ -0,0 +1,6 @@
+package org.jetbrains.ktor.benchmarks.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class Message(val message: String)

+ 6 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/World.kt

@@ -0,0 +1,6 @@
+package org.jetbrains.ktor.benchmarks.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class World(val id: Int, var randomNumber: Int)

+ 26 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/application.conf

@@ -0,0 +1,26 @@
+ktor {
+    deployment {
+        port = 9090
+        autoreload = false
+        watch = [ ]
+        shareWorkGroup = true
+    }
+
+    application {
+        modules = [ org.jetbrains.ktor.benchmarks.HelloKt.main ]
+    }
+}
+
+db {
+    driver = "pool"
+    protocol = "postgresql"
+    ssl = "false"
+    host = "tfb-database"
+    port = 5432
+    database = "hello_world"
+    initPoolSize = 512
+    maxPoolSize = 512
+    username = "benchmarkdbuser"
+    password = "benchmarkdbpass"
+    //url = "r2dbc:postgresql://"${db.host}":"${db.port}"/"${db.database}"?loggerLevel=OFF&disableColumnSanitiser=true&assumeMinServerVersion=16&sslmode=disable&maxSize="${db.poolSize}
+}

+ 21 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/logback.xml

@@ -0,0 +1,21 @@
+<configuration>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+     <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
+         <neverBlock>true</neverBlock>
+         <appender-ref ref="STDOUT" />
+     </appender>
+
+
+    <root level="INFO">
+        <appender-ref ref="ASYNC"/>
+    </root>
+
+    <logger name="org.eclipse.jetty" level="INFO"/>
+    <logger name="io.netty" level="INFO"/>
+
+</configuration>

+ 3 - 3
frameworks/Kotlin/ktor/ktor/README.md

@@ -5,13 +5,13 @@ More information is available at [ktor.io](http://ktor.io).
 
 # Setup
 
-* Java 17
-* MySQL server
+* Java 21
+* Postgres server
 
 # Requirements
 
 * Maven 3
-* JDK 17
+* JDK 21
 * Kotlin
 * ktor
 * netty 

+ 1 - 1
frameworks/Kotlin/ktor/ktor/pom.xml

@@ -13,7 +13,7 @@
 
     <properties>
         <kotlin.version>2.0.21</kotlin.version>
-        <ktor.version>3.0.2</ktor.version>
+        <ktor.version>3.0.3</ktor.version>
         <serialization.version>1.7.3</serialization.version>
         <kotlinx.html.version>0.11.0</kotlinx.html.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

+ 1 - 1
frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt

@@ -38,7 +38,7 @@ fun Application.main() {
 
     install(DefaultHeaders)
 
-    val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain).also { it.contentLength }
+    val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain)
 
     routing {
         get("/plaintext") {