1
1
/*
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.
3
3
*/
4
4
5
5
package io.ktor.server.engine
@@ -14,15 +14,24 @@ import io.ktor.util.logging.*
14
14
import io.ktor.util.pipeline.*
15
15
import io.ktor.utils.io.*
16
16
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
20
24
import java.nio.file.*
21
25
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
26
35
27
36
public actual class EmbeddedServer <
28
37
TEngine : ApplicationEngine ,
@@ -34,6 +43,7 @@ actual constructor(
34
43
engineConfigBlock: TConfiguration .() -> Unit
35
44
) {
36
45
46
+ @Suppress(" DEPRECATION" )
37
47
public actual val monitor: Events = rootConfig.environment.monitor
38
48
39
49
public actual val environment: ApplicationEnvironment = rootConfig.environment
@@ -50,11 +60,13 @@ actual constructor(
50
60
private val configuredWatchPath = environment.config.propertyOrNull(" ktor.deployment.watch" )?.getList().orEmpty()
51
61
private val watchPatterns: List <String > = configuredWatchPath + rootConfig.watchPaths
52
62
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()
56
65
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
+ }
58
70
59
71
private var applicationInstance: Application ? = Application (
60
72
environment,
@@ -356,23 +368,53 @@ actual constructor(
356
368
safeRaiseEvent(ApplicationStarting , newInstance)
357
369
358
370
avoidingDoubleStartup {
359
- modulesNames.forEach { name ->
360
- launchModuleByName(name, currentClassLoader, newInstance)
361
- }
371
+ modules.forEach { module -> module(newInstance, currentClassLoader) }
372
+ }
362
373
363
- rootConfig.modules.forEach { module ->
364
- val name = module.methodName()
374
+ safeRaiseEvent(ApplicationStarted , newInstance)
375
+ return newInstance
376
+ }
365
377
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 { cause ->
394
+ environment.log.debug(
395
+ " Module can't be loaded dynamically, auto-reloading won't work for this module" ,
396
+ cause,
397
+ )
398
+ }
399
+ .getOrElse { return null }
400
+
401
+ return { classLoader ->
402
+ val application = this
403
+ try {
404
+ launchModuleByName(name, classLoader, application)
405
+ } catch (cause: ReloadingException ) {
406
+ environment.log.debug(
407
+ " Failed to load module '$name ' by classpath reference, falling back to currently loaded value" ,
408
+ cause,
409
+ )
410
+ module.invoke(application)
371
411
}
372
412
}
413
+ }
373
414
374
- safeRaiseEvent(ApplicationStarted , newInstance)
375
- return newInstance
415
+ private fun ApplicationModule.wrapWithDynamicModule (): DynamicApplicationModule {
416
+ val module = this
417
+ return { module() }
376
418
}
377
419
378
420
private fun launchModuleByName (name : String , currentClassLoader : ClassLoader , newInstance : Application ) {
0 commit comments