Skip to content

Commit

Permalink
Load programmatic modules dynamically only in development mode
Browse files Browse the repository at this point in the history
  • Loading branch information
osipxd committed Feb 28, 2025
1 parent 7a6c3cb commit 7c7b9a6
Showing 1 changed file with 58 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.server.engine
@@ -14,15 +14,24 @@ import io.ktor.util.logging.*
import io.ktor.util.pipeline.*
import io.ktor.utils.io.*
import io.ktor.utils.io.core.*
import kotlinx.coroutines.*
import java.io.*
import java.net.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.net.URL
import java.net.URLDecoder
import java.nio.file.*
import java.nio.file.StandardWatchEventKinds.*
import java.nio.file.attribute.*
import java.util.concurrent.*
import java.util.concurrent.locks.*
import kotlin.concurrent.*
import java.nio.file.attribute.BasicFileAttributes
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.getOrSet
import kotlin.concurrent.read
import kotlin.concurrent.write

private typealias ApplicationModule = Application.() -> Unit
private typealias DynamicApplicationModule = Application.(ClassLoader) -> Unit

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

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

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

private val configModulesNames: List<String> = run {
environment.config.propertyOrNull("ktor.application.modules")?.getList() ?: emptyList()
}
private val configModulesNames: List<String> =
environment.config.propertyOrNull("ktor.application.modules")?.getList().orEmpty()

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

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

avoidingDoubleStartup {
modulesNames.forEach { name ->
launchModuleByName(name, currentClassLoader, newInstance)
}
modules.forEach { module -> module(newInstance, currentClassLoader) }
}

rootConfig.modules.forEach { module ->
val name = module.methodName()
safeRaiseEvent(ApplicationStarted, newInstance)
return newInstance
}

try {
launchModuleByName(name, currentClassLoader, newInstance)
} catch (_: ReloadingException) {
module(newInstance)
}
private fun dynamicModule(name: String): DynamicApplicationModule {
return { classLoader ->
val application = this
launchModuleByName(name, classLoader, application)
}
}

private fun ApplicationModule.toDynamicModuleOrNull(): DynamicApplicationModule? {
// Programmatic modules are loaded dynamically only when development mode is active
if (!rootConfig.developmentMode) return null

val module = this
// Method name getting might fail if method signature has been changed after compilation
// (for example by R8 or ProGuard)
val name = runCatching { module.methodName() }
.onFailure { environment.log.debug("Module can't be loaded dynamically", it) }
.getOrElse { return null }

return { classLoader ->
val application = this
try {
launchModuleByName(name, classLoader, application)
} catch (cause: ReloadingException) {
environment.log.debug("Failed to load module '$name' dynamically, falling back to static loading", cause)
module.invoke(application)
}
}
}

safeRaiseEvent(ApplicationStarted, newInstance)
return newInstance
private fun ApplicationModule.wrapWithDynamicModule(): DynamicApplicationModule {
val module = this
return { module() }
}

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

0 comments on commit 7c7b9a6

Please sign in to comment.