From db6a58235779b6ccadf16d087b01033410a3581a Mon Sep 17 00:00:00 2001 From: "Alexander.Glukhov" Date: Tue, 11 Nov 2025 17:14:01 +0100 Subject: [PATCH] [gradle][eel][IDEA-382138] introduce `GradleEelProxyManager` as a replacement for `GradleDebuggerProxyManager` GitOrigin-RevId: 9a4f5c455f0836ecf1982887ad2c51165ff3f146 --- .../eel/GradleEelProxyManager.kt} | 91 +++++++++---------- .../task/debugger/GradleDebuggerSupport.kt | 34 ++++--- 2 files changed, 66 insertions(+), 59 deletions(-) rename plugins/gradle/src/org/jetbrains/plugins/gradle/service/{task/debugger/GradleDebuggerProxyManager.kt => execution/eel/GradleEelProxyManager.kt} (63%) diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/debugger/GradleDebuggerProxyManager.kt b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/execution/eel/GradleEelProxyManager.kt similarity index 63% rename from plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/debugger/GradleDebuggerProxyManager.kt rename to plugins/gradle/src/org/jetbrains/plugins/gradle/service/execution/eel/GradleEelProxyManager.kt index 9fdabb9d36d8..3cbd822b6745 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/debugger/GradleDebuggerProxyManager.kt +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/execution/eel/GradleEelProxyManager.kt @@ -1,5 +1,5 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.plugins.gradle.service.task.debugger +package org.jetbrains.plugins.gradle.service.execution.eel import com.intellij.openapi.components.Service import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId @@ -12,82 +12,64 @@ import com.intellij.platform.eel.EelTunnelsApi import com.intellij.platform.eel.provider.getEelDescriptor import com.intellij.platform.eel.provider.utils.forwardLocalServer import com.intellij.platform.util.coroutines.childScope -import com.intellij.util.net.NetUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import java.util.concurrent.ConcurrentHashMap import kotlin.time.Duration.Companion.seconds /** - * A project-level service that could provide a proxy, that will be available during execution of an [ExternalSystemTaskId]. + * A project-level service that could provide a proxy, that will be available during execution of an + * [com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId]. * When the task is complete, the proxy server will be stopped. */ @Service(Service.Level.PROJECT) -internal class GradleDebuggerProxyManager( +internal class GradleEelProxyManager( private val project: Project, private val cs: CoroutineScope, ) { - private class Mapping { - - private data class Entry(val localPort: Int, val remotePort: Int) - - private val entries: MutableList = mutableListOf() - - fun add(localPort: Int, remotePort: Int) { - entries.add(Entry(localPort, remotePort)) - } - - fun getLocalToRemote(remotePort: Int): Int? = entries.find { it.remotePort == remotePort }?.localPort + companion object { + fun getInstance(project: Project): GradleEelProxyManager = project.getService(GradleEelProxyManager::class.java) } private val scopes: ConcurrentHashMap = ConcurrentHashMap() private val mappings: ConcurrentHashMap = ConcurrentHashMap() /** - * This proxy pipe will be used to establish the management connection between IDEA and our debugging-related tooling. - * - * @param taskId the corresponding task ID. - * @param dispatchLocalPort the port provided by `ExternalSystemRunnableState.getForkSocket()`. On this port an instance of - * `ForkedDebuggerThread` will wait for the connection from the daemon. - * @return an opened port on the remote side. - */ - fun launchProxy(taskId: ExternalSystemTaskId, dispatchLocalPort: Int): Int { - val eelDescriptor = project.getEelDescriptor() - return eelDescriptor.openSink(taskId, dispatchLocalPort) - } - - /** - * This proxy pipe will be used to establish the connection between IDEA and the debugger itself. - * When a connection between our init script and IDEA will be established, the init script will modify an execution-related task and - * start a debugger on this port. - * After IDEA receive a heartbeat from the daemon, we could establish a connection with the remote debugger - * on 127.0.0.1:[getReverseProxyLocalSink] - * There is no need to explicitly receive/return the local port, because it could be looked up later based on the REMOTE port via - * [getReverseProxyLocalSink]. + * Set up a proxy between %localhost%:[localPort] and %remote_localhost%:[@result]. * * @param taskId the corresponding task ID. + * @param localPort a local port that should be used to forward traffic to. * @return an opened port on the **remote** side. */ - fun setupReverseProxy(taskId: ExternalSystemTaskId): Int { - val localSink = NetUtils.findAvailableSocketPort() - val eelDescriptor = project.getEelDescriptor() - return eelDescriptor.openSink(taskId, localSink) + fun launchReverseProxy(taskId: ExternalSystemTaskId, localPort: Int): Int = project.getEelDescriptor() + .openLocalSink(taskId, localPort) + + /** + * Get mapping between remote and local port. + * + * @param remotePort a remote port + * @return a local port that could be used to forward traffic to %remoteHost%:[remotePort] + */ + fun getLocalToRemotePort(remotePort: Int): Int { + return mappings.values + .firstNotNullOfOrNull { it.getLocalToRemote(remotePort) } + ?: throw IllegalArgumentException("No local sink found for the remote port $remotePort") } /** - * Get a local port that corresponds to the remote port. + * Get mapping between local and remote port. * - * @param reverseProxyRemoteSink a remote port - * @return a local port that could be used to access [reverseProxyRemoteSink] + * @param localPort a local port + * @return a remote port that could be used to access %127.0.0.1%:[localPort] */ - fun getReverseProxyLocalSink(reverseProxyRemoteSink: Int): Int { + fun getRemoteToLocalPort(localPort: Int): Int { return mappings.values - .firstNotNullOfOrNull { it.getLocalToRemote(reverseProxyRemoteSink) } - ?: throw IllegalArgumentException("No local sink found for remote port $reverseProxyRemoteSink") + .firstNotNullOfOrNull { it.getRemoteToLocal(localPort) } + ?: throw IllegalArgumentException("No remote sink found for the local port $localPort") } - private fun EelDescriptor.openSink(taskId: ExternalSystemTaskId, localPort: Int): Int { + private fun EelDescriptor.openLocalSink(taskId: ExternalSystemTaskId, localPort: Int): Int { val remoteHostAddress = EelTunnelsApi.HostAddress.Builder() .connectionTimeout(90.seconds) .build() @@ -127,4 +109,19 @@ internal class GradleDebuggerProxyManager( } }) } -} + + private class Mapping { + + private data class Entry(val localPort: Int, val remotePort: Int) + + private val entries: MutableList = mutableListOf() + + fun add(localPort: Int, remotePort: Int) { + entries.add(Entry(localPort, remotePort)) + } + + fun getLocalToRemote(remotePort: Int): Int? = entries.find { it.remotePort == remotePort }?.localPort + + fun getRemoteToLocal(localPort: Int): Int? = entries.find { it.localPort == localPort }?.remotePort + } +} \ No newline at end of file diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/debugger/GradleDebuggerSupport.kt b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/debugger/GradleDebuggerSupport.kt index 6a71d3b10fd2..b308a88b196d 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/debugger/GradleDebuggerSupport.kt +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/debugger/GradleDebuggerSupport.kt @@ -8,8 +8,10 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.platform.eel.provider.LocalEelDescriptor import com.intellij.platform.eel.provider.getEelDescriptor +import com.intellij.util.net.NetUtils import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.gradle.service.execution.GradleExecutionContext +import org.jetbrains.plugins.gradle.service.execution.eel.GradleEelProxyManager import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings @ApiStatus.Internal @@ -19,7 +21,7 @@ object GradleDebuggerSupport { private const val DEBUGGER_AGENT_JVM_ARG_FORMAT = "-D${DEBUGGER_AGENT_SINK_PORT_SYS_PROP}=%d" /** - * See the documentation entry for [GradleDebuggerProxyManager.getReverseProxyLocalSink]. + * See the documentation entry for [GradleEelProxyManager.getLocalToRemotePort]. */ @JvmStatic fun getDebuggeeLocalPort(project: Project, mayBeRemotePort: String?): String? { @@ -30,13 +32,13 @@ object GradleDebuggerSupport { if (projectEelDescriptor == LocalEelDescriptor) { return mayBeRemotePort } - val proxyManager = project.getService(GradleDebuggerProxyManager::class.java) - val localSinkToDebuggee = proxyManager.getReverseProxyLocalSink(mayBeRemotePort.toInt()) + val proxyManager = GradleEelProxyManager.getInstance(project) + val localSinkToDebuggee = proxyManager.getLocalToRemotePort(mayBeRemotePort.toInt()) return localSinkToDebuggee.toString() } /** - * See the documentation entry for [GradleDebuggerProxyManager.setupReverseProxy]. + * See the documentation entry for [GradleEelProxyManager.launchReverseProxy]. */ @JvmStatic fun setupDebuggerProxy(context: GradleExecutionContext, settings: GradleExecutionSettings) { @@ -45,29 +47,37 @@ object GradleDebuggerSupport { } if (settings.isExecutionDebugRequired()) { - val proxyManager = context.project.getService(GradleDebuggerProxyManager::class.java) + val proxyManager = GradleEelProxyManager.getInstance(context.project) settings.replacePortWithProxy(proxyManager, context.taskId, DEBUGGER_DISPATCH_PORT_KEY) - // this port will be used by the debugger agent ON THE REMOTE SIDE. - val debuggerAgentSink = proxyManager.setupReverseProxy(context.taskId) + // This port will be used by the debugger agent ON THE REMOTE SIDE. + // This proxy pipe will be used to establish the connection between IDEA and the debugger itself. + // When a connection between our init script and IDEA will be established, the init script will modify an execution-related task and + // start a debugger on this port. + // After IDEA receive a heartbeat from the daemon, we could establish a connection with the remote debugger + // on 127.0.0.1:[getLocalToRemotePort] + val debuggerAgentSink = proxyManager.launchReverseProxy(context.taskId, NetUtils.findAvailableSocketPort()) val debugArgument = DEBUGGER_AGENT_JVM_ARG_FORMAT.format(debuggerAgentSink) settings.withArguments(debugArgument) } if (settings.isScriptDebugRequired()) { - val proxyManager = context.project.getService(GradleDebuggerProxyManager::class.java) + val proxyManager = GradleEelProxyManager.getInstance(context.project) settings.replacePortWithProxy(proxyManager, context.taskId, BUILD_PROCESS_DEBUGGER_PORT_KEY) } } private fun GradleExecutionSettings.replacePortWithProxy( - proxyManager: GradleDebuggerProxyManager, + proxyManager: GradleEelProxyManager, taskId: ExternalSystemTaskId, portKey: Key, ) { - val sourcePort = getUserData(portKey) - ?: throw IllegalStateException("The source port from key $portKey should not be null") - val proxyPort = proxyManager.launchProxy(taskId, sourcePort) + // the port provided by `ExternalSystemRunnableState.getForkSocket()` + // On this port an instance of `ForkedDebuggerThread` will wait for the connection from the daemon + val sourceLocalPort = getUserData(portKey) + ?: throw IllegalStateException("The source port from key $portKey should not be null") + + val proxyPort = proxyManager.launchReverseProxy(taskId, sourceLocalPort) putUserData(portKey, proxyPort) putUserData(DEBUGGER_DISPATCH_ADDR_KEY, LOCALHOST)