diff --git a/frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts b/frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts index 0622963101a..382ad0c83f9 100644 --- a/frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts +++ b/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" diff --git a/frameworks/Kotlin/ktor/ktor-cio.dockerfile b/frameworks/Kotlin/ktor/ktor-cio.dockerfile index 7443aed952f..aaa05c4659b 100644 --- a/frameworks/Kotlin/ktor/ktor-cio.dockerfile +++ b/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 diff --git a/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts b/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts index 1ce1ba7f1fb..5f29b997834 100644 --- a/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts +++ b/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().configureEach { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} diff --git a/frameworks/Kotlin/ktor/ktor-exposed/app/src/main/kotlin/App.kt b/frameworks/Kotlin/ktor/ktor-exposed/app/src/main/kotlin/App.kt index 50ffd37975d..4b4bf93741d 100644 --- a/frameworks/Kotlin/ktor/ktor-exposed/app/src/main/kotlin/App.kt +++ b/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) { } 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 withDatabaseContextAndTransaction(statement: Transaction.() -> T) = - withContext(Dispatchers.IO) { transaction(statement = statement) } + val database = Database.connect(pool) + val databaseDispatcher = Dispatchers.IO.limitedParallelism(poolSize) + suspend fun 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 - 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 + diff --git a/frameworks/Kotlin/ktor/ktor-jetty.dockerfile b/frameworks/Kotlin/ktor/ktor-jetty.dockerfile index 52855ab4a55..c8fd555f50f 100644 --- a/frameworks/Kotlin/ktor/ktor-jetty.dockerfile +++ b/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 diff --git a/frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts b/frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts index c98ef463979..baf22d05337 100644 --- a/frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts +++ b/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().configureEach { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + tasks.shadowJar { archiveBaseName.set("ktor-pgclient") archiveClassifier.set("") diff --git a/frameworks/Kotlin/ktor/ktor-pgclient/gradle/wrapper/gradle-wrapper.properties b/frameworks/Kotlin/ktor/ktor-pgclient/gradle/wrapper/gradle-wrapper.properties index 81aa1c0448a..2733ed5dc3c 100644 --- a/frameworks/Kotlin/ktor/ktor-pgclient/gradle/wrapper/gradle-wrapper.properties +++ b/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 diff --git a/frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/main.kt b/frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/main.kt index 1afafc28fcd..261e9749611 100644 --- a/frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/main.kt +++ b/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 suspend fun getFortunes(): List suspend fun updateWorlds(worlds: List) } @@ -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 { - 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 = 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) { - // 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) } } } diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc.dockerfile b/frameworks/Kotlin/ktor/ktor-r2dbc.dockerfile index 0c7fc7c2e15..9aa09916483 100644 --- a/frameworks/Kotlin/ktor/ktor-r2dbc.dockerfile +++ b/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 diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/README.md b/frameworks/Kotlin/ktor/ktor-r2dbc/README.md index 5b758d68dbd..0cf954902e5 100644 --- a/frameworks/Kotlin/ktor/ktor-r2dbc/README.md +++ b/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. diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/build.gradle.kts b/frameworks/Kotlin/ktor/ktor-r2dbc/build.gradle.kts new file mode 100644 index 00000000000..1a9fe97655b --- /dev/null +++ b/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().configureEach { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + +tasks.named("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) +} + diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/gradle/wrapper/gradle-wrapper.jar b/frameworks/Kotlin/ktor/ktor-r2dbc/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..28861d273a5 Binary files /dev/null and b/frameworks/Kotlin/ktor/ktor-r2dbc/gradle/wrapper/gradle-wrapper.jar differ diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/gradle/wrapper/gradle-wrapper.properties b/frameworks/Kotlin/ktor/ktor-r2dbc/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..e6045a98350 --- /dev/null +++ b/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 diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/gradlew b/frameworks/Kotlin/ktor/ktor-r2dbc/gradlew new file mode 100755 index 00000000000..cccdd3d517f --- /dev/null +++ b/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" "$@" diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/gradlew.bat b/frameworks/Kotlin/ktor/ktor-r2dbc/gradlew.bat new file mode 100644 index 00000000000..e95643d6a2c --- /dev/null +++ b/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 diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml b/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml deleted file mode 100644 index 9f0754b9512..00000000000 --- a/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml +++ /dev/null @@ -1,179 +0,0 @@ - - - - 4.0.0 - - org.jetbrains.ktor - tech-empower-framework-benchmark - 1.0-SNAPSHOT - jar - - org.jetbrains.ktor tech-empower-framework-benchmark - - - 2.1.21 - 1.10.1 - 3.1.3 - 1.8.1 - 0.12.0 - UTF-8 - 1.5.13 - 3.7.1 - 42.7.5 - 1.0.7.RELEASE - 1.0.2.RELEASE - - - - - - org.jetbrains.kotlin - kotlin-reflect - ${kotlin.version} - - - org.jetbrains.kotlinx - kotlinx-serialization-core - ${serialization.version} - - - org.jetbrains.kotlinx - kotlinx-serialization-json - ${serialization.version} - - - org.jetbrains.kotlinx - kotlinx-serialization-json-io - ${serialization.version} - - - org.jetbrains.kotlinx - kotlinx-html-jvm - ${kotlinx.html.version} - - - org.jetbrains.kotlinx - kotlinx-coroutines-core - ${kotlin.coroutines.version} - - - org.jetbrains.kotlinx - kotlinx-coroutines-reactor - ${kotlin.coroutines.version} - - - io.ktor - ktor-server-default-headers-jvm - ${ktor.version} - - - io.ktor - ktor-server-html-builder-jvm - ${ktor.version} - - - org.postgresql - r2dbc-postgresql - ${r2dbc.version} - - - io.r2dbc - r2dbc-pool - ${r2dbc.pool.version} - - - io.projectreactor - reactor-core - ${reactor.version} - - - ch.qos.logback - logback-classic - ${logback.version} - - - io.ktor - ktor-server-netty-jvm - ${ktor.version} - - - - - src/main/kotlin - - - - org.jetbrains.kotlin - kotlin-maven-plugin - ${kotlin.version} - - - compile - compile - - compile - - - - test-compile - test-compile - - test-compile - - - - - - kotlinx-serialization - - - - - org.jetbrains.kotlin - kotlin-maven-serialization - ${kotlin.version} - - - - - maven-jar-plugin - - true - - - - default-jar - none - - - - - maven-assembly-plugin - 3.7.1 - - - - netty - - single - - - package - - - - src/main/assembly/netty-bundle.xml - - - - io.ktor.server.netty.EngineMain - - - - - - - - - diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/settings.gradle.kts b/frameworks/Kotlin/ktor/ktor-r2dbc/settings.gradle.kts new file mode 100644 index 00000000000..b031caa3339 --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "tech-empower-framework-benchmark" + diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt index b0a05351157..54a77082a07 100644 --- a/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt +++ b/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 = 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() - - 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 = 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 { + 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 = + selectWorldPublisher(connection) + +private fun selectWorldPublisher(connection: Connection): Mono { + 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 = + 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() diff --git a/frameworks/Kotlin/ktor/ktor.dockerfile b/frameworks/Kotlin/ktor/ktor.dockerfile index c2624695540..d0d81f0217f 100644 --- a/frameworks/Kotlin/ktor/ktor.dockerfile +++ b/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 diff --git a/frameworks/Kotlin/ktor/ktor/README.md b/frameworks/Kotlin/ktor/ktor/README.md index 02a13760b07..a265333db59 100644 --- a/frameworks/Kotlin/ktor/ktor/README.md +++ b/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. diff --git a/frameworks/Kotlin/ktor/ktor/build.gradle.kts b/frameworks/Kotlin/ktor/ktor/build.gradle.kts new file mode 100644 index 00000000000..366dc9f1c04 --- /dev/null +++ b/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().configureEach { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + +tasks.named("shadowJar") { + enabled = false +} + +fun registerBundle( + name: String, + classifier: String, + mainClass: String, + exclusions: List +) = 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") +} + diff --git a/frameworks/Kotlin/ktor/ktor/gradle/wrapper/gradle-wrapper.jar b/frameworks/Kotlin/ktor/ktor/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..28861d273a5 Binary files /dev/null and b/frameworks/Kotlin/ktor/ktor/gradle/wrapper/gradle-wrapper.jar differ diff --git a/frameworks/Kotlin/ktor/ktor/gradle/wrapper/gradle-wrapper.properties b/frameworks/Kotlin/ktor/ktor/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..e6045a98350 --- /dev/null +++ b/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 diff --git a/frameworks/Kotlin/ktor/ktor/gradlew b/frameworks/Kotlin/ktor/ktor/gradlew new file mode 100755 index 00000000000..cccdd3d517f --- /dev/null +++ b/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" "$@" diff --git a/frameworks/Kotlin/ktor/ktor/gradlew.bat b/frameworks/Kotlin/ktor/ktor/gradlew.bat new file mode 100644 index 00000000000..e95643d6a2c --- /dev/null +++ b/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 diff --git a/frameworks/Kotlin/ktor/ktor/pom.xml b/frameworks/Kotlin/ktor/ktor/pom.xml deleted file mode 100644 index a6adb0a651b..00000000000 --- a/frameworks/Kotlin/ktor/ktor/pom.xml +++ /dev/null @@ -1,213 +0,0 @@ - - - - 4.0.0 - - org.jetbrains.ktor - tech-empower-framework-benchmark - 1.0-SNAPSHOT - jar - - org.jetbrains.ktor tech-empower-framework-benchmark - - - 2.1.21 - 3.1.3 - 1.8.1 - 0.12.0 - UTF-8 - 5.1.0 - 1.5.13 - 8.0.33 - 42.7.5 - - - - - org.jetbrains.kotlin - kotlin-stdlib - ${kotlin.version} - - - - org.jetbrains.kotlinx - kotlinx-serialization-core - ${serialization.version} - - - org.jetbrains.kotlinx - kotlinx-serialization-json - ${serialization.version} - - - org.jetbrains.kotlinx - kotlinx-html-jvm - ${kotlinx.html.version} - - - io.ktor - ktor-server-default-headers-jvm - ${ktor.version} - - - io.ktor - ktor-server-html-builder-jvm - ${ktor.version} - - - com.zaxxer - HikariCP - ${hikaricp.version} - - - - org.postgresql - postgresql - ${postgresql.version} - - - - mysql - mysql-connector-java - ${mysql.version} - - - - ch.qos.logback - logback-classic - ${logback.version} - - - io.ktor - ktor-server-netty-jvm - ${ktor.version} - - - io.ktor - ktor-server-jetty-jvm - ${ktor.version} - - - io.ktor - ktor-server-cio-jvm - ${ktor.version} - - - - - src/main/kotlin - - - - org.jetbrains.kotlin - kotlin-maven-plugin - ${kotlin.version} - - - compile - compile - - compile - - - - test-compile - test-compile - - test-compile - - - - - - kotlinx-serialization - - - - - org.jetbrains.kotlin - kotlin-maven-serialization - ${kotlin.version} - - - - - maven-jar-plugin - - true - - - - default-jar - none - - - - - maven-assembly-plugin - 3.7.1 - - - - cio - - single - - - package - - - - src/main/assembly/cio-bundle.xml - - - - io.ktor.server.cio.EngineMain - - - - - - netty - - single - - - package - - - - src/main/assembly/netty-bundle.xml - - - - io.ktor.server.netty.EngineMain - - - - - - jetty - - single - - - package - - - - src/main/assembly/jetty-bundle.xml - - - - io.ktor.server.jetty.EngineMain - - - - - - - - - diff --git a/frameworks/Kotlin/ktor/ktor/settings.gradle.kts b/frameworks/Kotlin/ktor/ktor/settings.gradle.kts new file mode 100644 index 00000000000..b031caa3339 --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "tech-empower-framework-benchmark" + diff --git a/frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/main.kt b/frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/main.kt index eea3566b953..aaca738fa49 100644 --- a/frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/main.kt +++ b/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 = - prepareStatement(WORLD_QUERY).use { statement -> - Array(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 + 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 = 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