Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
84ad1d9
ktor-kronos: Ktor on Kotlin/Native with the kron engine from the MDA2…
MDA2AV Oct 30, 2026
38f9d41
Benchmark results: ktor-kronos [skip ci]
github-actions[bot] Oct 30, 2026
063e4c5
ktor-kronos: the fork's per-request allocation cuts (a request's 304 …
MDA2AV Oct 30, 2026
0d576b1
ktor-kronos: json-comp, json-tls and 8gbit - Ktor's Compression and C…
MDA2AV Oct 30, 2026
25fa650
Benchmark results: ktor-kronos [skip ci]
github-actions[bot] Oct 30, 2026
894b649
ktor-kronos: a GC heap floor of 512 MiB per reactor - every collectio…
MDA2AV Oct 30, 2026
f9632fb
ktor-kronos: KRONOS_RUNTIME picks Kotlin/Native or the JVM - the same…
MDA2AV Oct 30, 2026
9d79ccb
ktor-kronos: the JVM runtime, with json-comp on Ktor's own Compressio…
MDA2AV Oct 30, 2026
3c51532
Benchmark results: ktor-kronos [skip ci]
github-actions[bot] Oct 30, 2026
328d0e2
ktor-kronos: diagnostics for the arena's JVM run, temporary - CPU by …
MDA2AV Oct 30, 2026
cf2a890
Benchmark results: ktor-kronos [skip ci]
github-actions[bot] Oct 30, 2026
ee9ed81
ktor-kronos: JSON written straight to UTF-8 by kotlinx.serialization'…
MDA2AV Oct 31, 2026
16aaf5a
Benchmark results: ktor-kronos [skip ci]
github-actions[bot] Oct 31, 2026
e48464d
ktor-kronos: the engine at 033263e, where nothing a request does is s…
MDA2AV Oct 31, 2026
4d29b6b
ktor-kronos: the JVM heap starts at 8% of RAM, half of it young (-XX:…
MDA2AV Oct 31, 2026
099efd6
Benchmark results: ktor-kronos [skip ci]
github-actions[bot] Oct 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions frameworks/ktor-kronos/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
build/
.gradle/
.kotlin/
80 changes: 80 additions & 0 deletions frameworks/ktor-kronos/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# ktor-kronos: Ktor with the kron engine (ktor-server-kron), which lives in the MDA2AV/ktor fork, on either runtime:
# KRONOS_RUNTIME=native one Kotlin/Native binary
# KRONOS_RUNTIME=jvm the same app as a jar on the JVM, kron reaching io_uring and BoringSSL through FFM
# The knob is the ARG below, or `docker build --build-arg KRONOS_RUNTIME=...`. json-comp needs the JVM, as Ktor has no
# gzip on Kotlin/Native: for native, take it out of meta.json. The fork and kron are published nowhere yet: they come
# in at pinned commits and are built from source. To build trees that are not pushed, replace either fetch with a
# checkout:
# docker build --build-context kron=<kron tree> --build-context ktor=<fork tree> -t httparena-ktor-kronos frameworks/ktor-kronos
ARG KRONOS_RUNTIME=jvm

# One fetch per tree, so that a --build-context in place of either skips its clone.
FROM gradle:9.8.2-jdk25 AS fetch-kron
ARG KRON_COMMIT=02f662f688aee3a73337c644e7a4841bf279d936
WORKDIR /src
RUN git init -q kron && cd kron \
&& git remote add origin https://github.com/MDA2AV/kron.git \
&& git fetch -q --depth 1 origin "${KRON_COMMIT}" \
&& git checkout -q FETCH_HEAD \
&& rm -rf .git

FROM gradle:9.8.2-jdk25 AS fetch-ktor
ARG KTOR_COMMIT=033263e0c5e832051b69e1f084207ce44488e8d0
WORKDIR /src
RUN git init -q ktor && cd ktor \
&& git remote add origin https://github.com/MDA2AV/ktor.git \
&& git fetch -q --depth 1 origin "${KTOR_COMMIT}" \
&& git checkout -q FETCH_HEAD \
&& rm -rf .git

FROM scratch AS kron
COPY --from=fetch-kron /src/kron /

FROM scratch AS ktor
COPY --from=fetch-ktor /src/ktor /

# Kotlin/Native: the first build downloads its toolchain (compiler, LLVM, a glibc sysroot) into ~/.konan.
FROM gradle:9.8.2-jdk25 AS build

# kron and its HTTP/1.1 parser into the local Maven repository the fork reads, built with the fork's Kotlin (2.3): a
# compiler cannot read libraries a newer one made. Both runtimes' artifacts: kron's BoringSSL travels inside its
# interop library natively, and inside its jar on the JVM.
COPY --from=kron / /kron
WORKDIR /kron
RUN gradle --no-daemon -q -PkotlinVersion=2.3.21 :kron:publishToMavenLocal :glyph11:publishToMavenLocal

# The arena app, as a project of the fork's own build.
COPY --from=ktor / /ktor
COPY app /ktor/ktor-httparena-kronos
WORKDIR /ktor
RUN echo 'include(":ktor-httparena-kronos")' >> settings.gradle.kts

FROM build AS build-native
RUN ./gradlew --no-daemon -q -Pktor.develocity.skipBuildScans=true :ktor-httparena-kronos:linkReleaseExecutableLinuxX64

FROM build AS build-jvm
RUN ./gradlew --no-daemon -q -Pktor.develocity.skipBuildScans=true :ktor-httparena-kronos:jvmFatJar

# One native binary; it needs nothing but glibc. The bench harness runs containers with --security-opt
# seccomp=unconfined, which io_uring_setup and io_uring_enter need.
FROM debian:bookworm-slim AS runtime-native
WORKDIR /app
COPY --from=build-native /ktor/ktor-httparena-kronos/build/bin/linuxX64/releaseExecutable/ktor-kronos.kexe ./ktor-kronos
EXPOSE 8080 8081
ENTRYPOINT ["/app/ktor-kronos"]

# The jar on JDK 25, with the flags the ktor entry runs Netty with. FFM needs native access, which the jar's manifest
# grants as well.
FROM eclipse-temurin:25-jre AS runtime-jvm
WORKDIR /app
COPY --from=build-jvm /ktor/ktor-httparena-kronos/build/libs/ktor-kronos.jar ./ktor-kronos.jar
EXPOSE 8080 8081
# The heap starts at 8% of RAM, half of it young: G1 started at 2 GiB and every young collection copies each call in
# flight - 32000 at once in async - so the fewer of them the better (async +15% locally).
# DIAGNOSTICS, temporary: GC pauses, the GC's sizing at start, and each safepoint's time to reach, to stdout.
ENTRYPOINT ["java", "-server", "-XX:+UseG1GC", "-XX:+UseNUMA", "-XX:+AlwaysPreTouch", \
"-XX:InitialRAMPercentage=8", "-XX:NewRatio=1", \
"-Xlog:gc,gc+init,safepoint:stdout:uptime,tags", \
"--enable-native-access=ALL-UNNAMED", "-jar", "/app/ktor-kronos.jar"]

FROM runtime-${KRONOS_RUNTIME}
59 changes: 59 additions & 0 deletions frameworks/ktor-kronos/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// The arena app, built inside the Ktor fork's own build: the Dockerfile puts this directory at the fork's root as
// :ktor-httparena-kronos, so the engine and Ktor come in as the fork's projects, at the fork's commit. It builds two
// ways from one source: the Kotlin/Native binary, and a jar for the JVM, where kron runs through FFM.
plugins {
id("ktorbuild.project.internal")
id("kotlinx-serialization")
}

kotlin {
jvmToolchain(25)

linuxX64 {
binaries.executable {
baseName = "ktor-kronos"
entryPoint = "httparena.main"
}
}

sourceSets {
// The JSON converter writes through kotlinx.serialization's writer interface, which its own json-io module uses
all {
languageSettings.optIn("kotlinx.serialization.json.internal.JsonFriendModuleApi")
}
commonMain.dependencies {
implementation(projects.ktorServerKron)
implementation(projects.ktorServerWebsockets)
implementation(projects.ktorServerContentNegotiation)
implementation(projects.ktorSerializationKotlinxJson)
}
jvmMain.dependencies {
// Ktor's Compression, for json-comp: it exists only on the JVM
implementation(projects.ktorServerCompression)
// Ktor logs through SLF4J on the JVM: the simple provider prints what the native build prints
implementation(libs.slf4j.simple)
}
}
}

// Nothing here uses atomicfu, and the transform would read kron's classes (JDK 25) on the build's own JVM (21).
atomicfu {
transformJvm = false
}

// The JVM build: the app and everything it runs on in one jar.
tasks.register<Jar>("jvmFatJar") {
group = "build"
archiveBaseName.set("ktor-kronos")
archiveVersion.set("")
manifest {
attributes("Main-Class" to "httparena.JvmMainKt", "Enable-Native-Access" to "ALL-UNNAMED")
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
val jvmJar = tasks.named<Jar>("jvmJar")
dependsOn(jvmJar)
from(jvmJar.map { zipTree(it.archiveFile) })
from(configurations.named("jvmRuntimeClasspath").map { classpath -> classpath.map { if (it.isDirectory) it else zipTree(it) } })
// A signed dependency's signature would not match the merged jar
exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA")
}
Loading