Browse Source

update ktor to user Gradle (#10365)

* update ktor to user Gradle

* fix r2dbc update

* improve updates

* use pipelining of r2dbc to improve perf

* fixed and optimized pgclient

* fixed jettyf
Ilya Nemtsev 1 week ago
parent
commit
c7591cfa34
28 changed files with 990 additions and 590 deletions
  1. 1 1
      frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts
  2. 4 5
      frameworks/Kotlin/ktor/ktor-cio.dockerfile
  3. 15 2
      frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts
  4. 23 26
      frameworks/Kotlin/ktor/ktor-exposed/app/src/main/kotlin/App.kt
  5. 4 5
      frameworks/Kotlin/ktor/ktor-jetty.dockerfile
  6. 18 5
      frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts
  7. 1 1
      frameworks/Kotlin/ktor/ktor-pgclient/gradle/wrapper/gradle-wrapper.properties
  8. 43 24
      frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/main.kt
  9. 4 5
      frameworks/Kotlin/ktor/ktor-r2dbc.dockerfile
  10. 5 7
      frameworks/Kotlin/ktor/ktor-r2dbc/README.md
  11. 99 0
      frameworks/Kotlin/ktor/ktor-r2dbc/build.gradle.kts
  12. BIN
      frameworks/Kotlin/ktor/ktor-r2dbc/gradle/wrapper/gradle-wrapper.jar
  13. 5 0
      frameworks/Kotlin/ktor/ktor-r2dbc/gradle/wrapper/gradle-wrapper.properties
  14. 172 0
      frameworks/Kotlin/ktor/ktor-r2dbc/gradlew
  15. 84 0
      frameworks/Kotlin/ktor/ktor-r2dbc/gradlew.bat
  16. 0 179
      frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml
  17. 2 0
      frameworks/Kotlin/ktor/ktor-r2dbc/settings.gradle.kts
  18. 83 72
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt
  19. 4 5
      frameworks/Kotlin/ktor/ktor.dockerfile
  20. 5 7
      frameworks/Kotlin/ktor/ktor/README.md
  21. 108 0
      frameworks/Kotlin/ktor/ktor/build.gradle.kts
  22. BIN
      frameworks/Kotlin/ktor/ktor/gradle/wrapper/gradle-wrapper.jar
  23. 5 0
      frameworks/Kotlin/ktor/ktor/gradle/wrapper/gradle-wrapper.properties
  24. 172 0
      frameworks/Kotlin/ktor/ktor/gradlew
  25. 84 0
      frameworks/Kotlin/ktor/ktor/gradlew.bat
  26. 0 213
      frameworks/Kotlin/ktor/ktor/pom.xml
  27. 2 0
      frameworks/Kotlin/ktor/ktor/settings.gradle.kts
  28. 47 33
      frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/main.kt

+ 1 - 1
frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts

@@ -2,7 +2,7 @@ plugins {
     application
     kotlin("jvm") version "2.0.21"
     kotlin("plugin.serialization") version "2.0.0"
-    id("com.github.johnrengelman.shadow") version "8.1.0"
+    id("com.gradleup.shadow") version "8.3.9"
 }
 
 group = "org.jetbrains.ktor"

+ 4 - 5
frameworks/Kotlin/ktor/ktor-cio.dockerfile

@@ -1,12 +1,11 @@
-FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven
+FROM gradle:8.13-jdk21 AS build
 WORKDIR /ktor
-COPY ktor/pom.xml pom.xml
-COPY ktor/src src
-RUN mvn clean package -q
+COPY ktor/ ./
+RUN chmod +x gradlew && ./gradlew --no-daemon clean cioBundle
 
 FROM amazoncorretto:21-al2023-headless
 WORKDIR /ktor
-COPY --from=maven /ktor/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-cio-bundle.jar app.jar
+COPY --from=build /ktor/build/libs/tech-empower-framework-benchmark-1.0-SNAPSHOT-cio-bundle.jar app.jar
 
 EXPOSE 9090
 

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

@@ -1,15 +1,18 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
 plugins {
     application
     kotlin("jvm") version "2.1.21"
     kotlin("plugin.serialization") version "2.1.21"
-    id("com.github.johnrengelman.shadow") version "8.1.0"
+    id("com.gradleup.shadow") version "8.3.9"
 }
 
 repositories {
     mavenCentral()
 }
 
-val ktorVersion = "3.1.3"
+val ktorVersion = "3.3.3"
 val kotlinxSerializationVersion = "1.8.1"
 val exposedVersion = "0.61.0"
 
@@ -31,3 +34,13 @@ dependencies {
 }
 
 application.mainClass.set("AppKt")
+
+kotlin {
+    jvmToolchain(21)
+}
+
+tasks.withType<KotlinCompile>().configureEach {
+    compilerOptions {
+        jvmTarget.set(JvmTarget.JVM_21)
+    }
+}

+ 23 - 26
frameworks/Kotlin/ktor/ktor-exposed/app/src/main/kotlin/App.kt

@@ -10,7 +10,6 @@ import io.ktor.server.plugins.defaultheaders.*
 import io.ktor.server.response.*
 import io.ktor.server.routing.*
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
 import kotlinx.html.*
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.json.Json
@@ -22,8 +21,9 @@ import org.jetbrains.exposed.sql.Database
 import org.jetbrains.exposed.sql.ResultRow
 import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
 import org.jetbrains.exposed.sql.Transaction
-import org.jetbrains.exposed.sql.transactions.transaction
-import org.jetbrains.exposed.sql.update
+import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
+import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
+import org.jetbrains.exposed.sql.transactions.TransactionManager
 import java.util.concurrent.ThreadLocalRandom
 
 @Serializable
@@ -73,12 +73,12 @@ fun main(args: Array<String>) {
 }
 
 fun Application.module(exposedMode: ExposedMode) {
-    val dbRows = 10000
-    val poolSize = 48
+    val poolSize = Runtime.getRuntime().availableProcessors() * 2
     val pool = HikariDataSource(HikariConfig().apply { configurePostgres(poolSize) })
-    Database.connect(pool)
-    suspend fun <T> withDatabaseContextAndTransaction(statement: Transaction.() -> T) =
-        withContext(Dispatchers.IO) { transaction(statement = statement) }
+    val database = Database.connect(pool)
+    val databaseDispatcher = Dispatchers.IO.limitedParallelism(poolSize)
+    suspend fun <T> withDatabaseTransaction(statement: suspend Transaction.() -> T) =
+        newSuspendedTransaction(context = databaseDispatcher, db = database, statement = statement)
 
     install(DefaultHeaders)
 
@@ -93,7 +93,7 @@ fun Application.module(exposedMode: ExposedMode) {
             Fortune(this[FortuneTable.id].value, this[FortuneTable.message])
 
         fun ThreadLocalRandom.nextIntWithinRows() =
-            nextInt(dbRows) + 1
+            nextInt(DB_ROWS) + 1
 
         fun selectSingleWorld(random: ThreadLocalRandom): World =
             selectWorldsWithIdQuery(random.nextIntWithinRows()).single().toWorld()
@@ -103,7 +103,7 @@ fun Application.module(exposedMode: ExposedMode) {
 
         get("/db") {
             val random = ThreadLocalRandom.current()
-            val result = withDatabaseContextAndTransaction {
+            val result = withDatabaseTransaction {
                 when (exposedMode) {
                     Dsl -> selectSingleWorld(random)
                     Dao -> WorldDao[random.nextIntWithinRows()].toWorld()
@@ -117,7 +117,7 @@ fun Application.module(exposedMode: ExposedMode) {
             val queries = call.queries()
             val random = ThreadLocalRandom.current()
 
-            val result = withDatabaseContextAndTransaction {
+            val result = withDatabaseTransaction {
                 when (exposedMode) {
                     Dsl -> selectWorlds(queries, random)
                     Dao -> //List(queries) { WorldDao[random.nextIntWithinRows()].toWorld() }
@@ -129,7 +129,7 @@ fun Application.module(exposedMode: ExposedMode) {
         }
 
         get("/fortunes") {
-            val result = withDatabaseContextAndTransaction {
+            val result = withDatabaseTransaction {
                 when (exposedMode) {
                     Dsl -> FortuneTable.select(FortuneTable.id, FortuneTable.message)
                         .asSequence().map { it.toFortune() }
@@ -164,23 +164,17 @@ fun Application.module(exposedMode: ExposedMode) {
             val random = ThreadLocalRandom.current()
             lateinit var result: List<World>
 
-            withDatabaseContextAndTransaction {
+            withDatabaseTransaction {
                 when (exposedMode) {
                     Dsl -> {
                         result = selectWorlds(queries, random)
-                        result.forEach { it.randomNumber = random.nextInt(dbRows) + 1 }
-                        result
-                            // sort the data to avoid data race because all updates are in one transaction
-                            .sortedBy { it.id }
-                            .forEach { world ->
-                                WorldTable.update({ WorldTable.id eq world.id }) {
-                                    it[randomNumber] = world.randomNumber
-                                }
-                                /*
-                                // An alternative approach: commit every change to avoid data race
-                                commit()
-                                */
-                            }
+                        result.forEach { it.randomNumber = random.nextIntWithinRows() }
+                        val batch = BatchUpdateStatement(WorldTable)
+                        result.sortedBy { it.id }.forEach { world ->
+                            batch.addBatch(EntityID(world.id, WorldTable))
+                            batch[WorldTable.randomNumber] = world.randomNumber
+                        }
+                        batch.execute(TransactionManager.current())
                     }
 
                     Dao -> /*{
@@ -202,6 +196,8 @@ fun Application.module(exposedMode: ExposedMode) {
     }
 }
 
+private const val DB_ROWS = 10_000
+
 fun HikariConfig.configurePostgres(poolSize: Int) {
     jdbcUrl = "jdbc:postgresql://tfb-database/hello_world?useSSL=false"
     driverClassName = org.postgresql.Driver::class.java.name
@@ -224,3 +220,4 @@ fun HikariConfig.configureCommon(poolSize: Int) {
 
 fun ApplicationCall.queries() =
     request.queryParameters["queries"]?.toIntOrNull()?.coerceIn(1, 500) ?: 1
+

+ 4 - 5
frameworks/Kotlin/ktor/ktor-jetty.dockerfile

@@ -1,12 +1,11 @@
-FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven
+FROM gradle:8.13-jdk21 AS build
 WORKDIR /ktor
-COPY ktor/pom.xml pom.xml
-COPY ktor/src src
-RUN mvn clean package -q
+COPY ktor/ ./
+RUN chmod +x gradlew && ./gradlew --no-daemon clean jettyBundle
 
 FROM amazoncorretto:21-al2023-headless
 WORKDIR /ktor
-COPY --from=maven /ktor/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-jetty-bundle.jar app.jar
+COPY --from=build /ktor/build/libs/tech-empower-framework-benchmark-1.0-SNAPSHOT-jetty-bundle.jar app.jar
 
 EXPOSE 9090
 

+ 18 - 5
frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts

@@ -1,8 +1,11 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
 plugins {
     application
-    kotlin("jvm") version "2.0.21"
-    kotlin("plugin.serialization") version "2.0.0"
-    id("com.github.johnrengelman.shadow") version "8.1.0"
+    kotlin("jvm") version "2.1.21"
+    kotlin("plugin.serialization") version "2.1.21"
+    id("com.gradleup.shadow") version "8.3.9"
 }
 
 group = "org.jetbrains.ktor"
@@ -16,8 +19,8 @@ application {
     mainClass = "io.ktor.server.netty.EngineMain"
 }
 
-val ktor_version = "3.1.2"
-val vertx_version = "4.5.11"
+val ktor_version = "3.3.3"
+val vertx_version = "5.0.5"
 
 dependencies {
     implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1")
@@ -36,6 +39,16 @@ java {
     }
 }
 
+kotlin {
+    jvmToolchain(21)
+}
+
+tasks.withType<KotlinCompile>().configureEach {
+    compilerOptions {
+        jvmTarget.set(JvmTarget.JVM_21)
+    }
+}
+
 tasks.shadowJar {
     archiveBaseName.set("ktor-pgclient")
     archiveClassifier.set("")

+ 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.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

+ 43 - 24
frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/main.kt

@@ -1,3 +1,5 @@
+import io.ktor.http.ContentType
+import io.ktor.http.content.TextContent
 import io.ktor.server.application.*
 import io.ktor.server.html.*
 import io.ktor.server.plugins.defaultheaders.*
@@ -8,14 +10,23 @@ import io.vertx.pgclient.PgBuilder
 import io.vertx.pgclient.PgConnectOptions
 import io.vertx.sqlclient.PoolOptions
 import io.vertx.sqlclient.Tuple
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
 import kotlinx.html.*
 import java.util.concurrent.ThreadLocalRandom
 
 val rand: ThreadLocalRandom
     get() = ThreadLocalRandom.current()
 
+private const val HELLO_WORLD = "Hello, World!"
+private const val WORLD_ROWS = 10_000
+
+private fun nextWorldId(): Int = rand.nextInt(1, WORLD_ROWS + 1)
+
 interface Repository {
     suspend fun getWorld(): World
+    suspend fun getWorlds(count: Int): List<World>
     suspend fun getFortunes(): List<Fortune>
     suspend fun updateWorlds(worlds: List<World>)
 }
@@ -23,8 +34,8 @@ interface Repository {
 class PgclientRepository : Repository {
     companion object {
         private const val FORTUNES_QUERY = "select id, message from FORTUNE"
-        private const val SELECT_WORLD_QUERY = "SELECT id, randomnumber from WORLD where id=$1"
-        private const val UPDATE_WORLD_QUERY = "UPDATE WORLD SET randomnumber=$1 WHERE id=$2"
+        private const val SELECT_WORLD_QUERY = "SELECT id, randomnumber from WORLD where id=\$1"
+        private const val UPDATE_WORLD_QUERY = "UPDATE WORLD SET randomnumber=\$1 WHERE id=\$2"
     }
 
     private val connectOptions =
@@ -38,35 +49,43 @@ class PgclientRepository : Repository {
             pipeliningLimit = 100000
         }
 
+    private val poolSize = Runtime.getRuntime().availableProcessors() * 2
     private val poolOptions = PoolOptions()
+        .setMaxSize(poolSize)
+        .setMaxWaitQueueSize(poolSize * 2)
     private val client = PgBuilder.client()
         .with(poolOptions)
         .connectingTo(connectOptions)
         .build()
 
+    private val selectWorldStatement = client.preparedQuery(SELECT_WORLD_QUERY)
+    private val updateWorldStatement = client.preparedQuery(UPDATE_WORLD_QUERY)
+    private val fortunesStatement = client.preparedQuery(FORTUNES_QUERY)
+
     override suspend fun getFortunes(): List<Fortune> {
-        val results = client.preparedQuery(FORTUNES_QUERY).execute().coAwait()
+        val results = fortunesStatement.execute().coAwait()
         return results.map { Fortune(it.getInteger(0), it.getString(1)) }
     }
 
-    override suspend fun getWorld(): World {
-        val worldId = rand.nextInt(1, 10001)
-        val result =
-            client
-                .preparedQuery(SELECT_WORLD_QUERY)
-                .execute(Tuple.of(worldId))
-                .coAwait()
+    override suspend fun getWorld(): World =
+        getWorlds(1).first()
+
+    override suspend fun getWorlds(count: Int): List<World> = coroutineScope {
+        List(count) {
+            async { fetchWorld(nextWorldId()) }
+        }.awaitAll()
+    }
+
+    private suspend fun fetchWorld(id: Int): World {
+        val result = selectWorldStatement.execute(Tuple.of(id)).coAwait()
         val row = result.first()
         return World(row.getInteger(0), row.getInteger(1)!!)
     }
 
     override suspend fun updateWorlds(worlds: List<World>) {
-        // Worlds should be sorted before being batch-updated with to avoid data race and deadlocks.
+        if (worlds.isEmpty()) return
         val batch = worlds.sortedBy { it.id }.map { Tuple.of(it.randomNumber, it.id) }
-        client
-            .preparedQuery(UPDATE_WORLD_QUERY)
-            .executeBatch(batch)
-            .coAwait()
+        updateWorldStatement.executeBatch(batch).coAwait()
     }
 }
 
@@ -115,11 +134,11 @@ fun Application.main() {
     install(DefaultHeaders)
     routing {
         get("/plaintext") {
-            call.respondText("Hello, World!")
+            call.respond(TextContent(HELLO_WORLD, ContentType.Text.Plain))
         }
 
         get("/json") {
-            call.respondJson(Message("Hello, World!"))
+            call.respondJson(Message(HELLO_WORLD))
         }
 
         get("/db") {
@@ -128,7 +147,7 @@ fun Application.main() {
 
         get("/query") {
             val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
-            val worlds = List(queries) { db.getWorld() }
+            val worlds = db.getWorlds(queries)
             call.respondJson(worlds)
         }
 
@@ -142,12 +161,12 @@ fun Application.main() {
 
         get("/updates") {
             val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
-            val worlds = List(queries) { db.getWorld() }
-            val newWorlds = worlds.map { it.copy(randomNumber = rand.nextInt(1, 10001)) }
-
-            db.updateWorlds(newWorlds)
-
-            call.respondJson(newWorlds)
+            val worlds = db.getWorlds(queries).map { world ->
+                world.randomNumber = nextWorldId()
+                world
+            }
+            db.updateWorlds(worlds)
+            call.respondJson(worlds)
         }
     }
 }

+ 4 - 5
frameworks/Kotlin/ktor/ktor-r2dbc.dockerfile

@@ -1,12 +1,11 @@
-FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven
+FROM gradle:8.13-jdk21 AS build
 WORKDIR /ktor-r2dbc
-COPY ktor-r2dbc/pom.xml pom.xml
-COPY ktor-r2dbc/src src
-RUN mvn clean package -q
+COPY ktor-r2dbc/ ./
+RUN chmod +x gradlew && ./gradlew --no-daemon clean nettyBundle
 
 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
+COPY --from=build /ktor-r2dbc/build/libs/tech-empower-framework-benchmark-1.0-SNAPSHOT-netty-bundle.jar app.jar
 
 EXPOSE 9090
 

+ 5 - 7
frameworks/Kotlin/ktor/ktor-r2dbc/README.md

@@ -10,27 +10,25 @@ More information is available at [ktor.io](http://ktor.io).
 
 # Requirements
 
-* Maven 3
 * JDK 21
+* Gradle (wrapper provided)
 * 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
+Use the Gradle wrapper to build the executable bundle:
 
 ```bash
-./mvnw package
+./gradlew nettyBundle
 ```
 
-Once bundle build complete and mysql server is running you can launch the application
+Once the bundle build completes and Postgres is running you can launch the application
 
 ```bash
-java -jar target/tech-empower-framework-benchmark-1.0-SNAPSHOT.jar
+java -jar build/libs/tech-empower-framework-benchmark-1.0-SNAPSHOT-netty-bundle.jar
 ```
 
 Please note that the server holds tty so you may need nohup. See `setup.sh` for details.

+ 99 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/build.gradle.kts

@@ -0,0 +1,99 @@
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+    kotlin("jvm") version "2.1.21"
+    kotlin("plugin.serialization") version "2.1.21"
+    id("com.gradleup.shadow") version "8.3.9"
+}
+
+group = "org.jetbrains.ktor"
+version = "1.0-SNAPSHOT"
+
+val ktorVersion = "3.3.3"
+val serializationVersion = "1.8.1"
+val kotlinxHtmlVersion = "0.12.0"
+val coroutinesVersion = "1.10.1"
+val logbackVersion = "1.5.13"
+val reactorVersion = "3.8.0"
+val r2dbcPstgrsVersion = "1.1.1.RELEASE"
+val r2dbcPoolVersion = "1.0.2.RELEASE"
+val postgresqlVersion = "42.7.5"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation(kotlin("reflect"))
+
+    implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
+    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
+    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-io:$serializationVersion")
+    implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:$kotlinxHtmlVersion")
+
+    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
+    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$coroutinesVersion")
+
+    implementation("io.ktor:ktor-server-default-headers-jvm:$ktorVersion")
+    implementation("io.ktor:ktor-server-html-builder-jvm:$ktorVersion")
+    implementation("io.ktor:ktor-server-netty-jvm:$ktorVersion")
+
+    implementation("org.postgresql:r2dbc-postgresql:$r2dbcPstgrsVersion")
+    implementation("io.r2dbc:r2dbc-pool:$r2dbcPoolVersion")
+    implementation("io.projectreactor:reactor-core:$reactorVersion")
+
+    implementation("ch.qos.logback:logback-classic:$logbackVersion")
+}
+
+sourceSets {
+    main {
+        java.srcDirs("src/main/kotlin")
+    }
+}
+
+kotlin {
+    jvmToolchain(21)
+}
+
+tasks.withType<KotlinCompile>().configureEach {
+    compilerOptions {
+        jvmTarget.set(JvmTarget.JVM_21)
+    }
+}
+
+tasks.named<ShadowJar>("shadowJar") {
+    enabled = false
+}
+
+fun registerBundle(
+    name: String,
+    classifier: String,
+    mainClass: String
+) = tasks.register(name, ShadowJar::class) {
+    archiveBaseName.set("tech-empower-framework-benchmark")
+    archiveVersion.set(project.version.toString())
+    archiveClassifier.set(classifier)
+    manifest {
+        attributes["Main-Class"] = mainClass
+    }
+    from(sourceSets.main.get().output)
+    configurations = listOf(project.configurations.runtimeClasspath.get())
+}
+
+val nettyBundle by registerBundle(
+    name = "nettyBundle",
+    classifier = "netty-bundle",
+    mainClass = "io.ktor.server.netty.EngineMain"
+)
+
+tasks.register("bundleAll") {
+    description = "Builds the runnable Netty uber-jar."
+    dependsOn(nettyBundle)
+}
+
+tasks.named("build") {
+    dependsOn(nettyBundle)
+}
+

BIN
frameworks/Kotlin/ktor/ktor-r2dbc/gradle/wrapper/gradle-wrapper.jar


+ 5 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/gradle/wrapper/gradle-wrapper.properties

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

+ 172 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+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.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/gradlew.bat

@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+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.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+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.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

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

@@ -1,179 +0,0 @@
-<?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.1.21</kotlin.version>
-        <kotlin.coroutines.version>1.10.1</kotlin.coroutines.version>
-        <ktor.version>3.1.3</ktor.version>
-        <serialization.version>1.8.1</serialization.version>
-        <kotlinx.html.version>0.12.0</kotlinx.html.version>
-        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-        <logback.version>1.5.13</logback.version>
-        <reactor.version>3.7.1</reactor.version>
-        <postgresql.version>42.7.5</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-serialization-json-io</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.7.1</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>

+ 2 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/settings.gradle.kts

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

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

@@ -15,15 +15,15 @@ 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.coroutines.reactor.awaitSingle
 import kotlinx.html.*
 import reactor.core.publisher.Flux
 import reactor.core.publisher.Mono
 import java.time.Duration
-import kotlin.random.Random
+import java.util.concurrent.ThreadLocalRandom
+import kotlin.math.min
 
 const val HELLO_WORLD = "Hello, World!"
 const val WORLD_QUERY = "SELECT id, randomnumber FROM world WHERE id = $1"
@@ -36,7 +36,6 @@ fun Application.main() {
     val dbConnFactory = configurePostgresR2DBC(config)
 
     val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain)
-    val random = Random.Default
 
     install(DefaultHeaders)
 
@@ -50,44 +49,18 @@ fun Application.main() {
         }
 
         get("/db") {
-            val request = getWorld(dbConnFactory, random)
-            val result = request.awaitFirstOrNull()
-
-            call.respondJson(result)
-        }
-
-        fun selectWorlds(queries: Int, random: Random): Flow<World> = flow {
-            repeat(queries) {
-                emit(getWorld(dbConnFactory, random).awaitFirst())
-            }
+            val world = dbConnFactory.fetchWorld()
+            call.respondJson(world)
         }
 
         get("/queries") {
             val queries = call.queries()
-
-            val result = buildList {
-                selectWorlds(queries, random).collect {
-                    add(it)
-                }
-            }
-
-            call.respondJson(result)
+            val worlds = dbConnFactory.fetchWorlds(queries)
+            call.respondJson(worlds)
         }
 
         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) }
+            val result = dbConnFactory.fetchFortunes().toMutableList()
 
             result.add(Fortune(0, "Additional fortune added at request time."))
             result.sortBy { it.message }
@@ -113,47 +86,85 @@ fun Application.main() {
         get("/updates") {
             val queries = call.queries()
 
-            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.respondJson(worldsUpdated)
+            val worlds = dbConnFactory.fetchWorlds(queries)
+            val updatedWorlds = worlds.map {
+                it.copy(randomNumber = ThreadLocalRandom.current().nextInt(1, DB_ROWS + 1))
+            }.sortedBy { it.id }
+
+            Mono.usingWhen(dbConnFactory.create(), { connection ->
+                Mono.from(connection.beginTransaction())
+                    .thenMany(
+                        Flux.fromIterable(updatedWorlds)
+                            .concatMap { world ->
+                                Mono.from(
+                                    connection.createStatement(UPDATE_QUERY)
+                                        .bind("$1", world.randomNumber)
+                                        .bind("$2", world.id)
+                                        .execute()
+                                ).flatMap { Mono.from(it.rowsUpdated) }
+                            }
+                    )
+                    .then(Mono.from(connection.commitTransaction()))
+            },
+                Connection::close,
+                { connection, _ -> connection.rollbackTransaction() },
+                { connection -> connection.rollbackTransaction() }
+            ).awaitFirstOrNull()
+
+            call.respondJson(updatedWorlds)
         }
     }
 }
 
-private fun getWorld(
-    dbConnFactory: ConnectionFactory, random: Random
-): Mono<World> = Mono.usingWhen(dbConnFactory.create(), { connection ->
-    Mono.from(connection.createStatement(WORLD_QUERY)
-        .bind("$1", random.nextInt(DB_ROWS) + 1)
-        .execute())
-        .flatMap { r ->
-            Mono.from(r.map { row, _ ->
-                val id = row.get(0, Int::class.java)
-                val randomNumber = row.get(1, Int::class.java)
-                if (id != null && randomNumber != null) {
-                    World(id, randomNumber)
-                } else {
-                    throw IllegalStateException("Database returned null values for required fields")
-                }
-            })
-        }
-}, Connection::close)
+private suspend fun ConnectionFactory.fetchWorld(): World =
+    Mono.usingWhen(create(), { connection ->
+        selectWorld(connection)
+    }, Connection::close).awaitSingle()
+
+private suspend fun ConnectionFactory.fetchWorlds(
+    count: Int
+): List<World> {
+    if (count <= 0) return emptyList()
+    val concurrency = min(count, 32)
+    return Mono.usingWhen(create(), { connection ->
+        Flux.range(0, count)
+            .flatMap({ selectWorldPublisher(connection) }, concurrency)
+            .collectList()
+    }, Connection::close).awaitSingle()
+}
+
+private fun selectWorld(connection: Connection): Mono<World> =
+    selectWorldPublisher(connection)
+
+private fun selectWorldPublisher(connection: Connection): Mono<World> {
+    val worldId = ThreadLocalRandom.current().nextInt(1, DB_ROWS + 1)
+    return Mono.from(
+        connection.createStatement(WORLD_QUERY)
+            .bind("$1", worldId)
+            .execute()
+    ).flatMap { result ->
+        Mono.from(result.map { row, _ ->
+            World(
+                row.get(0, Int::class.java) ?: error("id is null"),
+                row.get(1, Int::class.java) ?: error("randomNumber is null")
+            )
+        })
+    }
+}
+
+private suspend fun ConnectionFactory.fetchFortunes(): List<Fortune> =
+    Mono.usingWhen(create(), { connection ->
+        Flux.from(connection.createStatement(FORTUNES_QUERY).execute())
+            .flatMap { result ->
+                Flux.from(result.map { row, _ ->
+                    Fortune(
+                        row.get(0, Int::class.java) ?: error("id is null"),
+                        row.get(1, String::class.java) ?: error("message is null")
+                    )
+                })
+            }
+            .collectList()
+    }, Connection::close).awaitSingle()
 
 private fun configurePostgresR2DBC(config: ApplicationConfig): ConnectionFactory {
     val cfo = PostgresqlConnectionConfiguration.builder()

+ 4 - 5
frameworks/Kotlin/ktor/ktor.dockerfile

@@ -1,12 +1,11 @@
-FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven
+FROM gradle:8.13-jdk21 AS build
 WORKDIR /ktor
-COPY ktor/pom.xml pom.xml
-COPY ktor/src src
-RUN mvn clean package -q
+COPY ktor/ ./
+RUN chmod +x gradlew && ./gradlew --no-daemon clean nettyBundle
 
 FROM amazoncorretto:21-al2023-headless
 WORKDIR /ktor
-COPY --from=maven /ktor/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-netty-bundle.jar app.jar
+COPY --from=build /ktor/build/libs/tech-empower-framework-benchmark-1.0-SNAPSHOT-netty-bundle.jar app.jar
 
 EXPOSE 9090
 

+ 5 - 7
frameworks/Kotlin/ktor/ktor/README.md

@@ -10,27 +10,25 @@ More information is available at [ktor.io](http://ktor.io).
 
 # Requirements
 
-* Maven 3
 * JDK 21
+* Gradle (wrapper provided)
 * Kotlin
 * ktor
 * netty 
 * hikariCP
 
-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
+Use the Gradle wrapper to assemble the desired runnable bundle (Netty shown below).
 
 ```bash
-./mvnw package
+./gradlew nettyBundle
 ```
 
-Once bundle build complete and mysql server is running you can launch the application
+Once the bundle build completes and Postgres is running you can launch the application
 
 ```bash
-java -jar target/tech-empower-framework-benchmark-1.0-SNAPSHOT.jar
+java -jar build/libs/tech-empower-framework-benchmark-1.0-SNAPSHOT-netty-bundle.jar
 ```
 
 Please note that the server holds tty so you may need nohup. See `setup.sh` for details.

+ 108 - 0
frameworks/Kotlin/ktor/ktor/build.gradle.kts

@@ -0,0 +1,108 @@
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+    kotlin("jvm") version "2.1.21"
+    kotlin("plugin.serialization") version "2.1.21"
+    id("com.gradleup.shadow") version "8.3.9"
+}
+
+group = "org.jetbrains.ktor"
+version = "1.0-SNAPSHOT"
+
+val ktorVersion = "3.3.3"
+val serializationVersion = "1.8.1"
+val kotlinxHtmlVersion = "0.12.0"
+val hikariVersion = "5.1.0"
+val logbackVersion = "1.5.13"
+val mysqlVersion = "8.0.33"
+val postgresqlVersion = "42.7.5"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation(kotlin("stdlib"))
+    implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
+    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
+    implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:$kotlinxHtmlVersion")
+
+    implementation("io.ktor:ktor-server-default-headers-jvm:$ktorVersion")
+    implementation("io.ktor:ktor-server-html-builder-jvm:$ktorVersion")
+    implementation("io.ktor:ktor-server-cio-jvm:$ktorVersion")
+    implementation("io.ktor:ktor-server-netty-jvm:$ktorVersion")
+    implementation("io.ktor:ktor-server-jetty-jvm:$ktorVersion")
+
+    implementation("com.zaxxer:HikariCP:$hikariVersion")
+    implementation("ch.qos.logback:logback-classic:$logbackVersion")
+
+    implementation("org.postgresql:postgresql:$postgresqlVersion")
+    implementation("mysql:mysql-connector-java:$mysqlVersion")
+}
+
+kotlin {
+    jvmToolchain(21)
+}
+
+tasks.withType<KotlinCompile>().configureEach {
+    compilerOptions {
+        jvmTarget.set(JvmTarget.JVM_21)
+    }
+}
+
+tasks.named<ShadowJar>("shadowJar") {
+    enabled = false
+}
+
+fun registerBundle(
+    name: String,
+    classifier: String,
+    mainClass: String,
+    exclusions: List<String>
+) = tasks.register(name, ShadowJar::class) {
+    archiveBaseName.set("tech-empower-framework-benchmark")
+    archiveVersion.set(project.version.toString())
+    archiveClassifier.set(classifier)
+    manifest {
+        attributes["Main-Class"] = mainClass
+    }
+    from(sourceSets.main.get().output)
+    configurations = listOf(project.configurations.runtimeClasspath.get())
+    dependencies {
+        exclusions.forEach { exclude(dependency(it)) }
+    }
+    mergeServiceFiles()
+}
+
+val nettyBundle by registerBundle(
+    name = "nettyBundle",
+    classifier = "netty-bundle",
+    mainClass = "io.ktor.server.netty.EngineMain",
+    exclusions = listOf("io.ktor:ktor-server-jetty.*", "io.ktor:ktor-server-cio.*")
+)
+
+val jettyBundle by registerBundle(
+    name = "jettyBundle",
+    classifier = "jetty-bundle",
+    mainClass = "io.ktor.server.jetty.EngineMain",
+    exclusions = listOf("io.ktor:ktor-server-netty.*", "io.ktor:ktor-server-cio.*")
+)
+
+val cioBundle by registerBundle(
+    name = "cioBundle",
+    classifier = "cio-bundle",
+    mainClass = "io.ktor.server.cio.EngineMain",
+    exclusions = listOf("io.ktor:ktor-server-netty.*", "io.ktor:ktor-server-jetty.*")
+)
+
+tasks.register("bundleAll") {
+    description = "Builds all runnable uber-jars (CIO, Jetty, Netty)."
+    dependsOn(nettyBundle, jettyBundle, cioBundle)
+}
+
+tasks.named("build") {
+    dependsOn("bundleAll")
+}
+

BIN
frameworks/Kotlin/ktor/ktor/gradle/wrapper/gradle-wrapper.jar


+ 5 - 0
frameworks/Kotlin/ktor/ktor/gradle/wrapper/gradle-wrapper.properties

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

+ 172 - 0
frameworks/Kotlin/ktor/ktor/gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+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.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
frameworks/Kotlin/ktor/ktor/gradlew.bat

@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+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.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+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.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 0 - 213
frameworks/Kotlin/ktor/ktor/pom.xml

@@ -1,213 +0,0 @@
-<?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.1.21</kotlin.version>
-        <ktor.version>3.1.3</ktor.version>
-        <serialization.version>1.8.1</serialization.version>
-        <kotlinx.html.version>0.12.0</kotlinx.html.version>
-        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-        <hikaricp.version>5.1.0</hikaricp.version>
-        <logback.version>1.5.13</logback.version>
-        <mysql.version>8.0.33</mysql.version>
-        <postgresql.version>42.7.5</postgresql.version>
-    </properties>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.jetbrains.kotlin</groupId>
-            <artifactId>kotlin-stdlib</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>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>com.zaxxer</groupId>
-            <artifactId>HikariCP</artifactId>
-            <version>${hikaricp.version}</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.postgresql</groupId>
-            <artifactId>postgresql</artifactId>
-            <version>${postgresql.version}</version>
-        </dependency>
-
-        <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
-            <version>${mysql.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>
-        <dependency>
-            <groupId>io.ktor</groupId>
-            <artifactId>ktor-server-jetty-jvm</artifactId>
-            <version>${ktor.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>io.ktor</groupId>
-            <artifactId>ktor-server-cio-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.7.1</version>
-
-                <executions>
-                    <execution>
-                        <id>cio</id>
-                        <goals>
-                            <goal>single</goal>
-                        </goals>
-
-                        <phase>package</phase>
-
-                        <configuration>
-                            <descriptors>
-                                <descriptor>src/main/assembly/cio-bundle.xml</descriptor>
-                            </descriptors>
-                            <archive>
-                                <manifest>
-                                    <mainClass>io.ktor.server.cio.EngineMain</mainClass>
-                                </manifest>
-                            </archive>
-                        </configuration>
-                    </execution>
-                    <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>
-                    <execution>
-                        <id>jetty</id>
-                        <goals>
-                            <goal>single</goal>
-                        </goals>
-
-                        <phase>package</phase>
-
-                        <configuration>
-                            <descriptors>
-                                <descriptor>src/main/assembly/jetty-bundle.xml</descriptor>
-                            </descriptors>
-                            <archive>
-                                <manifest>
-                                    <mainClass>io.ktor.server.jetty.EngineMain</mainClass>
-                                </manifest>
-                            </archive>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-        </plugins>
-    </build>
-</project>

+ 2 - 0
frameworks/Kotlin/ktor/ktor/settings.gradle.kts

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

+ 47 - 33
frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/main.kt

@@ -13,9 +13,9 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.withContext
 import kotlinx.html.*
-import java.sql.Connection
 import java.util.StringJoiner
-import kotlin.random.Random
+import java.util.concurrent.ThreadLocalRandom
+import kotlinx.coroutines.CoroutineDispatcher
 
 const val HELLO_WORLD = "Hello, World!"
 const val WORLD_QUERY = "SELECT id, randomNumber FROM World WHERE id = ?"
@@ -30,7 +30,6 @@ fun Application.main() {
     // Create a dedicated dispatcher for database operations
     val databaseDispatcher = Dispatchers.IO.limitedParallelism(poolSize)
     val helloWorldContent = TextContent(HELLO_WORLD, ContentType.Text.Plain)
-    val random = Random.Default
 
     install(DefaultHeaders)
 
@@ -44,36 +43,13 @@ fun Application.main() {
         }
 
         get("/db") {
-            val world = withContext(databaseDispatcher) {
-                pool.connection.use { connection ->
-                    connection.prepareStatement(WORLD_QUERY).use { statement ->
-                        statement.setInt(1, random.nextInt(DB_ROWS) + 1)
-                        statement.executeQuery().use { rs ->
-                            rs.next()
-                            World(rs.getInt(1), rs.getInt(2))
-                        }
-                    }
-                }
-            }
+            val world = fetchWorld(pool, databaseDispatcher)
             call.respondJson(world)
         }
 
-        fun Connection.selectWorlds(queries: Int): Array<World> =
-            prepareStatement(WORLD_QUERY).use { statement ->
-                Array<World>(queries) { i ->
-                    statement.setInt(1, random.nextInt(DB_ROWS) + 1)
-                    statement.executeQuery().use { rs ->
-                        rs.next()
-                        World(rs.getInt(1), rs.getInt(2))
-                    }
-                }
-            }
-
         get("/queries") {
             val queries = call.queries()
-            val result = withContext(databaseDispatcher) {
-                pool.connection.use { it.selectWorlds(queries) }
-            }
+            val result = fetchWorlds(pool, queries, databaseDispatcher)
             call.respondJson(result)
         }
 
@@ -113,20 +89,18 @@ fun Application.main() {
 
         get("/updates") {
             val queries = call.queries()
-            val result: Array<World>
+            val result = fetchWorlds(pool, queries, databaseDispatcher)
 
             withContext(databaseDispatcher) {
                 pool.connection.use { connection ->
-                    result = connection.selectWorlds(queries)
-
                     val updateSql = StringJoiner(
                         ", ",
                         "UPDATE World SET randomNumber = temp.randomNumber FROM (VALUES ",
                         " ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = World.id"
                     )
 
-                    for (i in result.indices) {
-                        result[i].randomNumber = random.nextInt(DB_ROWS) + 1
+                    for (world in result) {
+                        world.randomNumber = ThreadLocalRandom.current().nextInt(1, DB_ROWS + 1)
                         updateSql.add("(?, ?)")
                     }
 
@@ -146,6 +120,46 @@ fun Application.main() {
     }
 }
 
+suspend fun fetchWorld(
+    pool: HikariDataSource,
+    dispatcher: CoroutineDispatcher
+): World = withContext(dispatcher) {
+    pool.connection.use { connection ->
+        fetchWorld(connection)
+    }
+}
+
+private fun fetchWorld(connection: java.sql.Connection): World =
+    connection.prepareStatement(WORLD_QUERY).use { statement ->
+        statement.setInt(1, ThreadLocalRandom.current().nextInt(1, DB_ROWS + 1))
+        statement.executeQuery().use { rs ->
+            rs.next()
+            World(rs.getInt(1), rs.getInt(2))
+        }
+    }
+
+suspend fun fetchWorlds(
+    pool: HikariDataSource,
+    queries: Int,
+    dispatcher: CoroutineDispatcher
+): Array<World> = withContext(dispatcher) {
+    if (queries <= 0) {
+        emptyArray()
+    } else {
+        pool.connection.use { connection ->
+            connection.prepareStatement(WORLD_QUERY).use { statement ->
+                Array(queries) {
+                    statement.setInt(1, ThreadLocalRandom.current().nextInt(1, DB_ROWS + 1))
+                    statement.executeQuery().use { rs ->
+                        rs.next()
+                        World(rs.getInt(1), rs.getInt(2))
+                    }
+                }
+            }
+        }
+    }
+}
+
 
 fun ApplicationCall.queries() =
     request.queryParameters["queries"]?.toIntOrNull()?.coerceIn(1, 500) ?: 1