Skip to content

Commit 583c53f

Browse files
committed
chore: kotlin 2.0.0-Beta update changes
1 parent d2e9eb4 commit 583c53f

File tree

22 files changed

+229
-137
lines changed

22 files changed

+229
-137
lines changed

backend/jvm/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ dependencies {
113113
implementation(libs.ap.loader.all)
114114
// Logging
115115
implementation(libs.logback.classic)
116-
implementation("io.ktor:ktor-server-partial-content-jvm:2.3.6")
117116
// Testing
118117
testImplementation(platform(libs.testcontainers.bom))
119118
testImplementation(libs.ktor.server.tests)
Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.suresh
22

33
import BuildConfig
4+
import dev.suresh.config.SysConfig
45
import dev.suresh.plugins.configureHTTP
56
import dev.suresh.plugins.configureSecurity
67
import dev.suresh.plugins.configureSerialization
@@ -9,11 +10,17 @@ import dev.suresh.routes.*
910
import io.ktor.server.application.*
1011
import io.ktor.server.netty.*
1112
import io.ktor.server.routing.*
13+
import io.ktor.util.logging.*
1214

13-
fun main(args: Array<String>) {
14-
println("Starting App ${BuildConfig.version}...")
15-
EngineMain.main(args)
16-
}
15+
fun main(args: Array<String>) =
16+
try {
17+
SysConfig.initSysProperty()
18+
println("Starting App ${BuildConfig.version}...")
19+
EngineMain.main(args)
20+
} catch (e: Throwable) {
21+
val log = KtorSimpleLogger("main")
22+
log.error("Failed to start the application: ${e.message}", e)
23+
}
1724

1825
fun Application.module() {
1926
configureHTTP()
@@ -24,6 +31,7 @@ fun Application.module() {
2431
adminRoutes()
2532
webApp()
2633
jvmFeatures()
34+
mgmtRoutes()
2735
}
2836
// CoroutineScope(coroutineContext).launch {}
2937
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package dev.suresh.config
2+
3+
import io.ktor.server.config.*
4+
import kotlin.reflect.full.withNullability
5+
import kotlin.reflect.typeOf
6+
import kotlin.time.Duration
7+
8+
data object SysConfig {
9+
10+
// Logback log directory
11+
val LOG_DIR by lazy {
12+
val os = System.getProperty("os.name")
13+
when {
14+
os.startsWith("Mac", ignoreCase = true) -> System.getProperty("user.dir")
15+
else -> System.getenv("LOG_DIR").orEmpty().ifBlank { "/log" }
16+
}
17+
}
18+
19+
/**
20+
* Initializes the system properties required for the application to run. This should be invoked
21+
* before the Engine main() method is called.
22+
*/
23+
fun initSysProperty() {
24+
System.setProperty("jdk.tls.maxCertificateChainLength", "15")
25+
System.setProperty("jdk.includeInExceptions", "hostInfo")
26+
System.setProperty("LOG_DIR", LOG_DIR)
27+
}
28+
}
29+
30+
/**
31+
* Extension function to get and convert config values to their respective type. Nullability is
32+
* disabled to support java types
33+
*/
34+
context(ApplicationConfig)
35+
@Suppress("IMPLICIT_CAST_TO_ANY")
36+
inline fun <reified T> prop(prop: String) =
37+
when (typeOf<T>().withNullability(false)) {
38+
typeOf<String>() -> property(prop).getString()
39+
typeOf<List<String>>() -> property(prop).getList()
40+
typeOf<Boolean>() -> property(prop).getString().toBoolean()
41+
typeOf<Int>() -> property(prop).getString().toInt()
42+
typeOf<Long>() -> property(prop).getString().toLong()
43+
typeOf<Double>() -> property(prop).getString().toDouble()
44+
typeOf<Duration>() -> Duration.parse(property(prop).getString().lowercase())
45+
else -> throw IllegalArgumentException("Unsupported type: ${typeOf<T>()}")
46+
}
47+
as T

backend/jvm/src/main/kotlin/dev/suresh/plugins/Serialization.kt

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,9 @@ import io.ktor.serialization.kotlinx.json.*
44
import io.ktor.server.application.*
55
import io.ktor.server.plugins.contentnegotiation.*
66
import io.ktor.server.resources.*
7-
import kotlinx.serialization.json.Json
87

98
fun Application.configureSerialization() {
10-
install(ContentNegotiation) {
11-
json(
12-
Json {
13-
prettyPrint = true
14-
isLenient = true
15-
ignoreUnknownKeys = true
16-
encodeDefaults = true
17-
decodeEnumsCaseInsensitive = true
18-
explicitNulls = false
19-
})
20-
}
9+
install(ContentNegotiation) { json(dev.suresh.json) }
2110

2211
install(Resources)
2312
}

backend/jvm/src/main/kotlin/dev/suresh/routes/Admin.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package dev.suresh.routes
22

33
import BuildConfig
4-
import io.ktor.http.*
54
import io.ktor.server.application.*
6-
import io.ktor.server.auth.*
7-
import io.ktor.server.http.content.*
85
import io.ktor.server.plugins.swagger.*
96
import io.ktor.server.response.*
107
import io.ktor.server.routing.*

backend/jvm/src/main/kotlin/dev/suresh/routes/JvmFeature.kt

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,13 @@ package dev.suresh.routes
33
import dev.suresh.lang.FFM
44
import dev.suresh.lang.VThread
55
import dev.suresh.log.LoggerDelegate
6-
import dev.suresh.runOnVirtualThread
76
import io.github.oshai.kotlinlogging.KotlinLogging
8-
import io.ktor.http.*
97
import io.ktor.server.application.*
108
import io.ktor.server.response.*
119
import io.ktor.server.routing.*
12-
import java.io.PrintStream
13-
import jdk.jfr.Configuration
14-
import jdk.jfr.consumer.RecordingStream
15-
import kotlin.io.path.deleteIfExists
16-
import kotlin.io.path.name
17-
import kotlin.io.path.pathString
18-
import kotlin.time.Duration.Companion.milliseconds
19-
import kotlin.time.toJavaDuration
20-
import kotlinx.coroutines.sync.Mutex
21-
import kotlinx.coroutines.sync.withLock
22-
import one.converter.Arguments
23-
import one.converter.FlameGraph
24-
import one.converter.jfr2flame
25-
import one.jfr.JfrReader
26-
import one.profiler.AsyncProfiler
27-
import one.profiler.AsyncProfilerLoader
28-
import one.profiler.Events
2910

3011
private val logger = KotlinLogging.logger {}
3112

32-
val mutex = Mutex()
33-
34-
val profiler: AsyncProfiler? by lazy {
35-
val ap = AsyncProfilerLoader.loadOrNull()
36-
ap.start(Events.CPU, 1000)
37-
ap
38-
}
39-
4013
fun Route.jvmFeatures() {
4114
get("/ffm") {
4215
call.respondTextWriter { with(LoggerDelegate(this, logger)) { FFM.memoryLayout() } }
@@ -45,47 +18,4 @@ fun Route.jvmFeatures() {
4518
get("/vthreads") {
4619
call.respondTextWriter { with(LoggerDelegate(this, logger)) { VThread.virtualThreads() } }
4720
}
48-
49-
get("/profile") {
50-
// Run the blocking operation on virtual thread and make sure
51-
// only one profile operation is running at a time.
52-
when {
53-
mutex.isLocked -> call.respondText("Profile operation is already running")
54-
else ->
55-
mutex.withLock {
56-
runOnVirtualThread {
57-
val jfrPath = kotlin.io.path.createTempFile("profile", ".jfr")
58-
RecordingStream(Configuration.getConfiguration("profile")).use {
59-
it.enable("jdk.CPULoad").withPeriod(100.milliseconds.toJavaDuration())
60-
it.enable("jdk.JavaMonitorEnter").withStackTrace()
61-
it.startAsync()
62-
Thread.sleep(5_000)
63-
it.dump(jfrPath)
64-
println("JFR file written to ${jfrPath.toAbsolutePath()}")
65-
}
66-
67-
when (call.request.queryParameters.contains("download")) {
68-
true -> {
69-
call.response.header(
70-
HttpHeaders.ContentDisposition,
71-
ContentDisposition.Attachment.withParameter(
72-
ContentDisposition.Parameters.FileName, jfrPath.fileName.name)
73-
.toString())
74-
call.respondFile(jfrPath.toFile())
75-
}
76-
else -> {
77-
val jfr2flame = jfr2flame(JfrReader(jfrPath.pathString), Arguments())
78-
val flameGraph = FlameGraph()
79-
jfr2flame.convert(flameGraph)
80-
81-
call.respondOutputStream(contentType = ContentType.Text.Html) {
82-
flameGraph.dump(PrintStream(this))
83-
}
84-
jfrPath.deleteIfExists()
85-
}
86-
}
87-
}
88-
}
89-
}
90-
}
9121
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package dev.suresh.routes
2+
3+
import com.sun.management.HotSpotDiagnosticMXBean
4+
import dev.suresh.jvmRuntimeInfo
5+
import dev.suresh.plugins.debug
6+
import dev.suresh.runOnVirtualThread
7+
import io.ktor.http.*
8+
import io.ktor.server.application.*
9+
import io.ktor.server.response.*
10+
import io.ktor.server.routing.*
11+
import java.io.PrintStream
12+
import java.lang.management.ManagementFactory
13+
import jdk.jfr.Configuration
14+
import jdk.jfr.consumer.RecordingStream
15+
import kotlin.io.path.deleteIfExists
16+
import kotlin.io.path.name
17+
import kotlin.io.path.pathString
18+
import kotlin.time.Duration.Companion.milliseconds
19+
import kotlin.time.Duration.Companion.minutes
20+
import kotlin.time.toJavaDuration
21+
import kotlinx.coroutines.sync.Mutex
22+
import kotlinx.coroutines.sync.withLock
23+
import one.converter.Arguments
24+
import one.converter.FlameGraph
25+
import one.converter.jfr2flame
26+
import one.jfr.JfrReader
27+
import one.profiler.AsyncProfiler
28+
import one.profiler.AsyncProfilerLoader
29+
import one.profiler.Events
30+
31+
private val DEBUG = ScopedValue.newInstance<Boolean>()
32+
33+
val mutex = Mutex()
34+
35+
val profiler: AsyncProfiler? by lazy {
36+
val ap = AsyncProfilerLoader.loadOrNull()
37+
ap.start(Events.CPU, 1000)
38+
ap
39+
}
40+
41+
fun Route.mgmtRoutes() {
42+
43+
get("/info") {
44+
call.respond(ScopedValue.where(DEBUG, call.debug).get { jvmRuntimeInfo(DEBUG.get()) })
45+
}
46+
47+
get("/profile") {
48+
// Run the blocking operation on virtual thread and make sure
49+
// only one profile operation is running at a time.
50+
when {
51+
mutex.isLocked -> call.respondText("Profile operation is already running")
52+
else ->
53+
mutex.withLock {
54+
runOnVirtualThread {
55+
val jfrPath = kotlin.io.path.createTempFile("profile", ".jfr")
56+
RecordingStream(Configuration.getConfiguration("profile")).use {
57+
it.setMaxSize(100 * 1024 * 1024)
58+
it.setMaxAge(2.minutes.toJavaDuration())
59+
it.enable("jdk.CPULoad").withPeriod(100.milliseconds.toJavaDuration())
60+
it.enable("jdk.JavaMonitorEnter").withStackTrace()
61+
it.startAsync()
62+
Thread.sleep(5_000)
63+
it.dump(jfrPath)
64+
println("JFR file written to ${jfrPath.toAbsolutePath()}")
65+
}
66+
67+
when (call.request.queryParameters.contains("download")) {
68+
true -> {
69+
call.response.header(
70+
HttpHeaders.ContentDisposition,
71+
ContentDisposition.Attachment.withParameter(
72+
ContentDisposition.Parameters.FileName, jfrPath.fileName.name)
73+
.toString())
74+
call.respondFile(jfrPath.toFile())
75+
}
76+
else -> {
77+
val jfr2flame = jfr2flame(JfrReader(jfrPath.pathString), Arguments())
78+
val flameGraph = FlameGraph()
79+
jfr2flame.convert(flameGraph)
80+
81+
call.respondOutputStream(contentType = ContentType.Text.Html) {
82+
flameGraph.dump(PrintStream(this))
83+
}
84+
jfrPath.deleteIfExists()
85+
}
86+
}
87+
}
88+
}
89+
}
90+
}
91+
92+
get("/heapdump") {
93+
val server = ManagementFactory.getPlatformMBeanServer()
94+
val hotspot =
95+
ManagementFactory.newPlatformMXBeanProxy(
96+
server,
97+
"com.sun.management:type=HotSpotDiagnostic",
98+
HotSpotDiagnosticMXBean::class.java)
99+
100+
val heapDumpPath = kotlin.io.path.createTempFile("heapdump", ".hprof")
101+
heapDumpPath.deleteIfExists()
102+
hotspot.dumpHeap(heapDumpPath.pathString, true)
103+
call.response.header(
104+
HttpHeaders.ContentDisposition,
105+
ContentDisposition.Attachment.withParameter(
106+
ContentDisposition.Parameters.FileName, heapDumpPath.fileName.name)
107+
.toString())
108+
call.respondFile(heapDumpPath.toFile())
109+
heapDumpPath.deleteIfExists()
110+
}
111+
}

backend/jvm/src/main/kotlin/dev/suresh/routes/Webapp.kt

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
package dev.suresh.routes
22

3-
import dev.suresh.jvmRuntimeInfo
4-
import dev.suresh.plugins.debug
5-
import io.ktor.server.application.*
63
import io.ktor.server.http.content.*
74
import io.ktor.server.request.*
85
import io.ktor.server.response.*
96
import io.ktor.server.routing.*
107

11-
private val DEBUG = ScopedValue.newInstance<Boolean>()
12-
138
fun Route.webApp() {
149
val webAppRoute = "/app"
1510

16-
get("/info") {
17-
call.respond(ScopedValue.where(DEBUG, call.debug).get { jvmRuntimeInfo(DEBUG.get()) })
18-
}
19-
2011
staticResources(webAppRoute, "webapp") {
2112
exclude { it.path.endsWith(".log") }
2213
default("index.html")

backend/jvm/src/main/resources/application.conf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ ktor {
33
host = 0.0.0.0
44
port = 8080
55
port = ${?PORT}
6-
shutdownGracePeriod = 500
7-
shutdownTimeout = 500
6+
shutdownGracePeriod = 200
7+
shutdownTimeout = 200
88
}
99

1010
development = false

common/src/commonMain/kotlin/dev/suresh/Platform.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ val json by lazy {
8989
encodeDefaults = true
9090
explicitNulls = false
9191
decodeEnumsCaseInsensitive = true
92+
allowTrailingComma = true
9293
}
9394
}
9495

0 commit comments

Comments
 (0)