diff --git a/platform/built-in-server/resources/META-INF/builtInServer.xml b/platform/built-in-server/resources/META-INF/builtInServer.xml index 833bfed153b1..af994072ab23 100644 --- a/platform/built-in-server/resources/META-INF/builtInServer.xml +++ b/platform/built-in-server/resources/META-INF/builtInServer.xml @@ -16,7 +16,8 @@ - + + @@ -27,7 +28,7 @@ - + diff --git a/platform/built-in-server/src/org/jetbrains/ide/BuiltInServerManagerImpl.kt b/platform/built-in-server/src/org/jetbrains/ide/BuiltInServerManagerImpl.kt index 2277bf344044..8c2a26912304 100644 --- a/platform/built-in-server/src/org/jetbrains/ide/BuiltInServerManagerImpl.kt +++ b/platform/built-in-server/src/org/jetbrains/ide/BuiltInServerManagerImpl.kt @@ -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() + class BuiltInServerManagerImpl(private val coroutineScope: CoroutineScope) : BuiltInServerManager() { private val authService = service() 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() - 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() + } +} \ No newline at end of file diff --git a/platform/built-in-server/src/org/jetbrains/ide/ToolboxRestService.kt b/platform/built-in-server/src/org/jetbrains/ide/ToolboxRestService.kt index ef4af6d71add..4efb5047ecaf 100644 --- a/platform/built-in-server/src/org/jetbrains/ide/ToolboxRestService.kt +++ b/platform/built-in-server/src/org/jetbrains/ide/ToolboxRestService.kt @@ -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 { /** * 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 wrapHandler(handler: ToolboxServiceHandler, request: JsonElem } } -internal class ToolboxRestServiceConfig : Disposable { - override fun dispose() = Unit +internal class ToolboxRestLauncher : ApplicationActivity { + override suspend fun execute() { + serviceAsync() + } +} +@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() 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() - } - override fun getServiceName() = "toolbox" override fun isSupported(request: FullHttpRequest): Boolean {