[startup] IJPL-163158 Startup: postpone built-in server init till the end of startup of the platform

GitOrigin-RevId: 5e8c06e6563b597290512f557cdc60a37a51fbec
This commit is contained in:
Yuriy Artamonov
2025-03-01 15:39:39 +00:00
committed by intellij-monorepo-bot
parent eac533a20d
commit 035ab04b1c
3 changed files with 48 additions and 20 deletions
@@ -16,7 +16,8 @@
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceInterface="org.jetbrains.ide.BuiltInServerManager" serviceImplementation="org.jetbrains.ide.BuiltInServerManagerImpl" preload="true"/>
<applicationService serviceInterface="org.jetbrains.ide.BuiltInServerManager" serviceImplementation="org.jetbrains.ide.BuiltInServerManagerImpl"/>
<applicationActivity implementation="org.jetbrains.ide.BuiltInServerManagerLauncher"/>
<applicationService serviceImplementation="org.jetbrains.builtInWebServer.BuiltInServerOptions"/>
<xdebugger.configurableProvider implementation="org.jetbrains.builtInWebServer.BuiltInServerOptions$BuiltInServerDebuggerConfigurableProvider"/>
@@ -27,7 +28,7 @@
<httpRequestHandler implementation="org.jetbrains.ide.InstallPluginService"/>
<httpRequestHandler implementation="org.jetbrains.ide.UploadLogsService"/>
<applicationService serviceImplementation="org.jetbrains.ide.ToolboxRestServiceConfig" preload="true"/>
<applicationActivity implementation="org.jetbrains.ide.ToolboxRestLauncher"/>
<httpRequestHandler implementation="org.jetbrains.ide.ToolboxRestService"/>
<toolboxServiceHandler implementation="org.jetbrains.ide.ToolboxUpdateNotificationHandler"/>
<toolboxServiceHandler implementation="org.jetbrains.ide.ToolboxRestartNotificationHandler"/>
@@ -1,14 +1,17 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.ide
import com.intellij.ide.ApplicationActivity
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ApplicationNamesInfo
import com.intellij.openapi.components.service
import com.intellij.openapi.components.serviceAsync
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.registry.RegistryManager
import com.intellij.util.SystemProperties
import com.intellij.util.Url
import com.intellij.util.Urls
@@ -30,6 +33,12 @@ import java.net.NetworkInterface
import java.net.URLConnection
import java.util.*
private const val PORTS_COUNT = 20
private const val PROPERTY_RPC_PORT = "rpc.port"
private const val PROPERTY_DISABLED = "idea.builtin.server.disabled"
private val LOG = logger<BuiltInServerManager>()
class BuiltInServerManagerImpl(private val coroutineScope: CoroutineScope) : BuiltInServerManager() {
private val authService = service<BuiltInWebServerAuth>()
private var serverStartFuture: Job? = null
@@ -46,19 +55,15 @@ class BuiltInServerManagerImpl(private val coroutineScope: CoroutineScope) : Bui
val app = ApplicationManager.getApplication()
serverStartFuture = when {
app.isUnitTestMode -> null
else -> coroutineScope.launch(Dispatchers.IO) { startServerInPooledThread() }
else -> coroutineScope.launch(Dispatchers.IO) {
startServerInPooledThread()
}
}
}
override fun createClientBootstrap(): Bootstrap = NettyUtil.nioClientBootstrap(server!!.childEventLoopGroup)
companion object {
private const val PORTS_COUNT = 20
private const val PROPERTY_RPC_PORT = "rpc.port"
private const val PROPERTY_DISABLED = "idea.builtin.server.disabled"
private val LOG = logger<BuiltInServerManager>()
internal const val NOTIFICATION_GROUP = "Built-in Server"
@JvmStatic
@@ -119,6 +124,9 @@ class BuiltInServerManagerImpl(private val coroutineScope: CoroutineScope) : Bui
return
}
// extensions may use registry to enable/disable URL handlers
RegistryManager.getInstanceAsync().awaitRegistryLoad()
try {
server = BuiltInServer.start(firstPort = getDefaultPort(), portsCount = PORTS_COUNT, tryAnyPort = true)
bindCustomPorts(server!!)
@@ -171,3 +179,12 @@ class BuiltInServerManagerImpl(private val coroutineScope: CoroutineScope) : Bui
}
}
}
/**
* Instead of preloading too early, we explicitly start the server at the end of the application boot sequence.
*/
internal class BuiltInServerManagerLauncher : ApplicationActivity {
override suspend fun execute() {
serviceAsync<BuiltInServerManager>()
}
}
@@ -4,17 +4,23 @@ package org.jetbrains.ide
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.intellij.ide.ApplicationActivity
import com.intellij.openapi.Disposable
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.serviceAsync
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.util.Disposer
import com.intellij.util.AwaitCancellationAndInvoke
import com.intellij.util.asSafely
import com.intellij.util.awaitCancellationAndInvoke
import com.intellij.util.concurrency.AppExecutorUtil
import com.intellij.util.io.delete
import io.netty.buffer.Unpooled
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelOption
import io.netty.handler.codec.http.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.io.addCommonHeaders
import java.nio.file.Files
@@ -63,7 +69,7 @@ interface ToolboxServiceHandler<T> {
/**
* Check if a given HTTP method is supported by this specific handler
*/
fun isMethodSupported(method: HttpMethod) = method == HttpMethod.POST
fun isMethodSupported(method: HttpMethod): Boolean = method == HttpMethod.POST
}
private fun findToolboxHandlerByUri(requestUri: String): ToolboxServiceHandler<*>? = toolboxHandlerEP.findFirstSafe {
@@ -87,19 +93,27 @@ private fun <T> wrapHandler(handler: ToolboxServiceHandler<T>, request: JsonElem
}
}
internal class ToolboxRestServiceConfig : Disposable {
override fun dispose() = Unit
internal class ToolboxRestLauncher : ApplicationActivity {
override suspend fun execute() {
serviceAsync<ToolboxRestServiceConfig>()
}
}
@Service(Service.Level.APP)
@OptIn(AwaitCancellationAndInvoke::class)
internal class ToolboxRestServiceConfig(cs: CoroutineScope) {
init {
val toolboxPortFilePath = System.getProperty("toolbox.notification.portFile")
if (toolboxPortFilePath != null) {
AppExecutorUtil.getAppExecutorService().submit {
val server = BuiltInServerManager.getInstance()
cs.launch {
val server = serviceAsync<BuiltInServerManager>()
val port = server.waitForStart().port
val portFile = Path.of(toolboxPortFilePath)
runCatching { Files.createDirectories(portFile.parent) }
runCatching { portFile.writeText("$port") }
Disposer.register(this) {
cs.awaitCancellationAndInvoke {
runCatching { portFile.delete() }
}
}
@@ -108,10 +122,6 @@ internal class ToolboxRestServiceConfig : Disposable {
}
internal class ToolboxRestService : RestService() {
internal companion object {
private val LOG = logger<ToolboxRestService>()
}
override fun getServiceName() = "toolbox"
override fun isSupported(request: FullHttpRequest): Boolean {