Repository navigation
ktor-kronos: Ktor on the JVM with the kron engine (experimental) - #1683
Conversation
…AV/ktor fork - io_uring reactors, Glyph11 parsing, no CIO (experimental)
|
/benchmark --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Composite score, main against this run, in the board's default view:
The results on main were measured in another session, so a few points up or down can be the machine and not the change. Full log |
…objects down to 59) and a GC heap floor of 64 MiB per reactor
|
/benchmark -t baseline |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Composite score, main against this run, in the board's default view:
The results on main were measured in another session, so a few points up or down can be the machine and not the change. Full log |
…ontentNegotiation on /json, /echo, and HTTPS on :8081 through the engine's TLS connector (kernel TX where the host has the tls module)
|
/benchmark --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Composite score, main against this run, in the board's default view:
The results on main were measured in another session, so a few points up or down can be the machine and not the change. Full log |
…n marks each connection's live state with the reactors stopped, so the more connections are held the fewer collections must run
… app on kron's JVM target, reaching io_uring and BoringSSL through FFM, a jar on JDK 25; json-comp dropped, as Ktor has no gzip on Native
…n plugin - JVM-only in Ktor, so the native build installs none and json-comp leaves meta.json for it
|
/benchmark --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Composite score, main against this run, in the board's default view:
The results on main were measured in another session, so a few points up or down can be the machine and not the change. Full log |
…thread group every 5 s, GC pauses and safepoint times to stdout, which --save keeps per profile
|
/benchmark --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Composite score, main against this run, in the board's default view:
The results on main were measured in another session, so a few points up or down can be the machine and not the change. Full log |
…s own encoder - encodeToString queued every reactor on one process-wide lock - and Ktor's gzip run on the reactor rather than Dispatchers.Default; the diagnostics add a Flight Recorder summary every 10 s (temporary)
|
/benchmark --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Composite score, main against this run, in the board's default view:
The results on main were measured in another session, so a few points up or down can be the machine and not the change. Full log |
…hared by every reactor - a job per reactor (async had 62-73% of its samples on the one root job's list of handlers), the application resolved once rather than under EmbeddedServer's read lock on every call (half of pipelined's), and kotlinx-io's segment pool sized before the reactors are pinned: first used on one, it had a single slot for all 64, and limited-conn, echo-ws and json-comp spent 40-96% spinning on it; the diagnostics add the pool's slots, the hottest stacks and who takes each contended lock; the native path's GC floor is 64 MiB a reactor, as ktor-kronos-native's
…InitialRAMPercentage=8 -XX:NewRatio=1) - G1 started at 2 GiB and each young collection copies every call in flight, 32000 at once in async: async +15% locally, echo-ws +5%, json-tls +2%
|
/benchmark --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Composite score, main against this run, in the board's default view:
The results on main were measured in another session, so a few points up or down can be the machine and not the change. Full log |
Adds ktor-kronos, an experimental entry: JetBrains Ktor with the kron engine (
ktor-server-kron, in the MDA2AV/ktor fork) instead of CIO, on Kotlin/Native or on the JVM. One build arg picks the runtime; it is set to the JVM.Runtime knob
ARG KRONOS_RUNTIMEat the top of the Dockerfile:jvm(set now): the app as a jar on JDK 25 (eclipse-temurin:25-jre), run with the ktor entry's JVM flags (G1, NUMA, AlwaysPreTouch) and a heap that starts at 8% of RAM, half of it young. kron reaches io_uring and BoringSSL through the FFM API; its jar carries its BoringSSL.native: the same app as one Kotlin/Native binary indebian:bookworm-slim, which also runs as its own entry, ktor-kronos-native (ktor-kronos-native: Ktor on Kotlin/Native with the kron engine (experimental) #1684).Both are built from the same source; only the
mainand a few platform calls differ. json-comp needs the JVM: Ktor'sCompressionplugin exists only there, so fornativejson-comp comes out ofmeta.json.What it is
delay()s and its connections' jobs go on. Outside development mode, the engine resolves the application once rather than per call throughEmbeddedServer, which on the JVM hands it out under a read lock. The engine also sizes kotlinx-io's segment pool before the reactors are pinned: the pool sizes itself on first use byavailableProcessors(), which counts the calling thread's CPUs, so if a pinned reactor gets there first the whole process gets one slot.call.receive,call.respond) runs on the reactor that accepted the connection, with no thread pool in between.delay()is an IORING_OP_TIMEOUT on the reactor's own ring.tlskernel module, the kernel encrypts the records (kTLS TX) and receive stays on BoringSSL; elsewhere BoringSSL does both.ContentNegotiationwith kotlinx.serialization, gzipped by Ktor's ownCompressionplugin on the JVM, as on the ktor entry. Two changes, both through Ktor's own extension points:encodeToStringborrows its buffer from one process-wide pool under a lock, which every reactor queues on.GZipEncoderruns its deflate on the reactor (Dispatchers.Unconfined) rather than onDispatchers.Default, where Compression's empty context sends it.KRONOS_GC_MIN_HEAP_MBoverrides it.-XX:InitialRAMPercentage=8 -XX:NewRatio=1). G1 started at 2 GiB and kept the young generation small. Each young collection copies every call in flight, 32000 of them at once in async, so fewer collections pay: async +15% locally.HttpServerCommonTestSuite35/35 andWebSocketEngineSuite17/19. The other 2 are disabled in Ktor itself, for every engine. On the JVM it also passes Ktor's JVM-only engine suites (content, sustainability, connection, config, plugins), but for six tests where kron differs, skipped with their reasons in the fork.Profiles
Build
Neither the engine nor kron is published, so the Dockerfile builds both from source at pinned commits:
02f662f(MDA2AV/kron) is published into the build's local Maven repository with Kotlin 2.3.21, the version Ktor builds with.033263e0c(MDA2AV/ktor, branchkron-engine) gets the arena app added as a project of its own build.The native image is 83 MB and builds in about 5.5 minutes cold; the JVM image is 355 MB and takes about 2.5 more on the shared build stage. To build trees that are not pushed:
--build-context kron=<tree> --build-context ktor=<tree>.Diagnostics (temporary)
On the JVM, the log has CPU per thread group and GC every 5 s, and a Flight Recorder summary every 10 s. The summary lists hot methods and stacks, contended locks with the code that takes them, and the slots of kotlinx-io's segment pool. They come out once the numbers are understood.
Validation
validate.sh ktor-kronos: on the JVM (set now) 64 passed, 0 failed, json-comp included; natively 59/59 without json-comp. Both: fragmentation 9/9, WebSocket 7/7, TLS verdict pass, kTLS TX.