Skip to content

Commit 7c7b9a6

Browse files
committed
Load programmatic modules dynamically only in development mode
1 parent 7a6c3cb commit 7c7b9a6

File tree

1 file changed

+58
-24
lines changed

1 file changed

+58
-24
lines changed

ktor-server/ktor-server-core/jvm/src/io/ktor/server/engine/EmbeddedServerJvm.kt

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package io.ktor.server.engine
@@ -14,15 +14,24 @@ import io.ktor.util.logging.*
1414
import io.ktor.util.pipeline.*
1515
import io.ktor.utils.io.*
1616
import io.ktor.utils.io.core.*
17-
import kotlinx.coroutines.*
18-
import java.io.*
19-
import java.net.*
17+
import kotlinx.coroutines.CoroutineScope
18+
import kotlinx.coroutines.Dispatchers
19+
import kotlinx.coroutines.launch
20+
import kotlinx.coroutines.withContext
21+
import java.io.File
22+
import java.net.URL
23+
import java.net.URLDecoder
2024
import java.nio.file.*
2125
import java.nio.file.StandardWatchEventKinds.*
22-
import java.nio.file.attribute.*
23-
import java.util.concurrent.*
24-
import java.util.concurrent.locks.*
25-
import kotlin.concurrent.*
26+
import java.nio.file.attribute.BasicFileAttributes
27+
import java.util.concurrent.TimeUnit
28+
import java.util.concurrent.locks.ReentrantReadWriteLock
29+
import kotlin.concurrent.getOrSet
30+
import kotlin.concurrent.read
31+
import kotlin.concurrent.write
32+
33+
private typealias ApplicationModule = Application.() -> Unit
34+
private typealias DynamicApplicationModule = Application.(ClassLoader) -> Unit
2635

2736
public actual class EmbeddedServer<
2837
TEngine : ApplicationEngine,
@@ -34,6 +43,7 @@ actual constructor(
3443
engineConfigBlock: TConfiguration.() -> Unit
3544
) {
3645

46+
@Suppress("DEPRECATION")
3747
public actual val monitor: Events = rootConfig.environment.monitor
3848

3949
public actual val environment: ApplicationEnvironment = rootConfig.environment
@@ -50,11 +60,13 @@ actual constructor(
5060
private val configuredWatchPath = environment.config.propertyOrNull("ktor.deployment.watch")?.getList().orEmpty()
5161
private val watchPatterns: List<String> = configuredWatchPath + rootConfig.watchPaths
5262

53-
private val configModulesNames: List<String> = run {
54-
environment.config.propertyOrNull("ktor.application.modules")?.getList() ?: emptyList()
55-
}
63+
private val configModulesNames: List<String> =
64+
environment.config.propertyOrNull("ktor.application.modules")?.getList().orEmpty()
5665

57-
private val modulesNames: List<String> = configModulesNames
66+
private val modules by lazy {
67+
configModulesNames.map(::dynamicModule) +
68+
rootConfig.modules.map { module -> module.toDynamicModuleOrNull() ?: module.wrapWithDynamicModule() }
69+
}
5870

5971
private var applicationInstance: Application? = Application(
6072
environment,
@@ -356,23 +368,45 @@ actual constructor(
356368
safeRaiseEvent(ApplicationStarting, newInstance)
357369

358370
avoidingDoubleStartup {
359-
modulesNames.forEach { name ->
360-
launchModuleByName(name, currentClassLoader, newInstance)
361-
}
371+
modules.forEach { module -> module(newInstance, currentClassLoader) }
372+
}
362373

363-
rootConfig.modules.forEach { module ->
364-
val name = module.methodName()
374+
safeRaiseEvent(ApplicationStarted, newInstance)
375+
return newInstance
376+
}
365377

366-
try {
367-
launchModuleByName(name, currentClassLoader, newInstance)
368-
} catch (_: ReloadingException) {
369-
module(newInstance)
370-
}
378+
private fun dynamicModule(name: String): DynamicApplicationModule {
379+
return { classLoader ->
380+
val application = this
381+
launchModuleByName(name, classLoader, application)
382+
}
383+
}
384+
385+
private fun ApplicationModule.toDynamicModuleOrNull(): DynamicApplicationModule? {
386+
// Programmatic modules are loaded dynamically only when development mode is active
387+
if (!rootConfig.developmentMode) return null
388+
389+
val module = this
390+
// Method name getting might fail if method signature has been changed after compilation
391+
// (for example by R8 or ProGuard)
392+
val name = runCatching { module.methodName() }
393+
.onFailure { environment.log.debug("Module can't be loaded dynamically", it) }
394+
.getOrElse { return null }
395+
396+
return { classLoader ->
397+
val application = this
398+
try {
399+
launchModuleByName(name, classLoader, application)
400+
} catch (cause: ReloadingException) {
401+
environment.log.debug("Failed to load module '$name' dynamically, falling back to static loading", cause)
402+
module.invoke(application)
371403
}
372404
}
405+
}
373406

374-
safeRaiseEvent(ApplicationStarted, newInstance)
375-
return newInstance
407+
private fun ApplicationModule.wrapWithDynamicModule(): DynamicApplicationModule {
408+
val module = this
409+
return { module() }
376410
}
377411

378412
private fun launchModuleByName(name: String, currentClassLoader: ClassLoader, newInstance: Application) {

0 commit comments

Comments
 (0)